首页/新闻资讯/正文详情

Linux内核驱动开发——字符设备与杂项设备驱动

发布时间:2026/9/26 20:09:35 来源:云帆数科 栏目:资讯中心
Linux内核驱动开发——字符设备与杂项设备驱动
字符设备流式按字节访问无固定块大小随机访问不要求块设备按固定大小块扇区512B访问必须经过内核块缓存支持随机读写。项目字符设备Char Device块设备Block Device访问单位字节流一个一个字节块扇区一般 512 字节访问方式顺序流式读写也可以 lseek 随机读必须支持随机访问内核会预读、缓存内核缓存无强制缓存read/write 直接到驱动强制块缓存page cache读写先操作缓存延迟刷盘设备节点/dev/xxx类型标记c/dev/xxx类型标记b典型硬件LED、按键、串口、ADC、触摸屏、传感器SD 卡、eMMC、硬盘、Flash驱动框架cdev、file_operationsblock_device、gendisk、request 队列open 行为直接调用驱动.open内核会先加载块驱动、处理分区表字符设备核心 4 大要素dev_t dev设备号主 次内核用来定位驱动。主设备号驱动编号次设备号同一驱动下不同设备实例MKDEV(major,minor)合成MAJOR(dev)/MINOR(dev)拆分register_chrdev_region()静态注册指定设备号alloc_chrdev_region()动态分配struct cdev字符设备内核对象代表一个字符设备实例cdev_init()绑定file_operationscdev_add()把 cdev 和设备号注册进内核cdev_del()卸载struct file_operations fops函数指针表应用层系统调用的映射.open.read.write.release.unlocked_ioctl应用open()→内核调用.openclass devicesysfs让 mdev 自动生成/dev/xxx节点可选没有就手动mknodclass_create创建设备类/sys/class/xxxdevice_create在 sysfs 创建设备发送 ueventmdev 自动 mknod手动mknod命令mknod /dev/dev_name c 255 0 命令 节点名字 字符设备 主设备号 次设备号tags 索引文件ctagstags 是源码符号索引文件由ctags生成Vim/Neovim 用来快速跳转到函数、结构体、宏、变量的定义阅读 Linux 内核、STM32、i.MX6ULL 这类大工程源码必备成 tags 文件进入源码根目录执行# -R 递归扫描所有子目录生成tags文件 ctags -R *Vim 核心跳转命令快捷键 / 命令功能Ctrl ]光标放在符号上跳转到定义最常用Ctrl t回退返回上一处跳转位置tag 栈:tag func_name直接搜索符号 func_name跳到定义:ts func_name/:tselect func_name列出该符号所有定义位置同名函数 / 结构体多选:tn多个匹配时下一个 tag:tp多个匹配时上一个 tag:tags查看跳转栈历史shell 直接打开并定位符号vim -t main字符设备驱动程序完整开发步骤标准 2.6 内核 cdev 框架动态申请设备号alloc_chrdev_region定义file_operations实现 open/read/write/release定义struct cdevcdev_init(cdev, fops)绑定操作集cdev_add(cdev, devno, count)把 cdev 注册进内核使用class_createdevice_create自动生成/dev/xxx节点udev/mdev旧方法手动mknod /dev/xxx c 主号 次号module_init 调用初始化函数module_exit 做反向清理对应代码开发模板//字符设备驱动模板 dev_t dev; // 定义设备号变量32位高12位主设备号低20位次设备号 // 文件操作函数集用户态open/read/write/release系统调用对应的驱动回调 struct file_operations fops { .open demo_open, .read demo_read, .write demo_write, .release demo_release, }; struct cdev cdev; //字符设备对象内核用来管理字符设备 //模块加载入口函数insmod时自动执行 demo_init() { MKDEV(); //拼接主、次设备号生成dev_t类型设备号 cdev_init(); //初始化cdev绑定file_operations操作集 cdev_add(); //向内核注册cdev添加字符设备内核可识别 register_chrdev_region(); //静态申请设备号分配主、次设备号 /*动态申请设备号*/ /* alloc_chrdev_region(dev, 0, 1, DEV_NAME); dev存放申请好的设备号 第二参数为申请的次设备号 第三参数为连续申请设备商量 */ class_create(); //创建设备类在/sys/class下生成目录 device_create(); //创建设备udev/mdev自动在/dev生成设备节点 } //模块卸载出口函数rmmod时自动执行 demo_exit() { device_destroy(); //销毁/dev下的设备节点 class_destroy(); //删除设备类 unregister_chrdev_region(dev, 1); //释放之前申请的设备号资源 cdev_del(cdev); //从内核删除cdev字符设备 } module_init(demo_init); //指定模块入口函数 module_exit(demo_exit); //指定模块出口函数实验测试添加一个字符设备名为demo2#include linux/init.h #include linux/printk.h #include linux/fs.h #include linux/export.h #include linux/types.h #include linux/cdev.h #include linux/kdev_t.h #include linux/device.h /*主设备号*/ #define MAJOR_DEMO 255 /*次设备号*/ #define MINOR_DEMO 0 /*设备名称*/ #define DEV_NAME demo2 static int open(struct inode * node,struct file *file) { printk(demo init \n); return 0; } static ssize_t read(struct file *file,char __user *buf,size_t size,loff_t *offset) { printk(demo read \n); return 0; } static ssize_t write(struct file *file,const char __user *buf,size_t size,loff_t *offset) { printk(demo write ****\n); return 0; } static int close(struct inode *node,struct file *file) { printk(demo close ...\n); return 0; } /*设备号类型本质是unsigned int 的32位变量存放主设备号和次设备号*/ dev_t dev; /*字符设备信息的结构体*/ struct cdev cdev; /*打包驱动内的操作函数给内核调用*/ static struct file_operations fops { .owner THIS_MODULE, .open open, .read read, .write write, .release close }; /*设备类型指针用于向内核链表添加设备*/ struct class * cls; static int __init demo_init(void) { /*结构体指针代表了/dev下面的设备文件*/ struct device *ret_dev; int ret 0; /*拼接生成设备号*/ dev MKDEV(MAJOR_DEMO,MINOR_DEMO); // (MAJOR_DEMO 20) | MINOR_DEMO /*初始化字符设备信息结构体绑定fops这个驱动函数指针的结构体*/ cdev_init(cdev,fops); /*将字符设备向内核注册设备号为dev注册设备数量为1*/ ret cdev_add(cdev,dev,1); if(ret 0) goto err_cdev_add; /*注册静态设备号向内核申请注册这一组的主次设备号*/ ret register_chrdev_region(dev,1,DEV_NAME); if(ret 0) goto err_register; /*创建设备类在/sys/class/DEV_NAME生成目录*/ cls class_create(fops.owner,DEV_NAME); /*创建设备节点自动在根目录下dev生成设备节点*/ ret_dev device_create(cls,NULL,dev,NULL,DEV_NAME); if(IS_ERR(ret_dev)) goto err_device_create; printk(demo_init ******************############\n); return 0; err_device_create: device_destroy(cls,dev); err_register: unregister_chrdev_region(dev,1); err_cdev_add: cdev_del(cdev); printk(demo_init failed**%d***************############\n,ret); return ret; } static void __exit demo_exit(void) { device_destroy(cls,dev); class_destroy(cls); unregister_chrdev_region(dev,1); cdev_del(cdev); printk(demo_exit ################**************\n); } module_init(demo_init); module_exit(demo_exit);misc 杂项设备miscdevicemisc 是字符设备的简化封装本质依然是字符设备驱动内核统一固定主设备号10不需要手动register_chrdev_region、cdev_init、cdev_add代码比原生字符设备简短很多LED、按键、简单外设驱动很常用。核心特点主设备号固定 10只需要自己指定次设备号封装了cdev、设备号管理底层还是字符设备帮你省去一大堆注册代码自动支持 udev/mdevdevice_create这一步也被封装进misc_register头文件#include linux/miscdevice.h核心结构体struct miscdevicestruct miscdevice { int minor; //次设备号MISC_DYNAMIC_MINOR 代表让内核自动分配次号 const char *name; //设备名字/dev/xxx 就是这个名字 const struct file_operations *fops; //文件操作集和字符设备完全一样 struct list_head list; struct device *parent; struct device *this_device; //...其他成员 };核心 APImisc_register(misc_dev)注册杂项设备模块加载时调用misc_deregister(misc_dev)卸载杂项设备rmmod 调用不用手动MKDEV、register_chrdev_region、cdev_init、cdev_add、class_create、device_create。全部被 misc 封装。misc 设备 VS 原生字符设备项目原生字符设备 cdevmisc 杂项设备主设备号自定义静态 / 动态分配固定主设备号 10cdev手动 cdev_init、cdev_add内核内部自动封装不用写设备号注册register_chrdev_region / alloc_chrdev_region不需要内核统一管理自动创建设备节点class_create device_createmisc_register 内部自动完成适用场景多个同类设备需要自定义主设备号简单单设备LED、按键、简单传感器misc类杂项设备驱动程序模板//杂项(混杂)设备驱动 主设备号固定为10本质是简化封装后的字符设备驱动 struct file_operations fops { .open demo_open, .read demo_read, .write demo_write, .release demo_release }; //杂项设备结构体一次性存放次设备号、设备名、操作函数集 struct miscdevice misc { .minor MISC_DYNAMIC_MINOR, //次设备号MISC_DYNAMIC_MINOR 表示内核动态分配次设备号 .name misc_demo, //设备名称加载后自动生成 /dev/misc_demo .fops fops //绑定文件操作函数集 }; //模块入口函数insmod加载模块执行 demo_init() { misc_register(misc); //注册杂项设备内部自动完成cdev初始化、 //设备号管理、class、device创建设备节点 } //模块出口函数rmmod卸载模块执行 demo_exit() { misc_deregister(misc); //注销杂项设备自动释放所有内核资源 } module_init(demo_init); //指定模块入口 module_exit(demo_exit); //指定模块出口实验测试添加一个demo2的杂项设备#include linux/init.h // 模块加载/卸载宏__init、__exit、module_init、module_exit #include linux/printk.h // 内核打印函数 printk #include linux/fs.h // 文件系统相关结构体inode、file、file_operations #include linux/export.h // 符号导出相关本例未使用 #include linux/types.h // 内核基础数据类型定义 #include linux/miscdevice.h // 杂项设备(miscdevice)驱动头文件 #define DEV_NAME demo2 // 杂项设备名称会在/dev/下生成 /dev/demo2 /** * brief 设备打开回调函数用户空间open(/dev/demo2)时触发 * param node 索引节点描述设备文件本身信息 * param file 文件结构体代表本次打开的文件句柄 * return 成功返回0 */ static int open(struct inode * node,struct file *file) { printk(demo init \n); return 0; } /** * brief 设备读回调用户空间调用read读取/dev/demo2触发 * param file 文件句柄 * param buf 用户态缓冲区指针__user标记这是用户空间地址不能直接内核解引用 * param size 用户想要读取的字节数 * param offset 文件读写偏移位置 * return 读到的字节数这里直接返回0 */ static ssize_t read(struct file *file,char __user *buf,size_t size,loff_t *offset) { printk(demo read \n); return 0; } /** * brief 设备写回调用户空间write写入/dev/demo2触发 * param buf 用户态写入缓冲区 * param size 用户写入的数据长度 * param offset 文件偏移 * return 写入字节数本例返回0 */ static ssize_t write(struct file *file,const char __user *buf,size_t size,loff_t *offset) { printk(demo write ****\n); return 0; } /** * brief 设备释放回调用户close(fd)时执行对应file_operations.release * param node 索引节点 * param file 文件句柄 * return 成功返回0 */ static int close(struct inode *node,struct file *file) { printk(demo close ...\n); return 0; } // 文件操作函数集把上面的回调函数注册到内核 static struct file_operations fops { .owner THIS_MODULE, // 标记属于当前模块防止模块在使用中被卸载 .open open, // 绑定open回调 .read read, // 绑定read回调 .write write, // 绑定write回调 .release close // 绑定close释放回调 }; // 杂项设备结构体 static struct miscdevice misc { .minor MISC_DYNAMIC_MINOR, // 动态申请次设备号由内核分配不用手动指定 .name DEV_NAME, // 设备名/dev/DEMO_NAME .fops fops // 绑定文件操作方法 }; /** * brief 模块入口函数insmod/rmmod加载模块时执行 * return 成功返回0失败返回负的错误码 */ static int __init demo_init(void) { // 注册杂项设备到内核 int ret misc_register(misc); if(ret 0) goto err_register; // 注册失败跳转到错误处理 printk(demo_init ********************\n); return ret; err_register: // 注册失败反注册这里其实misc_register失败不需要deregister只是示例写法 misc_deregister(misc); printk(demo_init failed *************\n); return ret; } /** * brief 模块出口函数rmmod卸载模块时执行 */ static void __exit demo_exit(void) { misc_deregister(misc); // 注销杂项设备从内核移除 printk(demo_exit ################**************\n); } module_init(demo_init); // 指定模块入口函数 module_exit(demo_exit); // 指定模块出口函数

相关推荐

CSP-S初赛备考指南:真题考点分布与2026年命题趋势解析
CSP-S初赛备考指南:真题考点分布与2026年命题趋势解析

1. 从一份真题说起:CSP-S初赛到底在考什么 每年九月中旬,总有一批刚打完CSP-J、觉得自己“还行”的同学,第一次翻开CSP-S初赛的卷子,然后陷入沉默。我见过太多这样的场景:J组初赛轻松80,到了S组连60都摸不到… · 2026/9/26 20:09:35

小米Bootloader解锁168小时等待期:账号、设备与时间策略全解析
小米Bootloader解锁168小时等待期:账号、设备与时间策略全解析

1. 解锁前的认知准备:为什么会有等待与限制1.1 解锁机制背后的基本逻辑小米的Bootloader解锁,本质上是一道安全闸门。厂商在出厂时把引导程序锁住,目的是防止设备被刷入未经验证的系统镜像,从而保护用户数据安全和设备完整性。这个… · 2026/9/26 20:09:35

基于微信小程序与SSM框架的会议发布预约系统设计与并发控制实践
基于微信小程序与SSM框架的会议发布预约系统设计与并发控制实践

简介:这是一份面向毕业设计或课程设计的完整项目源码,基于微信小程序和SSM框架实现会议发布与预约系统,涵盖小程序用户端与后台管理端,适合Java学习者、小程序开发爱好者以及需要快速搭建会议管理系统的学生参考。压缩包共1139个文… · 2026/9/26 20:09:28

mingw64解压即用:Windows上配置GCC工具链并编译ffmpeg
mingw64解压即用:Windows上配置GCC工具链并编译ffmpeg

简介:mingw64是Windows平台下的64位GNU编译器集合,本资源为亲测有效的免安装版本,直接解压即可使用,省去配置安装过程中的诸多麻烦,非常适合需要快速搭建C/C开发环境的开发者。压缩包共3125个文件,大小48.1… · 2026/9/26 22:47:15

网站的框架实战案例
网站的框架实战案例

搞懂网站框架7步图解 避开服务器备案坑 域名买错了?服务器配置不对?别慌,很多新手卡在第一步就劝退。 网站的框架 选错了,后面全是坑。 本文用 图解步骤 拆解,从选型到上线,避开工信部ICP备案系统的雷区。… · 2026/9/26 22:47:15

Amethyst 迁移实战指南:从 cgmath 到 nalgebra 的完整代码改造手册
Amethyst 迁移实战指南:从 cgmath 到 nalgebra 的完整代码改造手册

【免费下载链接】amethyst Data-oriented and data-driven game engine written in Rust 项目地址: https://gitcode.com/gh_mirrors/ame/amethyst 点击查看 免费下载 导读 Amethyst 在引擎内部以 nalgebra 全面取代了 cgmath 作为核心数学库,这对所有… · 2026/9/26 22:47:15

一分钱不花也能用大模型:awesome-freellm-apis 收录的6大本地自托管工具对比
一分钱不花也能用大模型:awesome-freellm-apis 收录的6大本地自托管工具对比

一分钱不花也能用大模型:awesome-freellm-apis 收录的6大本地自托管工具对比 【免费下载链接】awesome-freellm-apis 134 free LLM APIs & AI API keys from 40 providers. Google Gemini, NVIDIA NIM, Groq, OpenRouter & more. One-click setup for Claud… · 2026/9/26 22:47:15

个人的小说网站如何做:从零搭建完整流程与避坑指南
个人的小说网站如何做:从零搭建完整流程与避坑指南

个人的小说网站如何做:从零搭建完整流程与避坑指南 网站做好了没人访问,这是绝大多数个人站长上线后最崩溃的时刻。很多人花了两周时间把界面做得花里胡哨,结果后台数据全是零,甚至一天没几个UV。别急着怀疑人生,这往往不是内容的问题,而是你没跑通从… · 2026/9/26 22:47:15

wordpress站点标题添加实操图解步骤与成本避坑指南
wordpress站点标题添加实操图解步骤与成本避坑指南

wordpress站点标题添加实操图解步骤与成本避坑指南 备案流程一头雾水,很多站长在配置完服务器后,卡在最后一步的元数据调整上。尤其是WordPress这种开源系统,后台设置项繁杂,新手往往找不到【wordpress站点标题添加】的具体入… · 2026/9/26 22:47:08

数据库课后习题答案别硬背:当测试用例集刷,效率翻倍
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍

简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21

OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40

向下兼容与向上兼容:接口设计中的兼容性策略与工程实践
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践

一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码