开始 fs 模块之前,我发现如果对块设备/字符设备的驱动程序不了解的话,读 fs 代码时会困难重重。为了简化问题,本文及之后的 fs 模块都将只记录关于块设备(特指硬盘)的代码,先弄懂一个,剩下的读起来就轻松了。阅读本文或许会一头雾水,但和下篇文章联系起来看就会清楚许多了(x

块设备操作方式(以读数据为例)

提到 I/O 先来看一张图:

块设备操作方式

当程序需要从硬盘中读取数据(read 系统调用)时,缓冲区管理程序会先查询该数据块是否已经读入到缓冲区中。如果是,则直接将该缓冲头(涉及高速缓冲的管理方式,下篇文章将会记录)返回并唤醒等待此数据块的进程;否则调用 ll_rw_block 函数,告诉块设备驱动程序(内核代码)现在需要读数据块,该函数就会为其创建一个请求项,并挂入相应设备的请求队列,同时发出请求的进程会被挂起(不可中断睡眠态)。

当请求被处理时,设备控制器根据请求项中的参数,向硬盘驱动器发送读指令,硬盘驱动器就会将数据读取到设备控制器的缓冲区中(注意此时原发出读盘请求的进程已被挂起,CPU 正在被其他进程占用)。当设备控制器检测到数据读取完毕,就会产生一个中断请求信号发往 CPU,CPU 在硬盘中断处理程序 hd_interrupt 中调用 read_intr 函数将数据从设备控制器的缓冲区搬到内存的高速缓冲区中,并让设备控制器开始处理下一个请求(如果有的话)。最后内核将高速缓冲中的数据拷贝到调用 read 函数时第二个参数指向的地址中去。用一张图来总结:

块设备操作方式


请求项与请求队列

请求项

请求项的数据结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// kernel/blk_drv/blk.h Line 23
struct request {
int dev; // 使用的设备号,为 -1 表示该请求项空闲
int cmd; // 表示该请求项的操作是读还是写
int errors; // 操作时产生的错误次数
unsigned long sector; // 开始扇区
unsigned long nr_sectors; // 读/写 扇区数量
char * buffer; // 指向高速缓冲区,数据会从设备控制器的缓冲区搬到这里来
struct task_struct * waiting; // 等待该请求完成的任务
struct buffer_head * bh; // 缓冲区头指针
struct request * next; // 指向下一个请求项
};

// ll_rw_blk.c Line 21
struct request request[NR_REQUEST]; // 请求项数组,NR_REQUEST 值为 32

为什么请求项已经可以通过 next 指针构成单项链表了,还需要一个数组来维护呢?采用数组加链表结构其实是为了满足两个目的:

  • 数组结构使得在搜索空闲请求项的时候可以进行循环操作,搜索访问时间复杂度为常数
  • 链表结构是为了满足电梯算法插入请求项的操作

请求队列

对于各种块设备,内核使用块设备表 blk_dev 来管理,每种块设备在块设备表中占有一项,相关数据结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// blk.h Line 45
// 块设备表项
struct blk_dev_struct {
void (*request_fn)(void); // 处理请求项的函数指针
struct request * current_request; // 该设备当前请求队列头指针
};

// ll_rw_blk.c Line 32
// 初始化块设备表,NR_BLK_DEV 值为 7,以设备的主设备号为索引
struct blk_dev_struct blk_dev[NR_BLK_DEV] = {
{ NULL, NULL }, // 无
{ NULL, NULL }, // 虚拟盘,对应的请求处理函数为 do_rd_request
{ NULL, NULL }, // 软盘,对应的请求处理函数为 do_fd_request
{ NULL, NULL }, // 硬盘,对应的请求处理函数为 do_hd_request
{ NULL, NULL }, // 无用
{ NULL, NULL }, // 无用
{ NULL, NULL } // 无用
};

再来通过一张图直观地感受这些数据结构之间的关系:

请求项与请求队列

通过之前的描述不难看出,硬盘设备有 4 个请求,软盘设备有 1 个请求,虚拟盘暂无请求。下面正式开始块设备(仅硬盘)驱动程序部分源码的阅读


blk.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
// Line 1
#ifndef _BLK_H
#define _BLK_H

#define NR_BLK_DEV 7 // 块设备类型数量
#define NR_REQUEST 32 // 请求项数组长度

struct request { // 请求项结构体,上面已经提及
int dev;
int cmd;
int errors;
unsigned long sector;
unsigned long nr_sectors;
char * buffer;
struct task_struct * waiting;
struct buffer_head * bh;
struct request * next;
};

// 单项电梯算法对插入请求队列的请求项进行排序,将请求项插入到磁头移动距离最小的位置处
#define IN_ORDER(s1,s2) \
((s1)->cmd<(s2)->cmd || (s1)->cmd==(s2)->cmd && \
((s1)->dev < (s2)->dev || ((s1)->dev == (s2)->dev && \
(s1)->sector < (s2)->sector)))

struct blk_dev_struct { // 设备表项结构体,上面已经提及
void (*request_fn)(void);
struct request * current_request;
};

extern struct blk_dev_struct blk_dev[NR_BLK_DEV]; // 设备表
extern struct request request[NR_REQUEST]; // 请求项数组
extern struct task_struct * wait_for_request; // 等待空闲请求项的进程队列头指针

#ifdef MAJOR_NR
...
#elif (MAJOR_NR == 3) // 如果主设备号是 3,即硬盘
#define DEVICE_NAME "harddisk" // 设备名称
#define DEVICE_INTR do_hd // 设备中断处理函数
#define DEVICE_REQUEST do_hd_request // 设备请求项处理函数
#define DEVICE_NR(device) (MINOR(device)/5) // 硬盘设备号(0 - 1)
#define DEVICE_ON(device)
#define DEVICE_OFF(device) // 开机后硬盘总是运转
#elif (MAJOR_NR > 3) // 主设备号大于 3
#error "unknown blk device" // 未知块设备
#endif

#define CURRENT (blk_dev[MAJOR_NR].current_request) // 指定设备号的当前请求项指针
#define CURRENT_DEV DEVICE_NR(CURRENT->dev) // 当前请求项的设备号

#ifdef DEVICE_INTR // 如果定义了设备中断处理符号常数
void (*DEVICE_INTR)(void) = NULL; // 将其声明为一个值为 NULL 的函数指针
#endif
static void (DEVICE_REQUEST)(void); // 将符号常数 DEVICE_REQUEST 声明为一个静态函数指针

// 解锁指定缓冲块,参数是该缓冲块的缓冲块头指针
static inline void unlock_buffer(struct buffer_head * bh)
{
if (!bh->b_lock) // 如果已经处于解锁状态,发出警告
printk(DEVICE_NAME ": free buffer being unlocked\n");
bh->b_lock=0; // 解锁
wake_up(&bh->b_wait); // 唤醒等待该缓冲块的进程
}

// 结束请求,参数用于设置缓冲块数据更新标志
static inline void end_request(int uptodate)
{
DEVICE_OFF(CURRENT->dev); // 关闭设备
if (CURRENT->bh) { // 如果当前请求项的缓冲块头指针不为空
CURRENT->bh->b_uptodate = uptodate; // 设置数据更新标志
unlock_buffer(CURRENT->bh); // 解锁缓冲块
}
if (!uptodate) { // 如果参数给定的更新标志为 0,则显示出错信息
printk(DEVICE_NAME " I/O error\n\r");
printk("dev %04x, block %d\n\r",CURRENT->dev,
CURRENT->bh->b_blocknr);
}
wake_up(&CURRENT->waiting); // 唤醒等待该请求项完成的进程
wake_up(&wait_for_request); // 唤醒等待空闲请求项的进程
CURRENT->dev = -1; // 设置当前请求项空闲
CURRENT = CURRENT->next; // 指向下一请求项
}

#define INIT_REQUEST \ // 初始化请求项
repeat: \
if (!CURRENT) \ // 如果没有请求项,则返回
return; \
if (MAJOR(CURRENT->dev) != MAJOR_NR) \ // 如果当前设备主设备号不对则停机
panic(DEVICE_NAME ": request list destroyed"); \
if (CURRENT->bh) { \ // 如果缓冲块头指针不为空
if (!CURRENT->bh->b_lock) \ // 且没有被锁定
panic(DEVICE_NAME ": block not locked"); \ // 停机
}

#endif
#endif

hd.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// Line 16
#include <linux/config.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/hdreg.h>
#include <asm/system.h>
#include <asm/io.h>
#include <asm/segment.h>

#define MAJOR_NR 3 // 定义主设备号为 3,即硬盘
#include "blk.h"

#define CMOS_READ(addr) ({ \ // 读 CMOS 参数
outb_p(0x80|addr,0x70); \ // 0x80 | addr 是要读的 CMOS 内存地址,0x70 是写端口号
inb_p(0x71); \ // 0x71 是读端口号
})

#define MAX_ERRORS 7 // 读/写 一个扇区时允许的最多出错次数
#define MAX_HD 2 // 系统支持的最多硬盘数

static void recal_intr(void); // 重新校正处理函数

static int recalibrate = 0; // 重新校正标志,设置为 1 时,程序会调用 recal_intr
static int reset = 0; // 复位标志,当发生读写错误时会设置该标志并调用相关复位函数

// 硬盘信息结构体
// 包括磁头数、每磁道扇区数、柱面数、写前预补偿柱面号,磁头着陆区柱面号、控制字节
struct hd_i_struct {
int head,sect,cyl,wpcom,lzone,ctl;
};

// 如果在 include/linux/config.h 中已经定义了 HD_TYPE,就取定义好的参数
// 作为硬盘信息数组 hd_info 中的数据,否则填充为 0
#ifdef HD_TYPE
struct hd_i_struct hd_info[] = { HD_TYPE };
#define NR_HD ((sizeof (hd_info))/(sizeof (struct hd_i_struct)))
#else
struct hd_i_struct hd_info[] = { {0,0,0,0,0,0},{0,0,0,0,0,0} };
static int NR_HD = 0;
#endif

// 硬盘分区结构体
static struct hd_struct {
long start_sect; // 分区在硬盘中的起始(绝对)扇区
long nr_sects; // 分区所占的扇区总数
} hd[5*MAX_HD]={{0,0},};

// 读端口宏定义,从端口 port 读 nr 字,保存在 buf 中
#define port_read(port,buf,nr) \
__asm__("cld;rep;insw"::"d" (port),"D" (buf),"c" (nr))

// 写端口宏定义,从 buf 中取数据,向端口 port 写 nr 字
#define port_write(port,buf,nr) \
__asm__("cld;rep;outsw"::"d" (port),"S" (buf),"c" (nr))

extern void hd_interrupt(void);
extern void rd_load(void);

接下来是在 init 函数中调用的 setup 系统调用,参数 BIOS 是 setup.s 程序取得并放置在 0x90080 处的包含两个硬盘参数的硬盘参数表指针(大小为 32 字节)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// Line 71
int sys_setup(void * BIOS)
{
static int callable = 1; // 限制本函数只能被调用 1 次
int i,drive;
unsigned char cmos_disks;
struct partition *p;
struct buffer_head * bh;

if (!callable) // 判断是否是第一次调用
return -1;
callable = 0; // 调用一次该标志就设置为 0
#ifndef HD_TYPE // 如果没有定义 HD_TYPE,则读取硬盘参数
for (drive=0 ; drive<2 ; drive++) { // 循环读取两个硬盘的参数
hd_info[drive].cyl = *(unsigned short *) BIOS; // 柱面数
hd_info[drive].head = *(unsigned char *) (2+BIOS); // 磁头数
hd_info[drive].wpcom = *(unsigned short *) (5+BIOS); // 写前预补偿柱面号
hd_info[drive].ctl = *(unsigned char *) (8+BIOS); // 控制字节
hd_info[drive].lzone = *(unsigned short *) (12+BIOS); // 磁头着陆区柱面号
hd_info[drive].sect = *(unsigned char *) (14+BIOS); // 每磁道扇区数
BIOS += 16;
}
if (hd_info[1].cyl) // 判断第二个硬盘柱面数是否为 0,为零表示只有一个硬盘
NR_HD=2; // 设置硬盘数
else
NR_HD=1;
#endif
// hd 数组的第 0 项和 第 5 项分别表示两个硬盘的整体参数
// 第 1 ~ 4,6 ~ 9 项分别表示两个硬盘各自 4 个分区的参数
// 这里先只设置第 0 和第 5 项
for (i=0 ; i<NR_HD ; i++) {
hd[i*5].start_sect = 0; // 起始扇区
hd[i*5].nr_sects = hd_info[i].head* // 总扇区数
hd_info[i].sect*hd_info[i].cyl;
}

// 检测硬盘是否是 AT 控制器兼容的
// 从 CMOS 偏移地址 0x12 处读出硬盘类型字节,如果高半个字节不为 0,表示系统至少有一个兼容硬盘
if ((cmos_disks = CMOS_READ(0x12)) & 0xf0)
// 如果低半个字节不为 0,表示系统有两个兼容硬盘
if (cmos_disks & 0x0f)
NR_HD = 2;
else
NR_HD = 1;
// 否则(高半个字节为 0)系统没有兼容硬盘
else
NR_HD = 0;
// 将不兼容的硬盘信息清空
for (i = NR_HD ; i < 2 ; i++) {
hd[i*5].start_sect = 0;
hd[i*5].nr_sects = 0;
}
// 读取每个硬盘第一个盘块中的分区表信息,设置 hd 数组
for (drive=0 ; drive<NR_HD ; drive++) {
// 第一个参数 0x300 和 0x305 为两个硬盘的设备号,第二个参数 0 是取的块号
if (!(bh = bread(0x300 + drive*5,0))) { // bh 为空则停机
printk("Unable to read partition table of drive %d\n\r",
drive);
panic("");
}
// 判断第一个扇区最后两个字节是否为 0xAA55(硬盘的有效性)
if (bh->b_data[510] != 0x55 || (unsigned char)
bh->b_data[511] != 0xAA) {
printk("Bad partition table on drive %d\n\r",drive);
panic("");
}
p = 0x1BE + (void *)bh->b_data; // 获取分区表地址
for (i=1;i<5;i++,p++) { // 初始化 hd 数组 1 ~ 4,6 ~ 9 项
hd[i+5*drive].start_sect = p->start_sect;
hd[i+5*drive].nr_sects = p->nr_sects;
}
brelse(bh); // 释放 bh 缓冲块
}
if (NR_HD) // 打印初始化分区表完成的信息
printk("Partition table%s ok.\n\r",(NR_HD>1)?"s":"");
rd_load(); // 尝试在系统内存虚拟盘中加载启动盘中包含的根文件系统映像(如果有的话)
mount_root(); // 安装根文件系统
return (0);
}

然后是 main 函数中调用的 hd_init 函数

1
2
3
4
5
6
7
8
9
// Line 345
void hd_init(void)
{
// 设置硬盘设备请求处理函数为 do_hd_request
blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
set_intr_gate(0x2E,&hd_interrupt); // 设置硬盘中断服务程序入口
outb_p(inb_p(0x21)&0xfb,0x21); // 允许从片发出中断请求信号
outb(inb_p(0xA1)&0xbf,0xA1); // 允许硬盘控制器发送中断请求信号
}

向硬盘控制器发送命令的函数 hd_out,参数 drive 是驱动器号,nsect 是读写扇区数,sect 是起始盘区,head 是磁头号,cyl 是柱面号,cmd 是命令码,intr_addr 类型为函数指针,调用时此处需要传一个函数名,该函数将在硬盘中断处理程序中被调用(类似 hook)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Line 182
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))
{
register int port asm("dx");

if (drive>1 || head>15) // 驱动器号只能为 0/1,磁头号不能大于 15
panic("Trying to write bad sector");
if (!controller_ready()) // 等待硬盘控制器就绪,如果等待一段时间后还没有就绪,死机
panic("HD controller not ready");
do_hd = intr_addr; // 设置硬盘中断处理时调用的函数
outb_p(hd_info[drive].ctl,HD_CMD); // 向控制寄存器输出控制字节,建立指定硬盘的控制方式
port=HD_DATA; // 之后几行向控制器端口 0x1f1 ~ 0x1f7 发送 7 字节的参数命令块
outb_p(hd_info[drive].wpcom>>2,++port); // 写预补偿柱面号
outb_p(nsect,++port); // 读/写 扇区总数
outb_p(sect,++port); // 起始扇区
outb_p(cyl,++port); // 柱面号低 8 位
outb_p(cyl>>8,++port); // 柱面号高 8 位
outb_p(0xA0|(drive<<4)|head,++port); // 驱动器号 + 磁头号
outb(cmd,++port); // 硬盘控制命令(如 WRITE/READ)
}

处理硬盘当前请求项的函数 do_hd_request

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// Line 296
void do_hd_request(void)
{
int i,r;
unsigned int block,dev;
unsigned int sec,head,cyl;
unsigned int nsect;

INIT_REQUEST; // 一个宏定义,上面写过,检查请求队列中是否有请求项及请求项的合法性
dev = MINOR(CURRENT->dev); // 取得子设备号
block = CURRENT->sector; // 请求的起始扇区
// 如果子设备号超出设备号范围或请求的扇区号大于分区中倒数第二个扇区号
if (dev >= 5*NR_HD || block+2 > hd[dev].nr_sects) {
end_request(0); // 结束请求
goto repeat; // repeat 标号在 INIT_REQUEST 宏定义中
}
block += hd[dev].start_sect; // 加上子设备号对应分区的起始扇区号
dev /= 5; // 被 5 整除得到对应的硬盘号
__asm__("divl %4":"=a" (block),"=d" (sec):"0" (block),"1" (0),
"r" (hd_info[dev].sect)); // 计算扇区号(sec)
__asm__("divl %4":"=a" (cyl),"=d" (head):"0" (block),"1" (0),
"r" (hd_info[dev].head)); // 计算柱面号(cyl)
sec++;
nsect = CURRENT->nr_sectors; // 读/写 扇区数
if (reset) { // 若复位标志置位
reset = 0; // 复位
recalibrate = 1; // 重新校正标志
reset_hd(CURRENT_DEV); // 重新校正硬盘
return;
}
if (recalibrate) { // 如果重新校正标志置位
recalibrate = 0; //复位
hd_out(dev,hd_info[CURRENT_DEV].sect,0,0,0,
WIN_RESTORE,&recal_intr); // 向硬盘控制器发送重新校正命令
return;
}
if (CURRENT->cmd == WRITE) { // 如果是写操作
hd_out(dev,nsect,sec,head,cyl,WIN_WRITE,&write_intr); // 发送写命令
for(i=0 ; i<3000 && !(r=inb_p(HD_STATUS)&DRQ_STAT) ; i++)
/* nothing */ ; // 循环读取状态寄存器信息,判断请求标志 DRQ_STAT 是否置位
if (!r) { // 如果等待结束还没有置位,表示写命令失败
bad_rw_intr(); // 处理出现的问题,reset 标志置 1(硬盘复位),判断是否允许重试
goto repeat; // 继续处理请求
}
port_write(HD_DATA,CURRENT->buffer,256); // 没出问题直接写
} else if (CURRENT->cmd == READ) { // 如果是写操作
hd_out(dev,nsect,sec,head,cyl,WIN_READ,&read_intr); // 发送读命令
} else
panic("unknown hd-command"); // 不允许其他操作(cmd)
}

另一类函数是硬盘中断处理过程中可被调用的函数,它们有 read_intr、write_intr、bad_rw_intr、recal_intr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// Line 252
// 读盘请求完成后在硬盘中断中被调用,
// 将一个扇区的数据从硬盘控制器的缓冲区读到内存的高速缓冲
static void read_intr(void)
{
if (win_result()) { // 检查此次读命令操作是否出错
bad_rw_intr(); // 出错则处理问题,reset 标志置 1(硬盘复位),判断是否允许重试
do_hd_request(); // 继续处理请求
return;
}
port_read(HD_DATA,CURRENT->buffer,256); // 从数据寄存器端口将 1 各扇区读到高速缓冲中
CURRENT->errors = 0; // 设置出错次数为 0
CURRENT->buffer += 512; // 请求项的缓冲指针向后移动 1 个扇区
CURRENT->sector++; // 请求项的起始扇区递增
if (--CURRENT->nr_sectors) { // 读/写 操作的扇区数递减,并判断是否还有数据没读完
do_hd = &read_intr; // 如果是的话再次设置硬盘中断中调用的函数为 read_intr
return;
}
end_request(1); // 否则正常结束请求
do_hd_request(); // 接着处理下一个请求(如果有)
}

// 在硬盘写命令结束时引发的硬盘中断中被调用,与 read_intr 逻辑相似
static void write_intr(void)
{
if (win_result()) {
bad_rw_intr();
do_hd_request();
return;
}
if (--CURRENT->nr_sectors) { // 如果数据没写完
CURRENT->sector++;
CURRENT->buffer += 512;
do_hd = &write_intr; // 再次设置硬盘中断中调用的函数为 write_intr
port_write(HD_DATA,CURRENT->buffer,256);
return;
}
end_request(1);
do_hd_request();
}

// Line 244
// 读写硬盘失败调用的函数
static void bad_rw_intr(void)
{
if (++CURRENT->errors >= MAX_ERRORS) // 如果出错次数 >= 7
end_request(0); // 结束当前请求项
if (CURRENT->errors > MAX_ERRORS/2) // 如果出错次数 > 3
reset = 1; // 置位 reset 标志,复位硬盘控制器
}

// Line 289
// 重新校正函数
static void recal_intr(void)
{
if (win_result()) // 判断上次命令结束后的状态
bad_rw_intr(); // 如果出错,处理出错
do_hd_request(); // 接着处理请求
}

剩下的函数为操作硬盘控制器的辅助函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// Line 161
// 等待硬盘控制器就绪
static int controller_ready(void)
{
int retries=100000;

// 循环检测驱动器状态寄存器忙位(位 7)是否为 1,判断控制器是否处于忙状态
while (--retries && (inb_p(HD_STATUS)&0x80));
return (retries);
}

// 检测上次命令的结束状态
static int win_result(void)
{
int i=inb_p(HD_STATUS); // 读取状态寄存器中的命令执行结束状态

if ((i & (BUSY_STAT | READY_STAT | WRERR_STAT | SEEK_STAT | ERR_STAT))
== (READY_STAT | SEEK_STAT)) // 正常
return(0);
if (i&1) i=inb(HD_ERROR); // 如果 ERR_STAT 置位,读取错误寄存器
return (1);
}

// Line 204
// 等待硬盘就绪
static int drive_busy(void)
{
unsigned int i;

for (i = 0; i < 10000; i++)
// 如果就绪或寻道结束标志位置位表示硬盘就绪,跳出循环
if (READY_STAT == (inb_p(HD_STATUS) & (BUSY_STAT|READY_STAT)))
break;
i = inb(HD_STATUS); // 读取状态寄存器中的命令执行结束状态
i &= BUSY_STAT | READY_STAT | SEEK_STAT;
if (i == READY_STAT | SEEK_STAT) // 正常
return(0);
printk("HD controller times out\n\r"); // 等待超时
return(1);
}

// 重新校正硬盘控制器
static void reset_controller(void)
{
int i;

outb(4,HD_CMD); // 向控制寄存器端口发送复位控制字节
for(i = 0; i < 100; i++) nop(); // 等待一段时间
outb(hd_info[0].ctl & 0x0f ,HD_CMD); // 发送正常控制字节(不禁止重试、重读)
if (drive_busy()) // 等待硬盘就绪
printk("HD-controller still busy\n\r");
if ((i = inb(HD_ERROR)) != 1) // 读取错误寄存器内容,若不为 1 表示复位失败
printk("HD-controller reset failed: %02x\n\r",i);
}