3字符设备驱动基本编程.docx
- 文档编号:9027281
- 上传时间:2023-05-16
- 格式:DOCX
- 页数:18
- 大小:511.80KB
3字符设备驱动基本编程.docx
《3字符设备驱动基本编程.docx》由会员分享,可在线阅读,更多相关《3字符设备驱动基本编程.docx(18页珍藏版)》请在冰点文库上搜索。
3字符设备驱动基本编程
第三章字符设备驱动基本编程
本文将使用内存来虚拟4个同类型字符设备scull,并以该字符设备为例来进行字符设备驱动基本编程的讲解。
一、让我们先来体验一下scull设备吧
1、下载scull设备的驱动源码,解压后make,可得到scull.ko。
将其加载进内核:
insmodscull.ko
2、创建设备节点文件
dennis@dennis-desktop:
/work/studydriver/examples/scull$cat/proc/devices|grepscull
252scull
252scullp
dennis@dennis-desktop:
/work/studydriver/examples/scull$sudomknodscull0c2520
dennis@dennis-desktop:
/work/studydriver/examples/scull$sudomknodscull1c2521
dennis@dennis-desktop:
/work/studydriver/examples/scull$sudomknodscull2c2522
dennis@dennis-desktop:
/work/studydriver/examples/scull$sudomknodscull3c2523
dennis@dennis-desktop:
/work/studydriver/examples/scull$sudochmod666scull[0-3]
3、体验scull设备。
向该字符设备写入内容后再将内容读出
dennis@dennis-desktop:
/work/studydriver/examples/scull$catscull0
dennis@dennis-desktop:
/work/studydriver/examples/scull$echoyangzhu>scull0
dennis@dennis-desktop:
/work/studydriver/examples/scull$catscull0
yangzhu
二、实现字符设备驱动的工作
1、确定主设备号和次设备号
∙什么是主设备/次设备号
o主设备号是内核识别一个设备属于哪一个驱动的标识。
是一个整数,范围从0到(4096-1),但是一般使用1到255。
o次设备号是驱动程序自己用来区别多个设备的。
是一个整数,范围从0到(1048576-1),但是一般使用0到255。
o预定义的设备号:
详见Documentation/devices.txt
o查看设备号:
$ls–l/dev
∙设备编号的内部表示
o内核用32bit表示设备号
typedefunsignedlongdev_t;
o其中高12bit为主设备号,低20bit为次设备号。
要想获得一个dev_t的主或者次设备号,使用内核定义的宏:
MAJOR(dev_tdev);和MINOR(dev_tdev);
#defineMINORBITS 20
#defineMINORMASK ((1U< #defineMAJOR(dev) ((unsignedint)((dev)>>MINORBITS)) #defineMINOR(dev) ((unsignedint)((dev)&MINORMASK)) o主次设备号转换为一个dev_t,使用内核定义的宏: MKDEV(intmajor,intminor); #defineMKDEV(ma,mi) (((ma)< ∙分配主设备号/次设备号的方法和内核API o静态分配设备号: 请求操作系统分配驱动程序要求的特定设备号。 first为要求分配的第1个设备号(包含主、次设备号),count为请求的设备号数量,name为驱动名称(出现在/proc/devices中)。 失败返回负数,成功则操作系统将first到first+count-1,总共count个设备号分配给驱动。 例如: 如果register_chrdev_region((200: 2),4,"test")成功,则分配到的设备号为(200: 2)--(200: 5)。 请求分配的时机应该在驱动程序的初始化函数中。 注: (200: 2)表示1个设备号,该设备号的主设备号为200,次设备号为2。 在不引起混淆的情况下,今后均采用这种方法表示设备号。 intregister_chrdev_region(dev_tfirst,unsignedintcount,char*name); o动态申请设备号。 dev用于存放结果,其最终存放的是分配到的count个设备号中的第1个设备号,firstminor为期望分配到的第1个设备号的次设备号,count为请求的设备号数量,name为驱动名称(出现在/proc/devices中)。 失败返回负数,成功则操作系统将分配的第1个设备号放在dev中,并且分配出去的设备号是从dev到dev+count-1,共count个,而且保证第1个设备号(存放在dev中)的次设备号是firstminor。 例如: 如果alloc_chrdev_region(&dev,3,4,"test2")执行成功,则分配到的设备号为(201: 3)--(201: 6),并且dev的值变为(201: 3)。 申请的时机应该在驱动程序的初始化函数函数中。 intalloc_chrdev_region(dev_t*dev,unsignedintfirstminor,unsignedintcount,char*name); 以下是main.c中获取主设备号的代码 44intscull_major= SCULL_MAJOR;//宏SCULL_MAJOR在scull.h中被定义为0 45intscull_minor= 0; 688 if(scull_major){ 689 dev=MKDEV(scull_major,scull_minor); 690 result=register_chrdev_region(dev,scull_nr_devs,"scull"); 691 }else{ 692 result=alloc_chrdev_region(&dev,scull_minor,scull_nr_devs, 693 "scull"); 694 scull_major=MAJOR(dev); 695 } 696 if(result<0){ 697 printk(KERN_WARNING"scull: can'tgetmajor%d\n",scull_major); 698 returnresult; 699 } ∙释放主设备号/次设备号的方法和内核API 释放的时机应该在驱动程序的销毁函数中 652 unregister_chrdev_region(devno,scull_nr_devs); ∙早期的分配、释放主设备号/次设备号的内核API 由于目前内核源码中还有不少驱动使用早期的分配、释放主设备号/次设备号的内核API,所以这里也将早期的内核API做个列出,以帮助大家在阅读内核源码时能够理解它们。 ointregister_chrdev(unsignedintmajor,constchar*name,structfile_operations*fops) ointunregister_chrdev(unsignedintmajor,constchar*name) 2、确定设备文件名称并创建设备文件(由用户或者udev来完成)作为用户程序与驱动的接口界面 ∙设备文件名称是一个合法的文件名称即可。 一般是设备名称,或者是设备名称+数字。 比如设备名称为scull,设备文件名可以为scull0,scull1。 ∙设备类型主要有c(字符设备类型)和b(块设备类型) ∙创建设备文件mknod/dev/scull0c2520 3、将字符设备注册进操作系统 1)、字符设备的注册时机在驱动程序的初始化函数函数中,注销时机在驱动程序的销毁函数中 705 scull_devices=kmalloc(4*sizeof(structscull_dev),GFP_KERNEL);//kmalloc的作用相当于应用程序中malloc 710 memset(scull_devices,0,4*sizeof(structscull_dev)); 721 scull_setup_cdev(&scull_devices[i],i); //i是4个设备中第i个设备的编号 740module_init(scull_init_module); //函数scull_init_module的代码位于678-738行 642 cdev_del(&scull_devices[i].cdev); 741module_exit(scull_cleanup_module); //函数scull_cleanup_module的代码位于633-658行 字符设备驱动架构图 2)、字符设备是如何在操作系统中被注册和注销的? scull设备示意图 上图是1个scull设备的逻辑图(虚拟设备scull以内存作为设备存储数据的地方,其设计详情,请参阅《LinuxDeviceDriver》thirdedition一书3.1和3.6节)。 上图中的Scull_device是一个scull_dev结构体,其中最重要的是structcdev结构体,它被内核用来在内部表示一个字符设备 structscull_dev{ structscull_qset*data; /*指向设备存储区的第1个quantum集合*/ intquantum; /*表明1个quantum的大小*/ intqset; /*表明1个quantum集合中有几个quantum*/ unsignedlongsize; /*当前设备中总的数据量字节数*/ structcdevcdev; /*内核内部用于表示1个字符设备的结构体*/ }; structcdev{ structkobjectkobj; structmodule*owner; conststructfile_operations*ops; structlist_headlist; dev_tdev; unsignedintcount; }; 664staticvoidscull_setup_cdev(structscull_dev*dev,intindex) 665{ 666 interr,devno=MKDEV(scull_major,scull_minor+index); 667 668 cdev_init(&dev->cdev,&scull_fops); 669 dev->cdev.owner=THIS_MODULE; 670 dev->cdev.ops=&scull_fops; 671 err=cdev_add(&dev->cdev,devno,1); 672 /*Failgracefullyifneedbe*/ 673 if(err) 674 printk(KERN_NOTICE"Error%daddingscull%d",err,index); 675} 668行初始化了sturctcdev结构体(在不引起混淆的情况下,以后将称其为cdev)的各个字段,其中最重要的是把ops初始化为了scull_fops。 这样cdev结构体就与scull_fops(记录了驱动中实现的操作硬件的全部功能函数)建立了关联的关系。 671行将scull设备的设备号devno(252: 0)和cdev注册进操作系统(在将cdev的dev、ops、count字段正确填写后,链入操作系统的字符设备链表),这样一来操作系统内部就建立了设备号--cdev--scull_fops三者之间的关联关系。 642 cdev_del(&scull_devices[i].cdev); 而要从操作系统中注销字符设备,只需要执行内核APIcdev_del即可。 它将cdev从操作系统的字符设备链表中移除。 3)、应用程序调用open打开一个设备时,操作系统干了什么? 由于操作系统内部已经建立了设备号--cdev--scull_fops三者之间的关联关系,所以当用户程序调用open(fd,"/dev/scull0")打开设备文件的时候,操作系统就可以根据设备文件名得到设备号,再根据设备号找到cdev,进而找到fops,从而为该设备在内核空间中建立3张表: 文件描述符表(filedescriptortable)、文件表(filetable)、i节点表(i-nodetable),关于3张表的关系和作用,请参见“文件描述符表、文件表、i节点表关系与作用”一文或《LinuxDeviceDriver》thirdedition一书3.3节。 i节点表中含有: ∙i_rdev字段代表实际的设备号(open调用中设备文件对应的设备号); ∙i_cdev字段指向字符设备cdev。 文件表中含有: ∙f_op字段指向fops; ∙f_pos字段表示设备的当前读写位置; ∙f_flags字段标识文件打开时是否可读或可写; ∙private_data字段指向私有数据指针,驱动程序可以将这个成员用于任何目的或者忽略这个成员。 应用程序完成open调用后,内核的状况图 4)、应用程序调用read(fd,buff)读取设备时,操作系统干了什么? 操作系统根据fd和文件描述符表找到文件表,再根据文件表中的f_op字段找到fops,而fops(scull_fops)存放的函数指针就是对应于操作物理设备(例如read或write等)的各类驱动函数的函数名,这些函数就组成了驱动程序源代码的主体。 所以操作系统就可以根据用户程序想执行read这个systemcall,在scull_fops.read中找到函数指针scull_read,进而调用它完成对物理设备的读操作。 所以我们写驱动程序很大一部分工作就是要实现这些直接操作设备硬件的函数。 613structfile_operationsscull_fops={ 614 .owner= THIS_MODULE, 615// .llseek= scull_llseek, 616 .llseek=no_llseek, 617 .read= scull_read, 618 .write= scull_write, 619 .ioctl= scull_ioctl, 620 .open= scull_open, 621 .release= scull_release, 622}; file_operations的主要字段: ∙structmodule*owner: 指向模块自身。 ∙open: 打开设备。 ∙release: 关闭设备。 ∙read: 从设备上读数据。 ∙write: : 向设备上写数据。 ∙ioctl: 操作设备函数。 ∙llseek: 定位读写指针。 ∙mmap: 映射设备空间到进程的地址空间。 4、实现open函数 257intscull_open(structinode*inode,structfile*filp) 258{ 259 structscull_dev*dev;/*deviceinformation*/ 261 dev=container_of(inode->i_cdev,structscull_dev,cdev); 265 filp->private_data=dev;/*forothermethods*/ 267 /*nowtrimto0thelengthofthedeviceifopenwaswrite-only*/ 268 if((filp->f_flags&O_ACCMODE)==O_WRONLY){ 275 scull_trim(dev); 281 } 283 return0; /*success*/ 284} 用户程序调用open时,操作系统会建立并初始化好前述的3张表后,再调用驱动程序中的scull_open函数,传入的参数inode是i节点表指针,filp是文件表指针。 261行使用内核提供的宏container_of,根据字符设备结构体cdev的地址(inode->i_cdev)推算出驱动程序定义的设备结构体地址(scull_devices),并将其赋给文件表中的private_data字段,以便其它驱动函数将来比较容易地找到驱动程序定义的设备结构体。 268行根据文件表中的读写标志(由操作系统已经根据用户程序open时指定的标志设置好了该标志),决定是否要清空设备中存放的数据。 请思考,如何才能让echozhu>>/dev/scull0能得到预定的结果? 出于演示的目的,所以本驱动中的open函数比较简单。 其实open函数需要做的事情很多,总结如下: ∙模块使用计数加1。 ∙识别次设备号,如有必要更新f_op指针并调用新f_op指向的结构体中的open函数,以支持驱动拥有相同主设备号但却分属不同类型的设备(不同类型的设备驱动是不一样的)(这主要用于misc类型的设备。 有兴趣的话,请参阅内核源码的drivers/char/misc.c中的misc_open函数、misc_register函数和drivers/char/watchdog/s3c2410_wdt.c的Line418或者阅读“字符设备驱动实战——内核misc设备框架分析”一文)。 ∙填写filp->private_data字段。 ∙硬件操作: o检查设备相关错误(诸如设备未就绪或类似的硬件问题)。 o如果设备是首次打开,则对其初始化。 o如果有中断操作,申请中断处理程序 5、实现release函数 用户程序调用close时,操作系统一般都会调用驱动程序中的scull_release函数,传入的参数inode是i节点表指针,filp是文件表指针。 出于演示的目的,所以本驱动中的release函数简单到不能再简单了。 其实release函数需要做很多与open函数逆向的事情,总结如下: ∙模块使用计数减1 ∙释放放由open分配的,保存在filp->private_data中的所有内容 ∙硬件操作 o如果申请了中断,则释放中断处理程序。 o在最后一次关闭操作时关闭设备 特别说明,release函数被调用的时机: ∙当文件表被释放时,release函数被调用 ∙当用户程序调用close,但文件表并不被释放时(由于用户程序曾调用fork、dup,从而导致FILE结构体引用计数>1),release函数不会被调用。 由此可见,release函数与open函数的被调用次数,应该是相等的,等于设备被open的次数,但小于或等于被close的次数 6、实现read函数 323ssize_tscull_read(structfile*filp,char__user*buf,size_tcount, 324 loff_t*f_pos) 325{ 326 structscull_dev*dev=filp->private_data; 327 structscull_qset*dptr; /*thefirstlistitem*/ 328 intquantum=dev->quantum,qset=dev->qset; 329 intitemsize=quantum*qset;/*howmanybytesinthelistitem*/ 330 intitem,s_pos,q_pos,rest; 331 ssize_tretval=0; 349 if(*f_pos>=dev->size) 350 gotoout; 351 if(*f_pos+count>dev->size) 352 count=dev->size-*f_pos; 354 /*findlistitem,qsetindex,andoffsetinthequantum*/ 355 item=(long)*f_pos/itemsize; 356 rest=(long)*f_pos%itemsize; 357 s_pos=rest/quantum;q_pos=rest%quantum; 359 /*followthelistuptotherightposition(definedelsewhere)*/ 360 dptr=scull_follow(dev,item); 362 if(dptr==NULL|
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 字符 设备 驱动 基本 编程