设备号一个字符设备和块设备都有一个设备号分为主设备号(某一类驱动)和次设备号这个驱动下的各个设备比如鼠标键盘都属于USB驱动设备号数据类型dev_t 也即是u32无符号整型32位数高12位主设备号低20位次设备号几个函数解析#define MINORBITS 20 #define MINORMASK ((1U MINORBITS) - 1) #define MAJOR(dev) ((unsigned int)((dev) MINORBITS)) #define MINOR(dev) ((unsigned int)((dev) MINORMASK)) #define MKDEV(ma, mi) (((ma) MINORBITS) | (mi)) /* 最主要就是下面三个宏定义 MAJOR(dev) 就是传进去设备号得到主设备号 MINOR(dev) 就是传进去设备号得到次设备号 MKDEV(ma, mi) 就是传主次设备号得到完整设备号 */申请设备号类型:1:静态申请要先知道哪些设备号还没用到/* param1:你要申请的设备号起始比如MKDEV(200,0)主200次0开始 param2:盛情几个次设备号从第一个参数开始:比如1 param3:主设备号名字 return :成功返回0失败小于0 */ int register_chrdev_region(dev_t,unsigned,const char *)2动态申请系统帮你申请用的很多/* param1申请到的设备号 param2:次设备号起始地址一般位0 param3:盛情数量 param4名字 Character devices: 1 mem 4 tty 5 /dev/tty 236 chrdev_name ← 这里你传入的 chrdev_name 会显示在这里r */ alloc_chrdev_region(dev_t *,unsigned a,unsigned b ,const char * p)3申请了不用了之后要释放/* param1设备号起始地址 param2数量 */ unregister_chrdev_region(dev_t,unsigned)包含头文件#include linux/moduleparam.h #include linux/kernel.h // printk 内核打印 #include linux/fs.h // 设备号、register_chrdev_region 等 #include linux/kdev_t.h // MAJOR、MINOR、MKDEV 宏定义module_param(major, int, S_IRUGO); // 定义可加载模块参数主设备号 module_param(minor, int, S_IRUGO); // 定义可加载模块参数次设备号 dev_t dev_num; // 保存最终合成的设备号dev_t是32位整数 static int moduleparam_init(void) { int ret; // 保存函数返回值错误码 if(major) { printk(major is %d\n, major); // 打印传入的主设备号 printk(minor is %d\n, minor); // 打印传入的次设备号 dev_num MKDEV(major, minor); // 合成完整设备号主设备号左移20位 | 次设备号 // 注册指定范围的设备号1个设备名称为chrdev_naem ret register_chrdev_region(dev_num, 1, chrdev_naem); if(ret 0){ printk(register_chrdev_region is error\n); // 注册失败 } printk(register_chrdev_region is ok\n); // 注册成功 } else { // 动态分配设备号从0开始找分配1个设备名称为alloc_name ret alloc_chrdev_region(dev_num, 0, 1, alloc_name); if(ret 0){ printk(alloc_chrdev_region is error\n); // 分配失败 } printk(alloc_chrdev_region is ok\n); // 分配成功 major MAJOR(dev_num); // 从分配到的设备号中提取主设备号 minor MINOR(dev_num); // 从分配到的设备号中提取次设备号 printk(major is %d\n, major); // 打印分配到的主设备号 printk(minor is %d\n, minor); // 打印分配到的次设备号 } return 0; // 模块初始化成功返回0 }注册字符设备用cdev结构体向内核注册一个字符设备#include linux/cdev.hcdev结构体如下struct cdev { struct kobject kobj; struct module *owner; //所属模块 const struct file_operations *ops; //文件操作结构体 struct list_head list; dev_t dev; //设备号 unsigned int count; };部分完整代码如下主要是最后4行代码static int mychar_cdev_open(struct inode *inode, struct file *file) { printk(KERN_INFO mychar_cdev Module Opening...\n); return 0; } static ssize_t mychar_cdev_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { printk(KERN_INFO mychar_cdev Module Reading...\n); return 1; } static ssize_t mychar_cdev_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { printk(KERN_INFO mychar_cdev Module Writing...\n); return count; } static int mychar_cdev_release(struct inode *inode, struct file *file) { printk(KERN_INFO mychar_cdev Module Releasing...\n); return 0; } struct file_operations mychar_cdev_ops { .owner THIS_MODULE, // 其他 file_operations 成员可以根据需要添加 .open mychar_cdev_open, .release mychar_cdev_release, .read mychar_cdev_read, .write mychar_cdev_write, } //初始化cdev struct cdev mychar_dev; cdev_init(mychar_dev,mychar_dev.ops)//建立cdev和file_operations的连接 mychar_dev.owner THIS_MODULE; cdev_add(mychar_dev,dev_num,1) //把设备号与cdev与file_operations连接起来创建设备节点Linux一切皆文件也就是在/dev/目录下面创建一个目录这个目录与你的设备号绑定就可以了不然和上图一样你没有给用户操作设备节点我怎么操作呢也就是你只把设备号与驱动绑定了但是没有目录操作他们方式1手动mknod 设备节点名称 设备名称(字符) 主设备号 次设备号 例子 mknod /dev/hello c 249 0这样就可以用户空间打开设备read函数就是你写的内核read函数了,比如char buf; fdopen(/dev/hello,O_RDWR) read(fd,buf,1)方式2自动创建节点当驱动加载时udev 会自动扫描 /sys/class/ 下的设备信息并根据这些信息在 /dev/ 目录下自动创建对应的设备节点。所以我们要把驱动放到/sys/class/下面一些api函数和头文件#include linux/class.h #include linux/device.hstruct class *my_class; struct device *my_device int ret; / 1. 创建设备类对应 /sys/class/hello_class my_class class_create(THIS_MODULE, hello_class); if (IS_ERR(my_class)) { // 你写错了 printk(KERN_ERR 创建类失败\n); return PTR_ERR(my_class); } // 2. 创建设备节点 → udev 自动生成 /dev/hello my_device device_create( my_class, // 所属的类 NULL, // 父设备一般填 NULL dev_num, // 设备号关键 NULL, // 驱动数据一般填 NULL hello // 设备节点名字 → /dev/hello ); if (IS_ERR(my_device)) { printk(KERN_ERR 创建设备节点失败\n); ret PTR_ERR(my_device); return ret; }把上面的结合起来就是完整代码了完整代码如下#include linux/module.h #include linux/fs.h #include linux/cdev.h #include linux/device.h #include linux/kdev_t.h // 1. 模块参数允许加载时指定主/次设备号 static int major 0; static int minor 0; module_param(major, int, S_IRUGO); module_param(minor, int, S_IRUGO); // 2. 设备号 cdev结构体 static dev_t dev_num; static struct cdev mychar_dev; // 3. 类 设备节点 static struct class *my_class; static struct device *my_device; // 4. 文件操作函数实现 static int mychar_cdev_open(struct inode *inode, struct file *file) { printk(KERN_INFO mychar_cdev: open success\n); return 0; } static ssize_t mychar_cdev_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { printk(KERN_INFO mychar_cdev: read success\n); return 0; } static ssize_t mychar_cdev_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { printk(KERN_INFO mychar_cdev: write success\n); return count; } static int mychar_cdev_release(struct inode *inode, struct file *file) { printk(KERN_INFO mychar_cdev: release success\n); return 0; } // 5. 文件操作集合 static const struct file_operations mychar_cdev_ops { .owner THIS_MODULE, .open mychar_cdev_open, .read mychar_cdev_read, .write mychar_cdev_write, .release mychar_cdev_release, }; // 6. 驱动入口函数 static int __init mychar_drv_init(void) { int ret; // 1. 申请设备号 if (major) { dev_num MKDEV(major, minor); ret register_chrdev_region(dev_num, 1, mychar_drv); if (ret 0) { printk(register chrdev failed\n); return ret; } } else { ret alloc_chrdev_region(dev_num, 0, 1, mychar_drv); if (ret 0) { printk(alloc chrdev failed\n); return ret; } major MAJOR(dev_num); minor MINOR(dev_num); } printk(major %d, minor %d\n, major, minor); // 2. 初始化并添加 cdev cdev_init(mychar_dev, mychar_cdev_ops); mychar_dev.owner THIS_MODULE; ret cdev_add(mychar_dev, dev_num, 1); if (ret 0) { printk(cdev add failed\n); goto unregister_chrdev; } // 3. 创建 class 类 my_class class_create(THIS_MODULE, hello_class); if (IS_ERR(my_class)) { printk(class create failed\n); ret PTR_ERR(my_class); goto del_cdev; } // 4. 创建 device 节点 → /dev/hello my_device device_create(my_class, NULL, dev_num, NULL, hello); if (IS_ERR(my_device)) { printk(device create failed\n); ret PTR_ERR(my_device); goto destroy_class; } printk(mychar driver init success!\n); return 0; // 错误处理内核标准写法 destroy_class: class_destroy(my_class); del_cdev: cdev_del(mychar_dev); unregister_chrdev: unregister_chrdev_region(dev_num, 1); return ret; } // 7. 驱动出口函数 static void __exit mychar_drv_exit(void) { device_destroy(my_class, dev_num); class_destroy(my_class); cdev_del(mychar_dev); unregister_chrdev_region(dev_num, 1); printk(mychar driver exit success!\n); } module_init(mychar_drv_init); module_exit(mychar_drv_exit); MODULE_LICENSE(GPL); MODULE_AUTHOR(Your Name); MODULE_DESCRIPTION(Complete char driver with paramcdevclassdev);