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

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

📁 献给ARM初学者
💻 TXT
📖 第 1 页 / 共 4 页
字号:
(LDD) Ch03-字符设备驱动程序(转载)

第3章 字符设备驱动程序
       
       
      本章的目标是编写一个完整的字符设备驱动程序。由于这类驱动程序适合于大多数简单
      的硬件设备,我们首先开放一个字符设备驱动程序。字符也相对比较好理解,比如说块
      设备驱动程序。我们的最终目标是写一个模块化的字符设备驱动程序,但本章我们不再
      讲述有关模块化的问题。
       
      本章通篇都是从一个真实的设备驱动程序截取出的代码块:这个设备就是scull,是“Si
      mple Character Utility for Loading Localities”的缩写。尽管scull是一个设备,
      但它却是操作内存的字符设备。这种情况的一个副作用就是,只要涉及scull,“设备”
      这个词就可以同“scull使用的内存区”互换使用。
       
      scull的优点是,由于每台电脑都有内存,所以它与硬件无关。scull用kmalloc分配内存
      ,而且仅仅操作内存。任何人都可以编译和运行scull,而且scull可以移植到所有Linux

      ,而且仅仅操作内存。任何人都可以编译和运行scull,而且scull可以移植到所有Linux
      支持的平台上。但另一方面,除了演示内核于字符设备驱动程序间的交互过程,可以让
      用户运行某些测试例程外,scull做不了“有用的”事。
       
      scull的设计
      编写设备驱动程序的第一步就是定义驱动程序提供给用户程序的能力(“机制”)。由
      于我们的“设备”是电脑内存的一部分,我做什么都可以。它可以是顺便存取设备,也
      可以是随机存取设备,可以是一个设备,也可以是多个,等等。
       
      为了是scull更有用,可以成为编写真实设备的驱动程序的模板,我将向你展示如何在电
      脑的内存之上实现若干设备抽象操作,每一种操作都有自己的特点。
       
      scull的源码实现如下设备。由模块实现的每一种设备都涉及一种类型:
       
      scull0-3
       
      4个设备,共保护了4片内存区,都是全局性的和持久性的。“全局性”是指,如果打开
      设备多次,所有打开它的文件描述符共享其中的数据。“持久性”是指,如果设备关闭
      后再次打开,数据不丢失。由于可以使用常用命令访问这个设备,如cp,cat以及shell
      I/O重定向等,这个设备操作非常有趣;本章将深入探讨它的内部结构。
       
      scullpipe0-3
       

       
      4个“fifo”设备,操作起来有点象管道。一个进程读取另一个进程写入的数据。如果有
      多个进程读同一个设备,他们彼此间竞争数据。通过scullpipe的内部结构可以了解阻塞
      型和非阻塞型读/写是如何实现的;没有中断也会出现这样的情况。尽管真实的驱动程序
      利用中断与它们的设备同步,但阻塞型和非阻塞型操作是非常重要的内容,从概念上讲
      与中断处理(第9章,中断处理,介绍)无关。
       
      scullsingle
       
      scullpriv
       
      sculluid
       
      scullwuid
       
      这些设备与scull0相似,但在何时允许open操作时都不同方式的限制。第一个(scullsi
      ngle)只允许一次一个进程使用驱动程序,而scullpriv对每个虚拟控制台是私有的(每
      个设备对虚拟控制台是私有的)。sculluid和scullwuid可以多次打开,但每次只能有一
      个用户;如果另一个用户锁住了设备,前者返回-EBUSY,而后者则实现为阻塞型open。
      通过这些可以展示如何实现不同的访问策略。
       
      每一个scull设备都展示了驱动程序不同的功能,而且都不同的难度。本章主要讲解scul
      l0-3的内部结构;第5章,字符设备驱动程序的扩展操作,将介绍更复杂的设备: “一

      l0-3的内部结构;第5章,字符设备驱动程序的扩展操作,将介绍更复杂的设备: “一
      个样例实现:scullpipe”介绍scullpipe,“设备文件的访问控制”介绍其他设备。
       
      主设备号和次设备号
      通过访问文件系统的名字(或“节点”)访问字符设备,通常这些文件位于/dev目录。
      设备文件是特殊文件,这一点可以通过ls -l输出的第一列中的“c”标明,它说明它们
      是字符节点。/dev下还有块设备,但它们的第一列是“b”;尽管如下介绍的某些内容也
      同样适用于块设备,现在我们只关注字符设备。如果你执行ls命令,在设备文件条目的
      最新修改日期前你会看到两个数(用逗号分隔),这个位置通常显示文件长度。这些数
      就是相应设备的主设备号和次设备号。下面的列表给出了我使用的系统上的一些设备。
      它们的主设备号是10,1和4,而次设备号是0,3,5,64-65和128-129。
       
      (代码)
       
      主设备号标识设备对应的驱动程序。例如,/dev/null和/dev/zero都有驱动程序1管理,
      而所有的tty和pty都由驱动程序4管理。内核利用主设备号将设备与相应的驱动程序对应
      起来。
       
      次设备号只由设备驱动程序使用;内核的其他部分不使用它,仅将它传递给驱动程序。
      一个驱动程序控制若干个设备并不为奇(如上面的例子所示)――次顺便号提供了一种
      区分它们的方法。
       
      向系统增加一个驱动程序意味着要赋予它一个主设备号。这一赋值过程应该在驱动程序

      向系统增加一个驱动程序意味着要赋予它一个主设备号。这一赋值过程应该在驱动程序
      (模块)的初始化过程中完成,它调用如下函数,这个函数定义在<linux/fs.h>:
       
      (代码)
       
      返回值是错误码。当出错时返回一个负值;成功时返回零或正值。参数major是所请求的
      主设备号,name是你的设备的名字,它将在/proc/devices中出现,fops是一个指向跳转
      表的指针,利用这个跳转表完成对设备函数的调用,本章稍后将在“文件操作”一节中
      介绍这些函数。
       
      主设备号是一个用来索引静态字符设备数组的整数。在1.2.13和早期的2.x内核中,这个
      数组有64项,而2.0.6到2.1.11的内核则升至128。由于只有设备才处理次设备号,regis
      ter_chrdev不传递次设备号。
       
      一旦设备已经注册到内核表中,无论何时操作与你的设备驱动程序的主设备号匹配的设
      备文件,内核都会通过在fops跳转表索引调用驱动程序中的正确函数。
       
      接下来的问题就是如何给程序一个它们可以请求你的设备驱动程序的名字。这个名字必
      须插入到/dev目录中,并与你的驱动程序的主设备号和次设备号相连。
       
      在文件系统上创建一个设备节点的命令是mknod,而且你必须是超级用户才能创建设备。
      除了要创建的节点名字外,该命令还带三个参数。例如,命令:
       

       
      (代码)
       
      创建一个字符设备(c),主设备号是127,次设备号是0。由于历史原因,次设备号应该
      在0-255范围内,有时它们存储在一个字节中。存在很多原因扩展可使用的次设备号的范
      围,但就现在而言,仍然有8位限制。
       
      动态分配主设备号
      某些主设备号已经静态地分配给了大部分公用设备。在内核源码树的Documentation/dev
      ice.txt文件中可以找到这些设备的列表。由于许多数字已经分配了,为新设备选择一个
      唯一的号码是很困难的――不同的设备要不主设备号多得多。
       
      很幸运(或是感谢某些人天才),你可以动态分配主设备号了。如果你调用register_ch
      rdev时的major为零的话,这个函数就会选择一个空闲号码并做为返回值返回。主设备号
      总是正的,因此不会和错误码混淆。
       
      我强烈推荐你不要随便选择一个一个当前不用的设备号做为主设备号,而使用动态分配
      机制获取你的主设备号。
       
      动态分配的缺点是,由于分配给你的主设备号不能保证总是一样的,无法事先创建设备
      节点。然而这不是什么问题,这是因为一旦分配了设备号,你就可以从/proc/devices读
      到。为了加载一个设备驱动程序,对insmod的调用被替换为一个简单的脚本,它通过/pr
      oc/devices获得新分配的主设备号,并创建节点。

      oc/devices获得新分配的主设备号,并创建节点。
       
      /proc/devices一般如下所示:
       
      (代码)
       
      加载动态分配主设备号驱动程序的脚本可以利用象awk这类工具从/proc/devices中获取
      信息,并在/dev中创建文件。
       
      下面这个脚本,scull_load,是scull发行中的一部分。使用以模块形式发行的驱动程序
      的用户可以在/etc/rc.d/rc.local中调用这个脚本,或是在需要模块时手工调用。此外
      还有另一种方法:使用kerneld。这个方法和其他模块的高级功能将在第11章“Kerneld
      和高级模块化”中介绍。
       
      (代码)
       
      这个脚本同样可以适用于其他驱动程序,只要重新定义变量和调整mknod那几行就可以了
      。上面那个脚本创建4个设备,4是scull源码中的默认值。
       
      脚本的最后两行看起来有点怪怪的:为什么要改变设备的组和权限呢?原因是这样的,
      由root创建的节点自然也属于root。默认权限位只允许root对其有写访问权,而其他只
      有读权限。正常情况下,设备节点需要不同的策略,因此需要进行某些修改。通常允许
      一组用户访问对设备,但实现细节却依赖于设备和系统管理员。安全是个大问题,这超

      一组用户访问对设备,但实现细节却依赖于设备和系统管理员。安全是个大问题,这超
      出了本书的范围。scull_load中的chmod和chgrp那两行仅仅是最为处理权限问题的一点
      提示。稍后,在第5章的“设备文件的访问控制”一节中将介绍sculluid源码,展示设备
      驱动程序如何实现自己的设备访问授权。
       
      如果重复地创建和删除/dev节点似乎有点过分的话,有一个解决的方法。如果你看了内
      核源码fs/devices.c的话,你可以看到动态设备号是从127(或63)之后开始的,你可以
      用127做为主设备号创建一个长命节点,同时可以避免在每次相关设备加载时调用脚本。
      如果你使用了几个动态设备,或是新版本的内核改变了动态分配的特性,这个技巧就不
      能用了。(如果内核发生了修改,基于内核内部结构编写的代码并不能保证继续可以工
      作。)不管怎样,由于开发期间模块要不断地加载 托对 ,你会发现这一技术在开发期
      间还是很有用的。
       
      就我看来,分配主设备号的最佳方式是,默认采用动态分配,同时留给你在加载时,甚
      至是编译时,指定主设备号的余地。使用我建议的代码将与自动端口探测的代码十分相
      似。scull的实现使用了一个全局变量,scull_major,来保存所选择的设备号。该变量
      的默认值是SCULL_MAJOR,在所发行的源码中为0,即“选择动态分配”。用户可以使用
      这个默认值或选择某个特定的主设备号,既可以在编译前修改宏定义,也可以在ins_mod
      命令行中指定。最后,通过使用scull_load脚本,用户可以在scull_load中命令行中将
      参数传递给insmod。
       

⌨️ 快捷键说明

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