您好,欢迎访问三七文档
当前位置:首页 > 商业/管理/HR > 管理学资料 > Linux设备驱动程序中的代码分析--并发和竞争情况
LinuxDeviceDriver书籍(6)并发和竞争情况(2008-09-2816:28)分类:LDD3第6章高级字符驱动操作在第3章,我们建立了一个完整的设备驱动,用户可用来写入和读取.但是一个真正的设备常常提供比同步读和写更多的功能.现在我们已装备有调试工具如果发生错误,并且一个牢固的并发的理解来帮助避免事情进入错误--我们可安全地前进并且创建一个更高级的驱动.本章检查几个你需要理解的概念来编写全特性的字符设备驱动.我们从实现ioctl系统调用开始,它是用作设备控制的普通接口.接着我们进入各种和用户空间同步的方法;在本章结尾,你有一个充分的认识对于如何使进程睡眠(并且唤醒它们),实现非阻塞的I/O,并且通知用户空间当你的设备可用来读或写.我们以查看如何在驱动中实现几个不同的设备存取策略来结束.这里讨论的概念通过scull驱动的几个修改版本来演示.再一次,所有的都使用内存中的虚拟设备来实现,因此你可自己试验这些代码而不必使用任何特别的硬件.到此为止,你可能在想亲手使用真正的硬件,但是那将必须等到第9章.6.1.ioctl接口大部分驱动需要--除了读写设备的能力--通过设备驱动进行各种硬件控制的能力.大部分设备可进行超出简单的数据传输之外的操作;用户空间必须常常能够请求,例如,设备锁上它的门,弹出它的介质,报告错误信息,改变波特率,或者自我销毁.这些操作常常通过ioctl方法来支持,它通过相同名子的系统调用来实现.在用户空间,ioctl系统调用有下面的原型:intioctl(intfd,unsignedlongcmd,...);这个原型由于这些点而凸现于Unix系统调用列表,这些点常常表示函数有数目不定的参数.在实际系统中,但是,一个系统调用不能真正有变数目的参数.系统调用必须有一个很好定义的原型,因为用户程序可存取它们只能通过硬件的门.因此,原型中的点不表示一个变数目的参数,而是一个单个可选的参数,传统上标识为char*argp.这些点在那里只是为了阻止在编译时的类型检查.第3个参数的实际特点依赖所发出的特定的控制命令(第2个参数).一些命令不用参数,一些用一个整数值,以及一些使用指向其他数据的指针.使用一个指针是传递任意数据到ioctl调用的方法;设备接着可与用户空间交换任何数量的数据.ioctl调用的非结构化特性使它在内核开发者中失宠.每个ioctl命令,基本上,是一个单独的,常常无文档的系统调用,并且没有方法以任何类型的全面的方式核查这些调用.也难于使非结构化的ioctl参数在所有系统上一致工作;例如,考虑运行在32-位模式的一个用户进程的64-位系统.结果,有很大的压力来实现混杂的控制操作,只通过任何其他的方法.可能的选择包括嵌入命令到数据流(本章稍后我们将讨论这个方法)或者使用虚拟文件系统,要么是sysfs要么是设备特定的文件系统.(我们将在14章看看sysfs).但是,事实是ioctl常常是最容易的和最直接的选择,对于真正的设备操作.ioctl驱动方法有和用户空间版本不同的原型:int(*ioctl)(structinode*inode,structfile*filp,unsignedintcmd,unsignedlongarg);inode和filp指针是对应应用程序传递的文件描述符fd的值,和传递给open方法的相同参数.cmd参数从用户那里不改变地传下来,并且可选的参数arg参数以一个unsignedlong的形式传递,不管它是否由用户给定为一个整数或一个指针.如果调用程序不传递第3个参数,被驱动操作收到的arg值是无定义的.因为类型检查在这个额外参数上被关闭,编译器不能警告你如果一个无效的参数被传递给ioctl,并且任何关联的错误将难以查找.如果你可能想到的,大部分ioctl实现包括一个大的switch语句来根据cmd参数,选择正确的做法.不同的命令有不同的数值,它们常常被给予符号名来简化编码.符号名通过一个预处理定义来安排.定制的驱动常常声明这样的符号在它们的头文件中;scull.h为scull声明它们.用户程序必须,当然,包含那个头文件来存取这些符号.6.1.1.选择ioctl命令在为ioctl编写代码之前,你需要选择对应命令的数字.许多程序员的第一个本能的反应是选择一组小数从0或1开始,并且从此开始向上.但是,有充分的理由不这样做.ioctl命令数字应当在这个系统是唯一的,为了阻止向错误的设备发出正确的命令而引起的错误.这样的不匹配不会不可能发生,并且一个程序可能发现它自己试图改变一个非串口输入系统的波特率,例如一个FIFO或者一个音频设备.如果这样的ioctl号是唯一的,这个应用程序得到一个EINVAL错误而不是继续做不应当做的事情.为帮助程序员创建唯一的ioctl命令代码,这些编码已被划分为几个位段.Linux的第一个版本使用16-位数:高8位是关联这个设备的魔数,低8位是一个顺序号,在设备内唯一.这样做是因为Linus是无能的(他自己的话);一个更好的位段划分仅在后来被设想.不幸的是,许多驱动仍然使用老传统.它们不得不:改变命令编码会破坏大量的二进制程序,并且这不是内核开发者愿意见到的.根据Linux内核惯例来为你的驱动选择ioctl号,你应当首先检查include/asm/ioctl.h和Documentation/ioctl-number.txt.这个头文件定义你将使用的位段:type(魔数),序号,传输方向,和参数大小.ioctl-number.txt文件列举了在内核中使用的魔数,[20]因此你将可选择你自己的魔数并且避免交叠.这个文本文件也列举了为什么应当使用惯例的原因.定义ioctl命令号的正确方法使用4个位段,它们有下列的含义.这个列表中介绍的新符号定义在linux/ioctl.h.type魔数.只是选择一个数(在参考了ioctl-number.txt之后)并且使用它在整个驱动中.这个成员是8位宽(_IOC_TYPEBITS).number序(顺序)号.它是8位(_IOC_NRBITS)宽.direction数据传送的方向,如果这个特殊的命令涉及数据传送.可能的值是_IOC_NONE(没有数据传输),_IOC_READ,_IOC_WRITE,和_IOC_READ|_IOC_WRITE(数据在2个方向被传送).数据传送是从应用程序的观点来看待的;_IOC_READ意思是从设备读,因此设备必须写到用户空间.注意这个成员是一个位掩码,因此_IOC_READ和_IOC_WRITE可使用一个逻辑AND操作来抽取.size涉及到的用户数据的大小.这个成员的宽度是依赖体系的,但是常常是13或者14位.你可为你的特定体系在宏_IOC_SIZEBITS中找到它的值.你使用这个size成员不是强制的-内核不检查它--但是它是一个好主意.正确使用这个成员可帮助检测用户空间程序的错误并使你实现向后兼容,如果你曾需要改变相关数据项的大小.如果你需要更大的数据结构,但是,你可忽略这个size成员.我们很快见到如何使用这个成员.头文件asm/ioctl.h,它包含在linux/ioctl.h中,定义宏来帮助建立命令号,如下:_IO(type,nr)(给没有参数的命令),_IOR(type,nre,datatype)(给从驱动中读数据的),_IOW(type,nr,datatype)(给写数据),和_IOWR(type,nr,datatype)(给双向传送).type和number成员作为参数被传递,并且size成员通过应用sizeof到datatype参数而得到.这个头文件还定义宏,可被用在你的驱动中来解码这个号:_IOC_DIR(nr),_IOC_TYPE(nr),_IOC_NR(nr),和_IOC_SIZE(nr).我们不进入任何这些宏的细节,因为头文件是清楚的,并且在本节稍后有例子代码展示.这里是一些ioctl命令如何在scull被定义的.特别地,这些命令设置和获得驱动的可配置参数./*Use'k'asmagicnumber*/#defineSCULL_IOC_MAGIC'k'/*Pleaseuseadifferent8-bitnumberinyourcode*/#defineSCULL_IOCRESET_IO(SCULL_IOC_MAGIC,0)/**SmeansSetthroughaptr,*TmeansTelldirectlywiththeargumentvalue*GmeansGet:replybysettingthroughapointer*QmeansQuery:responseisonthereturnvalue*XmeanseXchange:switchGandSatomically*HmeanssHift:switchTandQatomically*/#defineSCULL_IOCSQUANTUM_IOW(SCULL_IOC_MAGIC,1,int)#defineSCULL_IOCSQSET_IOW(SCULL_IOC_MAGIC,2,int)#defineSCULL_IOCTQUANTUM_IO(SCULL_IOC_MAGIC,3)#defineSCULL_IOCTQSET_IO(SCULL_IOC_MAGIC,4)#defineSCULL_IOCGQUANTUM_IOR(SCULL_IOC_MAGIC,5,int)#defineSCULL_IOCGQSET_IOR(SCULL_IOC_MAGIC,6,int)#defineSCULL_IOCQQUANTUM_IO(SCULL_IOC_MAGIC,7)#defineSCULL_IOCQQSET_IO(SCULL_IOC_MAGIC,8)#defineSCULL_IOCXQUANTUM_IOWR(SCULL_IOC_MAGIC,9,int)#defineSCULL_IOCXQSET_IOWR(SCULL_IOC_MAGIC,10,int)#defineSCULL_IOCHQUANTUM_IO(SCULL_IOC_MAGIC,11)#defineSCULL_IOCHQSET_IO(SCULL_IOC_MAGIC,12)#defineSCULL_IOC_MAXNR14真正的源文件定义几个额外的这里没有出现的命令.我们选择实现2种方法传递整数参数:通过指针和通过明确的值(尽管,由于一个已存在的惯例,ioclt应当通过指针交换值).类似地,2种方法被用来返回一个整数值:通过指针和通过设置返回值.这个有效只要返回值是一个正的整数;如同你现在所知道的,在从任何系统调用返回时,一个正值被保留(如同我们在read和write中见到的),而一个负值被看作一个错误并且被用来在用户空间设置errno.[21]exchange和shift操作对于scull没有特别的用处.我们实现exchange来显示驱动如何结合独立的操作到单个的原子的操作,并且shift来连接tell和query.有时需要象这样的原子的测试-和-设置操作,特别地,当应用程序需要设置和释放锁.命令的明确的序号没有特别的含义.它只用来区分命令.实际上,你甚至可使用相同的序号给一个读命令和一个写命令,因为实际的ioctl号在方向位是不同的,但是你没有理由这样做.我们选择在任何地方不使用命令的序号除了声明中,因此我们不分配一个返回值给它.这就是为什么明确的号出现在之前给定的定义中.这个例子展示了一个使用命令号的方法,但是你有自由不这样做.除了少数几个预定义的命令(马上就讨论),ioctl的cmd参数的值当前不被内核使用,并且在将来也很不可能.因此,你可以,如果你觉得懒,避免前面展示的复杂的声明并明确声明一组调整数字.另一方面,如果你做了,你不会从使用这些位段中获益,并且你会遇到困难如果你曾提交你的代码来包含在主线内核中.头文件linux/kd.h是这个老式方法的例子,使用16-位的调整值来定义ioctl命令.那个源代码依靠调整数因为使用那个时候遵循的惯例,不是由于
本文标题:Linux设备驱动程序中的代码分析--并发和竞争情况
链接地址:https://www.777doc.com/doc-1307021 .html