字符设备注册和设备号
设备号:一个字符设备和块设备都有一个设备号
分为主设备号(某一类驱动)和次设备号(这个驱动下的各个设备)比如鼠标,键盘都属于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.h>cdev结构体如下
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; fd=open("/dev/hello",O_RDWR) read(fd,&buf,1)方式2:自动创建节点
当驱动加载时,udev 会自动扫描 /sys/class/ 下的设备信息,并根据这些信息在 /dev/ 目录下自动创建对应的设备节点。所以我们要把驱动放到/sys/class/下面
一些api函数和头文件
#include <linux/class.h> #include <linux/device.h>struct 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 param+cdev+class+dev");