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

📄 (ldd) ch03-字符设备驱动程序(转载).txt

📁 献给ARM初学者
💻 TXT
📖 第 1 页 / 共 4 页
字号:
      于scull0-3设备被设计为全局的和永久性的,这段代码无需做什么。特别是,由于我们
      无法维护scull的打开计数,也就是模块的使用计数,因此没有类似于“首次打开时初始
      化设备”这类动作。
       
      唯一实际操作在设备上的操作是,当设备写打开时将设备截断为长度0。截断是scull设
      计的一部分:用一个较短的文件覆盖设备,以便缩小设备数据区,这与普通文件写打开
      截断为0很相似。
       
      但“打开是截断”有一个严重的缺点:若干因为某些原因设备内存正在使用,释放这些
      内存会导致系统失效。尽管可能性不大,这种情况总会发生:如果read和write方法在数
      据传输时睡眠了,另一个进程可能写打开这个设备,这时麻烦就来了。处理竞争条件是
      一个相当高级的主题,我将在第9章的“竞争条件”中讲解。scull处理这个问题的简单
      方法就是在内存还是使用时不释放内存,“Scull的内存使用”一节中将说明。
       
      以后我们看到其他scull个体(personality)时将会看到一个真正的初始化工作如何完
      成。
       
      release方法
      release方法的作用正好与open相反。这个设备方法有时也称为close。它应该:
       

       
      l        使用计数减1。
       
      l        释放open分配在filp->private_data中的内存。
       
      l        在最后一次关闭操作时关闭设备。
       
      scull的基本模型无需进行关闭设备动作,所以所需代码是很少的*:
       
      (代码)
       
      使用计数减1是非常重要的,因为如果使用计数不归0,内核是不会卸载模块的。
       
      如果某个时刻一个从没被打开的文件被关闭了计数将如何保证一致呢?我们都知道,dup
      和fork都会在不调用open的情况下,将一个打开文件复制为2个,但每一个都会在程序终
      止时关闭。例如,大多数程序从来不打开它们的stdin文件(或设备),但它们都会在终
      止关闭它。
       
      答案很简单。如果open没有调用,release也不会调。内核维护一个file结构被使用了多
      少次的使用计数。无论是fork还是dup都不创建新的数据结构;它们仅是增加已有结构的
      计数。
       
      新的struct file仅由open创建。只有在该结构的计数归0时close系统调用才会执行clos

      新的struct file仅由open创建。只有在该结构的计数归0时close系统调用才会执行clos
      e方法,这只有在删除这个结构时才会进行。close方法与close系统调用间的关系保证了
      模块使用计数永远是一致的。
       
      Scull的内存使用
      在介绍读写操作以前,我们最好先看看scull如何完成内存分配以及为什么要完成内存分
      配。为了全面理解代码我们需要知道“如何分配”,而“为什么”则反映了驱动程序编
      写者需要做出的选择,尽管scull绝不是一个典型设备,但同样需要。
       
      本节只讲解scull中的内存分配策略,而不会讲解你写实际驱动程序时需要的硬件管理技
      巧。这些技巧将在第8章“硬件管理”和第9章中介绍。因此,如果你对针对内存操作的s
      cull驱动程序的内部工作原理不感兴趣的话,你可以跳过这一节。
       
      scull使用的内存,这里也称为“设备”,是变长的。你写的越多,它就增长得越多;消
      减的过程只在用短文件覆盖设备时发生。
       
      所选的实现scull的方法不是很聪明。实现较聪明的源码会更难读,而且本节的目的只是
      讲解read和write,而不是内存管理。这也就是为什么虽然整个页面分配会更有效,但代
      码只使用了kmalloc和kfree,而没有涉及整个页面的分配的操作。
       
      而另一面,从理论和实际角度考虑,我又不想限制“设备”区的尺寸。理论上将,给所
      管理的数据项强加任何限制总是很糟糕的想法。从实际出发,为了测试系统在内存短缺
      时的性能,scull可以帮助将系统的剩余内存用光。进行这样的测试有助于你理解系统的

      时的性能,scull可以帮助将系统的剩余内存用光。进行这样的测试有助于你理解系统的
      内部行为。你可以使用命令cp /dev/zero /dev/scull用光所有的物理内存,而且你也可
      以用工具dd选择复制到scull设备中多少数据。
       
      在scull中,每个设备都是一组指针的链表,而每一个指针又指向一个Scull_Dev结构。
      每一个这样的结构通过一个中间级指针数组最多可引用4,000,000个字节。发行的源码中
      使用了一个有1000个指针的数组,每个指针指向4000个字节。我把每一个内存区称为一
      个“量子”,数组(或它的长度)称为“量子集”。scull设备和它的内存区如图3-1所
      示。
       
      所选择的数字是这样的,向scull写一个字节就会消耗内存8000了字节:每个量子4个,
      量子集4个(在大多数平台上,一个指针是4个字节;当在Alpha平台编译时量子集本身就
      会耗费8000个字节,在Alpha平台上指针是8个字节)。但另一方面,如果你向scull写大
      量的数据,由于每4MB数据只对应一个表项,而且设备的最大尺寸只限于若干MB,不可能
      超出计算机内存的大小,遍历这张链表的代价不是很大。
       
      为量子和量子集选择合适的数值是一个策略问题,而非机制问题,而且最优数值依赖于
      如何使用设备。源码中为处理这些问题允许用户修改这些值:
       
      l        在编译时,可以修改scull.h中的SCULL_QUANTUM和SCULL_QSET。
       
      l        在加载时,可以利用insmod修改scull_quantum和scull_qset整数值。
       

       
      l        在运行时,用ioctl方法改变默认值和当前值。ioctl将在第5章的“ioctl”一
      节中介绍。
       
      使用宏和整数值进行编译时和加载时配置让人想起前面提到的如何选择主设备号。无论
      何时驱动程序需要一个随意的数值或这个数值与策略相关,我都使用这种技术。
       
      留下来的唯一问题就是如何选择默认数值。尽管有时驱动程序编写者也需要事先调整配
      置参数,但他们在编写自己的模块时不会碰到同样的问题。在这个特殊的例子里,问题
      的关键在于寻找因未填满的量子和量子集导致的内存浪费和量子和量子集太小带来的分
      配、释放和指针连接等操作的代价之间的平衡。
       
      此外,还必须考虑kmalloc的内部设计。现在我们还无法讲述太多的细节,只能简单规定
      “比2次幂稍小一点是最佳尺寸”比较好。kmalloc的内部结构将在第7章“Getting
      Hold of Memory”的“The Real Story of kmalloc”一节中探讨。
       
      默认数值的选择基于这样的假设,大部分程序员不会受限与4MB的物理内存,那样大的数
      据量有可能会写到scull中。一台内存很多的计算机的属主可能因测试向设备写数十MB的
      数据。因此,所选的默认值是为了优化中等规模的系统和大数据量的使用。
       
      保存设备信息的数据机构如下:
       
      (代码)

      (代码)
       
      下面的代码给出了实际工作时是如何利用Scull_Dev保存数据的。其中给出的函数负责释
      放整个数据区,并且在文件写打开时由scull_open调用。如果当前设备内存正在使用,
      该函数就不释放这些内存(象“open方法”中所说那样);否则,它简单地遍历链表,
      释放所有找到的量子和量子集。
       
      (代码)
       
      读和写
      读写scull设备也就意味着要完成内核空间和用户进程空间的数据传输。由于指针只能在
      当前地址空间操作,而驱动程序运行在内核空间,数据缓冲区则在用户空间,这一操作
      不能通过通常利用指针或memcpy完成。
       
      由于驱动程序不过怎样都要在内核空间和用户缓冲区间复制数据,如果目标设备不是RAM
      而是扩展卡,也有同样的问题。事实上,设备驱动程序的主要作用就是管理设备(内核
      空间)和应用(用户空间)间的数据传输。
       
      在Linux里,跨空间复制是通过定义在<asm/segment.h>里的特殊函数实现的。完成这种
      操作函数针对不同数据尺寸(char,short,int,long)进行了优化;它们中的大部分
      将在第5章的“使用ioctl参数”一节中介绍。
       
      scull中read和write的驱动程序代码需要完成到用户空间和来自用户空间的整个数据段

      scull中read和write的驱动程序代码需要完成到用户空间和来自用户空间的整个数据段
      的复制。下面这些提供这些功能,它们可以传输任意字节:
       
      (代码)
       
      这两个函数的名字可以追溯到第1版Linux,那时唯一支持的体系结构是i386,而且C代码
      中还可以窥见许多汇编码。在Intel平台上,Linux通过FS段寄存器访问用户空间,到Lin
      ux 2.0时仍沿用了以前的名字。在Linux 2.1中它们改变了,但是2.0是本书的主要目标
      。详情可见第17章的“访问用户空间”。
       
      尽管上面介绍的函数看起来很象正常的memcpy函数,但当在内核代码中访问用户空间时
      必须额外注意一些问题;正在被访问的用户页面现在可能不在内存中,而且页面失效处
      理函数有可能在传送页面的时候让进程进入睡眠状态。例如,必须从交换区读取页面时
      会发生这种情况。对驱动程序编写者来说,静效果就是对于任何访问用户空间的函数都
      必须是可重入的,而且能够与其他驱动程序函数并发执行。这就是为什么scull实现中不
      允许在dev->usage不为0时释放设备:read和write方法在它们使用memcpy函数前先完成u
      sage计数加1。
       
      现在谈谈实际的设备方法,读方法的任务是将数据从设备复制到用户空间(使用memcpy_
      tofs),而写方法必须将数据从用户空间复制到设备(使用memcpy_tofs)。每一个read
      或write系统调用请求传输一定量的字节,但驱动程序可以随意传送其中一部分数据――
      读与写的具体规则稍有不同。
       

      允许在dev->usage不为0时释放设备:read和write方法在它们使用memcpy函数前先完成u
      sage计数加1。
       
      现在谈谈实际的设备方法,读方法的任务是将数据从设备复制到用户空间(使用memcpy_
      tofs),而写方法必须将数据从用户空间复制到设备(使用memcpy_tofs)。每一个read
      或write系统调用请求传输一定量的字节,但驱动程序可以随意传送其中一部分数据――
      读与写的具体规则稍有不同。
       
      如果有错误发生,read和write都返回一个负值。返回给调用程序一个大于等于0的数值
      ,告诉它成功传输了多少字节。如果某个数据成功地传输了,随后发生了错误,返回值
      必须是成功传输
      --
       
     

⌨️ 快捷键说明

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