论坛交流
首页办公自动化| 网页制作| 平面设计| 动画制作| 数据库开发| 程序设计| 全部视频教程
应用视频: Windows | Word2007 | Excel2007 | PowerPoint2007 | Dreamweaver 8 | Fireworks 8 | Flash 8 | Photoshop cs | CorelDraw 12
编程视频: C语言视频教程 | HTML | Div+Css布局 | Javascript | Access数据库 | Asp | Sql Server数据库Asp.net  | Flash AS
当前位置 > 文字教程 > C语言程序设计教程
Tag:新手,函数,指针,数据类型,对象,Turbo,入门,运算符,数组,结构,二级,,tc,游戏,试题,问答,编译,视频教程

Device Drivers(设备驱动程序)

文章类别:C语言程序设计 | 发表日期:2008-9-24 14:37:07

操作系统其中一个目的就是向用户掩盖系统硬件设备的非凡性。例如,虚拟文件系统呈现了安装的文件系统的一个统一的试图,而和底层的物理设备无关。本章描述Linux核心是如何治理系统中的物理设备的。

 

CPU不是系统中唯一的智能设备,每一个物理设备都由它自己的硬件控制器。键盘、鼠标和串行口由SuperIO芯片控制,IDE磁盘由IDE控制器控制,SCSI磁盘由SCSI控制器控制,等等。每一个硬件控制器都由自己的控制和状态控制器(CSR),不同的设备之间是不同的。一个Adaptec 2940 SCSI控制器的CSR和NCR 810 SCSI控制器的完全不同。CSR用于启动和停止设备,初始化设备和诊断它的问题。治理这些硬件控制器的代码不是放在每一个应用程序里边,而是放在Linux核心。这些处理或者治理硬件控制器的软件脚做设备驱动程序。Linux核心的设备驱动程序本质上是特权的、驻留内存的低级的硬件控制例程的共享库。是Linux的设备驱动程序在处理它们治理的设备的特质。

 

UN*X的一个基本特点是它抽象了设备的处理。所有的硬件设备都象常规文件一样看待:它们可以使用和操作文件相同的、标准的系统调用来进行打开、关闭和读写。系统中的每一个设备都用一个设备非凡文件代表。例如系统中第一个IDE硬盘用/dev/had表示。对于块(磁盘)和字符设备,这些设备非凡文件用mknod命令创建,并使用主(major)和次(minor)设备编号来描述设备。网络设备也用设备非凡文件表达,但是它们由Linux在找到并初始化系统中的网络控制器的时候创建。同一个设备驱动程序控制的所有设备都由一个共同的major设备编号。次设备编号用于在不同的设备和它们的控制器之间进行区分。例如,主IDE磁盘的不同分区都由一个不同的次设备编号。所以,/dev/hda2,主IDE磁盘的第2个分区的主设备号是3,而次设备号是2。Linux使用主设备号表和一些系统表(例如字符设备表chrdevs)把系统调用中传递的设备非凡文件(比如在一个块设备上安装一个文件系统)映射到这个设备的设备驱动程序中。

参见fs/devices.c

 

Linux支持三类的硬件设备:字符、块和网络。字符设备直接读写,没有缓冲区,例如系统的串行端口/dev/cua0和/dev/cua1。块设备只能按照一个块(一般是512字节或者1024字节)的倍数进行读写。块设备通过buffer cache访问,可以随机存取,就是说,任何块都可以读写而不必考虑它在设备的什么地方。块设备可以通过它们的设备非凡文件访问,但是更常见的是通过文件系统进行访问。只有一个块设备可以支持一个安装的文件系统。网络设备通过BSD socket接口访问,网络子系统在网络章(第10章)描述。

 

Linux有许多不同的设备驱动程序(这也是Linux的力量之一)但是它们都具有一些一般的属性:

 

Kernel code 设备驱动程序和核心中的其他代码相似,是kenel的一部分,假如发生错误,可能严重损害系统。一个写错的驱动程序甚至可能摧毁系统,可能破坏文件系统,丢失数据。

Kenel interfaces 设备驱动程序必须向Linux核心或者它所在的子系统提供一个标准的接口。例如,终端驱动程序向Linux核心提供了一个文件I/O接口,而SCSI设备驱动程序向SCSI子系统提供了SCSI设备接口,接着,向核心提供了文件I/O和buffer cache的接口。

Kernel mechanisms and services 设备驱动程序使用标准的核心服务例如内存分配、中断转发和等待队列来完成工作

Loadable Linux大多数的设备驱动程序可以在需要的时候作为核心模块加载,在不再需要的时候卸载。这使得核心对于系统资源非常具有适应性和效率。

Configurable Linux设备驱动程序可以建立在核心。哪些设备建立到核心在核心编译的时候是可以配置的。

Dynamic 在系统启动,每一个设备启动程序初始化的时候它查找它治理的硬件设备。假如一个设备驱动程序所控制的设备不存在并没有关系。这时这个设备驱动程序只是多余的,占用很少的系统内存,而不会产生危害。

 

8.1 Poling and Interrupts(轮询和中断)

 

每一次给设备命令的时候,例如“把读磁头移到软盘的第42扇区“,设备驱动程序可以选择它如何判定命令是否执行结束。设备驱动程序可以轮询设备或者使用中断。

 

轮询设备通常意味着不断读取它的状态寄存器,直到设备的状态改变指示它已经完成了请求。因为设备驱动程序是核心的一部分,假如驱动程序一直在轮询,核心在设备完成请求之前不能运行其他任何东西,会是损失惨重的。所以轮询的设备驱动程序使用一个系统计时器,让系统在晚些时候调用设备驱动程序中的一个例程。这个定时器例程会检查命令的状态,Linux的软盘驱动程序就是这样工作的。使用计时器进行轮询是一种最好的接近,而更加有效的方法是使用中断。

 

中断设备驱动程序在它控制的硬件设备需要服务的时候会发出一个硬件中断。例如:一个以太网设备驱动程序会在设备在网络上接收到一个以太网报文的时候被中断。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体系结构的遗留物。例如软驱磁盘控制器总是用中断6。其他中断,例如PCI设备的中断,在启动的时候动态分配。这时设备驱动程序必须首先找出它所控制的设备的中断号,然后才能请求拥有这个中断(的处理权)。对于PCI中断,Linux支持标准的PCI BIOS回调(callback)来确定系统中设备的信息,包括它们的IRQ。

 

一个中断本身是如何转发到CPU依靠于体系结构。但是在大多数的体系上,中断都用一种非凡的模式传递,而停止系统中发生其他中断。设备驱动程序在 它的中断处理例程中应该做尽可能少的工作,使得Linux核心可以结束中断并返回到它中断之前的地方。收到中断后需要做大量工作的设备驱动程序可以使用核心的bottom half handler或者任务队列把例程排在后面,以便在以后调用。

 

8.2 Direct Memory Access (DMA)

 

当数据量比较少的时候用中断驱动的设备驱动程序向设备或者通过设备传输数据工作地相当好。例如,一个9600波特率的modem每一毫秒(1/1000秒)大约可以传输一个字符。假如中断延迟,就是从硬件设备发出中断到开始调用设备驱动程序中的中断处理程序所花的时间比较少(比如2毫秒),那么数据传输对系统整体的映像就非常小。9600波特率的modem数据传出只会占用0.002%的CPU处理时间。但是对于高速的设备,比如硬盘控制器或者以太网设备,数据传输速率相当高。一个SCSI设备每秒可以传输高达40M字节的信息。

 

直接内存存取,或者说DMA,就是发明来解决这个问题的。一个DMA控制器答应设备不需要处理器的干预而和系统内存创树数据。PC的ISA DMA控制器由8个DMA通道,其中7个可用于设备驱动程序。每一个DMA通道都关联一个16位的地址寄存器和一个16位的计数寄存器(count register)。为了初始化一次数据传输,设备驱动程序需要建立DMA通道的地址和计数寄存器,加上数据传输的方向,读或写。当传输结束的时候,设备中断PC。这样,传输发生的时候,CPU可以作其他事情。

 

使用DMA的时候设备驱动程序必须小心。首先,所有的DMA控制器都不了解虚拟内存,它只能访问系统中的物理内存。因此,需要进行DMA传输的内存必须是物理内存中连续的块。这意味着你不能对于一个进程的虚拟地址空间进行DMA访问。但是你可以在执行DMA操作的时候把进程的物理也锁定到内存中。第二:DMA控制器无法访问全部的物理内存。DMA通道的地址寄存器表示DMA地址的首16位,跟着的8位来自于页寄存器(page register)。这意味着DMA请求限制在底部的16M内存中。

 

DMA通道是稀少的资源,只有7个,又不能在设备驱动程序之间共享。象中断一样,设备驱动程序必须有能力发现它可以使用哪一个DMA通道。象中断一样,一些设备有固定的DMA通道。比如软驱设备,总是用DMA通道2。有时,设备的DMA通道可以用跳线设置:一些以太网设备用这种技术。一些更灵活的设备可以告诉它(通过它们的CSR)使用哪一个DMA通道,这时,设备驱动程序可以简单地找出一个可用的DMA通道。

 

Linux使用dma_chan数据结构向量表(每一个DMA通道一个)跟踪DMA通道的使用。Dma_chan数据结构只有两个玉:一个字符指针,描述这个DMA通道的属主,一个标志显示这个DMA通道是否被分配。当你cat /proc/dma的时候显示的就是dma_chan向量表。

 

8.3 Memory(内存)

 

设备驱动程序必须小心使用内存。因为它们是Linux核心的一部分,它们不能使用虚拟内存。每一次设备驱动程序运行的时候,可能是接收到了中断或者调度了一个buttom half handler或任务队列,当前的进程都可能改变。设备驱动程序不能依靠于一个正在运行的非凡进程。象核心中其他部分一样,设备驱动程序使用数据结构跟踪它控制的设备。这些数据结构可以在设备驱动程序的代码部分静态分配,但是这会让核心不必要地增大而浪费。多数设备驱动程序分配核心的、不分页的内存存放它们的数据。

 

Linux核心提供了核心的内存分配和释放例程,设备驱动程序正是使用了这些例程。核心内存按照2的幂数的块进行分配。例如128或512字节,即使设备驱动程序请求的数量没有这么多。设备驱动程序请求的字节数按照下一个块的大小取整。这使得核心的内存回收更轻易,因为较小的空闲块可以组合成更大的块。

 

请求核心内存的时候Linux还需要做更多的附加工作。假如空闲内存的总数太少,物理页需要废弃或者写到交换设备。通常,Linux 会挂起请求者,把这个进程放到一个等待队列,直到有了足够的物理内存。不是所有的设备驱动程序(或者实际是Linux的核心代码)希望发生这样的事情,核心内存分配例程可以请求假如不能马上分配内存就失败。假如设备驱动程序希望为DMA访问分配内存,它也需要指出这块内存是可以进行DMA的。因为需要让Linux核心明白系统中哪些是连续的可以进行DMA的内存,而不是让设备驱动程序决定。

 

8.4 Interfacing Device Drivers with the Kernel(设备驱动程序和核心接口)

 

Linux核心必须能够用标准的方式和它们作用。每一类的设备驱动程序:字符、块和网络,都提供了通用的接口供核心在需要请求它们的服务的时候使用。这些通用的接口意味着核心可以完全相同地看待通常是非常不同的设备和它们的设备驱动程序。例如,SCSI和IDE磁盘的行为非常不同,但是Linux核心对它们使用相同的接口。

Linux非常地动态,每一次Linux核心启动,它都可能碰到不同的物理设备从而需要不同的设备驱动程序。Linux答应你在核心建立的时间通过配置脚本包含设备驱动程序。当启动的时候这些设备驱动程序初始化,它们可能没发现它们可以控制的任何硬件。其他驱动程序可以在需要的时候作为核心模块加载。为了处理设备驱动程序的这种动态的特质,设备驱动程序在它们初始化的时候向核心登记。Linux维护已经登记的设备驱动程序列表,作为和它们接口的一部分。这些列表包括了例程的指针和支持这一类设备的接口的信息。

 

8.4.1 Character Devices(字符设备)

 

字符设备,Linux最简单的设备,象文件一样访问。应用程序使用标准系统调用打开、读取、写和关闭,完全似乎这个设备是一个普通文件一样。甚至连接一个Linux系统上网的PPP守护进程使用的modem,也是这样的。当字符设备初始化的时候,它的设备驱动程序向Linux核心登记,在chrdevs向量表增加一个device_struct数据结构条目。这个设备的主设备标识符(例如对于tty设备是4),用作这个向量表的索引。一个设备的主设备标识符是固定的。Chrdevs向量表中的每一个条目,一个device_struct数据结构,包括两个元素:一个登记的设备驱动程序的名称的指针和一个指向一组文件操作的指针。这块文件操作本身位于这个设备的字符设备驱动程序中,每一个都处理特定的文件操 作比如打开、读、写和关闭。/proc/devices中字符设备的内容来自chrdevs向量表

参见include/linux/major.h



当代表一个字符设备(例如/dev/cua0)的字符非凡文件打开,核心必须做一些事情,从而去掉用正确的字符设备驱动程序的文件操作例程。和普通文件或目录一样,每一个设备非凡文件都用VFS I节点表达。这个字符非凡文件的VFS inode(实际上所有的设备非凡文件)都包括设备的major和minor标识符。这个VFS I节点由底层的文件系统(例如EXT2),在查找这个设备非凡文件的时候根据实际的文件系统创建。

参见fs/ext2/inode.c ext2_read_inode()

 

每一个VFS I节点都联系着一组文件操作,依靠于I节点所代表的文件系统对象不同而不同。不管代表一个字符非凡文件的VFS I节点什么时候创建,它的文件操作被设置成字符设备的缺省操作。这只有一种文件操作:open操作。当一个应用程序打开这个字符非凡文件的时候,通用的open文件操作使用设备的主设备标识符作为chrdevs向量表中的索引,取出这种非凡设备的文件操作块。它也建立描述这个字符非凡文件的file数据结构,让它的文件操作指向设备驱动程序中的操作。然后应用程序所有的文件系统操作都被映射到字符设备的文件操作。

参见fs/devices.c chrdev_open() def_chr_fops

 

8.4.2 Block Devices(块设备)

 

块设备也支持象文件一样被访问。这种为打开的块非凡文件提供正确的文件操作组的机制和字符设备的十分相似。Linux用blkdevs向量表维护已经登记的块设备文件。它象chrdevs向量表一样,使用设备的主设备号作为索引。它的条目也是device_struct数据结构。和字符设备不同,块设备进行分类。SCSI是其中一类,而IDE是另一类。类向Linux核心登记并向核心提供文件操作。一种块设备类的设备驱动程序向这种类提供和类相关的接口。例如,SCSI设备驱动程序必须向SCSI子系统提供接口,让SCSI子系统用来对核心提供这种设备的文件操作

参见fs/devices.c

 



每一个块设备驱动程序必须提供普通的文件操作接口和对于buffer cache的接口。每一个块设备驱动程序填充blk_dev向量表中它的blk_dev_struct数据结构。这个向量表的索引还是设备的主设备号。这个blk_dev_struct数据结构包括一个请求例程的地址和一个指针,指向一个request数据结构的列表,每一个都表达buffer cache向设备读写一块数据的一个请求。

参见drivers/block/ll_rw_blk.c include/linux/blkdev.h

 

每一次buffer cache希望读写一块数据到或从一个登记的设备的时候它就在它的blk_dev_struc中增加一个request数据结构。图8.2显示了每一个request都有一个指针指向一个或多个buffer_head数据结构,每一个都是一个读写一块数据的请求。这个buffer_head数据结构被锁定(buffer cache),可能会有一个进程在等待这个缓冲区的阻塞进程完成。每一个request结构都是从一个静态表,all_request表中分配的。假如这个request增加到一个空的request列表,就调用驱动程序的request函数处理这个request队列。否则,驱动程序只是简单地处理request队列中的每一个请求。

 

一旦设备驱动程序完成了一个请求,它必须把每一个buffer_head结构从request结构中删除,标记它们为最新的,然后解锁。对于buffer_head的解锁会唤醒任何正在等待这个阻塞操作完成的进程。这样的例子包括文件解析的时候:必须等待EXT2文件系统从包括这个文件系统的块设备上读取包括下一个EXT2目录条目的数据块,这个进程将会在将要包括目录条目的buff_head队列中睡眠,直到设备驱动程序唤醒它。这个request数据结构会被标记为空闲,可以被另一个块请求使用。

 

8.5 Hard Disks(硬盘)

 

硬盘把数据存放在转动的磁碟上,提供了一个更永久存储数据的方式。为了写入数据,微小的磁头把磁碟表面的一个微小的点磁化。通过磁头可以探测指定的微粒是否被磁化,从而可以读出数据。

 

一个磁盘驱动器由一个或多个磁碟组成,每一个都用相当光滑的玻璃或者陶瓷制成,并覆盖上一层精细的金属氧化物。磁碟放在一个中心轴上面,并按照稳定的速度转动。转动速度根据型号不同从3000到1000RPM(转/每分钟)。磁盘的读/写磁头负责读写数据,每一个磁碟有一对,每一面一个。读/写磁头和磁碟表面并没有物理的接触,而是在一个很薄的空气垫(十万分之一英寸)上面漂浮。读写磁头通过一个驱动器在磁碟表面移动。所有的磁头都粘在一起,一起在磁碟表面移动。

 

每一个磁碟的表面都分成多个狭窄的同心环,叫做磁道(track)。磁道0是最外面的磁道,最高编号的磁道是最接近中心轴的磁道。一个柱面(cylinder)是相同编号磁道的组合。所以每一个磁碟的每一面的所有的第5磁道就是第5柱面。因为柱面数和磁道数相同,所以磁盘的尺寸常用柱面来描述。每一个磁道分成扇区。一个扇区是可以从硬盘读写的最小数据单元,也就是磁盘的块大小。通常扇区大小是512字节,扇区大小通常是在制造磁盘的时候进行格式化的时候设定的。

 

磁盘通常用它的尺寸(geometry)描述:柱面数、磁头数和扇区数。例如,启动的时候Linux这样描述我的IDE磁盘:

 

hdb: Conner Peripherals 540MB - CFS540A, 516MB w/64kB Cache, CHS=1050/16/63

 

这意味着它由1050柱面(磁道),16头(8个磁碟)和63个扇区/磁道。对于512字节的扇区或块大小,磁盘的容量是529200K字节。这和磁盘声明的516M的存储能力不符合,因为一些扇区用作存储磁盘的分区信息。一些磁盘可以自动找出坏的扇区,对其进行重新索引。

 

硬盘可以再分为分区。一个分区是分配用于特定目的的一大组扇区。对磁盘分区答应磁盘用于几个操作系统或多个目的。大多数单个磁盘的Linux系统都由3个分区:一个包含DOS文件系统,另一个是EXT2文件系统,第三个是交换分区。硬盘的分区用分区表描述,每一个条目用磁头、扇区和柱面号描述分区的起止位置。对于用fdisk格式化的DOS磁盘,可以有4个主磁盘分区。不是分区表所有的4个条目都必须用到。Fdisk支持三种类型的分区:主分区、扩展分区和逻辑分区。扩展分区不是真正的分区,它可以包括任意数目的 逻辑分区。发明扩展分区和逻辑分区是为了突破4个主分区的限制。下面是一个包括2个主分区的磁盘的fdisk的输出:

 

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个柱面。第2个分区,交换分区,开始于下一个柱面(478)并扩展到磁盘最里面的柱面。



在初始化的时候Linux映射系统中的硬盘的拓扑结构。它找出系统中有多少个硬盘以及硬盘的类型。Linux还找出每一个磁盘如何分区。这些都是由gendisk_head指针列表指向的一组gendisk数据结构的列表表达。对于每一个磁盘子系统,例如IDE,初始化的时候生成gendisk数据结构表示它找到的磁盘。这个过程和它登记它的文件操作和在blk_dev数据结构中增加它的条目发生在同一时间。每一个gendisk数据结构都由一个唯一的主设备号,和块非凡设备的相同。例如,SCSI磁盘子系统会创建一个独立的gendisk条目(“sd”),主设备号是8(所有SCSI磁盘设备的主设备号)。图8.3显示了两个gendisk条目,第一个是SCSI磁盘子系统,第二个是IDE磁盘控制器。这里是ide0,主IDE控制器。

 

虽然磁盘子系统在初始化的时候会建立相应的gendisk条目,Linux只是在进行分区检查的时候才用到。每一个磁盘子系统必须维护自己的数据结构,让它自己可以把设备的主设备号和次设备号映射到物理磁盘的分区上。不管什么时候读写块设备,不管是通过buffer cache或者文件操作,核心都根据它在块非凡设备文件(例如/dev/sda2)中找到的主设备号和次设备号把操作定向到合适的设备。是每一个设备驱动程序或子系统把次设备号映射到真正的物理设备上。

 

8.5.1 IDE Disks(IDE磁盘)

 

今天Linux系统中最常用的磁盘是IDE磁盘(Integrated Disk Electronic)。IDE和SCSI一样是一个磁盘接口而不是一个I/O总线。每一个IDE控制器可以支持最多2个磁盘,一个是master,另一个是slave。Master和slave通常用磁盘上的跳线设置。系统中的第一个IDE控制器叫做主IDE控制器,下一个叫从属控制器等等。IDE可以从/向磁盘进行3.3M/秒的传输,IDE磁盘的最大尺寸是538M字节。扩展IDE或EIDE把最大磁盘尺寸增加到8.6G字节,数据传输速率高达16.6M/秒。IDE和EIDE磁盘比SCSI磁盘便宜,大多数现代PC都有一个或更多的主板上的IDE控制器。

 

Linux按照它发现的控制器的顺序命名IDE磁盘。主控制器上的主磁盘是/dev/had,slave磁盘是/dev/hdb。/dev/hdc是次IDE控制器上的master磁盘。IDE子系统向Linux登记IDE控制器而不是磁盘。主IDE控制器的主标识符是3,次IDE控制器的标识符是22。这意味着假如一个系统有两个IDE控制器,那么在blk_dev和blkdevs向量表中在索引3和22会有IDE子系统的条目。IDE磁盘的块非凡文件反映了这种编号:磁盘/dev/had和/dev/hdb,都连接在主IDE控制器上,主设备号都是3。核心使用主设备标识符作为索引,对于这些块非凡文件的IDE子系统进行的所有的文件或者buffer cache操作都被定向到相应的IDE子系统。当执行一个请求的时候,IDE子系统负责判定这个请求是针对哪一个IDE磁盘。为此,IDE子系统使用设备非凡文件中的次设备号,这些信息答应它把请求定向到正确的磁盘的正确的分区。/dev/hdb,主IDE控制器上的slave IDE磁盘的设备标识符是(3,64)。它的第一个分区(/dev/hdb1)的设备标识符是(3,65)。

 

8.5.2 Initializing the IDE Subsystem(初始化IDE子系统)

 

IBM PC的大部分历史中都有IDE磁盘。这期间这些设备的接口发生了变化。这让IDE子系统的初始化过程比它第一次出现的时候更加复杂。

 

Linux可以支持的最大IDE控制器数目是4。每一个控制器都用一个ide_hwifs向量表中的一个ide_hwif_t数据结构表示。每一个ide_hwif_t数据结构包含两个ide_drive_t数据结构,分别表示可能支持的master和slave IDE驱动器。在IDE子系统初始化期间,Linux首先查看在系统的CMOS内存中记录的磁盘的信息。这种用电池做后备的内存在PC关机的时候不会丢失它的内容。这个CMOS内存实际上在系统的实时时钟设备里面,不管你的PC开或者关,它都在运行。CMOS内存的位置由系统的BIOS设置,同时告诉Linux系统中找到了什么IDE控制器和驱动器。Linux从BIOS中获取找到的磁盘的尺寸(geometry),用这些信息设置这个驱动器的ide_hwif_t的数据结构。大多数现代PC使用PCI芯片组例如Intel的82430 VX芯片组,包括了一个PCI EIDE控制器。IDE子系统使用PCI BIOS回调(callback)定位系统中的PCI (E)IDE控制器。然后调用这些芯片组的询问例程。

 

一旦发现一个IDE接口或者控制器,就设置它的ide_hwif_t来反映这个控制器和上面的磁盘。操作过程中IDE驱动程序向I/O内存空间的IDE命令寄存器写命令。主IDE控制器的控制和状态寄存器的缺省的I/O地址是0x1F0-0x1F7。这些地址是早期的IBM PC约定下来的。IDE驱动程序向Linux的buffer cache和VFS登记每一个控制器,分别把它加到blk_dev和blkdevs向量表中。IDE驱动程序也请求控制适当的中断。同样,这些中断也有约定,主IDE控制器是14,次IDE控制器是15。但是,象所有的IDE细节一样,这些都可以用核心的命令行选项改变。IDE驱动程序在启动的时候也为每一个找到的IDE控制器在gendisk列表中增加一个gendisk条目。这个列表稍后用于查看启动时找到的所有的硬盘的分区表。分区检查代码明白每一个IDE控制器可以控制两个 IDE磁盘。

 

8.5.3 SCSI Disks(SCSI磁盘)

 

SCSI(Small Computer System Interface小型计算机系统接口)总线是一种有效的点对点的数据总线,每个总线支持多达8个设备,每个主机可以有一或者多个。每一个设备都必须由一个唯一的标识符,通常用磁盘上的跳线设置。数据可以在总线上的任意两个设备之间同步或者异步传输,可以用32位宽的数据传输,速度可能高达40M/秒。SCSI总线可以在设备之间传输数据和状态信息,发起者(initiator)和目标(target)之间的事务会涉及多达8个不同的阶段。你可以通过SCSI总线上的5种信号判定出当前的阶段。这8个阶段是:

 

BUS FREE 没有设备有总线的控制权,当前没有发生任何事务。

ARBITRATION (仲裁)一个SCSI设备试图得到SCSI总线的控制权,它在地址管脚上声明(assert)它的SCSI标识符。最高编号的SCSI标识符成功。

SELECTION 一个设备通过仲裁成功地得到了SCSI总线的控制权,现在它必须向它要发送命令的SCSI目标发送信号。它在地址管脚上声明目标的SCSI标识符。

RESELECTION SCSI设备在处理请求的过程中可能断线,目标会重新选择发起者。并非所有的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 host通常总是SCSI命令的发起者(initiator)。

 

Device SCSI设备通常是磁盘,但是SCSI标准支持多种类型:磁带、CD-ROM和通用(generic)SCSI设备。SCSI设备通常都是SCSI命令的目标。这些设备必须不同地对待。例如可移动介质如CD-ROM或磁带,Linux需要探测介质是否取出。不同的磁盘类型有不同的主设备编号,答应Linux把块设备请求定向到合适的SCSI类型。

 

Initializing the SCSI Subsystem(初始化SCSI子系统)

 

初始化SCSI子系统相当复杂,反映出SCSI总线和设备的动态的实质。Linux在启动的时候初始化SCSI子系统:它查找系统中的SCSI控制器(SCSI host),并探测每一个SCSI总线,查找每一个设备。然后初始化这些设备,让Linux核心的其余部分可以通过普通的文件和buffer cache块设备操作访问它们。这个初始化过程有四个阶段:



首先,Linux找出核心建立的时候建立到核心的哪一个SCSI host适配器或控制器有可以控制的硬件。每一个内建的SCSI host在buildin_scsi_hosts向量表中都有一个Scsi_Host_Template的条目。这个Scsi_Host_Template数据结构包括例程的指针,这些例程可以执行和SCSI host相关的动作例如探测这个SCSI host上粘附了什么SCSI设备。这些例程在SCSI子系统配置期间被调用,是支持这种host类型的SCSI设备驱动程序的一部分。每一个查到的SCSI控制器(有真实的SCSI设备粘附),它的Scsi_Host_Template数据结构都加到scsi_hosts列表中,表示有效的SCSI host。每一个探测到的host类型的每一个实例都用scsi_hostlist列表中的一个Scsi_Host数据结构表示。例如一个系统有两个NCR810 PCI SCSI控制器,在这个列表中会有两个Scsi_Host条目,每一个控制器一个。每一个Scsi_Host指向的Scsi_Host_Template表示它的设备驱动程序。

 

现在每一个SCSI host都找到了,SCSI子系统必须找到每一个host总线上的所有的SCSI设备。SCSI设备编号从0到7,每一个设备编号或者SCSI标识符在它所粘附的SCSI总线上都是唯一的。SCSI标识符通常用设备上的跳线设置。SCSI初始化代码通过向每一个设备发送TEST_UNIT_READY命令来查找一个SCSI总线上的每一个SCSI设备。当一个设备回应,再向它发送一个ENQUIRY命令来完成它的判别。这向Linux给出Vendor的名称和设备的型号和修订号。SCSI命令用一个Scsi_Cmnd数据结构来表示,这些命令通过调用这个SCSI host的Scsi_Host_Template数据结构中的设备驱动程序例程传递给设备驱动程序。每一个找到的SCSI设备用一个Scsi_Device数据结构表示,每一个都指向它的父Scsi_Host。所有的Scsi_Device数据结构都加到scsi_devices列表中。图8.4显示了主要的数据结构和其他数据结构的关系。

 

有四种SCSI设备类型:磁盘、磁带、CD和通用(generic)。每一种SCSI类型都分别向核心登记,有不同的主块设备类型。但是,它们只有在一个或多个给定的SCSI设备类型的设备找到的时候才登记自己。每一个SCSI类型,例如SCSI磁盘,维护它自己的设备表。它用这些表把核心的块操作(文件或buffer cache)定向到正确的设备驱动程序或SCSI host。每一个SCSI类型都用一个Scsi_Type_Template数据结构表示。它包括这种类型的SCSI设备的信息和执行多种任务的例程的地址。SCSI子系统使用这些模板调用每一种SCSI设备类型的SCSI类型例程。换句话说,假如SCSI子系统希望粘附一个SCSI磁盘设备,它会调用SCSI 磁盘类型的例程。假如探测到某类型的一个或多个SCSI设备,它的Scsi_Type_Templates的数据结构就加到了scsi_devicelist列表中。

 

SCSI子系统初始化的最后阶段是调用每一个登记的Scsi_Device_Template的完成函数。对于SCSI磁盘类型让所有的SCSI磁盘转动起来并记录它们的磁盘尺寸。它也把表示所有SCSI磁盘的gendisk数据结构增脚的磁盘的链接列表中,如图8.3。

 

Delivering Block Device Requests(传递块设备请求)

 

一旦Linux初始化了SCSI子系统,就可以使用SCSI设备了。每一个有效的SCSI设备类型都在核心中登记自己,所以Linux可以把块设备请求定向到它那里。这些请求可能是通过blk_dev的buffer cache请求或者是通过blkdevs的文件操作。拿一个由一个或多个EXT2文件系统分区的 SCSI磁盘驱动器为例,当它的EXT2分区安装上的时候核心的缓冲区请求是如何定向到正确的SCSI磁盘呢?

 

每一个向/从一个SCSI磁盘分区读/写一块数据的请求都会在blk_dev向量表中这个SCSI磁盘的current_request列表中加入一个新的request数据结构。假如这个request列表正在处理,那么buffer cache不需要做什么。否则它必须让SCSI磁盘子系统处理它的请求队列。系统中的每一个SCSI磁盘用一个Scsi_Disk数据结构表示。它们保存在rscsi_disks向量表中,用SCSI磁盘分区的次设备号的一部分作为索引。例如,/dev/sdb1主设备号8,次设备号17,它的所以是1。每一个Scsi_Disk的数据结构包括一个指向表示这个设备的Scsi_Device数据结构的指针。Scsi_Device又指向一个“拥有它”的Scsi_Host数据结构。Buffer cache中的request数据结构转换成为描述需要发送到SCSI设备的SCSI命令的Scsi_Cmd数据结构中,并在表示这个设备的Scsi_Host数据结构中排队。一旦适当的数据块读/写之后,会由各自的SCSI设备驱动程序处理。

 

8.6 Network Devices(网络设备)

 

一个网络设备,只要关系到Linux的网络子系统,是一个发送和接收数据包的实体。通常是一个物理的设备,例如一个以太网卡。但是一些网络设备是纯软件的,例如loopback设备,用于向自己发送数据。每一个网络设备用一个device数据结构表示。网络设备驱动程序在核心启动网络初始化的时候向Linux登记它控制的设备。Device数据结构包括这个设备的信息和答应大量支持的网络协议使用这个设备的服务的函数的地址。这些函数多数和使用这个网络设备传输数据有关。设备使用标准的网络支持机制,向适当的协议层传输接收的数据。传输和接收的所有的网络数据(包packets)都用sk_buff数据结构表示,这是灵活的数据结构,答应网络协议头很轻易地增加和删除。网络协议层如何使用网络设备,它们如何使用sk_buff数据结构往返传递数据,在网络章(第10章)有具体的描述。本章集中在device数据结构以及网络设备如何被发现和初始化。

参见include/linux/netdevice.h

 

device数据结构包括网络设备的信息:

 

Name 不象块和字符设备,它们的设备非凡文件用mknod命令创建,网络设备非凡文件在系统的网络设备发现并初始化的时候自然出现。它们的名字是标准的,每一个名字都表示了它的设备类型。同种类型的多个设备从0向上依次编号。因此以太网设备编号为/dev/eth0、/dev/eth1、/dev/eth2等等。一些常见的网络设备是:

 

/dev/ethN 以太网设备

/dev/slN SLIP设备

/dev/pppN PPP设备

/dev/lo loopback 设备

 

Bus Information 这是设备驱动程序控制设备需要的信息。Irq是设备使用的中断。Base address是设备的控制和状态寄存器在I/O内存种的地址。DMA通道是这个网络设备使用的DMA通道号。所有这些信息在启动时设备初始化的时候设置。

 

Interface Flags 这些描述了这个网络设备的特性和能力。

IFF_UP 接口up ,正在运行

IFF_BROADCAST 设备的广播地址有效

IFF_DEBUG 设备的debug选项打开

IFF_LOOPBACK 这是一个loopback设备

IFF_POINTTOPOINT 这是点对点的连接(SLIP and PPP)

IFF_NOTRAILERS No network trailers

IFF_RUNNING 分配了资源

IFF_NOARP 不支持ARP协议

IF_PROMISC 设备在混合(promiscuous)接收模式,它会接收所有的包,不管它们的地址是谁。

IFF_ALLMULTI 接收所有的IP Multicast帧

IFF_MULTICAST 可以接收IP multicast帧

 

Protocal Information 每一个设备都描述它可以被网络协议层如何使用:

Mtu 不包括需要增加的链路层的头这个网络能够传输的最大尺寸的包。这个最大值用于协议层例如IP,来选择一个合适的包大小进行发送。

Family family显示了设备可以支持的协议族。所有Linux网络设备都支持的family是AF_INET,Internet地址family。

Type 硬件接口类型描述了这个网络设备连接的介质。Linux网络设备支持多种介质类型。包括Ethernet、X.25,Token Ring、Slip、PPP和Apple Localtalk。

Addresses device 数据结构保存一些和这个网络设备相关的地址,包括IP地址

 

Packet Queue 这是一个sk_buff的包队列,等待网络设备进行传输

 

Support Functions 每一个设备都提供了一组标准的例程,让协议层调用,作为对于设备链路层的接口的一部分。包括设置和帧传输例程,以及增加标准帧头和收集统计信息的例程。这些统计信息可以用ifcnfig看到

 

8.6.1 Initializing Network Devices(初始化网络设备)

 

网络设备驱动程序象其他Linux设备驱动程序一样,可以建立到Linux核心中。每一个可能的网络设备都用dev_base列表指针指向的网络设备列表中的一个device数据结构表示。假如需要设备相关的操作,网络层调用网络设备服务例程(放在device数据结构中)其中的一个。但是,初始的时候,每一个device数据结构只是放了初始化或者探测例程的地址。

 

网络驱动程序必须解决两个问题。首先,不是所有建立在Linux核心的网络设备驱动程序都会有控制的设备;第二,系统中的以太网设备总是叫做/dev/eth0、/dev/eth1等等,而不管底层的设备驱动程序是什么。“丢失“网络设备的问题轻易解决。在调用每一个网络设备的初始化例程的时候,它返回一个状态,显示它是否定位到了它驱动的控制器的一个实例。假如驱动程序没有找到任何设备,它由dev_base指向的device列表中的条目就被删除。假如驱动程序可以找到一个设备,它就用这个设备的信息和网络设备驱动程序中的支持函数的地址填充device数据结构其余的部分。

 

第二个问题,就是动态地分配以太网设备到标准的/dev/ethN设备非凡文件上,用更优雅的方式解决。Device列表中有8个标准的条目:eth0、eth1到eth7。所有条目的初始化例程都一样。它顺序尝试建立在核心的每一个以太网设备驱动程序,直到找到一个设备。当驱动程序找到它的以太网设备,它就填充它现在拥有的ethN的device数据结构。这时网络驱动程序也要初始化它控制的物理硬件,并找出它使用的IRQ、DMA等等。驱动程序可能找到它控制的网络设备的几个实例,在这种情况下,它就占用几个/dev/ethN的device数据结构。一旦所有的8个标准的/dev/ethN都分配了,就不会再探测更多的以太网设备。
视频教程列表
文章教程搜索
 
C语言程序设计推荐教程
C语言程序设计热门教程
看全部视频教程
购买方式/价格
购买视频教程: 咨询客服
tel:15972130058