11.3.5 硬盘驱动程序的实现

1、磁盘硬件

所有实际的磁盘都组织成许多柱面,每个柱面上的磁道数和磁头数相同。磁道又被划分成许多扇区。如果每条磁道上的扇区数相同的话,外圈磁道的数据的密度就会小一些,这就意味着会牺牲一些磁盘容量,也意味着必须存在更复杂的系统。现代大容量的硬盘中外圈磁道有的扇区数比内圈多,这就是IDE(Integrated Drive Electronics) 驱动器,它内置的电子器件屏蔽了复杂的细节,对于操作系统来说仍呈现出简单的几何结构,每条磁道具有相同的扇区。

    下面我们看一下硬盘控制器的硬件结构,以便于我们进一步了解硬盘驱动程序。对于驱动程序,了解硬盘的各种寄存器以及寄存器的各个位是重要的,表11.1(a)(e)给出各个寄存器的具体描述。

 

11.1                        (a) IDE 硬盘的控制寄存        

A2   A1    A0

读功能

写功能

0     0        0

数据

数据

0     0        0

出错

写预补偿

0     0        0

扇区计数

扇区计数

0     1        1

扇区号(07

扇区号(07

1     0        0

柱面号低位815

柱面号低位815

1     0        0

柱面号高位(1623

柱面号高位(1623

1     1        0

选择驱动器磁头(2427

选择驱动器磁头(2427

1     1        1

状态

状态

 

 

11.1              (b)驱动器/磁头  寄存器的选择域

  7

  6

  5

  4

  3

  2

  1

  0

  1

LBA

  1

  D

HS3

HS2

HS1

HS0

LBA0 = 柱面数/磁头/扇区模式

       1 = 逻辑寻块模式

D   0 = 主驱动器

       1 =  从驱动器

HSn  CHS模式:在CHS模式下磁头选择

        LBA模式:块选择位24 27

 

 11.1         (c)出错标志寄存器各个域的功能

 

功能

  D0

当它置1,表明找到了扇区但不能找到数据地址的标记

  D1

当它置1,表明回零道命令时,发了1024个脉冲仍未找到 0

  D2

当它置1,表明无效命令

  D4

当它置1,表示磁盘转了8圈仍未找到所要求的参数或出现IDCRC

  D6

当它置1,表明数据段CRC错或未找到数据地址标志

  D7

当它置1,表明在ID 段有坏块标志

 

 11.1    (d)状态寄存器各个域的含义

  

功能

   D0

当它置位时,标志错误寄存器存在错误标志

   D1

当它置位时,表示正在进行命令不能接受新的命令

   D2

未用

   D3

当它置位时,表明WDC请求与主机交换数据

   D4

寻找完成标志

   D5

当它置位时,表示有写错误

   D6

当它置位时,表示盘准备好

   D7

命令写入时将它置位,命令结束时将它清除

 

 11.1          (e) 命令寄存器的功能

命令

D7D6D5D4D3D2D1D0

回零道

0001R3R2R1R0

寻道

1111R3R2R1R0

读扇区

0010IM0T

写扇区

00110M0T

扫描ID

01000000

格式化

01010000

R3 R0 的编码规定了步进脉冲的间隔

I 是中断允许位.当I=0 时,将在BDRQ(BDRQ为扇区缓冲区数据请求输出信号)有效时产生中断.

        当I=1 时,是在命令结束时产生中断.

M 是多扇区读写标志.当M=0 时,只写一个扇区.

           当 M=1 , 传送多个扇区.

T  是允许重试命令.  T=0 时,允许重试 .

            T=1 时,不允许重试.

 

上面对于硬盘控制器的几个寄存器的功能作了简要的说明,由此我们可以了解到,对于硬盘的很大一部分工作,由硬盘控制器就可以完成。而对于软盘来说,其控制器则简单得多,我们需要编程去完成各种功能,这样软盘驱动程序就变得比较复杂。在这儿我们只讨论硬盘驱动程序。

2Linux 中硬盘驱动程序的实现

    接下来我们将要讨论的驱动程序在drivers/ide/hd.c中 ,在文件为include/linux/hdreg.h 中,定义了控制器寄存器、状态位和命令、数据结构和原形。这些宏定义可以根据其名字并结合上面所说的硬件内容去理解。

     Linux 中,硬盘被认为是计算机的最基本的配置,所以在装载内核的时候,硬盘驱动程序必须就被编译进内核,不能作为模块编译。硬盘驱动程序提供内核的接口为: 

   static struct block_device_operations hd_fops = {

         open:           hd_open,

         release:        hd_release,

         ioctl:          hd_ioctl,

   };

 

对硬盘的操作只有三个函数。 我们来看一下 hd_open ( ) hd_release ( ) 函数,打开操作首先检测了设备的有效性,接着测试了它的忙标志,最后对请求硬盘的总数加1,来标识对硬盘的请求个数,hd_release ( ) 函数则将请求的总数减1

前面说过,对于块设备的读写操作是先对缓冲区操作,但是当需要真正同硬盘交换数据的时候,驱动程序又干了些什么?在 hd.c 中有一个函数 hd_out ( ) ,可以说它在实际的数据交换中起着主要的作用.它的原形是:

 

static void  hd_out(unsigned int drive,unsigned int nsect,unsigned int sect,

                  unsigned int head,unsigned int cyl,unsigned int cmd,

                   void (*intr_addr)(void));

 

其中参数drive是进行操作的设备号;  nsect 是每次读写的扇区数; sect 是读写的开始扇区号; head 是读写的磁头号;cmd是 操作命令控制命令字。

 

通过这个函数向硬盘控制器的寄存器中写入数据,启动硬盘进行实际的操作。同时这个函数也配合完成 cmd 命令相应的中断服务子程序,通过SET_INIT(intr_addr) 宏定义将其地址赋给 DEVICE_INTR

hd_request () 函数就是通过这个函数进行实际的数据交换,同其它驱动程序不同的是该函数还要根据每个命令的不同来确定一些参数,最基本的是读写方式的确定,关于硬盘的读写方式有两种,一种是单扇区的读写,另一种是多扇区的读写,单扇区的读写是指每次操作只对一个扇区操作,而多扇区则指每次对多个扇区进行操作,不同的方式其中断服务子程序不同,其相应的地址就作为参数传给 hd_out ( ) ,由它设置DEVICE_INIThd_request( ) 函数确定的其它参数也就是hd_out()所需要的参数:

我们知道块设备的实际数据交换需要中断服务子程序的配合,在本驱动程序中的中断服务子程序有以下几个主要函数:

  1 void unexpected_hd_interrupt(void)

      功能:对不期望的中断进行处理(设置SET_TIMER)

 

  2 static void bad_rw_intr(void)

      功能: 当硬盘的读写操作出现错误时进行处理。

             每重复四次磁头复位 ;

             每重复八次控制器复位;

             每重复十六次放弃操作。

 

  3 static void recal_intr(void)

       功能:重新进行硬盘的本次操作。

 

  4static void read_intr(void)

       功能:从硬盘读数据到缓冲区 。

 

  5  static void write_intr(void)

       功能: 从缓冲区读数据到硬盘。

 

 6static void hd_interupt(void)

  功能:决定硬盘中断所要调用的中断程序。

 

在注册的时候,同硬盘中断联系的是  hd_interupt ( ) ,也就是说当硬盘中断到来的时候,执行的函数是hd_interupt ( ),在此函数中调用 DEVICE_INTR 所指向的中断函数,如果 DEVICE_INTR 为空,则执行unexpected_hd_interrupt() 函数。

对硬盘的操作离不开控制寄存器,为了控制磁盘要经常的去检测磁盘的运行状态,在本驱动程序中有一系列的函数是完成这项工作的,check_status ( ) 检测硬盘的运行状态,如果出现错误则进行处理。contorller_ready ( ) 检测控制器是否准备好。drive_busy ( ) 检测硬盘设备是否处于忙态。当出现错误的时候,由 dump_status ( ) 函数去检测出错的原因。wait_DRQ ( ) 对数据请求位进行测试。

当硬盘的操作出现错误的时候,硬盘驱动程序会把它尽量在接近硬件的地方解决掉,其方法是进行重复操作,这些在 bad_rw_intr ( ) 中进行,与其相关的函数有 reset_controller ( ) reset_hd ( )

函数 hd_init ( ) 是对硬盘进行初始化的,这个函数的过程同其它块设备基本一致。还有一些对硬盘的参数进行测试和确定的函数,这里就不一说明。

通过对 RAM 盘和硬盘驱动程序的分析我们对一个块设备的驱动程序应已经有一个大概的认识,那么对于其它的块设备驱动程序我们就可以根据硬盘驱动程序的分析方法去分析,在分析的时候要切记不能脱离硬件控制器,也不能一开始就扎入技术细节,那样我们就会陷入一个不可自拔的泥潭。如果要写一个块设备的驱动程序,我们除了要了解硬件寄存器外还要弄清具体驱动程序所要解决的问题,然后再根据驱动程序写法去做进一步的工作。