LDD3--字符设备驱动
设备号通过访问设备节点可以调用字符设备驱动程序设备节点通常位于 /dev 目录下。 ls -l 命令输出权限字符串的首字母 c 来标记字符设备同时在原本显示文件大小的位置会显示出该设备的主设备号和次设备号。 主设备号与特定的字符设备驱动程序绑定用户空间对设备节点进行的打开、读写等操作最终会被内核路由调用该驱动程序对应的函数。 次设备号由驱动程序内部用于区分同一驱动管理下的不同设备实例驱动通过次设备号来定位具体的物理或逻辑设备。手动创建设备节点#1.加载模块 sudo insmod scull.ko #2.自动获取主设备号并赋值给变量 scull_major$(awk$2scull {print $1}/proc/devices)#3.创建设备节点 sudo mknod/dev/scull0 c $scull_major0#4.修改节点权限让普通用户也能读写否则默认只有 root 能访问 sudo chmod666/dev/scull0 #5.手动创建的节点也需要手动删除 sudo rmmod scull sudo rm/dev/scull0file_operations file inode1、struct file_operations驱动的能力清单核心定位它是驱动向内核暴露功能的接口定义了驱动程序支持哪些操作如 open、read、write 等。本质这是一个函数指针的集合。用面向对象编程的术语来说我们可以将 file 视为“对象”而 file_operations 中的函数就是作用于该对象的“方法”。关键机制结构体中的每个字段必须指向驱动中实现特定操作的函数。对于不支持的操作可以将其设为 NULL。内核在遇到 NULL 指针时会根据具体函数执行不同的默认行为例如 read 为 NULL 时会返回 -EINVAL 错误。第一个成员通常是 owner一般被初始化为 THIS_MODULE用于防止模块在操作还在被使用时被意外卸载。2、inode设备文件元信息载体当用户程序调用 fd open(“/dev/mydev”, O_RDWR|O_NONBLOCK) 1、VFS虚拟文件系统拿到字符串 “/dev/mydev”在目录树中找到对应的目录项并提取出该文件对应的 inode 结构体。2、VFS 发现 inode 属于字符设备通过 inode-i_mode然后从 inode 中读取之前存好的设备号 dev_t dev inode-i_rdev;3、VFS 拿着提取出来的 dev_t通过内核全局字符设备映射表kobj_map 中进行查找对应的 cdev 指针。4、VFS 从 cdev 中取出 file_operations将其赋值给当前打开的文件结构体file-f_op cdev-ops。如果 fops 里定义了 open 函数VFS 就会调用驱动的 open 函数并且把第 1 步找到的 inode 和当前的 file 作为参数传递进去driver_open(struct inode *inode, struct file *filp)核心链路节点文件 ➔ inode ➔ 设备号 ➔ cdev ➔ 操作方法 (fops)3、struct file 用户与设备的交互会话当用户程序调用 fd open(“/dev/mydev”, O_RDWR|O_NONBLOCK) 内核不仅会找到对应的 inode还会在内存中动态分配一个全新的 struct file 。file 只关心进程 “怎么操作这个设备”。 它记录了这次打开操作的所有状态信息。结构体核心字段1、f_mode 和 f_flagsf_flags直接来自用户态 open(fd, flags) 中的 flags 参数如 O_NONBLOCK 供驱动开发者使用用于判断用户的具体操作意图和状态。f_mode内核 VFS 拿到 f_flags 后解析、转换生成的内部标志如 FMODE_READ 供内核 VFS 使用用于底层权限校验。。2、f_pos文件偏移量记录当前读写操作到了设备的哪个位置偏移量。作用在 read 和 write 函数中必须根据 filp-f_pos 来决定从设备的哪个地址开始读/写数据操作完后还要更新 f_pos。3、private_data在 open 函数中我们通过 inode 找到了具体的硬件设备结构体比如 struct my_dev *dev然后把它赋值给 filp-private_data dev;。这样在后续的 read/write/release 函数中只需通过 filp-private_data 就能瞬间找回这个设备的上下文。4、f_op操作方法指针VFS 在权限校验通过并创建 file 结构体后会将底层 cdev 中绑定的 file_operations 指针赋值给 file-f_op。VFS 最终通过 file-f_op-open(inode, file) 这一函数指针将控制权正式移交给驱动开发者编写的 open 函数。驱动 open 函数接收的两个参数正是 VFS 一路解析得到的 inode包含设备号和刚刚实例化的 file包含操作上下文。open(“/dev/mydev”, O_RDWR|O_NONBLOCK) 完整链路路径解析与权限校验VFS 层VFS 根据路径 /dev/mydev 逐层解析最终找到设备节点对应的 inode。VFS 提取 flags 中的读写意图转换为 f_modeFMODE_READ | FMODE_WRITE并与 inode 的权限位i_mode进行严格校验。校验失败则直接返回 -EACCES。实例化 file 与设备映射VFS 层校验通过后内核分配并创建 file 结构体将原始的 flags含 O_NONBLOCK存入 file-f_flags将 f_mode 存入 file-f_mode。VFS 从 inode-i_rdev 中取出完整的设备号dev_t通过字符设备映射表kobj_map查找到对应的 cdev 指针。VFS 将 cdev-ops即驱动注册的 file_operations赋值给 file-f_op。回调驱动接口驱动层VFS 最终通过函数指针 file-f_op-open(inode, filp)将找到的 inode 和刚创建的 filefilp作为参数将控制权正式移交给驱动开发者编写的 open 函数完成整个调用闭环。#includelinux/module.h#includelinux/init.h#includelinux/types.h#includelinux/kdev_t.h#includelinux/cdev.h#includelinux/fs.h#includelinux/device.h#includelinux/mutex.h#includelinux/uaccess.h#includelinux/slab.h#includelinux/version.h#includelinux/kernel.h#defineSCULL_DEVICES4#defineSCULL_NAMEscull#defineQSET_SIZE1000#defineQUANTUM4000structscull_qset{void**data;structscull_qset*next;};structscull_dev{structscull_qset*qset_head;unsignedintquantum;unsignedintqset_size;unsignedsize;structmutexlock;dev_tdevno;structcdevcdev;};staticunsignedintscull_major0;staticstructclass*scull_class;structscull_devscull_devs[SCULL_DEVICES];staticvoidscull_trim(structscull_dev*dev){inti;structscull_qset*tmp_next,*dptr;for(dptrdev-qset_head;dptr!NULL;dptrtmp_next){tmp_nextdptr-next;for(i0;idev-qset_size;i){if(dptr-data[i]!NULL){kfree(dptr-data[i]);}}kfree(dptr-data);kfree(dptr);}dev-qset_headNULL;dev-size0;printk(KERN_INFOscull_trim successed!\n);}staticintscull_open(structinode*inode,structfile*filp){structscull_dev*devcontainer_of(inode-i_cdev,structscull_dev,cdev);filp-private_datadev;if(filp-f_flags(O_RDWR|O_WRONLY)){if(filp-f_flagsO_TRUNC){mutex_lock(dev-lock);scull_trim(dev);mutex_unlock(dev-lock);}}printk(KERN_INFOscull_open succeed!\n);return0;}staticintscull_release(structinode*inode,structfile*filp){printk(KERN_INFOscull_release successed!\n);return0;}staticssize_tscull_read(structfile*filp,char__user*buf,size_tcount,loff_t*f_pos){structscull_dev*devfilp-private_data;structscull_qset*dptr;unsignedintitem_sizedev-quantum*dev-qset_size;intitem,s_pos,q_pos,rest;ssize_tretval0;if(mutex_lock_interruptible(dev-lock))return-EINTR;if(*f_posdev-size)gotoout;if(countdev-size-*f_pos)countdev-size-*f_pos;item*f_pos/item_size;rest*f_pos%item_size;q_posrest/dev-quantum;s_posrest%dev-quantum;dptrdev-qset_head;while(item--dptr){dptrdptr-next;}if(countdev-quantum-s_pos){countdev-quantum-s_pos;}if(dptrdptr-datadptr-data[q_pos]){if(copy_to_user(buf,dptr-data[q_pos]s_pos,count)){retval-EFAULT;gotoout;}*f_poscount;retvalcount;}else{gotoout;}out:mutex_unlock(dev-lock);returnretval;}staticssize_tscull_write(structfile*filp,constchar__user*buf,size_tcount,loff_t*f_pos){structscull_dev*devfilp-private_data;structscull_qset*dptr;unsignedintitem_sizedev-quantum*dev-qset_size;intitem,s_pos,q_pos,rest;ssize_tretval0;if(mutex_lock_interruptible(dev-lock))return-EINTR;item*f_pos/item_size;rest*f_pos%item_size;q_posrest/dev-quantum;s_posrest%dev-quantum;dptrdev-qset_head;if(dptrNULL){dptrkmalloc(sizeof(structscull_qset),GFP_KERNEL);if(!dptr)gotoout;memset(dptr,0,sizeof(structscull_qset));dev-qset_headdptr;}while(item--0){if(!dptr-next){dptr-nextkmalloc(sizeof(structscull_qset),GFP_KERNEL);if(!dptr-next)gotoout;memset(dptr-next,0,sizeof(structscull_qset));}dptrdptr-next;}if(!dptr-data){dptr-datakmalloc(dev-qset_size*sizeof(char*),GFP_KERNEL);if(!dptr-data)gotoout;memset(dptr-data,0,dev-qset_size*sizeof(char*));}if(!dptr-data[q_pos]){dptr-data[q_pos]kmalloc(dev-quantum,GFP_KERNEL);if(!dptr-data[q_pos])gotoout;memset(dptr-data[q_pos],0,dev-quantum);}if(countdev-quantum-s_pos){countdev-quantum-s_pos;}if(copy_from_user(dptr-data[q_pos]s_pos,buf,count)){retval-EFAULT;gotoout;}*f_poscount;retvalcount;if(dev-size*f_pos){dev-size*f_pos;}out:mutex_unlock(dev-lock);returnretval;}staticconststructfile_operationsscull_fops{.ownerTHIS_MODULE,.openscull_open,.releasescull_release,.readscull_read,.writescull_write,};staticint__initscull_init(void){intret,i;dev_tdevno;retalloc_chrdev_region(devno,0,SCULL_DEVICES,SCULL_NAME);if(ret){printk(KERN_ERRalloc_chrdev_region failed \n);returnret;}scull_majorMAJOR(devno);printk(KERN_INFOscull_major %d,scull_major);#ifLINUX_VERSION_CODEKERNEL_VERSION(6,4,0)scull_classclass_create(SCULL_NAME);#elsescull_classclass_create(THIS_MODULE,SCULL_NAME);#endifif(IS_ERR(scull_class)){retPTR_ERR(scull_class);printk(KERN_ERRscull failed create class ret %d \n,ret);gotoerr_unregister_region;}for(i0;iSCULL_DEVICES;i){structscull_dev*devscull_devs[i];dev-qset_sizeQSET_SIZE;dev-quantumQUANTUM;dev-size0;dev-devnoMKDEV(scull_major,i);mutex_init(dev-lock);dev-qset_headNULL;cdev_init(dev-cdev,scull_fops);dev-cdev.ownerTHIS_MODULE;device_create(scull_class,NULL,dev-devno,NULL,scull%d,i);retcdev_add(dev-cdev,dev-devno,1);if(ret){while(i--){cdev_del(scull_devs[i].cdev);device_destroy(scull_class,scull_devs[i].devno);}printk(KERN_ERRfailed to add scull_cdev %d\n,ret);gotoerr_create_class;}}printk(KERN_INFOscull_init succeed!\n);return0;err_create_class:class_destroy(scull_class);err_unregister_region:unregister_chrdev_region(devno,SCULL_DEVICES);returnret;}staticvoid__exitscull_exit(void){inti;dev_tdevnoMKDEV(scull_major,0);for(i0;iSCULL_DEVICES;i){structscull_dev*devscull_devs[i];device_destroy(scull_class,dev-devno);cdev_del(dev-cdev);scull_trim(dev);}class_destroy(scull_class);unregister_chrdev_region(devno,SCULL_DEVICES);printk(KERN_INFOscull module unloaded!);}MODULE_LICENSE(GPL);module_init(scull_init);module_exit(scull_exit);字符设备驱动程序通过以下步骤实现驱动程序的 init分配设备号 创建设备实例 将设备实例与 file_operations 绑定 将设备实例与设备号绑定登记内核映射表 创建设备类 根据设备号与设备类创建设备节点