cdev 对象是个啥? (附代码说明)
author: hjjdebugdate: 2026年 05月 10日 星期日 10:51:59 CSTdescrip: cdev 对象是个啥? (附代码说明)文章目录1. 定义设备号、设备名2 定义cdev对象g_cdev_obj.3. 自动创建 /dev/DEV_NAME 节点,4. 其它知识(使用的宏的含义).4.1 module_init, module_exit 宏4.2 MODULE_LICENSE,MODULE_DESCRIPTION宏5. 测试:6. 完整代码cdev 创造的对象, 它是字符设备的管理对象. 由它把用户对设备的请求(读写控制)转化为内核对设备的(读写控制).所以cdev就是一个桥梁,就是一个匹配器,就是一个红娘.深刻理解cdev的作用,还需要从实际代码入手.看清它的底层工作原理.1. 定义设备号、设备名每个设备都有个设备名,这容易理解.设备号: 64机器上就是一个64位值,其中搞12位为主设备号,低20位为次设备号.常言道,主设备区分驱动,次设备号定位设备,这句话怎么理解?以字符设备为例,设想机器上有很多字符设备, 这些设备就是用cdev对象来表示的.每个设备都有设备驱动,这里所谓的驱动就是cdev内的struct file_operations.相同的主设备号对应着相同的file_operations.不过如果你不遵守这个约定,也没关系. 这不是重点. 例如/dev/zero 等内存设备.在open的时候,就修改了file_operations 操作, 相当于由主,次设备号联合,确定了file_operations操作int ret alloc_chrdev_region(g_devno, 0, 1, DEV_NAME);向系统申请字符设备号, 0自动分配, 1占用的次设备号范围,输出最低开始的设备号.想查看一下g_devno 的值,写一个打印语句即可major MAJOR(g_devno);minor MINOR(g_devno);pr_info(“g_devno:0x%x, major:%x, minor:%x\n”,g_devno,major,minor);2 定义cdev对象g_cdev_obj.cdev本身是把用户对设备的请求(读,写,控制)转化为内核对设备的请求一个媒介对象static struct cdev g_cdev_obj;struct cdev {struct kobject kobj; //基类,例如保存设备类型信息等struct module *owner; //持有module 指针const struct file_operations *ops; //文件操作,接受用户的操作struct list_head list; //持有链表头,初始化时设备为dev_t dev; //设备号unsigned int count; //子设备号个数} __randomize_layout;// 初始化cdev并绑定fops cdev_init(g_cdev_obj, my_fops); //初始化g_cdev_obj,并设置file_operations g_cdev_obj.owner THIS_MODULE; cdev_add(g_cdev_obj, g_devno, 1); //g_devno,count 也被赋值,并向系统注册 向系统注册过, 当用户请求该设备时(提供devno),系统能根据devno找到这个cdev设备 然后open,close, read,write,ioctl 就能被执行3. 自动创建 /dev/DEV_NAME 节点,想省略 mknod /dev/DEV_NAME c major minor手工创建节点,让程序创建该节点增加下面2行my_class_obj class_create(THIS_MODULE, “myclass”); //创建一个类,每个设备都归属一个类my_device_obj device_create(my_class_obj, NULL, g_devno, NULL, DEV_NAME);//在该类下根据设备名,设备号创建设备class_create 后, 会生成/sys/class/my_class_obj 目录device_create 后, 除了生成/dev/mydev 字符节点文件外, 还会生成/sys/4. 其它知识(使用的宏的含义).4.1 module_init, module_exit 宏module_init(mydrv_init);会被展开为: int init_module(void)attribute((alias(“mydrv_init”)))就是把mydrv_init 静态函数, 以int init_module(void) 函数的方式对外公开,成为公有函数使外部可以引用.module_exit(mydrv_exit);会被展开为: void cleanup_module(void)attribute((alias(“mydrv_exit”)4.2 MODULE_LICENSE,MODULE_DESCRIPTION宏MODULE_LICENSE(“GPL”); 会被展开为:static const char __UNIQUE_ID_license60[]attribute((section(“.modinfo”), unused, aligned(1))) “license” “” “GPL”;c 语言看就是定义了一个字符串licenseGPL 并把它放到了.modinfo 节.你可以用以下命令来验证$ readelf -x .modinfo test_cdev.ko$ readelf -p .modinfo test_cdev.koMODULE_DESCRIPTION(“Simplest Real Char Device Driver”); 会被展开为:static const char __UNIQUE_ID_description61[])attribute((section(“.modinfo”), unused, aligned(1))) “description” “” “Simplest Real Char Device Driver”;即定义了一个字符数组 __UNIQUE_ID_description61[], 内容为descriptionSimplest Real Char Device Driver, 放到.modinfo 节一切都解释清楚了,安装测试5. 测试:$ sudo insmod test_cdev.ko查看打印信息输出$ dmesg[ 7270.873061] mydrv_init[ 7270.873063] g_devno:0x1ff00000, major:1ff, minor:0[ 7270.873173] mydrv: driver install ok, /dev/mydev查看/dev/mydev, 字符设备节点已经创建$ ll /dev/mydevcrw------- 1 root root 511, 0 5月 10 10:26 /dev/mydev操作这个字符文件$ echo “hi”| sudo tee /dev/mydevhitee: /dev/mydev: 无效的参数$dmesg[ 8134.609072] mydrv: device opened[ 8134.609241] mydrv: device closed看到设备打开,关闭已经执行. 对应着echo 命令对 /dev/mydev 的打开和关闭过程.因驱动中未定义write 函数,所以hi没法向驱动中写,内核提示了无效的参数 补齐读写函数,则可写入和读出内容.查看sysfs, 拓展设备的概念,这里创建了一个真实的struct device 设备,但这跟cdev 没有关系,是device_create创建的./sys/devices/virtual/ 目录下确实生成了myclass目录,myclass目录下生成了mydev目录$cd /sys/devices/virtual/myclass/mydev$ ls -l总用量 0-r–r–r-- 1 root root 4096 5月 10 10:28 devdrwxr-xr-x 2 root root 0 5月 10 10:29 powerlrwxrwxrwx 1 root root 0 5月 10 10:28 subsystem - …/…/…/…/class/myclass-rw-r–r-- 1 root root 4096 5月 10 10:26 uevent还可以进一步看看内核暴露给用户的文件. 这是用户接口文件,是内存型文件,内存文件系统管理$ cat dev511:0$ cat ueventMAJOR511MINOR0DEVNAMEmydev6. 完整代码$ cat test_cdev.c#includelinux/init.h#includelinux/module.h#includelinux/fs.h#includelinux/cdev.h#includelinux/device.h// 1. 定义设备号、设备名#defineDEV_NAMEmydevstaticdev_t g_devno;// 2. 核心结构对象g_cdev_obj. cdev本身是把用户对设备的请求(读,写,控制)转化为内核对设备的请求staticstructcdevg_cdev_obj;// 3. 如果自动生成设备节点,再添加2个变量staticstructclass*my_class_obj;staticstructdevice*my_device_obj;//这个my_device_obj 是什么? 跟g_cdev_obj 有什么关系? 没关系// 2. 设备文件操作open / releasestaticintmydrv_open(structinode*inode,structfile*file){pr_info(mydrv: device opened\n);return0;}staticintmydrv_release(structinode*inode,structfile*file){pr_info(mydrv: device closed\n);return0;}// 3. 绑定操作集staticstructfile_operationsmy_fops{.ownerTHIS_MODULE,.openmydrv_open,.releasemydrv_release,};// 4. 驱动入口staticint__initmydrv_init(void){intret;intmajor,minor;pr_info(mydrv_init\n);// 分配设备号retalloc_chrdev_region(g_devno,0,1,DEV_NAME);if(ret0){pr_info(alloc_chrdev_region: ret is %d\n,ret);returnret;}majorMAJOR(g_devno);minorMINOR(g_devno);pr_info(g_devno:0x%x, major:%x, minor:%x\n,g_devno,major,minor);// 初始化cdev并绑定fopscdev_init(g_cdev_obj,my_fops);//初始化g_cdev_obj,并设置file_operationsg_cdev_obj.ownerTHIS_MODULE;cdev_add(g_cdev_obj,g_devno,1);//g_devno,count 也被赋值,并向系统注册// 想要自动创建 /dev/DEV_NAME 节点,增加下面2行my_class_objclass_create(THIS_MODULE,myclass);//创建一个类my_device_objdevice_create(my_class_obj,NULL,g_devno,NULL,DEV_NAME);//在该类下根据设备名,设备号创建设备pr_info(mydrv: driver install ok, /dev/%s\n,DEV_NAME);return0;}// 5. 驱动出口staticvoid__exitmydrv_exit(void){// 逆序注销device_destroy(my_class_obj,g_devno);class_destroy(my_class_obj);cdev_del(g_cdev_obj);unregister_chrdev_region(g_devno,1);pr_info(mydrv: driver uninstall ok\n);}module_init(mydrv_init);module_exit(mydrv_exit);MODULE_LICENSE(GPL);MODULE_DESCRIPTION(Simplest Real Char Device Driver);