文章目录
通用块设备层
I/O体系结构
与外设的通信通常称为输入输出,缩写为I/O。在实现外设的I/O时,内核需要处理好3个问题:
- 根据具体的设备型号和模型,使用各种方法对硬件寻址
- 内核必须向用户应用程序和系统工具提供访问各种设备的方法,应该采用统一的方案
- 用户空间需要知道内核中有哪些设备可用
与外设的通信是层次化的,如下图
在层次系统的底部是设备本身,它通过总线系统连接到其它设备和系统CPU,设备与内核的通信经由该路径进行
硬件设备可以以多种方式连接到系统,主板上的扩展槽和外部连接器是最常用的方法
-
总线系统
外设不直接连接到CPU,它们通过总线连接起来,总线负责设备与CPU之间以及各个设备之间的通信。下列是一些代表性的总线:
- PCI:许多体系结构使用的主要系统总线,PCI设备插入到系统主板的扩展槽中
- ISA:一种比较古老的总线
- IEEE1394,:也成为了FireWire,是高端电脑中非常流行的一种外部总线
- USB:广泛应用的外部总线
- SCSI:过去称为专业人员的总线,主要用在服务器系统上寻址硬盘
- 并口和串口:非常简单且速率很低,用于外部连接
系统一般是将一些总线组合在一起,当前的PC设计通常包括两个通过桥接器互连的PCI总线,一些总线(如USB和FireWire)无法作为主总线,需要经由另一个系统总线将数据传递到处理器。如下图是系统中不同总线的连接方式
-
与外设的交互
与外设通信的方法如下
- I/O端口:内核发送数据到I/O控制器,数据的目标设备通过唯一的端口号标识,数据被传输到设备进行处理。处理器管理了一个独立的虚拟地址空间,可用于管理所有I/O地址
- I/O内存映射:现代处理器提供了对I/O端口进行内存映射的选项,将特定外设的端口地址映射到普通内存中,可以像操作普通内存那样操作外设,图形卡通常会使用这类操作
- 轮询和中断:通过轮询或者中断来感知到某个设备的数据是否就绪
-
通过总线控制设备
总线类型分为系统和扩展总线
就系统总线而言(例如PCI总线),可以使用I/O语句和内存映射与总线本身和附接的设备通信,而扩展总线(如USE、SCSI等),通过明确定义的总线协议和附接的设备交换数据和命令
访问设备
设备文件用于访问扩展设备,它们不关联到硬盘或任何其他存储介质上的数据段,而是建立了与某个设备驱动程序的连接,以支持与扩展设备的通信
设备文件
设备不是通过文件名标识,而是通过文件的主、从设备号来标识
字符设备、块设备与其它设备
有些设备因为数据传输量低,适合于面向字符的数据交换,其它设备则更适合于处理包含固定数目字节的数据块,内核会区分字符设备和块设备,前一类包括串行接口和文本终端,后一类则包括硬盘、光驱等设备
- 标识设备文件
- 访问权限之前的字母是b或c,分别代表块设备和字符设备
- 设备文件没有文件长度,而是增加了另外两个值,分别是主设备号和从设备号,二者共同形成唯一的号码,内核可用于查找对应的设备驱动程序
主设备号用于寻址设备驱动程序本身,驱动程序管理的各个设备(如第1个和第2个硬盘)则通过不同的从设备号指定,如下所示连续的从设备号用于标识各个分区,硬盘的各个分区可以通过设备文件进行寻址(如/dev/vda、/dev/vda1),而/dev/vda*则代表整个硬盘。
系统可能包含几个同样类型的设备,由同一个设备驱动程序管理(避免多次将同样的代码加载到内核),其次可以将同类设备合并起来,便于插入到内核的数据结构中进行管理,一个驱动程序可以分配多个主设备号
注意:块设备和字符设备的主设备号可能是相同的,除非同时指定设备号和设备类型,否则找到的驱动程序可能不是唯一的
-
动态创建设备文件
/dev的设备结点一般是基于磁盘的文件系统静态创建的,随着支持的设备越来越多,几乎所有的Linux设备版都将/dev内容的管理工作切换到udevd,这是一个守护进程,允许从用户层动态创建设备文件
每当内核检测到一个设备时,都会创建一个内核对象kobject,该对象借助sysfs导出到用户层,同时内核向用户空间发送一个热插拔消息,热插拔消息包含了驱动程序为设备分配的主从设备号,udevd守护进程的工作就是监听这些消息。在注册新设备时,会在/dev中创建对应的项
由于引入了udev机制,/dev不再放置到基于磁盘的文件系统中,而是使用tmpfs(内存文件系统),非持久性
使用ioctl进行设备寻址
ioctl可以用于对设备I/O通道进行管理,设置设备的配置选项,它基于ioctl系统调用实现,由内核的sys_ioctl处理
网卡:网卡比较特殊,它没有设备文件,用户必须使用套接字和网卡通信,套接字是一个抽象层,对所有网卡提供一个抽象视图
设备数据库
内核中用于跟踪所有可用设备的数据库是相同的,由于字符设备和块设备都通过唯一的设备号标识
- 每个字符设备表示为struct cdev的实例
- struct genhd用于管理块设备的分区(作用类似于字符设备的cdev)
如下图是内核跟踪所有cdev和genhd实例的方式,有两个全局数组(bdev_map和cdev_map,都是kobj_map结构的实例)用来实现散列表,使用主设备号作为散列键,散列方式是:major % 255(由于很少有设备的主设备号大于255,因此哈希碰撞很少)