// 块设备结构。
struct blk_dev_struct
{
void (*request_fn) (void); // 请求操作的函数指针。
struct request *current_request; // 请求信息结构。
};
extern struct blk_dev_struct blk_dev[NR_BLK_DEV];
块设备有7个,即NR_BLK_DEV=7;
其中blk_dev[3].request_fn=do_hd_request();这是在硬盘初始化函数hd_init()函数中赋值的
request结构如下所示
// 下面是请求队列中项的结构。其中如果dev=-1,则表示该项没有被使用。
struct request
{
int dev; /* -1 if no request */// 使用的设备号。
int cmd; /* READ or WRITE */// 命令(READ 或WRITE)。
int errors; //操作时产生的错误次数。
unsigned long sector; // 起始扇区。(1 块=2 扇区)
unsigned long nr_sectors; // 读/写扇区数。
char *buffer; // 数据缓冲区。
struct task_struct *waiting; // 任务等待操作执行完成的地方。
struct buffer_head *bh; // 缓冲区头指针(include/linux/fs.h,68)。
struct request *next; // 指向下一请求项。
};
以下从函数调用的先后程序说下读写硬盘的过程,首先要读硬盘先要调用bread()函数,该函数首先会看要读的块(一个块是1024字节,即要读取两个扇区)是否存在于内存缓冲区中,如果存在,即返回该缓冲区,如果不存在,即要把数据读入空闲缓冲区中,调用ll_rw_block()准备读取数据。而ll_rw_block()函数还不是直接读取硬盘的函数,它基本不做什么实质操作,只是验证一下参数中的主设备号是否存在或者该设备的读写操作函数是否存在,如果验证无误,即调用make_request()函数,该函数主要作用是创建请求项并插入请求队列。她先在请求队列中(有32项)搜索空闲可用的请求项,然后往该请求项中填写数据,然后调用add_request()函数,该函数很简单,就是把请求项赋值给blk_dev[3]结构的current_request,然后开始调用do_hd_request()函数
do_hd_request()函数基本就是真正的读写硬盘函数,但是它还得通过hd_out()函数操作硬盘控制器,而hd_out()函数通过outb_p()等函数来操作硬盘控制器,outb_p函数就是真正的底层汇编代码了,终于找到这个幕后黑手了。但是事情还没有完,hd_ou()函数还做了一个件事,就是把do_hd指针指向intr_addr,而intr_addr参数是由do_hd_request()传进来的,如果do_hd_request()处理的是读请求,即intr_add=read_intr,如果do_hd_request()处理的是写请求,即intr_add=write_intr.而do_hd是被硬盘中断调用的,当硬盘准备好被读写时(已经找到要读写的扇区)即发出一个硬盘中断,硬盘中断即根据不同的请求调用不同的函数,write_intr或者read_intr
大家看下这个过程一共调用了多少个函数。。。着实坑爹。bread()-->ll_rw_block()-->make_request()-->add_request()-->do_hd_request()-->hd_out()-->outb_p()