🚀 前言
本文是初始化最后一部分了,对硬盘的初始化,对应于书中的第20回。希望各位给个三连,拜托啦,这对我真的很重要!!!
目录
- 🚀 前言
- 🏆块设备管理
- 🏆块设备初始化
- 🏆块设备读取
- 🎯总结
- 📖参考资料
🏆块设备管理
关于块设备,可以参考之前的博客:linux0.11内核源码修仙传第七章——块设备请求项初始化。在内核中管理块设备采用了一个结构体blk_dev
进行管理,每一个索引表示一个块设备,每一个块设备的详细含义如下注释所示:
struct blk_dev_struct blk_dev[NR_BLK_DEV] = {
{ NULL, NULL }, /* 表示没有块设备,访问会返回错误或者提示无有效设备 */
{ NULL, NULL }, /* 内存设备 */
{ NULL, NULL }, /* 软盘设备 */
{ NULL, NULL }, /* 硬盘设备 */
{ NULL, NULL }, /* 特定的终端设备 */
{ NULL, NULL }, /* 终端设备 */
{ NULL, NULL } /* 打印机设备 */
};
从上面的定义可以看到,索引为3的位置(第四个)表示硬盘。这个数组里面每个成员的定义如下:
struct request {
int dev; /* -1 if no request */
int cmd; /* READ or WRITE */
int errors;
unsigned long sector;
unsigned long nr_sectors;
char * buffer;
struct task_struct * waiting;
struct buffer_head * bh;
struct request * next;
};
struct blk_dev_struct {
void (*request_fn)(void); // 处理函数
struct request * current_request; // 块设备的请求
};
结合上面的blk_dev
数组,可以发现一个有意思的事情:每个块设备执行读写请求都有自己的函数,在上层看来都是一个统一函数request_fn
,具体实现各有不同,对于硬盘来说,这个实现就是do_hd_request
函数。换句话说,只要初始化了所有块设备的request_fn
函数指针是哪个处理函数,后面就可以直接调用对应块设备的request_fn
,而无需关心具体我该调用哪个函数名字。这个就是多态思想在C语言的体现。也就是C++中的父类指针request_fn
指向子类对象do_hd_request
,或者Java中的父类引用指向子类对象。
🏆块设备初始化
下面说完前置的一些信息,来看具体的初始化函数:
void hd_init(void)
{
blk_dev[3].request_fn = do_hd_request;
set_intr_gate(0x2E,&hd_interrupt);
outb_p(inb_p(0x21)&0xfb,0x21);
outb(inb_p(0xA1)&0xbf,0xA1);
}
首先像上一节所讲的,设置了硬盘设备的request_fn
所对应的函数。之后设置了一个中断,中断号为0x2E,中断处理函数是hd_interrupt
。也就是说,当硬盘发生读写时,硬盘会发出中断信号给CPU,之后CPU陷入中断处理程序。
这里有个区分,上面设置了一个do_hd_request
,下面中断设置了一个hd_interrupt
。这两个函数是不一样的,上面是发送请求的函数,下面的函数是读取中断。打个比方:一个是请假的打报告阶段,一个是实际请假后休假的阶段。
回到初始化函数里面,最后就是往IO端口上读写,作用是允许硬盘控制器发送中断请求信号。
🏆块设备读取
这里给个引子,如何读取硬盘上的数据。首先看硬盘的端口表:
读硬盘就是,往除了第一个以外的后面几个端口写数据,告诉要读硬盘的哪个扇区,读多少。然后再从 0x1F0
端口一个字节一个字节的读数据。这就完成了一次硬盘读操作。更具体如下:
在
0x1F2
写入要读取的扇区数
在0x1F3 ~ 0x1F6
这四个端口写入计算好的起始 LBA 地址
在0x1F7
处写入读命令的指令号
不断检测0x1F7
(此时已成为状态寄存器的含义)的忙位
如果第四步骤为不忙,则开始不断从0x1F0
处读取数据到内存指定位置,直到读完
代码的话如下所示:
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))
{
···
port=0x1f0;
outb_p(hd_info[drive].wpcom>>2,++port); // 0x1f1,错误寄存器
outb_p(nsect,++port); // 0x1f2,扇区计数器
outb_p(sect,++port); // 0x1f3,扇区号寄存器
outb_p(cyl,++port); // 0x1f4,磁道数低8位
outb_p(cyl>>8,++port); // 0x1f5,磁道数高8位
outb_p(0xA0|(drive<<4)|head,++port); // 0x1f6,驱动器
outb(cmd,++port); // 0x1f7,命令
}
🎯总结
到这里,初始化就都结束了,目前为止的中断如下所示:
中断号 | 中断处理函数 |
---|---|
0 ~ 0x10 | trap_init 里设置的一堆 |
0x20 | timer_interrupt |
0x21 | keyboard_interrupt |
0x80 | system_call |
0x2E | hd_interrupt |
整个操作系统就是一个靠中断驱动的死循环而已,如果不发生任何中断,操作系统会一直在一个死循环里等待。换句话说,让操作系统工作的唯一方式,就是触发中断。
📖参考资料
[1] linux源码趣读
[2] 一个64位操作系统的设计与实现