Linux-0.11 kernel目录hd.c详解

程序员小x大约 20 分钟LinuxLinux-0.11代码解读系列

Linux-0.11 kernel目录hd.c详解

模块简介

hd.c程序是硬盘控制器驱动程序,提供对硬盘控制器块设备的读写驱动和硬盘初始化处理。程序中所有函数按照功能的不同可分为5类:

  • 初始化硬盘和设置硬盘所用数据结构信息的函数,如sys_setup和hd_init;
  • 向硬盘控制器发送命令的函数hd_out;
  • 处理硬盘当前请求项的函数do_hd_request();
  • 硬盘终端处理过程中调用的c函数,如read_intr()、write_intr()、bad_rw_intr()和recal_intr()。
  • 硬盘控制器操作辅助函数,如controler_ready()、drive_busy()、win_result()、hd_out和reset_controller等等。

在讲解hd.c的函数之前,需要先介绍一些宏定义,inb, inb_p, outb, outb_p。这些宏定义来源于io.h

inb宏的作用是去IO端口读取一个byte的数据。

在内嵌汇编中, :"d" (port))是输入,将port值写入了edx。 :"=a" (_v)是输出,即将AL的值写入_v中。

而汇编指令inb %%dx,%%al的作用是从端口dx中读取一个字节放入al中。

#define inb(port) ({ \
unsigned char _v; \
__asm__ volatile ("inb %%dx,%%al"
		:"=a" (_v)
		:"d" (port)); \
_v; \
})

inb_p宏的作用也是去IO端口读取一个字节的数据,但是其使用两个jmp 1f进行延迟。

#define inb_p(port) ({ \
unsigned char _v; \
__asm__ volatile ("inb %%dx,%%al\n" \
    "\tjmp 1f\n" \
    "1:\tjmp 1f\n" \
    "1:"
	:"=a" (_v)
	:"d" (port)); \
_v; \
})

outb宏的作用是向IO端口写入一个字节的数据。

value写入al中,将port写入edx中,最后使用汇编指令outbport写入数据内容。

#define outb(value,port) \
__asm__ ("outb %%al,%%dx"
			:
			:"a" (value),"d" (port))

outb_p宏的作用与outb作用类似,只不过使用了jmp进行延时。

#define outb_p(value,port) \
__asm__ ("outb %%al,%%dx\n" \
		"\tjmp 1f\n" \
		"1:\tjmp 1f\n" \
		"1:"
		:
		:"a" (value),"d" (port))

函数详解

sys_setup

int sys_setup(void * BIOS)

该函数在main.c文件中的init方法中被调用。

void init(void)
{
	int pid,i;
	setup((void *) &drive_info);
	...
}

从调用关系可以得出, 入参BIOS指针指向drive_infodrive_info的定义如下所示:

#define DRIVE_INFO (*(struct drive_info *)0x90080)

这里可以回顾一下setup.s的中加载的一些数据的分布:

内存地址长度(字节)名称描述
0x9008016硬盘参数表第1个硬盘的参数表
0x9009016硬盘参数表第2个硬盘的参数表

0x90080正好是第1个硬盘的参数表的地址。

接下来看sys_setup的主体,该方法的最先定义了一些变量,其中利用static变量callable控制该方法只会被调用一次。

static int callable = 1;
int i,drive;
unsigned char cmos_disks;
struct partition *p;
struct buffer_head * bh;

if (!callable)
	return -1;
callable = 0;

hd_info的类型是hd_i_struct,其中各个字段分别是磁头数,每磁道扇区数据,柱面数,写前预补偿柱面号、磁头着陆区柱面号、控制字节。

struct hd_i_struct {
	int head,sect,cyl,wpcom,lzone,ctl;
	};

接下来如果定义了HD_TYPE,则一次去BIOS内存地址出去读取数据拷贝到hd_info变量中。如果hd_info[1].cyl有数据,则代表有两块硬盘,否则代表只有一块硬盘。

#ifndef 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)
		NR_HD=2;//磁盘数
	else
		NR_HD=1;
#endif

接下来设置硬盘分区数据:

	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;//硬盘总扇区数
	}

接下来的操作涉及对CMOS的操作,这里简单减少一下CMOS。

CMOS(互补金属氧化物半导体)内存是计算机系统中的一种特殊内存,通常用于存储系统配置信息和实时时钟数据。它通常位于主板上,并且保持在通电和断电状态下数据的持久性。在通电状态下,CMOS电池提供电源以保持存储的数据不受影响。

CMOS内存中存储了诸如以下信息:

  • 实时时钟数据:包括年、月、日、小时、分钟和秒等时间信息。
  • BIOS设置:存储了系统的基本输入/输出系统(BIOS)配置,例如启动设备顺序、硬盘参数等。
  • 硬件配置:可能包括与硬件相关的配置信息,例如中断分配、硬盘类型等。
  • 密码:某些系统可能会将密码或安全密钥存储在CMOS中,以进行访问控制。

CMOS内存可以通过特定的端口访问,例如在x86架构的计算机中,使用端口0x70来选择CMOS内存的地址,使用端口0x71来读取或写入数据。在这段代码中,通过使用端口0x70选择地址,并使用端口0x71读取数据,实现了从CMOS内存中读取数据的操作。

有了CMOS的概念,再来理解下面的代码。下面的代码Linus做了如下解释:

我们对CMOS有关硬盘的信息有些怀疑: 可能会出现这样的情况,我们有一块SCSI/ESDI等的控制器,它是以ST-506方式与BIOS相兼容的,因而会出现在我们的BIOS参数表中,但却又不是寄存器兼容的,因此这些参数在CMOS中又不存在。第一个驱动器的参数存放在CMOS字节0x12的高半字节中,第2个存放在低半字节中。该4位字节信息可以是驱动器类型。也可能是0xf。0xf表示使用CMOS中0x19字节作为驱动器1的8位类型字节,使用CMOS中的0x1A字节作为驱动器2的类型字节。

总之,这里的代码就是用来检测两个硬盘都是不是AT控制器兼容的。

if ((cmos_disks = CMOS_READ(0x12)) & 0xf0)
	if (cmos_disks & 0x0f)
		NR_HD = 2;
	else
		NR_HD = 1;
else
	NR_HD = 0;

如果NR_HD=0,则两个硬盘都不是AT控制器兼容的,则将两个硬盘的结构全部清零。如果NR_HD=1,则将第二块硬盘结构清零。

for (i = NR_HD ; i < 2 ; i++) {
	hd[i*5].start_sect = 0;
	hd[i*5].nr_sects = 0;
}

接下来就是获取硬盘的分区表信息。硬盘上的第一个扇区存放的是引导块。这里需要了解一下MBR分区的,引导扇区的分布情况。

**MBR(Master Boot Record)**引导扇区的布局如下:

  • 1.引导代码区域(446字节):MBR的前446字节用于存储引导代码,这是一段特定的机器码程序,用于引导操作系统。这段代码会被计算机启动时加载到内存中执行,以启动操作系统。

  • 2.分区表(64字节):接下来的64字节用于存储分区表,其中包含4个16字节的分区表项,每个分区表项用于描述硬盘上的一个分区。每个分区表项包含以下信息:

    • 起始扇区地址(4字节):描述分区在硬盘上的起始位置。
    • 分区大小(4字节):描述分区的大小。
    • 分区类型(1字节):描述分区的类型,指示分区的用途或内容。
    • 标志(1字节):用于标识分区的活动状态(启动分区)。
  • 3.结束标志(2字节):MBR的最后两个字节通常是0x55AA,用于指示这是一个有效的MBR扇区。

for (drive=0 ; drive<NR_HD ; drive++) {
	if (!(bh = bread(0x300 + drive*5,0))) {//300 305是设备号
		printk("Unable to read partition table of drive %d\n\r",
			drive);
		panic("");
	}
	if (bh->b_data[510] != 0x55 || (unsigned char)
		bh->b_data[511] != 0xAA) {//硬盘标志位0xAA55
		printk("Bad partition table on drive %d\n\r",drive);
		panic("");
	}
	p = 0x1BE + (void *)bh->b_data; // 0x1BE=446
	for (i=1;i<5;i++,p++) {
		hd[i+5*drive].start_sect = p->start_sect;
		hd[i+5*drive].nr_sects = p->nr_sects;
	}
	brelse(bh);
}

程序的最后将加载虚拟盘和挂载根文件系统。

	if (NR_HD)
		printk("Partition table%s ok.\n\r",(NR_HD>1)?"s":"");
	rd_load();//加载虚拟盘
	mount_root();//挂载根文件系统。

controller_ready

static int controller_ready(void)

该函数的作用就是循环等待硬盘控制器就绪。如果返回值retires为0,代表等待控制器空闲的时间已经超时发生错误。若返回值不为0,则说明等待时间内控制器已经回到了空闲状态。

硬盘控制器状态寄存器端口是0x1f7, 循环检测其中的驱动器就绪比特位(位6)是否被置位并且控制器忙位(位7)是否被复位。

int retries=100000;

while (--retries && (inb_p(HD_STATUS)&0xc0)!=0x40);
return (retries);

实际上,我们仅需要检测状态寄存器忙位(位7)是否位1来判断控制器是否处于忙状态。如果为0,就代表已经就绪,如果为1, 则代表尚未就绪。改写后的代码如下所示:

int retries=100000;

while (--retries && (inb_p(HD_STATUS)&0x80));
return (retries);

win_result

static int win_result(void)

该函数的作用是检查硬盘执行命令后的结果。0为正常, 1为错误。

首先使用inb_p去读取HD_STATUS的值,如果控制器忙, 读写错误或命令执行错误, 则返回1。如果没有错误,则返回0。

int i=inb_p(HD_STATUS);//取出硬盘控制器的状态信息。

if ((i & (BUSY_STAT | READY_STAT | WRERR_STAT | SEEK_STAT | ERR_STAT))
	== (READY_STAT | SEEK_STAT))
	return(0); /* ok */
if (i&1) i=inb(HD_ERROR);//如果ERR_STAT置位
return (1);

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 硬盘号(0-1)
  • nsect 读写扇区数
  • sect 起始扇区
  • head 磁头号
  • cyl 柱面号
  • cmd 命令码
  • intr 硬盘中断处理程序中将调用的c处理函数指针

该函数的开头对参数进行校验,如果不合法则抛出内核错误。

register int port asm("dx");//定义局部变量,并放入寄存器dx中。

if (drive>1 || head>15)//驱动器号大于1(驱动器号只能是0或者1), 磁头号大于15.
  panic("Trying to write bad sector");
if (!controller_ready())//控制器没有准备好
  panic("HD controller not ready");

接下来就要对一些IO端口进行写数据,首先我们先了解一下这些端口。

端口名称读操作写操作
0x1f0HD_DATA数据寄存器数据寄存器
0x1f1HD_ERROR错误寄存器(HD_ERROR)写前预补偿寄存器(HD_PRECOMP)
0x1f2HD_NSECTOR扇区数寄存器 总扇区数扇区数寄存器 总扇区数
0x1f3HD_SECTOR扇区号寄存器 起始扇区扇区号寄存器 起始扇区
0x1f4HD_LCYL柱面号寄存器 柱面号低字节柱面号寄存器 柱面号低字节
0x1f5HD_HCYL柱面号寄存器 柱面号高字节柱面号寄存器 柱面号高字节
0x1f6HD_CURRENT磁头寄存器 磁头号磁头寄存器 磁头号
0x1f7HD_STATUS主状态寄存器命令寄存器
0x3f6HD_CMD...硬盘控制寄存器
0x3f7数字输入寄存器

hd_out接下来的过程就是向这些端口依次写入数据。

do_hd = intr_addr; // do_hd 函数指针将在硬盘中断程序中被调用
outb_p(hd_info[drive].ctl,HD_CMD); //向控制寄存器(0x3f6)输出控制字节
port=HD_DATA;  //置dx 为数据寄存器端口(0x1f0)
outb_p(hd_info[drive].wpcom>>2,++port);  //0x1f1
outb_p(nsect,++port);  //参数:读/写扇区总数 0x1f2
outb_p(sect,++port);   //参数:起始扇区 0x1f3
outb_p(cyl,++port);    //参数:柱面号低8 位  0x1f4
outb_p(cyl>>8,++port); //参数:柱面号高8 位  0x1f5
outb_p(0xA0|(drive<<4)|head,++port);  //参数:驱动器号+磁头号 0x1f6
outb(cmd,++port);      //命令:硬盘控制命令 0x1f7

cmd的取值与主状态寄存器(读)/命令寄存器(写) 0x1f7的设计相关。

hd_out涉及对0x1f7的写操作,其含义如下:

命令含义高4位D3D2D1D0默认值命令执行结束形式
WIN_RESTORE驱动器重新校正(复位)0x1RRRR0x10中断
WIN_READ读扇区0x200LT0x20中断
WIN_WRITE写扇区0x300LT0x30中断
WIN_VERIFY扇区检验0x4000T0x40中断
WIN_FORMAT格式化磁道0x500000x50中断
WIN_INIT控制器初始化0x600000x60中断
WIN_SEEK寻道0x7RRRR0x70中断
WIN_DIAGNOSE控制器诊断0x900000x90中断或空闲
WIN_SPECIFY建立驱动器参数0x900010x91中断

表中命令码字节的低4位是附加参数,其含义为:

  • R是步进速率。R=0,则步进速率为35us;R=1为0.5ms,以此量递增。程序中默认R=0。
  • L是数据模式。L=0表示读/写扇区为512字节,L=1表示读/写扇区为512加4字节的ECC码。程序中默认值是L=0。
  • T是重试模式。T=0表示允许充实;T=1则禁止充实。程序中取T=0。

命令码的高四位和低四位组合形成了最终的含义,这里给出常用的cmd的详细解释:

  • 0x1X WIN_RESTORE 驱动器重新校正(Recalibrate)

    该命令把读/写磁头从磁盘上的任何位置移动到0柱面。当收到命令时,驱动器会设置BUSY_STAT标志并且发出一个0柱面寻道指令。然后驱动器等待寻道操作结束,更新状态,复位BUSY_STAT标志并且产生一个中断。

  • 0x20 WIN_READ:

    读扇区命令可以从指定扇区开始读取1-256个扇区。若所指定的命令块中的扇区计数为0,则表示读256个扇区。当驱动器接受了该命令时,将会设置BUSY_STAT标志并且发出并开始执行该指令。对于单个扇区的读取操作,若磁头的磁道位置不对,则驱动器会隐含地执行一次寻道操作。

  • 0x30 WIN_WRITE:

    写扇区命令可以从指定扇区开始写入1-256个扇区。若所指定的命令块中的扇区计数为0,则表示写256个扇区。当驱动器接受了该命令时,将会设置DRQ_STAT标志并且等待扇区缓冲区被填满数据。

  • 0x91 WIN_SPECIFY:

    该命令用于让主机设置多扇区操作时磁头交换和扇区计数循环值。在收到该命令驱动器会设置BUSY_STAT比特位并产生一个中断。该命令仅使用两个寄存器的值。一个是扇区计数寄存器,用于指定扇区数,另一个是驱动器/磁头寄存器,用于指定磁头数-1。

drive_busy

static int drive_busy(void)

该函数的作用是等待硬盘就绪

该函数循环检查硬盘的状态寄存器的忙标志位。如果busy位复位,则返回0。如果没有复位,则返回1。

unsigned int i;

for (i = 0; i < 10000; i++)//循环读取硬盘的主状态寄存器HD_STATUS,等待就绪位
	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);

reset_controller

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)
		printk("HD-controller reset failed: %02x\n\r",i);

首先使用outb(4,HD_CMD)向硬盘控制寄存器端口发送允许复位控制字节。然后循环空操作等待一段时间让控制器执行复位操作。

接着使用outb(hd_info[0].ctl & 0x0f ,HD_CMD);发送正常的控制字节。

如果等待硬盘就绪超时,则显示警告信息。然后读取错误寄存器内容,若其不等于1,则显示硬盘控制器复位失败信息。

reset_hd

static void reset_hd(int nr)

该函数的作用是复位硬盘。

首先调用了复位硬盘控制器的方法,接着发送硬盘控制器命令"建立驱动器参数"。

	reset_controller();//复位硬盘控制器
	hd_out(nr,hd_info[nr].sect,hd_info[nr].sect,hd_info[nr].head-1,
		hd_info[nr].cyl,WIN_SPECIFY,&recal_intr);//发送硬盘控制命令, recal_intr是硬盘中断处理函数中调用的函数

unexpected_hd_interrupt

void unexpected_hd_interrupt(void)

该函数仅仅用于在出现意外硬盘中断的时候打印一行日志。

printk("Unexpected HD interrupt\n\r");

bad_rw_intr

static void bad_rw_intr(void)

该函数是读写硬盘失败的处理函数。如果读写扇区出错次数大于等于7次时,结束当前请求项,并唤醒等待该请求的进程。

如果读写扇区的时候,出错次数超过了3次,则对硬盘控制器进行复位。

if (++CURRENT->errors >= MAX_ERRORS)
	end_request(0);
if (CURRENT->errors > MAX_ERRORS/2)
	reset = 1;

read_intr

static void read_intr(void)

该函数是磁盘的读中断调用函数

硬盘的中断处理函数是hd_interrupt,这个是在hd_init函数中设置的。当硬盘中断发生的时候,将调用do_hd指向的函数, 而do_hd则是在do_hd_request函数中通过hd_out进行设置的。

因此当do_hd_request要去读扇区时,就会设置do_hdread_intr,这样当硬盘中断到来时,就会调用read_intr进行处理。器处理流程如下图所示:

read_intr
read_intr

首先检查硬盘控制器是否返回错误信息。

if (win_result()) {
	bad_rw_intr();
	do_hd_request();
	return;
}

HD_DATA端口依次读取256个字(512字节)到buffer中。

	port_read(HD_DATA,CURRENT->buffer,256);

接着对请求中的一些标记进行修改。

CURRENT->errors = 0;//清除出错次数
CURRENT->buffer += 512;//调整buffer指针
CURRENT->sector++;//起始扇区+1
if (--CURRENT->nr_sectors) {
  do_hd = &read_intr;//尚有数据还未读完,因此设置下一次的中断处理函数还是read_intr
  return;
}

end_request(1);
do_hd_request();//再次调用do_hd_request去处理其他硬盘请求项

write_intr

static void write_intr(void)

该函数是磁盘的写中断调用函数。

首先检查硬盘控制器是否返回错误信息。

if (win_result()) {
	bad_rw_intr();
	do_hd_request();
	return;
}
if (--CURRENT->nr_sectors) {//如果还有扇区要写
    CURRENT->sector++;//当前请求扇区号+1
    CURRENT->buffer += 512;//当前请求缓冲区指针增加512
    do_hd = &write_intr; //设置函数指针位write_intr
    port_write(HD_DATA,CURRENT->buffer,256);//向数据端口写256字(512字节)
    return;
}
end_request(1);
do_hd_request();

recal_intr

static void recal_intr(void)

该函数的作用是重新复位中断调用函数。

if (win_result())
	bad_rw_intr();
do_hd_request();

do_hd_request

void do_hd_request(void)

该函数是硬盘设备的读写函数。

首先对读写请求进行校验。如果请求队列中没有硬盘的读写任务,则退出。

#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

使用dev = MINOR(CURRENT->dev);取出次设备号,即硬盘各分区,次设备号不能大于5*NR_HD

设备号不能大于请求的起始扇区+至少读写2扇区(1K)不能大于磁盘分区的最后一个扇区。

	if (dev >= 5*NR_HD || block+2 > hd[dev].nr_sects) {
		end_request(0);
		goto repeat;
	}

下面是本函数的一个难点,将绝对的块号转换为磁盘的(柱面C, 磁头H ,扇区S)。

其中, block(C,H,S)的换算公式如下所示:

	block=C*总磁头数*每磁道扇区数+H*每磁道扇区数+S

这里可以回顾一下磁盘的结构:

rp_read
rp_read

可以看出前两项都是每磁道扇区数的倍数, 因此使用block除以每磁道扇区数,其余数就是扇区号S,其商如下所示:

block = block/每磁道扇区数 = C*总磁头数 + H

使用中间值block除以总磁头数, 那么其余数就是H, 商就是C。

下面看代码,输入eax = blockedx = 0divl %4中的%4就是hd_info[dev].sect, 代表每磁道扇区数,结果将余数赋值给变量sec, 商赋值给block。 这与我们上面的步骤是一致的。

__asm__("divl %4"
		:"=a" (block),"=d" (sec)
		:"0" (block),"1" (0),"r" (hd_info[dev].sect));

接下来输入edx = blockeax = 0divl %4中的%4就是hd_info[dev].head, 代表系统中的总磁头数,结果将余数赋值给head, 商赋值给cyl,这与我们的分析也一致。

__asm__("divl %4"
	:"=a" (cyl),"=d" (head)
	:"0" (block),"1" (0),
	"r" (hd_info[dev].head));

如果此时的复位标志是1, 那么就调用reset_hd进行硬盘的复位。

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 */ ;
	if (!r) {s
		bad_rw_intr();
		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");

读和写的流程略有不同。

如果是是写操作,则首先设置硬盘控制器调用的c函数为write_intr,向控制器发送写操作的命令参数块,并循环查询控制器的状态寄存器,以判断请求服务标志(DRQ)是否置位。若该标志置位,表示控制器同意接受数据,于是接着就把请求项所指缓冲区中的数据写入控制器的数据缓冲区中。随后do_hd_request返回。当硬盘控制器写盘完毕之后会立刻向CPU发送中断请求,中断处理程序会调用write_intr写完剩下的数据。

因此我们看到,对于写操作而言,do_hd_request会首先执行一次port_write

	port_write(HD_DATA,CURRENT->buffer,256);

write_intr中,由于do_hd_request已经写过一个扇区,因此这里会先增加请求扇区号和buffer指针。

if (--CURRENT->nr_sectors) {//如果还有扇区要写
    CURRENT->sector++;//当前请求扇区号+1
    CURRENT->buffer += 512;//当前请求缓冲区指针增加512
    do_hd = &write_intr; //设置函数指针位write_intr
    port_write(HD_DATA,CURRENT->buffer,256);//向数据端口写256字(512字节)
    return;
}

梳理起来就是,写操作会写一个扇区,随后通过中断写完剩下的所有扇区。流程下图所示:

write_intr
write_intr

而读操作则完全通过中断进行实现。首先会设置硬盘控制器调用的c函数为read_intr(),并向控制器发送读盘操作命令。控制器接到读命令之后就执行从硬盘读数据到控制器缓冲区的过程。操作完之后就触发中断。中断函数read_intr读取一个扇区数据到高速缓冲区中。随后循环往复,直到读完所有数据。

读操作请求发送完毕之后,需要主动等待请求的buffer解锁(wait_on_buffer),才可以返回给上层应用。而写操作通常不必等待。这是读写操作的另一个区别。

hd_init

void hd_init(void)

该函数用于硬盘系统的初始化。

	blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;//设置硬盘的请求处理方法
	set_intr_gate(0x2E,&hd_interrupt);//设置硬盘的中断,中断号是0x2E(46)
	outb_p(inb_p(0x21)&0xfb,0x21);//允许从片发出中断
	outb(inb_p(0xA1)&0xbf,0xA1);//允许硬盘的中断

outb_p(inb_p(0x21)&0xfb,0x21):

0x21是主片命令字OCW1的端口地址, 0xfb = 11111011, 即将主片IR2的位置复位, 主片的IR2用于级联从片, 因此该语句的作用是允许从片发出中断

outb(inb_p(0xA1)&0xbf,0xA1):

0xA1是从片命令字OCW1的端口地址,0xbf = 10111111, 即将从片IR6的位置复位,从片的IR6用于接受硬盘的中断,因此该语句的作用是允许硬盘的中断

因此以上两句的作用就是允许了来自硬盘的中断

有关更多8259A中断控制器, 可以阅读 详解8259Aopen in new window

Loading...