⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 (ldd) ch05-字符设备驱动程序的扩展操作.txt

📁 献给ARM初学者
💻 TXT
📖 第 1 页 / 共 4 页
字号:
(LDD) Ch05-字符设备驱动程序的扩展操作

      第5章 字符设备驱动程序的扩展操作
       
       
      在关于字符设备驱动程序的那一章中,我们构建了一个完整的设备驱动程序,从中用户
      可以读也可以写。但实际一个驱动程序通常会提供比同步read和write更多的功能。现在
      如果出了什么毛病,我已经配备了调试工具,我们可以大胆的实验并实现新操作。
       
      通过补充设备读写操作的功能之一就是控制硬件,最常用的通过设备驱动程序完成控制
      动作的方法就是实现ioctl方法。另一种方法是检查写到设备中的数据流,使用特殊序列
      做为控制命令。尽管有时也使用后者,但应该尽量避免这样使用。不过稍后我们还是会
      在本章的“非ioctl设备控制”一节中介绍这项技术。
       
      正如我在前一章中所猜想的,ioctl系统调用为驱动程序执行“命令”提供了一个设备相
      关的入口点。与read和其他方法不同,ioctl是设备相关的,它允许应用程序访问被驱动
      硬件的特殊功能――配置设备以及进入或退出操作模式。这些“控制操作”通常无法通

      硬件的特殊功能――配置设备以及进入或退出操作模式。这些“控制操作”通常无法通
      过read/write文件操作完成。例如,你向串口写的所有数据都通过串口发送出去了,你
      无法通过写设备改变波特率。这就是ioctl所要做的:控制I/O通道。
       
      实际设备(与scull不同)的另一个重要功能是,读或写的数据需要同其他硬件交互,需
      要某些同步机制。阻塞型I/O和异步触发的概念将满足这些需求,本章将通过一个改写的
      scull设备介绍这些内容。驱动程序利用不同进程间的交互产生异步事件。与最初的scul
      l相同,你无需特殊硬件来测试驱动程序是否可以工作。直到第8章“硬件管理”我才会
      真正去与硬件打交道。
       
      ioctl
      在用户空间内调用ioctl函数的原型大致如下:
       
      (代码)
       
      由于使用了一连串的“.”的缘故,该原型在Unix系统调用列表之中非常突出,这些点代
      表可变数目参数。但是在实际系统中,系统调用实际上不会有可变数目个参数。因为用
      户程序只能通过第2章“编写和运行模块”的“用户空间和内核空间”一节中介绍的硬件
      “门”才能访问内核,系统调用必须有精确定义的参数个数。因此,ioctl的第3个参数
      事实上只是一个可选参数,这里用点只是为了在编译时防止编译器进行类型检查。第3个
      参数的具体情况与要完成的控制命令(第2个参数)有关。某些命令不需要参数,某些需
      要一个整数做参数,而某些则需要一个指针做参数。使用指针通常是可以用来向ioctl传
      递任意数目数据;设备可以从用户空间接收任意大小的数据。

      递任意数目数据;设备可以从用户空间接收任意大小的数据。
       
      系统调用的参数根据方法的声明传递给驱动程序方法:
       
      (代码)
       
      inode和filp指针是根据应用程序传递的文件描述符fd计算而得的,与read和write的用
      法一致。参数cmd不经修改地传递给驱动程序,可选的arg参数无论是指针还是整数值,
      它都以unsigned long的形式传递给驱动程序。如果调用程序没有传递第3个参数,驱动
      程序所接收的arg没有任何意义。
       
      由于附加参数的类型检查被关闭了,如果非法参数传递给ioctl,编译器无法向你报警,
      程序员在运行前是无法注意这个错误的。这是我所见到的ioctl语义方面的唯一一个问题
       
      如你所想,大多数ioctl实现都包括一个switch语句来根据cmd参数选择正确的操作。不
      同的命令对应不同的数值,为了简化代码我们通常会使用符号名代替数值。这些符号名
      都是在预处理中赋值的。不同的驱动程序通常会在它们的头文件中声明这些符号;scull
      就在scull.h中声明了这些符号。
       
      选择ioctl命令
      在编写ioctl代码之前,你需要选择对应不同命令的命令号。遗憾的是,简单地从1开始
      选择号码是不能奏效的。

      选择号码是不能奏效的。
       
      为了防止对错误的设备使用正确的命令,命令号应该在系统范围内是唯一的。这种失配
      并不是不很容易发生,程序可能发现自己正在对象FIFO和kmouse这类非串口输入流修改
      波特率。如果每一个ioctl命令都是唯一的,应用程序就会获得一个EINVAL错误,而不是
      无意间成功地完成了操作。
       
      为了达到唯一性的目的,每一个命令号都应该由多个位字段组成。Linux的第一版使用了
      一个16位整数:高8位是与设备相关的“幻”数,低8位是一个序列号码,在设备内是唯
      一的。这是因为,用Linus的话说,他有点“无头绪”,后来才接收了一个更好的位字段
      分割方案。遗憾的是,很少有驱动程序使用新的约定,这就挫伤了程序员使用新约定的
      热情。在我的源码中,为了发掘这种约定都提供了那些功能,同时防止被其他开发人员
      当成异教徒而禁止,我使用了新的定义命令的方法。
       
      为了给我的驱动程序选择ioctl号,你应该首先看看include/asm/ioctl.h和Documentati
      on/ioctl-number.txt这两个文件。头文件定义了位字段:类型(幻数),基数,传送方
      向,参数的尺寸等等。ioctl-number.txt文件中罗列了在内核中使用的幻数。这个文件
      的新版本(2.0以及后继内核)也给出了为什么应该使用这个约定的原因。
       
      很不幸,在1.2.x中发行的头文件没有给出切分ioctl位字段宏的全集。如果你需要象我
      的scull一样使用这种新方法,同时还要保持向后兼容性,你使用scull/sysdep.h中的若
      干代码行,我在那里给出了解决问题的文档的代码。
       

       
      现在已经不赞成使用的选择ioctl号码的旧方法非常简单:选择一个8位幻数,比如“k”
      (十六进制为0x6b),然后加上一个基数,就象这样:
       
      (代码)
       
      如果应用程序和驱动程序都使用了相同的号码,你只要在驱动程序里实现switch语句就
      可以了。但是,这种在传统Unix中有基础的定义ioctl号码的方法,不应该再在新约定中
      使用。这里我介绍就方法只是想给你看看一个ioctl号码大致是个什么样子的。
       
      新的定义号码的方法使用了4个位字段,它们有如下意义。下面我所介绍的新符号都定义
      在<linux/ioctl.h>中。
       
      类型
       
      幻数。选择一个号码,并在整个驱动程序中使用这个号码。这个字段有8位宽(_IOC_TYP
      EBITS)。
       
      号码
       
      基(序列)数。它也是8位宽(_IOC_NRBITS)。
       
      方向

      方向
       
      如果该命令有数据传输,它定义数据传输的方向。可以使用的值有,_IOC_NONE(没有数
      据传输),_IOC_READ,_IOC_WRITE和_IOC_READ | _IOC_WRITE(双向传输数据)。数据
      传输是从应用程序的角度看的;IOC_READ意味着从设备中读数据,驱动程序必须向用户
      空间写数据。注意,该字段是一个位屏蔽码,因此可以用逻辑AND操作从中分解出_IOC_R
      EAD和_IOC_WRITE。
       
      尺寸
       
      所涉及的数据大小。这个字段的宽度与体系结构有关,当前的范围从8位到14位不等。你
      可以在宏_IOC_SIZEBITS中找到某种体系结构的具体数值。不过,如果你想要你的驱动程
      序可移植,你只能认为最大尺寸可达255个字节。系统并不强制你使用这个字段。如果你
      需要更大尺度的数据传输,你可以忽略这个字段。下面我们将介绍如何使用这个字段。
       
      包含在<linux/ioctl.h>之中的头文件<asm/ioctl.h>定义了可以用来构造命令号码的宏
      :_IO(type,nr),_IOR(type,nr,size),_IOW(type,nr,size)和IOWR(type,nr,size)。
      每一个宏都对应一种可能的数据传输方向,其他字段通过参数传递。头文件还定义了解
      码宏:_IOC_DIR(nr),_IOC_TYPE(nr),_IOC_NR(nr)和_IOC_SIZE(nr)。我不打算详细介
      绍这些宏,头文件里的定义已经足够清楚了,本节稍后会给出样例。
       
      这里是scull中如果定义ioctl命令的。特别地,这些命令设置并获取驱动程序的配置参
      数。在标准的宏定义中,要传送的数据项的尺寸有数据项自身的实例代表,而不是sizeo

      数。在标准的宏定义中,要传送的数据项的尺寸有数据项自身的实例代表,而不是sizeo
      f(item),这是因为sizeof是宏扩展后的一部分。
       
      (代码)
       
      最后一条命令,HARDRESET,用来将模块使用计数器复位为0,这样就可以在计数器发生
      错误时就可以卸载模块了。实际的源码定义了从IOCHQSET到HARDRESET间的所有命令,但
      这里没有列出。
       
      我选择用两种方法实现整数参数传 莰D―通过指针和显式数值,尽管根据已有的约定,i
      octl应该使用指针完成数据交换。同样,这两种方法还用于返回整数:通过指针和设置
      返回值。如果返回值是正的,这就可以工作;对与任何一个系统调用的返回值,正值是
      受保护的(如我们在read和write所见到的),而负值则被认为是一个错误值,用其设置
      用户空间中的errno变量。
       
      “交换”和“移位”操作并不专用于scull设备。我实现“交换”操作是为了给出“方向
      ”字段的所有可能值,而“移位”操作则将“告知”和“查询”操作组合在一起。某些
      时候是需要原子性*测试兼设置这类操作的――特别是当应用程序需要加锁和解锁时。
       
      显式的命令基数没有什么特殊意义。它只是用来区分命令的。事实上,由于ioctl号码的
      “方向”为会有所不同,你甚至可以在读命令和写命令中使用同一个基数。我选择除了
      在声明中使用基数外,其他地方都不使用它,这样我就不必为符号值赋值了。这也是为
      什么显式的号码出现在上面的定义中。我只是向你介绍一种使用命令号码的方法,你可

      什么显式的号码出现在上面的定义中。我只是向你介绍一种使用命令号码的方法,你可
      以自由地采用不同的方法使用它。
       
      当前,参数cmd的值内核并没有使用,而且以后也不可能使用。因此,如果你想偷懒,你
      可以省去上面那些复杂的声明,而直接显式地使用一组16位数值。但另一方面,如果你
      这样做了,你就无法从使用位字段中受益了。头文件<linux/kd.h>就是这种旧风格方法
      的例子,但是它们并不是因为偷懒才这样做的。修改这个文件需要重新编译许多应用程
      序。
       

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -