Linux设备驱动程序(第一章 设备驱动程序简介)

第一章 设备驱动程序简介

设备驱动程序是进入Linux内核世界的大门。

设备驱动程序在Linux内核中扮演着特殊的角色,它们是一个个独立的“黑盒子”,使某个特定硬件响应一个定义良好的内部编程接口,这些接口完全隐藏了设备的工作细节。用户的操作通过一组标准化的调用执行,而这些调用独立于特定的驱动程序。将这些调用映射到作用于实际硬件的设备特有操作上,则是设备驱动程序的任务

这个编程接口能够使得驱动程序独立于内核的其他部分而建立,必要的情况下可在运行时“插入”内核。这种模块化的特点使得编写Linux驱动程序变得简单,因此内核驱动程序的数目也迅速增长。

本文中采取独立于硬件的方法讲述有关驱动程序编程方法以及内核的相关知识,所讲述的编程技巧和接口尽可能不依赖任何具体设备。

设备驱动程序的作用

作为驱动程序编写者,需要在所需的编程时间以及驱动程序的灵活性之间选择一个可接受的折中。强调驱动程序的灵活性实际上是强调设备驱动程序的作用在于提供机制,而不是提供策略。

大多数编程问题实际上都可以分为两部分:“需要提供什么功能”(机制)“如何使用这些功能”(策略)

驱动程序同样存在机制和策略的分离问题。不同的环境通常需要不同的方式来使用硬件,我们应当尽可能做到让驱动程序不带策略。

编写驱动程序时,程序员应该特别注意以下概念:编写访问硬件的内核代码时,不要给用户强加任何特定策略。因为不同的用户有不同的需求,驱动程序应该处理如何使硬件可用的问题,而将怎样使用硬件的问题留给上层应用程序。

从另一个角度,驱动程序还可以看做是应用程序和实际设备之间的一个软件层。这种特性可以让编写者选择如何展现设备特性,也就是说,即使对于相同的设备,不同的驱动程序可能提供不同的功能

驱动程序设计主要综合考虑以下三个方面的因素:

  • 提供给用户尽可能多的选项
  • 编写驱动程序要占用的时间
  • 尽量保持程序简单而不至于错误丛生

不带策略的驱动程序包含一些典型的特征:

  • 同时支持同步和异步操作
  • 驱动程序能够被多次打开
  • 充分利用硬件特性
  • 不具备用来“简化任务”的或提供与策略相关的软件层等

不带策略是软件设计者的一个共同目标


内核功能划分

Unix系统支持多个进程的并发运行,每个进程都请求系统资源,比如运算、内存、网络、连接或者其他一些资源等。内核负责处理所有这些请求,根据内核完成任务的不同,可将内核功能分成如下几个部分

  • 进程管理

  • 内存管理

  • 文件系统

  • 设备管理

  • 网络功能

    image-20220122110206581


可装载模块

Linux有一个很好的特性:内核提供的特性可在运行时进行扩展。意味着当系统启动并运行时,我们可以向内核添加功能。

可以在运行时添加到内核中的代码被称为“模块”。

Linux内核支持好几种模块类型(或者类),包括但不局限于设备驱动程序。每个模块由目标代码组成(没有链接成一个完整的可执行程序),我们可以使用insmod程序将模块连接到正在运行的内核,也可以使用rmmod程序移除连接。

设备和模块的分类

Linux系统将设备分成三种基本类型,每个模块通常实现为其中某一类:

  • 字符模块
  • 块模块
  • 网络模块

然而这种将模块分为不同类型或类的分类方式并不是非常严格,程序员可以构造一个大的模块,在其中实现不同类型的设备驱动程序。然而,优秀的程序员通常还是为每个新功能创建一个不同的模块,从而实现良好的伸缩性和扩展性。

这三种类型如下:

  • 字符设备

    字符(char)设备是个能够向字节流(类似文件)一样被访问的设备,由字符设备驱动程序来实现这种特性。

  • 块设备

    和字符设备类似,块设备也是通过/dev目录下的文件系统节点来访问。块设备(例如磁盘)上能够容纳文件系统。在内核中,和字符设备相比,块设备驱动程序具有完全不同的接口。

  • 网络接口

    任何网络事务都经过一个网络接口形成,即一个能够和其他主机交换数据的设备。通常接口是个硬件设备但也可能是个纯软件设备,比如回环(loopback)接口网络接口由内核中的网络子系统驱动,负责发送和接收数据包,但它不需要了解每项事务如何映射到实际传送的数据包。

    许多网络连接(尤其是使用TCP协议的连接)是面向流的,但网络设备却围绕数据包的传输和接收而设计。网络驱动程序不需要知道各个连接的相关信息,它只要处理数据包即可。

    由于不是面向流的设备,因此将网络接口映射到文件系统中的节点比较困难。Unix访问网络接口的唯一方法是给他们分配一个唯一的名字(如eth0),但是这个名字在文件系统中不存在对应的节点。

另外一种划分驱动程序模块类型的方法。一般,某些驱动程序类型,同,内核用来支持某种给定类型设备的附加层,一起工作。比如:通用串行总线(USB)模块、串行模块、SCSI模块,等等。

每个USB设备由一个USB模块驱动,而该USB模块与USB子系统一同工作,但设备本身在系统中表现为一个字符设备(比如USB串口)、一个块设备(比如USB存储卡读取器),或者一个网络设备(比如USB以太网设备接口)。

一个文件系统类型决定了如何在块设备上组织数据,以表示目录和文件形成的树。

文件系统并不是设备驱动程序;相反,文件系统类型是个软件驱动程序,它将低层数据结构映射到高层数据结构,决定文件名可以有多长以及在目录项中存储文件的哪些信息等等。

安全问题

系统中所有的安全检查都是由内核代码进行的,如果内核有安全漏洞,则整个系统就会有安全漏洞。

在正式发行的内核版本中,只有授权用户才能装载模块;也就是说,系统调用init_moudle检查调用进程是否具有将模块装载到内核的权利。因此,运行正式发布的内核时,只有超级用户或者成功成为超级用户的入侵者才能使用特权代码。

  • 驱动程序编写者应当尽量避免在代码中实现安全策略。安全策略问题最好在系统管理员的控制之下,由内核的高层实现。
  • 驱动程序编写者还应该避免由于自身原因引入安全方面的缺陷。例如避免缓冲区溢出,覆盖系统中的其他数据导致危及整个系统的安全。
  • 任何从用户进程得到的输入只有经过内核严格验证后才能使用。必须小心对待未初始化的内存:任何从内核中得到的内存,都必须在提供给用户进程或者设备之前清零或者以其他方式初始化,否则可能发生信息泄露。
  • 应当考虑设备操作造成的影响,如果某些特定操作可能会影响整个系统,则应当将此类操作限于特权用户。
  • 小心使用从第三方获得的软件,特别是与内核相关的。例如一个恶意修改过得到内核可能会允许任何人装载模块,这样,一扇通过init_module的后门就打开了。
  • Linux内核也可以编译为不支持模块方式,从而可以关闭任何模块相关的安全漏洞。但在这种情况下,所有所需的驱动程序必须直接编译到内核中。

版本编号

Linux系统中的每个软件包都有自己的发行编号,而且他们之间经常存在相互间的依赖关系,也就是说,只有存在某个软件包的特定版本时,才能运行另一个软件包的特定版本。

对内核来说,偶数编号的内核版本(如2.6.x)是用于正式发行的稳定版本,而奇数编号的版本(如2.7.x)则是开发过程中的一个快照,它将很快被下一个开发版本更新,最新的开发版本只是代表了内核开发目前的状态,几天后可能就会过时。

本书中描述内核的2.6版本,主要在于向读者展示2.6.10内核中有关设备驱动程序编写的所有可用功能特性。


许可证条款

image-20220122233214541

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2022-2024 lk
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信