第1章Linux设备驱动简介三

1.3. 设备和模块的分类

        以 Linux 的方式看待设备可区分为 3 种基本设备类型. 每个模块常常实现 3 种类型中的 1 种, 因此可分类成字符模块, 块模块, 或者一个网络模块.这种将模块分成不同类型或类别的方法并非是固定不变的; 程序员可以选择建立在一个大块代码中实现了不同驱动的巨大模块. 但是, 好的程序员, 常常创建一个不同的模块给每个它们实现的新功能,因为分解是可伸缩性和可扩张性的关键因素.

3 类驱动如下:

1、字符设备

        一个字符( char ) 设备是一种可以当作一个字节流来存取的设备( 如同一个文件 ); 一个字符驱动负责实现这种行为. 这样的驱动常常至少实现 open, close,read, 和 write 系统调用.控制台( /dev/console )和串口( /dev/ttySn)是字符设备的例子, 因为它们很好地展现了流的抽象. 字符设备通过文件系统节点来存取, 例如 /dev/tty1 和 /dev/lp0.在一个字符设备和一个普通文件之间唯一有关的不同就是,经常可以在普通文件中移来移去,但是大部分字符设备仅仅是数据通道, 只能顺序存取.然而, 存在看起来像数据区的字符设备,例如, frame grabber 经常这样, 应用程序可以使用 mmap 或者 lseek 存取整个要求的图像.

2、块设备

        如同字符设备, 块设备通过位于 /dev 目录的文件系统节点来存取.一个块设备(例如一个磁盘)应该是可以驻有一个文件系统的. 在大部分的 Unix 系统, 一个块设备只能处理这样的 I/O(输入/输出) 操作, 传送一个或多个长度经常是 512 字节( 或一个更大的 2 的幂的数 )的整块. Linux, 相反, 允许应用程序读写一个块设备像一个字符设备一样 -- 它允许一次传送任意数目的字节.结果就是, 块和字符设备的区别仅仅在内核在内部管理数据的方式上, 并且因此在内核/驱动的软件接口上不同.如同一个字符设备, 每个块设备都通过一个文件系统节点被存取的, 它们之间的区别对用户是透明的. 块驱动和字符驱动相比, 与内核的接口完全不同.

3、网络设备

        任何网络事务都通过一个接口来进行,一个能够与其他主机交换数据的设备. 通常, 一个接口是一个硬件设备, 但是它也可能是一个纯粹的软件设备, 比如回环接口. 一个网络接口负责发送和接收数据报文, 在内核网络子系统的驱动下,不必知道单个事务是如何映射到实际的被发送的报文上的.很多网络连接( 特别那些使用 TCP 的)是面向流的, 但是网络设备却常常设计成处理报文的发送和接收.一个网络驱动对单个连接一无所知; 它只处理报文.

        既然不是一个面向流的设备, 一个网络接口就不像 /dev/tty1 那么容易映射到文件系统的一个节点上. Unix 提供的对接口的存取的方式仍然是通过分配一个名字给它们( 例如 eth0 ), 但是这个名字在文件系统中没有对应的入口. 内核与网络设备驱动间的通信与字符和块设备驱动所用的完全不同.不用 read 和 write, 内核调用和报文传递相关的函数.

        有其它的划分驱动模块的方式, 与上面的设备类型是正交的. 通常, 某些类型的驱动与给定类型设备的其它层的内核支持函数一起工作. 例如, 可以说 USB 模块, 串口模块,等等.每个 USB 设备由一个 USB 模块驱动, 与 USB 子系统一起工作, 但是设备自身在系统中表现为一个字符设备( 比如一个 USB 串口 ), 一个块设备( 一个 USB内存读卡器 ), 或者一个网络设备( 一个 USB 以太网接口 ).

        另外的设备驱动类别近来已经添加到内核中, 包括 FireWire 驱动和 I2O 驱动.以它们处理 USB 和 SCSI 驱动相同的方式, 内核开发者集合了类别范围内的特性, 并把它们输出给驱动实现者, 以避免重复工作和 bug, 因此简化和加强了编写类似驱动的过程.

        在设备驱动之外, 别的功能, 不论硬件和软件, 在内核中都是模块化的. 一个普通的例子是文件系统. 一个文件系统类型决定了在块设备上信息是如何组织的, 以便能表示一棵目录与文件的树. 这样的实体不是设备驱动, 因为没有明确的设备与信息摆放方式相联系;文件系统类型却是一种软件驱动, 因为它将低级数据结构映射为高级的数据结构. 文件系统决定一个文件名多长, 以及在一个目录入口中存储每个文件的什么信息. 文件系统模块必须实现最低级的系统调用, 来存取目录和文件, 通过映射文件名和路径到保存在数据块中的数据结构.这样的一个接口是完全与数据被传送来去磁盘( 或其他介质 )相互独立, 这个传送是由一个块设备驱动完成的.

1.4. 安全问题

        安全是当今重要性不断增长的关注点.系统中任何安全检查都由内核代码强加上去. 如果内核有安全漏洞, 系统作为一个整体就有漏洞. 在官方的内核发布里, 只有一个有授权的用户可以加载模块;系统调用init_module 检查调用进程是否是有权加载模块到内核里. 因此, 当运行一个官方内核时,只有超级用户 或者一个成功获得特权的入侵者, 才可以利用特权代码的能力.

        在可能时, 驱动编写者应当避免将安全策略编到其代码中.安全是一个策略问题, 最好在内核高层来处理, 在系统管理员的控制下. 但是, 常有例外.

        作为一个设备驱动编写者,应当知道在什么情形下, 某些类型的设备存取可能反面地影响系统作为一个整体, 并且应当提供足够地控制.例如, 会影响全局资源的设备操作,可能会损坏硬件,或者可能会影响其他用户,常常是只对有足够授权的用户, 并且这种检查必须由驱动自身进行.

        驱动编写者也必须要小心, 当然, 来避免引入安全 bug. C 编程语言使得易于犯下几类的错误. 例如, 许多现今的安全问题是由于缓冲区覆盖引起, 它是由于程序员忘记检查有多少数据写入缓冲区, 数据在缓冲区结尾之外结束, 因此覆盖了无关的数据. 这样的错误可能会危及整个系统的安全, 必须避免. 

        一些其他的通用的安全观念也值得牢记. 任何从用户进程接收的输入应当以极大的怀疑态度来对待; 除非你能核实它, 否则不要信任它.小心对待未初始化的内存; 从内核获取的任何内存应当清零或者在其对用户进程或设备可用之前进行初始化.否则, 可能发生信息泄漏( 数据, 密码的暴露等等 ). 如果你的设备解析发送给它的数据, 要确保用户不能发送任何能危及系统的东西. 最后, 考虑一下设备操作的可能后果; 如果有特定的操作,能影响到系统的, 这些操作应该完全确定地要限制在授权的用户中.

        也要小心, 当从第三方接收软件时, 特别是与内核有关: 因为每个人都可以接触到源码,每个人都可以分拆和重组东西.尽管你能够信任在你的发布中的预编译的内核, 你应当避免运行一个由不能信任的朋友编译的内核 -- 如果你不能作为 root 运行预编译的二进制文件, 那么你最好不要运行一个预编译的内核.例如, 一个经过了恶意修改的内核可能会允许任何人加载模块, 这样就通过 init_module 开启了一个不想要的后门.

总结:

        Linux 内核可以编译成不支持任何属于模块的东西, 因此关闭了任何模块相关的安全漏洞.在这种情况下, 当然, 所有需要的驱动必须直接建立到内核自身内部.在 2.2和以后的内核, 也可以在系统启动之后,通过 capability 机制来禁止内核模块的加载.


猜你喜欢

转载自blog.csdn.net/xiezhi123456/article/details/80831789
今日推荐