[ZLG-ARM] 高级字符驱动程序操作[(1)ioctl and llseek]

[复制链接]
1884|1
 楼主| reeper 发表于 2009-4-9 14:37 | 显示全部楼层 |阅读模式
一、ioctl<br />大部分设备除了读写能力,还可进行超出简单的数据传输之外的操作,所以设备驱动也必须具备进行各种硬件控制操作的能力.&nbsp;这些操作常常通过&nbsp;ioctl&nbsp;方法来支持,它有和用户空间版本不同的原型:<br />int&nbsp;(*ioctl)&nbsp;(struct&nbsp;inode&nbsp;*inode,&nbsp;struct&nbsp;file&nbsp;*filp,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;int&nbsp;cmd,&nbsp;unsigned&nbsp;long&nbsp;arg);<br /><br /><br />需要注意的是:不管可选的参数arg是否由用户给定为一个整数或一个指针,它都以一个unsigned&nbsp;long的形式传递。如果调用程序不传递arg参数,&nbsp;被驱动收到的&nbsp;arg&nbsp;值是未定义的。因为在arg参数上的类型检查被关闭了,所以若一个非法参数传递给&nbsp;ioctl,编译器是无法报警的,且任何关联的错误难以查找.<br /><br />选择ioctl命令<br /><br />为了防止向错误的设备使用正确的命令,命令号应该在系统范围内唯一。为方便程序员创建唯一的&nbsp;ioctl&nbsp;命令代号,&nbsp;每个命令号被划分为多个位字段。要按&nbsp;Linux&nbsp;内核的约定方法为驱动选择&nbsp;ioctl&nbsp;的命令号,&nbsp;应该首先看看&nbsp;include/asm/ioctl.h&nbsp;和&nbsp;Documentation/ioctl-number.txt。&nbsp;要使用的位字段符号定义在&nbsp;:<br /><br />type(幻数):8&nbsp;位宽(_IOC_TYPEBITS),参考ioctl-number.txt选择一个数,并在整个驱动中使用它。<br /><br />number(序数):顺序编号,8&nbsp;位宽(_IOC_NRBITS)。<br /><br />direction(数据传送的方向):可能的值是&nbsp;_IOC_NONE(没有数据传输)、_IOC_READ、&nbsp;_IOC_WRITE和&nbsp;_IOC_READ|_IOC_WRITE&nbsp;(双向传输数据)。该字段是一个位掩码(两位),&nbsp;因此可使用&nbsp;AND&nbsp;操作来抽取_IOC_READ&nbsp;和&nbsp;_IOC_WRITE。<br /><br />size(数据的大小):宽度与体系结构有关,ARM为14位.可在宏&nbsp;_IOC_SIZEBITS&nbsp;中找到特定体系的值.&nbsp;&nbsp;<br /><br />中包含的&nbsp;定义了一些构造命令编号的宏:&nbsp;<br />_IO(type,nr)/*没有参数的命令*/<br />_IOR(type,&nbsp;nr,&nbsp;datatype)/*从驱动中读数据*/<br />_IOW(type,nr,datatype)/*写数据*/<br />_IOWR(type,nr,datatype)/*双向传送*/<br />/*type&nbsp;和&nbsp;number&nbsp;成员作为参数被传递,&nbsp;并且&nbsp;size&nbsp;成员通过应用&nbsp;sizeof&nbsp;到&nbsp;datatype&nbsp;参数而得到*/<br /><br /><br />这个头文件还定义了用来解开这个字段的宏:<br /><br />_IOC_DIR(nr)<br />_IOC_TYPE(nr)<br />_IOC_NR(nr)<br />_IOC_SIZE(nr)<br /><br /><br />具体的使用方法在实验中展示。<br /><br />返回值<br /><br />POSIX&nbsp;标准规定:如果使用了不合适的&nbsp;ioctl&nbsp;命令号,应当返回-ENOTTY&nbsp;。这个错误码被&nbsp;C&nbsp;库解释为'不合适的设备&nbsp;ioctl。然而,它返回-EINVAL仍是相当普遍的。&nbsp;<br /><br />预定义命令<br /><br />有一些ioctl命令是由内核识别的,当这些命令用于自己的设备时,他们会在我们自己的文件操作被调用之前被解码.&nbsp;因此,&nbsp;如果你选择一个ioctl命令编号和系统预定义的相同时,你永远不会看到该命令的请求,而且因为ioctl&nbsp;号之间的冲突,应用程序的行为将无法预测。预定义命令分为&nbsp;3&nbsp;类:<br /><br />(1)用于任何文件(常规,&nbsp;设备,&nbsp;FIFO和socket)&nbsp;的命令<br /><br />(2)只用于常规文件的命令<br /><br />(3)特定于文件系统类型的命令&nbsp;<br /><br />下列&nbsp;ioctl&nbsp;命令是预定义给任何文件,包括设备特定文件:<br /><br />FIOCLEX&nbsp;:设置&nbsp;close-on-exec&nbsp;标志(File&nbsp;IOctl&nbsp;Close&nbsp;on&nbsp;EXec)。&nbsp;<br />FIONCLEX&nbsp;:清除&nbsp;close-no-exec&nbsp;标志(File&nbsp;IOctl&nbsp;Not&nbsp;CLose&nbsp;on&nbsp;EXec)。&nbsp;<br />FIOQSIZE&nbsp;:这个命令返回一个文件或者目录的大小;&nbsp;当用作一个设备文件,&nbsp;但是,&nbsp;它返回一个&nbsp;ENOTTY&nbsp;错误。&nbsp;<br />FIONBIO:'File&nbsp;IOctl&nbsp;Non-Blocking&nbsp;I/O'(在'阻塞和非阻塞操作'一节中描述)。&nbsp;&nbsp;<br />使用ioctl参数<br /><br />在使用ioctl的可选arg参数时,如果传递的是一个整数,它可以直接使用。如果是一个指针,,就必须小心。当用一个指针引用用户空间,&nbsp;我们必须确保用户地址是有效的,其校验(不传送数据)由函数&nbsp;access_ok&nbsp;实现,定义在&nbsp;:&nbsp;int&nbsp;access_ok(int&nbsp;type,&nbsp;const&nbsp;void&nbsp;*addr,&nbsp;unsigned&nbsp;long&nbsp;size);&nbsp;<br /><br /><br /><br /><br />第一个参数应当是&nbsp;VERIFY_READ(读)或VERIFY_WRITE(读写);addr&nbsp;参数为用户空间地址,size&nbsp;为字节数,可使用sizeof()。access_ok&nbsp;返回一个布尔值:&nbsp;1&nbsp;是成功(存取没问题)和&nbsp;0&nbsp;是失败(存取有问题)。如果它返回假,驱动应当返回&nbsp;-EFAULT&nbsp;给调用者。<br /><br />注意:首先,&nbsp;access_ok不做校验内存存取的完整工作;&nbsp;它只检查内存引用是否在这个进程有合理权限的内存范围中,且确保这个地址不指向内核空间内存。其次,大部分驱动代码不需要真正调用&nbsp;access_ok,而直接使用put_user(datum,&nbsp;ptr)和get_user(local,&nbsp;ptr),它们带有校验的功能,确保进程能够写入给定的内存地址,成功时返回&nbsp;0,&nbsp;并且在错误时返回&nbsp;-EFAULT.。&nbsp;put_user(datum,&nbsp;ptr)&nbsp;<br />__put_user(datum,&nbsp;ptr)&nbsp;<br />get_user(local,&nbsp;ptr)&nbsp;<br />__get_user(local,&nbsp;ptr)&nbsp;<br /><br />这些宏它们相对copy_to_user&nbsp;和copy_from_user快,&nbsp;并且这些宏已被编写来允许传递任何类型的指针,只要它是一个用户空间地址.&nbsp;传送的数据大小依赖&nbsp;prt&nbsp;参数的类型,&nbsp;并且在编译时使用&nbsp;sizeof&nbsp;和&nbsp;typeof&nbsp;等编译器内建宏确定。他们只传送1、2、4或8&nbsp;个字节。如果使用以上函数来传送一个大小不适合的值,结果常常是一个来自编译器的奇怪消息,如'coversion&nbsp;to&nbsp;non-scalar&nbsp;type&nbsp;requested'.&nbsp;在这些情况中,必须使用&nbsp;copy_to_user&nbsp;或者&nbsp;copy_from_user。<br /><br />__put_user和__get_user&nbsp;进行更少的检查(不调用&nbsp;access_ok),&nbsp;但是仍然能够失败如果被指向的内存对用户是不可写的,所以他们应只用在内存区已经用&nbsp;access_ok&nbsp;检查过的时候。作为通用的规则:当实现一个&nbsp;read&nbsp;方法时,调用&nbsp;__put_user&nbsp;来节省几个周期,&nbsp;或者当你拷贝几个项时,因此,在第一次数据传送之前调用&nbsp;access_ok&nbsp;一次。<br /><br />权能与受限操作<br /><br />Linux&nbsp;内核提供了一个更加灵活的系统,&nbsp;称为权能(capability)。内核专为许可管理上使用权能并导出了两个系统调用&nbsp;capget&nbsp;和&nbsp;capset,这样可以从用户空间管理权能,其定义在&nbsp;中。对设备驱动编写者有意义的权能如下:&nbsp;CAP_DAC_OVERRIDE&nbsp;/*越过在文件和目录上的访问限制(数据访问控制或&nbsp;DAC)的能力。*/<br /><br />CAP_NET_ADMIN&nbsp;/*进行网络管理任务的能力,&nbsp;包括那些能够影响网络接口的任务*/<br /><br />CAP_SYS_MODULE&nbsp;/*加载或去除内核模块的能力*/<br /><br />CAP_SYS_RAWIO&nbsp;/*进行&nbsp;'raw'(裸)I/O&nbsp;操作的能力.&nbsp;例子包括存取设备端口或者直接和&nbsp;USB&nbsp;设备通讯*/<br /><br />CAP_SYS_ADMIN&nbsp;/*截获的能力,&nbsp;提供对许多系统管理操作的途径*/<br /><br />CAP_SYS_TTY_CONFIG&nbsp;/*执行&nbsp;tty&nbsp;配置任务的能力*/<br /><br /><br /><br />在进行一个特权操作之前,&nbsp;一个设备驱动应当检查调用进程有合适的能力,检查是通过&nbsp;capable&nbsp;函数来进行的(定义在&nbsp;)范例如下:&nbsp;if&nbsp;(!&nbsp;capable&nbsp;(CAP_SYS_ADMIN))<br />return&nbsp;-EPERM;<br /><br /><br /><br /><br />--------------------------------------------------------------------------------<br />二、定位设备(llseek实现)&nbsp;<br /><br />llseek是修改文件中的当前读写位置的系统调用。内核中的缺省的实现进行移位通过修改&nbsp;filp-&gtf_pos,&nbsp;这是文件中的当前读写位置。对于&nbsp;lseek&nbsp;系统调用要正确工作,读和写方法必须通过更新它们收到的偏移量来配合。<br /><br />如果设备是不允许移位的,你不能只制止声明&nbsp;llseek&nbsp;操作,因为缺省的方法允许移位。应当在你的&nbsp;open&nbsp;方法中,通过调用&nbsp;nonseekable_open&nbsp;通知内核你的设备不支持&nbsp;llseek&nbsp;:&nbsp;int&nbsp;nonseekable_open(struct&nbsp;inode&nbsp;*inode;&nbsp;struct&nbsp;file&nbsp;*filp);&nbsp;<br /><br /><br /><br />完整起见,&nbsp;你也应该在你的&nbsp;file_operations&nbsp;结构中设置&nbsp;llseek&nbsp;方法到一个特殊的帮助函数&nbsp;no_llseek(定义在&nbsp;)。&nbsp;具体的应用在试验程序中学习.&nbsp;<br /><br /><br />--------------------------------------------------------------------------------<br />三、ioctl和llseek实验。<br /><br />模块程序链接:ioctl_and_llseek<br />模块测试程序链接:ioctl_and_llseek-test<br /><br />ARM9实验板的实验现象是:<br />[Tekkaman2440@SBC2440V4]#cd&nbsp;/lib/modules/<br />[Tekkaman2440@SBC2440V4]#insmod&nbsp;scull.ko&nbsp;scull_nr_devs=1<br />[Tekkaman2440@SBC2440V4]#cd&nbsp;/tmp/<br />[Tekkaman2440@SBC2440V4]#./scull_test2<br />open&nbsp;scull&nbsp;!<br />SCULL_IOCSQUANTUM-SCULL_IOCQQUANTUM&nbsp;:&nbsp;scull_quantum=10<br />SCULL_IOCTQUANTUM-SCULL_IOCGQUANTUM&nbsp;:&nbsp;scull_quantum=6<br />SCULL_IOCXQUANTUM&nbsp;:&nbsp;scull_quantum=6&nbsp;--&gt&nbsp;10<br />SCULL_IOCHQUANTUM&nbsp;:&nbsp;scull_quantum=10&nbsp;--&gt&nbsp;6<br />SCULL_IOCSQSET-SCULL_IOCQQSET&nbsp;:&nbsp;scull_qset=2<br />SCULL_IOCTQSET-SCULL_IOCGQSET&nbsp;:&nbsp;scull_qset=4<br />SCULL_IOCXQSET&nbsp;:&nbsp;scull_qset=4&nbsp;--&gt&nbsp;2<br />SCULL_IOCHQSET&nbsp;:&nbsp;scull_qset=2&nbsp;--&gt&nbsp;4<br />before&nbsp;reset&nbsp;:&nbsp;scull_quantum=6&nbsp;scull_qset=4<br />close&nbsp;scull&nbsp;!<br />reopen&nbsp;scull&nbsp;!<br />reopen&nbsp;:&nbsp;scull_quantum=6&nbsp;scull_qset=4<br />write&nbsp;code=6&nbsp;i=20<br />write&nbsp;code=6&nbsp;i=14<br />write&nbsp;code=6&nbsp;i=8<br />write&nbsp;code=2<br />lseek&nbsp;scull&nbsp;SEEK_SET--&gt0&nbsp;!<br />read&nbsp;code=6&nbsp;i=20<br />read&nbsp;code=6&nbsp;i=14<br />read&nbsp;code=6&nbsp;i=8<br />read&nbsp;code=2<br />[0]=0&nbsp;[1]=1&nbsp;[2]=2&nbsp;[3]=3&nbsp;[4]=4<br />[5]=5&nbsp;[6]=6&nbsp;[7]=7&nbsp;[8]=8&nbsp;[9]=9<br />[10]=10&nbsp;[11]=11&nbsp;[12]=12&nbsp;[13]=13&nbsp;[14]=14<br />[15]=15&nbsp;[16]=16&nbsp;[17]=17&nbsp;[18]=18&nbsp;[19]=19<br />SCULL_IOCRESET<br />after&nbsp;reset&nbsp;:&nbsp;scull_quantum=4000&nbsp;scull_qset=1000<br />close&nbsp;scull&nbsp;!<br />reopen&nbsp;scull&nbsp;!<br />write&nbsp;code=20<br />lseek&nbsp;scull&nbsp;SEEK_CUR-10--&gt10&nbsp;!<br />read&nbsp;code=10<br />[0]=10&nbsp;[1]=11&nbsp;[2]=12&nbsp;[3]=13&nbsp;[4]=14<br />[5]=15&nbsp;[6]=16&nbsp;[7]=17&nbsp;[8]=18&nbsp;[9]=19<br />lseek&nbsp;scull&nbsp;SEEK_END-20--&gt0&nbsp;!<br />read&nbsp;code=20<br />[0]=0&nbsp;[1]=1&nbsp;[2]=2&nbsp;[3]=3&nbsp;[4]=4<br />[5]=5&nbsp;[6]=6&nbsp;[7]=7&nbsp;[8]=8&nbsp;[9]=9<br />[10]=10&nbsp;[11]=11&nbsp;[12]=12&nbsp;[13]=13&nbsp;[14]=14<br />[15]=15&nbsp;[16]=16&nbsp;[17]=17&nbsp;[18]=18&nbsp;[19]=19<br />close&nbsp;scull&nbsp;!<br /><br />[Tekkaman2440@SBC2440V4]#cat&nbsp;/proc/scullseq<br /><br />Device&nbsp;0:&nbsp;qset&nbsp;1000,&nbsp;q&nbsp;4000,&nbsp;sz&nbsp;20<br />&nbsp;&nbsp;item&nbsp;at&nbsp;c3dd3d74,&nbsp;qset&nbsp;at&nbsp;c3f54000<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0:&nbsp;c3e71000<br />[Tekkaman2440@SBC2440V4]#
zcying 发表于 2009-4-9 14:39 | 显示全部楼层

这些都是深入的讲解了!有些深奥了啊

  
您需要登录后才可以回帖 登录 | 注册

本版积分规则

139

主题

185

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部