LINUX核心 之(第八章)
第八章 设备驱动程序
--------------------------------------------------------------------------------
操作系统的目的之一就是掩盖掉各种硬件的特殊性。使得系统中的硬件设备对于用
户而言是透明的,例如,不管底层是什么样的物理设备,虚拟文件系统提供一个一致的,
安装好的文件系统。本章将描述Linux核心如何管理系统中的物理设备。
系统中CPU不是唯一的智能设备,每一个物理外设都有其设备控制器。键盘,鼠标和
串行接口由多功能卡(SuperIO)控制,IDE磁盘由IDE控制器掌握,SCSI磁盘有SCSI控制器
控制。每一个于硬件控制器都有其自己的控制和状态寄存器(CSR)。这些CSR在不同的设备
中是不一样的。一个Adaptec 2940 SCSI控制器的CSR与NCR810 SCSI控制器差别很大。CSR用
来启动和停止一个设备,用来初始化一个设备和检测故障。用来管理系统中硬件控
制器的代码位于Linux核心中,而不是在每个应用程序中。用来管理硬件控制器的软件通
常叫做设备驱动程序。Linux核心的设备驱动程序基本上是一些共享库(Shared Library),在
库中含有一些特权的,常住内存的,一些用来处理底层硬件的例程。Linux的设备驱动程
序用来处理各种硬件的多样性。
操作系统的基本功能之一是对设备处理的抽象化。所有的物理设备被当做正规的文
件来处理,可以被“打开”,“关闭”,“读”和”写“,就像我们用系统调用处理文
件一样。(译者注:“文件”是一个逻辑上的概念;设备是一个实体。这里谈的是把设备
抽象在/dev文件系统下。)系统中每一个设备都对应一个设备特殊文件(device special
file),例如,系统中的第一个IDE磁盘的设备文件名是/dev/hda。对于块设备(如,磁盘)
和字符设备,它们的的设备特殊文件通常是通过mknod命令用主设备号和次设备号来描述和
创建。(译者注:主设备号和次设备号用来定位系统中两个表。一个主设备对应一个设备驱
动程序。次设备的含义是系统中可以存在多个设备属于同一类,比如多个IDE磁盘。 但它们
只需要一个同样的设备驱动程序来管理。)网络设备也同样是一个设备特殊文件,但它是由
Linux核心来创建当系统发现并初始化网络控制器的时候。被同样一个设备驱动程序所管理的
所有设备拥有一个同样的主设备号。次设备号用来区分不同的设备和设备控制器。例如,每个
IDE磁盘主设备的每个分区都有个不同的次设备号。所以,/dev/hda2,这个第2个分区的主设备
号是3,次设备号是2。Linux将系统调用中(比如将一个文件系统安装在一个块设备上)传递过来
的设备特殊文件名映射到相应的设备驱动程序(根据其相应的主设备名)和许多系统表中,如字
符设备表,chrdevs。
Linux支持三种硬件设备类型:字符,块和网络设备。字符设备的读写不需要缓冲,
例如系统的串行接口/dev/cua0和/dev/cua1。块设备的读和写只能以块的单位来进行,块的大
小一般是512字节或1024字节。块设备的读写是通过缓冲Cache并且可以被随机存取。
随机存取意味著你可以定位块设备的任一个块并进行读取;块设备的存取可以通过
其设备特殊文件,但更通常的是通过文件系统。只有块设备支持文件系统的安装(Mount)
。网络设备的存取是通过BSD的Socket接口和网络子系统(请参阅网络章节)。
Linux支持许多不同的设备驱动程序(Linux的优点之一)。它们都具备一些共同的属
性:
核心态:
设备驱动程序是核心的一部份,就象核心中其他代码一样,如果不正确运行,会严
重地毁坏系统。一个写的不好的驱动程序 可能使系统崩溃,并可能将文件系统打乱
丢失数据. (译者注:作者在这提“核心态”的目的是指核心态下运行的代码可以几
乎完全控制一个系统。)
核心接口:
设备驱动程序必须提供一个标准的接口给Linux核心或相应的子系统。例如,终端
驱动程序提供一个文件I/O接口给Linux核心;SCSI设备驱动程序提供一个SCSI设备
接口给SCSI子系统。SCSI设备接口提供文件I/O,SCSI子系统提供缓冲机制。
核心机制和服务:
设备驱动程序利用标准的核心服务,如内存分配,中断传送,等待队列来运行。
可装卸的:
大多数的Linux设备驱动程序可以在需要时被载进系统作为核心的一个模块;可以
被卸下当不再被使用。这使的核心的自适应性非常好,系统的资源可以有效地被利
用。(译者注:读者可以联想一下Windows 操作系统中的DLL(Dynamic Link Library)的
概念)
可重构的:
Linux设备驱动程序可以被构造进核心。当核心重新编译时,那些设备就是可重构
的。
动态的:
当系统启动时,每一个设备驱动程序进行初始化,寻找其控制的设备。如果核心中
一个设备驱动程序所对应的控制设备不存在(译者注:例如没有安装SCSI磁盘虽然系
统有SCSI驱动程序),也没有关系。这种情况下,系统中只不过是多了一个“多余的”
驱动程序,占用了一些系统内存而已。对系统本身无碍。
8.1 检测与中断
每次设备接受一个命令,例如,“移动读磁头到软盘的第42扇区”,为了知道这个
命令是否完成,设备驱动程序有两种选择:(不断地)检测这个设备或使用中断。(译
者注:“不断地”可以理解为:“while(!(read_device_status_register()));”
)
检测一个设备意味著频繁地读(设备的)状态寄存器直到状态寄存器值的变化显示该
设备已经完成请求。如果一个设备驱动程序是核心的一部份,上述行为将是一种灾
难性的因为核心什么其他的也不能作直到设备完成服务请求(译者注:这种方法极大
地牺牲了系统的并发性。例如,其他进程全部被阻塞因为在核心态时,进程是不可
被抢先的(或被剥夺的。)。一个替代的方法是使用一个系统定时器,设备驱动程序
每隔一定时间调用设备驱动程序中的一个例程去检测服务命令是否完成。Linux的软
盘驱动程序就是这样工作的(译者注:不知道这种方法的优点何在?)。一种更有效
的方法是使用中断。
中断驱动的设备驱动程序意味著:任何时候,它所管理的设备需要被处理时,该设
备会发出一个中断。例如,每当一个Ethernet网卡控制器从网络上接收一个Ethernet数
据包时,系统将会接收到一个中断。Linux核心需要能够传送这个来自设备的中断到相应的设
备驱动程序。这是通过该设备驱动程序(在初始化时)登记它所管理的中断号来达到
的(译者注:请参阅中断处理章节)。它并且登记对应该中断的中断处理程序的地址。
读者可以通过/proc/interrupts来查阅哪一个中断别哪一个设备驱动程序所使用和
其中断的类型。
0: 727432 timer
1: 20534 keyboard
2: 0 cascade
3: 79691 + serial
4: 28258 + serial
5: 1 sound blaster
11: 20868 + aic7xxx
13: 1 math error
14: 247 + ide0
15: 170 + ide1
这个申请中断资源的过程发生在驱动程序初始化的时候。系统中有一些中断号的使
用是固定的,这是由于IBM PC体系结构的习惯遗留(Legacy)而来。例如,软盘控制
器将一直使用中断6。其他中断,如PCI设备的中断是在系统启动时动态分配的
(译者注:请注意ISA设备与PCI设备在中断号占用方面的区别)。这种情况下,设备
驱动程序在登记/申请系统中一个中断号之前,将首先探测它所管理的设备所将占用
的IRQ。对PCI中断,Linux支持标准的PCI BIOS回调函数,以用来决定系统中设备的
信息,包括其中断号。
一个中断如何被传递到CPU中,不同的硬件体系结构有不同的方法。但大多数系统中,
中断的传递是通过一种特殊的模式,在这种模式下,系统其他的中断不会发生(译者
注:这与处理中断时,屏蔽掉同等级的中断不是一回事,这里讲的是“传递”中断
)。一个设备驱动程序的中断处理例程要尽可能地简单快速,从而Linux核心可以能
够很快地撤销(Dismiss)这个中断并回到被中断之前的现场(译者注:系统被中断时,
有可能一个进程正在用户态下运行)。需要为接收/处理中断作很多工作的设备驱动
程序可以使用核心的bottom half handlers 或任务队列。该任务队列存放着那些将
被待后调用的函数例程。
8.2 直接内存存取-DMA(Direct Memeory Access)
当数据量很小的情况下,使用中断驱动的设备驱动程序来从/向硬件设备传递数据是
合理的,可以工作的很好。例如,一个9600波特率的Modem的传输速率近似于没毫秒
(millisecond)一个字符。如果中断的延迟,硬件设备发出中断和设备驱动程序处理
该中断的时间非常小(比如2毫秒),那么数据传输的总体系统影响也非常小。这个9600波
特率的Modem数据传输只要占用0.002%的CPU处理时间。但是对于高速设备,比如硬
盘控制器或Ethernet设备,它们的的传输速率要高很多。 一个SCSI设备能达到40M字
节每秒。
直接内存存取,或DMA,被提出用来解决传输上述大批量数据的问题。一个DMA控制
器允许设备与内存之间发送或接收数据,但不影响处理器CPU。PC的ISA DMA控制器
有8个DMA通道。第7个通道被用来为设备驱动程序服务。每一个DMA通道与一个16位
的地址寄存器器和一个16位的计数寄存器相关联。当想要发起一次数据交换时,设
备驱动程序设置相应DMA通道的地址,计数寄存器的大小,这次数据传输的方向(读
或写)。然后通知设备可以启动DMA操作。当DMA结束时,设备才中断系统。因此,在
数据传输的过程中,CPU可以作其他的事情。
在使用DMA时,设备驱动程序必须额外小心。首先,对于DMA控制器而言,没有虚拟
内存的概念,它所面对的,存取的是系统中的物理内存。因此被DMA的内存必须是一
块连续的物理内存块。这意味著你不能通过DMA去“直接”存取进程的虚拟空间地址。
当然一个方法是在DMA期间,可以锁住一个进程的一些物理页面,防止操作系统将其
对换到swap空间上,从而保证DMA正确地完成。
DMA通道是“短缺”资源,只有7个通道。而且通道不能被设备驱动程序间共享。就
象中断一样,一个设备驱动程序必须能够知道哪一个DMA通道它要使用。有些设备使
用固定的中断号,就象有些设备使用固定的中断号一样。例如,软驱设备使用的DMA通
道一直是通道2。有时一个设备的DMA通道可以由跳线来设置。许多以太(Ethernet)设
备使用这种技术。一些更灵活的设备可以通过其CSR得知当前系统中哪些DMA通道是
空着的。从而设备驱动程序可以随便挑选一个DMA通道使用。
Linux通过一个向量数据结构dma_chan(每一个DMA通道对应一个这样的数据结构)来
掌握DMA通道的使用情况。dma_chan结构中只包含两个域:一个指向一个字符串的指
针,这个指针描述了这个DMA通道拥有者。另外一个域是一个标志,用来显示当前的
DMA通道是空着的还是已被占据。当你使用命令\"cat /proc/dma\"时,其实是核心中
的向量dma_chan被打印出来了。
8.3 存储器
在使用内存时,设备驱动程序要小心,因为它们是核心的一部份,故不能使用虚拟
内存(译者注:作者在上一节和这里反复强调“虚拟内存”是因为运行在不同“虚拟
内存”空间中的用户态进程之间不会发生冲突。操作系统的内存管理机制将负责。
)。每一次设备驱动程序因为来了中断,或者bottom half或任务队列中的句柄被调
度到而运行,当前的进程有可能被剥夺。所以设备驱动程序不能依赖于一个特殊的
运行的进程,虽然设备驱动程序运行在一个进程的上下文上。象核心中的其他部份
一样,设备驱动程序使用数据结构来管理跟踪它所控制的设备。这些数据结构可以
静态地分配,作为设备驱动程序代码的(数据的)一部份,但这样会使得核心变的太
大,造成资源的浪费。大多数设备驱动程序采用从核心中动态分配非页面的内存用
来存储数据。
Linux提供核心内存分配和释放的例程以供设备驱动程序使用。核心内存的分配是以
2的幂次方为单位的。例如,128字节或512字节即使设备驱动程序需要的内存量少于
这些值。设备驱动程序申请的(被分配的)字节数被“凑”到下一个块的边界处。这
种方法使得内存的释放回收更容易因为系统可以将这些小的空闲块合并成更大的内
存块。(译者注:以2的幂次方为单位进行内存分配可以减少系统中内存被弄的零碎。
)
当核心内存被申请时,Linux有可能要作许多额外的工作。如果剩余的内存太下的话,
一些物理页面需要被丢弃或写进对换磁盘空间。通常地,Linux将这个处理挂起并放
到一个等待队列中直到系统中有足够的物理内存。当然不是所有的设备驱动程序(至
少Linux核心代码)都希望这样被处理。所以当不能立刻分配内存时,核心内存分配
例程可以直接返回一个“失败”。如果设备驱动程序希望用DMA与被分配的内存来交
换数据,它可以指定这片内存是DMA\"able的。这种情况下Linux核心需要了解系统中
什么地方构成了DMA\"able的内存。
8.4 设备驱动程序与核心的接口
Linux核心必须能够通过一些标准的方法来和设备驱动程序接口。每一类设备驱动程
序,(字符,块和网络)都提供一个一致的,共同的接口给核心以用来核心向它们申
请服务。这些共同接口(common interfaces)意味著核心可以将这些不同的设备和其
驱动程序一样来对待。例如,SCSI和IDE磁盘的行为是不同的。但Linux核心对它们
使用一个同样的接口进行操作。
Linux是非常动态的,可重构的。每次一个Linux核心启动时,可能遇到不同的物理
设备,因此需要不同的相应的设备驱动程序。在核心重新构建(Build)的时候,Linux允
许通过配置文件将设备驱动程序带进核心。当这些驱动程序在机器启动时初始化的
时候,有可能系统中并没不存在相应的物理设备。有些驱动程序可以在需要时被装
载进入核心。为了处理设备驱动程序的这种动态特性,系统要求设备驱动程序在初
始化时向系统进行登记。Linux核心负责维护一些含有登记了的设备驱动程序的表。
这些表中包含了一些例程(rountines)的指针和其他一些信息以用来支持核心与那些
设备的接口。
8.4.1 字符设备
图8.1 字符设备
字符设备,Linux中最简单的设备,是通过”文件“的形式被存取。应用程序使用标
准的系统调用“打开”,“读”,“写”,和“关闭”字符设备就像它是一个文件
一样,即使这个设备是一个被PPP监控程序(Daemon)用来将Linux系统连接上网的Modem。
当一个字符设备初始化时,它的设备驱动程序在Linux核心中登记,通过添加一个入
口项(Entry)在含有device_struct数据结构的chrdevs向量中。这个设备的主设备号
(例如,4对于tty设备)被用来作为其在这个向量的索引。一个设备的主索引号是固
定的。
chrdevs向量的每一个入口项是一个device_struct数据结构,含有两个元素。一个
指向那个登记”在这个入口处“的设备驱动程序名字的指针;一个指向一系列文件
操作函数地址的指针。这些文件操作函数位于这个字符设备的驱动程序里并负责处
理相应的具体的文件操作如:打开,读,写和关闭。文件/proc/devices中对于字符
设备的内容是从chrdevs向量获取的。
当一个代表一个字符设备的字符特殊文件被打开时(例如/dev/cua0),系统必须正确
地工作保证相应的字符设备驱动程序的文件操作例程被调用。就象一个普通文件或
目录一样,每一个设备特殊文件对应一个VFS inode。这个VFS inode(数据结构)中
含有这个设备的主和次设备号。VFS inode是当一个特殊设备文件名被查询时,由文
件系统所创见。
每一个VFS inode与一套文件操作相联系。每当一个代表字符特殊文件的VFS inode被
创建时,对应这个 VFS inode的文件操作被设置成缺省的字符设备操作。
当一个字符特殊文件被一个应用程序打开时,这个“open\"操作将使用这个设备的主
设备号作为chrdevs向量的索引来查找对应这个设备的文件操作集的(例程的)地址。
并且还要设置一个描述这个字符特殊文件的数据结构--file,使得file结构中关于
文件操作的指针指向设备驱动程序中相应的部份。经过这些之后,所有用户层的文
件操作将被映射到对于这个字符设备的设备驱动程序提供的文件操作。
8.4.2 块设备
块设备同样支持以文件的形式被存取。当遇到打开块设备操作时,用来提供一套对
应的文件操作的机制与字符设备基本上是一样的。Linux在blkdevs向量中维护登记
了的块设备。与chrdevs向量一样,blkdevs向量使用设备的主设备号作为其索引。
向量的每一个入口仍是一个device_struct数据结构。与字符设备不同的是,这些数
据结构是属于块设备的。SCSI设备和IDE设备是其中两个例子。这些设备数据结构在
核心中登记并为核心提供对应于其设备的文件操作。对应于某类设备的设备驱动程
序提供实现这些接口的细节。例如,一个SCSI设备驱动程序必须为SCSI子系统提供
接口。SCSI子系统利用这些接口,提供给核心一个一致的文件接口。
除了文件操作接口,每个块设备还必须提供缓冲区接口。每一个块设备驱动程序在
一个blk_dev向量中添加其入口。blk_dev向量的每个元素是一个blk_dev_struct数
据结构。向量的索引仍然是设备的主设备号。blk_dev_struct数据结构中含有一个
请求例程的地址和一个指向“request\"数据结构的指针。每一个“request\"数据结
构代表了一个从缓冲区到驱动程序的读或写数据块的请求。
图8.2 块设备的缓冲
每次一个缓冲区想要读或写一块数据从/到一个登记了的设备,它将插入一个\"request\"数
据结构在blk_dev_struct中。由图8.2所示,每一个申请含
有一个指向一个或多个”buffer_head\"的数据结构。每一个buffer_head是读或写一个块数
据的请求(译者注:请参阅Linux数据结构章节)。Buffer_head结构是被缓冲区锁住的。因此
有可能存在一个进程正在等待对这个缓冲区操作的完成。每一个“request\"数据结构是从一
个静态的链表中(all_requests)分配而来。如果一个请求(request)被加在一个空的请求队列
上,设备驱动程序的请求函数(译者注:blk_dev_struc结构中函数
指针所指向的函数)将被立即调用来处理这个请求队列。否则,驱动程序将顺序地处
理请求队列中的所有请求。
一旦设备驱动程序完成一个请求,它必须从这个请求中移去每一个buffer_head结构,
将它们标志成为更新并释放对其的锁。对一个buffer_head锁的释放将唤醒所有在睡
眠中等待这个块操作完成的进程。一个例子是:当要解释一个文件名时,EXT2文件
系统必须从块设备中读取下一个EXT2目录项。这个进程将睡眠在那个含有目录项的
buffer_head上直到被设备驱动程序唤醒。这个request数据结构将被回收从而可以
被其他的块请求使用。
8.5 硬盘
磁盘将数据保存在磁盘片上,提供一种持久的存储方式。为了写数据,一个很小的
磁头在磁盘片的表面上磁化小微粒(minute particles)。数据也通过磁头来读写。
磁头能检测一个小微粒是否被磁化。
一个磁盘有一个或多个磁盘片(platters)组成。每个磁盘片的表面分成一些小的同
心圆---磁道(track)。磁道0是最外层的磁道,最大编号的磁道最靠近圆心。一个柱
面(cylinder)是指一个有同样编号的磁道集合。因此所有磁片上的所有磁道5构成了
柱面5。因为柱面的数目等于磁道德数目,我们经常看见人们使用柱面来描述磁盘。
每一个磁道分为一些扇区(sectors)。一个扇区是一个硬盘读或写的最小单位。一个
扇区的大小就是一个块的大小(译者注:换句话说,磁盘的读写是以块为单位的)。
通常一个扇区的大小是512字节。一个扇区的大小通常是在磁盘格式化的时候就被确
定了。
一个磁盘通常用其几何参数来描述,柱面的数目,磁头的数目和扇区的数目。例如,
在启动时,Linux描述一个IDE磁盘:
hdb: Conner Peripherals 540MB - CFS540A, 516MB w/64kB Cache, CHS=1050/16/63
上述意味著这个磁盘含有1050个柱面,16个磁头(8个磁片)和63个扇区/每个磁道。
如果每个扇区(或每个块)大小是512字节,这个磁盘的大小是529200字节。这个大小
与系统声称的516M大小不一致。这是因为磁盘的一些扇区已被用来存放磁盘分区信
息。例外,一些磁盘可以自动地发现坏扇区并重心索引磁盘以绕过这些坏扇区。
硬盘可以更深一步地分为一些分区(partitions)。一个分区是一个用来作某个特殊
用途的扇区的集合。将一个磁盘分区允许这个磁盘被几个操作系统使用,或允许用
来作不同的用途。许多Linux系统只有一个磁盘,但分为3个分区。一个含有DOS文件
系统;一个含有EXT2文件系统;第3个是对换分区(译者注:用于虚拟内存管理系统
)。一个硬盘的分区由一个分区表来描述。分区表中的每一个条目(entry)通过磁头,
扇区和柱面,描述了这个分区的起始和结束地址。fdisk支持3类分区类型。主分区,
扩展分区和逻辑分区。扩展分区不是一个真正的分区,可以含有任意数目的逻辑分
区。扩展和逻辑分区的发明是用来绕过系统中只允许4个主分区的限制。下面是用fdisk对
一个含有2个主分区的磁盘分区的信息:
Disk /dev/sda: 64 heads, 32 sectors, 510 cylinders
Units = cylinders of 2048 * 512 bytes
Device Boot Begin Start End Blocks Id System
/dev/sda1 1 1 478 489456 83 Linux native
/dev/sda2 479 479 510 32768 82 Linux swap
Expert command (m for help): p
Disk /dev/sda: 64 heads, 32 sectors, 510 cylinders
Nr AF Hd Sec Cyl Hd Sec Cyl Start Size ID
1 00 1 1 0 63 32 477 32 978912 83
2 00 0 1 478 63 32 509 978944 65536 82
3 00 0 0 0 0 0 0 0 0 00
4 00 0 0 0 0 0 0 0 0 00
上面所示,第一个分区起始于柱面或磁道0,磁头1和扇区1,共延伸到477柱面,扇
区32和磁头63。因为一个磁道有32个扇区,整个磁盘有64个读/写磁头,所以这个分
区占据的柱面是完整的。确省地,fdisk自动将分区依照柱面的边界对齐。它起始于
最外层的柱面(0)并向里面延伸478个柱面。第二个分区是对换分区,起始于下一个
柱面(478)并一直延伸到最里面的那个柱面。
图8.3 磁盘的链接表
在系统初始化期间,Linux将映射系统中所有硬盘的拓朴。它发现有多少硬盘和其类
型。另外,Linux发现这些磁盘是如何分区的。这一切将体现在由指针gendisk_head
指向的,一个元素为gendisk数据结构的链表中。当每个磁盘子系统初始化时,例如
IDE,它负责产生代表它所发现的磁盘的gendisk数据结构。这个行为发生在与登记
它的文件操作接口,插入一个入口项在blk_dev数据结构中的同一时间。每一个gendisk数
据结构有一个唯一的主设备号。这个主设备号与该块设备的主设备号一致。例如,
SCSI磁盘子系统产生一个单一的gendisk记录”sd“。记录中,其主设备号是8。系
统中所有的SCSI磁盘设备都拥有这个同样的主设备号。图8.3所示是两个gendisk记录,
第一个是为SCSI磁盘子系统,第二个是为IDE磁盘控制器,ide0, 主磁盘控制器。
虽然磁盘子系统在初始化时创建gendisk记录,这些记录只在Linux进行分区检测时
使用。然而,每个磁盘子系统维护一个自己的数据结构,以用来映射设备的主设备
号和次设备号到物理磁盘的分区中。任何时刻当一个块设备被读或写,不管是来自
缓冲区还是文件操作,核心将使用在块设备特殊文件中发现的主设备号(例如,/dev/sda2)
,引导读或写操作指向正确的设备。值得注意的是各个设备驱动程序负责映射次设
备号到具体的物理设备中。
8.5.1 IDE 磁盘
在Linux系统中,最常见的是IDE(Integrated Disk Electronic)磁盘。IDE是一个磁
盘接口,而不是一个I/O总线,例如象SCSI。每个IDE控制器可以支持多达2个磁盘。
一个主(master)磁盘;一个副(slave)磁盘。主和副磁盘是通过设置磁盘的跳线来完
成的。系统中的第一个IDE控制器叫做主IDE控制器。依此类推,下一个叫做第二IDE控
制器。IDE接口可以达到3.3M的传输速率。IDE磁盘容量最大是538M字节。扩展IDE(EIDE)
可以达到8.6G字节和16.6M字节的传输速率每秒。IDE和EIDE磁盘比SCSI磁盘要便宜。
大多数PC机都有一个或多个IDE控制器。
Linux依据发现IDE控制器的顺序对其上的IDE磁盘命名。在主控制器上的主磁盘是/dev/hda,
副磁盘是/dev/hdb。/dev/hdc是第二个IDE控制器 上的主磁盘。IDE子系统在核心中
登记IDE主控制器,而不是磁盘。主控制器的主设备号是3;副IDE控制器
的主设备号是22。这意味著如果系统有两个IDE控制器,在向量blk_dev和blkdevs中
将插入两个IDE子系统记录在向量索引3和22的地方。从设备特殊文件名中可以体现
这点。在主IDE控制器上的磁盘/dev/hda 和/dev/hdb的主设备号是3。任何对这两个
设备特殊文件的操作都会被核心根据被访问的主设备号传到其对应的IDE子系统中。
IDE子系统将负责是哪一个IDE磁盘被申请,通过设备特殊文件的次设备号。次设备
号里含有信息关于哪一个分区和哪一个磁盘。/dev/hdb的设备标识是(3,64)。该磁
盘上的第一个分区(/dev/hdb1)的设备标识是(3,65)。
8.5.2 IDE子系统的初始化
IDE磁盘一直贯穿在IBM PC机的历史。在这个期间,IDE接口发生了许多变化。这使
得IDE子系统的初始化变得越来越复杂。
Linux最多可以支持4个IDE控制器。每个控制器将体现在向量ide_hwifs的ide_hwif_t数
据结构中。每个ide_hwif_t中含有两个ide_drive_t数据结构,对应于可能的主和副
IDE驱动器。IDE子系统初始化期间,Linux首先通过系统的CMOS中信息查看是否有磁
盘。CMOS的位置由系统的BIOS设定并可以告诉Linux什么IDE控制器和磁盘驱动器在
系统中。Linux从BIOS中得到磁盘的几何描述信息并为这些驱动器设立ide_hwif_t
数据结构。目前越来越多的PC机使用包含了PCI EIDE控制器的PCI芯片集(chipsets)(例
如,Intel\"s 82430 VX )。IDE子系统使用PCI BIOS的回调(callback)来定位系统中
的PCI E(IDE)控制器,然后调用PCI专门的为这些控制器准备的例程(来初始化核心
的向量结构)。
一旦每个IDE接口和控制器被发现,相应的ide_hwif_t数据结构将被建立以反映这些
控制器和其上的磁盘。在运行期间,IDE驱动程序向I/O空间的IDE命令寄存器写入命
令。主IDE控制器的控制和状态寄存器的缺省I/O地址在0x1F0 - 0x1F7。IDE驱动程
序在Linux的块缓冲数据结构中登记每个控制器,在blk_dev和blkdevs分别加入记录。
IDE驱动器还将申请占有某个中断。对于主IDE控制器,约定的中断号是14;第二个
IDE控制器是15。然而,上述都可以别核心的命令选项所覆盖。IDE驱动
程序还要为每个IDE控制器在gendisk链中加入一个gendisk记录。这个链表用来查找
所有在启动时发现的磁盘的分区表信息。
8.5.3 SCSI磁盘
SCSI(Small Computer System Interface)总线是一个快速的端到端的数据总线。每
个SCSI总线上支持8个设备,其中包含一或多个hosts。每个SCSI设备必须有一个唯
一的标识名(一般通过磁盘的跳线来设置)。总线上的两个设备可以同步地或异步地
32位地交换数据。速率可达40M字节。SCSI总线可在设备之间数据和状态信息,并且
在发起者(initiator)和目标(target )之间,一个单一的事务(transaction)可以包
含多达8个不同状态的信息。我们可以从来自总线上的5个信号来辨别SCSI总线上当
前的状态(phase )。
BUS FREE (总线空)
当前没有设备正占据总线。没有活跃的事务处理。
ARBITRATION (仲裁)
一个SCSI设备试图占据总线。它通过将其SCSI标识数据“插入”地址线。(如有竞
争,)SCSI标识最大的获得总线。
SELECTION (选择)
当一个设备通过仲裁,成功地获得总线后,它必须向目标(Target)发出信号表示它
想要对目标发出命令。这是通过将目标的SCSI标识放入地址线而达到的。
RESELECTION
SCSI设备有可能断开连接在一个请求处理过程中。目标有可能等会儿重新选择发起
者(initiator)。不是所有的SCSI设备都支持这个状态。
COMMAND
在此状态下,可以从发起者传到目标端传送6,10 或 12 字节的命令。
DATA IN, DATA OUT
在这些状态下,数据在发起者和目标端之间传送。
STATUS
当所有命令都完成时,可以进入这个状态。允许目标发出一个状态字节以向发起者
表明成功或失败。
MESSAGE IN, MESSAGE OUT
附加的信息将在发起端和目标端传送。
Linux的SCSI子系统有两个基本元素组成。每一个都通过一些数据结构在核心中得到
体现。
host
一个SCSI host是一个物理硬件,即一个SCSI控制器。NCR810 PCI SCSI控制器就是
一个SCSI host的例子。如果一个Linux系统存在多于一个的,同样类型的SCSI控制
器,每一个将对应于不同的SCSI host。这意味著一个SCSI设备驱动程序可能控制多
于一个的SCSI控制器。SCSI host一般都是SCSI命令的发起者。
device(设备)
最常见的SCSI设备就是SCSI磁盘。但SCSI标准还支持其他的几类设备,如磁带,CD-ROM和
通用(generic)SCSI设备。SCSI设备一般都是SCSI命令的目标。这些设备必须不同的
对待。例如,可移动的介质CD-ROMs或磁带,Linux需要检测介质是否被移动了。不同
的设备类型有不同的主设备号,Linux可以依此引导不同的对块设备的请求到其相应
的SCSI设备类型上。
SCSI子系统的初始化
由于SCSI总线和设备的动态特性,SCSI子系统的初始化比较复杂。Linux在系统启动
时初始化SCSI子系统。它首先发现系统中的SCSI控制器(即SCSI hosts)然后探测在
所有SCSI总线上的所有的设备。然后初始化这些设备。通过向核心提供一套规范的
文件操作和缓冲区操作例程集,使得对于Linux核心系统来说“可见”。这个初始化
的过程分为4个阶段:
首先,Linux检测在核心构建时已加入核心的那些SCSI hosts或控制器上是否有设备
需要控制。上面每个SCSI hosts在builtin_scsi_hosts 向量中有一个入口记录相对
应。每个记录是一个Scsi_Host_Template 数据结构。Scsi_Host_Template 中含有
一些函数指针。这些函数用来执行SCSI hosts的一些特定功能,如检测什么设备正
挂在SCSI hosts上。这些例程SCSI子系统所调用,属于这种hosts设备类型的设备驱
动程序的一部份。每个在其上存在SCSI设备的SCSI host将Scsi_Host_Template 数
据结构加入一个Scsi_Host结构到一个scsi_hostslist列表 链中。例如,如果一个
系统有两个NCR810 PCI SCSI 控制器,系统数据结构中将有两个Scsi_Host记录在
scsi_hostslist列表链中。每一个Scsi_Host指向代表起设备驱动程序的
Scsi_Host_Template。
图8.4 SCSI 数据结构
到现在,系统中所有的SCSI host都已备发现,SCSI子系统必须知道在每个host上是
些什么SCSI设备。SCSI设备是按照0-7来标号的。每个SCSI设备的标号在其所安装的
host上是唯一的。SCSI标号通常是由设备上的跳线来设置的。SCSI初始化代码通过
发出TEST_UNIT_READY命令来探测一个SCSI总线上的SCSI设备。当一个设备存在并回
答时,它的SCSI标识信息(包括厂商,设备型号和版本号)被读取,通过一个ENQUIRY
命令。SCSI命令含在一个Scsi_Cmnd数据结构中。这些Scsi_Cmnd数据被传递到属于
这个SCSI host的设备驱动程序的相关函数中。这些函数的指针在先前已被登记在
Scsi_Host_Template结构中。每个已发现的SCSI设备将对应一个Scsi_Device数据结构.
每个 Scsi_Device数据结构指向其host的数据结构Scsi_Host。所有的Scsi_Device结构
链在一个叫做scsi_devices的链表上(译者注:Scsi_Device:SCSI设备;Scsi_Host:
SCSI host或控制器;Scsi_Host_Template:SCSI host的设备驱动程序入口)。图8.4所
示是上述数据结构的关系。
SCSI设备有四种:磁盘,磁带,CD和generic。每一种都分别在核心中以不同的主块
设备号进行登记。当然,只有在发现系统中存在相关的SCSI设备时才会进行登记。
每个SCSI类型,例如SCSI磁盘,维护一套其自己的表数据结构。这些表用来将来自
核心的块操作请求映射到相应的设备驱动程序上或相应的SCSI host上。每一个SCSI类
型在核心中对应于一个Scsi_Device_Template 数据结构。这个结构中含有这种设备
的信息和各种针对这种设备的例程地址。SCSI子系统使用这些模板来调用对应于每
种SCSI设备的SCSI类型函数。换句话说,如果SCSI子系统想要”attach“(添加)一
个SCSI磁盘,它将调用SCSI磁盘类型的”attach“函数。Scsi_Device_Template 数
据结构全部挂在scsi_devicelist链表上。(译者注:请注意 Scsi_Device_Template与
Scsi_Host_Template 的关系和区别)
SCSI子系统初始化的最后一步是对应于每一个登记了的Scsi_Device_Template调用”
完成“(finish)函数。对应于SCSI磁盘类型,这意味著旋转机器上所有的SCSI磁盘
然后读取它们的几何参数。(根据获得的几何参数),核心填写为每个SCSI磁盘填写
gendisk数据结构在gendisk链中。
传送块设备请求
一旦Linux 完成SCSI子系统的初始化,SCSI设备就可以被使用了。每个存在相应设
备的设备类型在核心进行了登记,从而Linux可以正确地将块设备请求定位/传送到
正确的设备上。这些请求可以是来自blk_dev 的缓冲区操作或来自blkdevs的文件操
作。举一个例子,如果有一个SCSI磁盘含有一个或两个EXT2文件系统分区,当其中
一个EXT2文件系统已被安装(mounted),核心的缓冲区申请如何被定位到正确的磁盘
上?
每个读或写一块SCSI磁盘数据的请求都导致在blk_dev向量中的current_request链
表中加入一个新的request 结构。如果这个申请队列正在被处理,缓冲区不需要做
其他的事情。否则,必须提醒SCSI磁盘子系统去处理request 队列。系统中每个SCSI磁
盘对应于一个Scsi_Disk 数据结构在rscsi_disks 向量中。rscsi_disks的索引使用
了部份SCSI磁盘分区的次设备号信息。例如,/dev/sdb1的主设备号是8,次设备号
是17,其在rscsi_disks中的索引号是1。 每个Scsi_Disk结构中含有一个指针指向
代表这个设备的Scsi_Device 结构。然后通过Scsi_Device 指向对应的Scsi_Host结
构(译者注:对应于这个SCSI磁盘所属的SCSI磁盘控制器)。 从缓冲区来的request结
构被转换成描述SCSI命令的Scsi_Cmd 结构并将其放入这个对应的Scsi_Host的队列
中。当一旦要求的块数据被读或写完成之后,SCSI设备驱动程序将会处理这些Scsi_Cmd
结构。
8.6 网络设备
从Linux的网络子系统的角度而言,一个网络设备是用来发送和接收数据的一个”实
体“或一个”东西“,比如一个ethernet网卡。每个网络设备在核心中对应于一个
device 数据结构。核心启动时,网络设备驱动程序初始化并登记其控制的设备。这
个device 结构中包含了关于这个设备的信息和一些函数的地址。这些函数被用来对
各种高层的网络协议提供底层支撑。它们大多数是关于在物理网络设备上传输数据。
网络设备使用标准的网络机制将接收到的数据向高层网络协议传送。所有传送的和
接收的数据报(packets)都对应于sk_buff数据结构。sk_buff是非常灵活的数据结构,
允许网络协议头(network protocol headers)很轻松地被加入和移去。网络协议层
如何使用网络设备,如何使用sk_buff来回传递数据,请参阅第10章网络。本章关于
网络方面的重点是网络设备数据结构和网络设备如何被检错与初始化。
device 数据结构含有网络设备的如下信息:
Name
与用mknod 命令来创建设备特殊文件的块和字符设备不同的是,网络设备特殊文件
是当系统网络设备被发现并初始化时出现的。它们的名字是标准的。每一个名字代表
了它是哪一种网络设备类型。属于同一类型的设备的名字从数字0开始往上走。因此
ethernet设备名是/dev/eth0,/dev/eth1,/dev/eth2等等 。下面是一些通用的网络
设备名:
/dev/ethN Ethernet设备
/dev/slN SLIP设备
/dev/pppN PPP设备
/dev/lo Loopback设备
Bus Information
这个信息被设备驱动程序用来控制设备。irq 数据是这个设备使用的中断。
base address 是设备的控制和状态寄存器在I/O空间的地址。DMA channel 是这个网
络设备用的DMA通道。所有的上述信息在设备初始化时被设置。
Interface Flags
用来描述网络设备的特性和能力:
IFF_UP (网络)接口在运行,
IFF_BORADCAST device中的广播地址是有效的,
IFF_DEBUG 设备的调试功能已被打开,
IFF_LOOPBACK 当前设备是一个loopback设备,
IFF_POINTTOPOINT 这是个点到点的连接(SLP和PPP),
IFF_NOTRAILERS 没有网络跟踪(No network trailers),
IFF_RUNNING 分配的资源,
IFF_NOARY 不支持ARP协议,
IFF_PROMISC 设备处在混杂接收模式,将接收任何网上数据包,
IFF_ALLMULTI 接收所有的IP多点广播(multicast)数据帧,
IFF_MULTICAST 能够接收IP多点广播(multicast)数据帧。
Protocol Information
通过这些信息,设备描述自己将如何被网络协议层所使用。
mtu 该设备能传输的最大报文大小(不包括所需要的报文头)。这个最大值
协议层被用来,例如IP,选择适当的发送报文的大小。
Family 显示该设备可以支持的协议族。所有Linux网络设备支持的协议族是
AF_INET, Internet 地址族。
Type 这个硬件接口类型描述该网络设备正与什么介质相连。在Linux网络
设备中,可以支持很多种不同的设备,包括Ethernet, X.25, Token Ring,
Slip, PPP 和 Apple Localtalk。
Address
device数据结构含有许多与该设备相关的地址,例如:IP地址。
Packet Queue
sk_buff 报文的队列。等待在这个网络设备上传输。
Support Functions
每个设备提供一套标准的例程作为该设备连接层接口一部份。
从而协议层可以进行调用。这些例程包括:设置和帧传输例程;
添加标准报文帧头和收集统计信息的例程。这些统计信息可以通过
ifconfig命令来查看。
8.6.1 网络设备的初始化
与其他Linux设备驱动程序一样,网络设备驱动程序也可以被预先构造在核心中。
每一个潜在的网络设备都对应于一个device数据结构。这些结构组成一个由dev_base
指向的链表。如果网络层需要网络设备完成一个特定的任务,它调用 一个地址已在
device结构中的网络设备服务例程。在最开始,device结构中只含有初始化(initiali发布人:netbull 来自:linuxeden