韦东山老师帮我们把框架搭建起来了,我们先来看一下:
框架:
app: open,read,write "1.txt"
--------------------------------------------- 文件的读写
文件系统: vfat, ext2, ext3, yaffs2, jffs2 (把文件的读写转换为扇区的读写)
-----------------ll_rw_block----------------- 扇区的读写
1. 把"读写"放入队列
2. 调用队列的处理函数(优化/调顺序/合并)
块设备驱动程序
---------------------------------------------
硬件: 硬盘,flash
我们在来啰嗦一下:在应用程序对文件的读写通过具体的文件系统被转换为对扇区的读写,对扇区的读写调用的是ll_rw_block这个函数,对扇区进行读写的时候会根据某种调度进行优化,之后通过块设备驱动程
序来访问具体的硬件。
那么到底是不是这个样子呢?闲话少说,来看源代码:
由于我们主要关心内核而不是文件系统,所以我们从
ll_rw_block函数开始分析
ll_rw_block(int rw, int nr, struct buffer_head *bhs[])
for (i = 0; i < nr; i++) {
struct buffer_head *bh = bhs[i];
//将数据存进bh
submit_bh(rw, bh);}//提交数据
struct bio *bio;
// bio即block input/output,接下来根据bh来构造bio
bio->bi_size = bh->b_size;
//这是一个例子
submit_bio(rw, bio);
//提交bio
generic_make_request(bio);
// 通用的构造请求: 使用bio来构造请求(request)
__generic_make_request(bio);
q = bdev_get_queue(bio->bi_bdev);
// 找到队列
ret = q->make_request_fn(q, bio);
//调用q中的构造请求函数,详见注释1
__make_request;
//上面的那条代码实际上是执行这个函数
elv_merge(q, &req, bio);
//先尝试将bio合并到请求队列中的某个请求中
req = get_request_wait(q, rw_flags, bio);
//如果无法将bio合并到某一请求,就会构造一个请求
init_request_from_bio(req, bio);
//根据bio初始化这个请求
add_request(q, req);
//把请求放入队列 ,这里采用的电梯调度算法,详见注释2
__generic_unplug_device(q);
//在适当的时候处理队列里面的请求,这个有内核决定
q->request_fn(q);
//调用队列的"处理函数,我们猜想他应该在设备驱动程序里 //面被设置
上面就是读写块设备的流程框架了,由于本人比较笨,所以还得再来罗嗦一遍它的流程:
首先应用程序里面用读写函数读写文件,通过文件系统调用
ll_rw_block函数,在这个函数里会根据传递进来的数据构造出来一个块设备输入输出结构体。然后先试图将这个结构体合并到块设备的请求队列中的某一个请求中,如果如法合并的话,就会根据这个结构体构造出来一个新的请求,然后根据电梯调度算法将这个请求加入到请求队列中去。
之后内核在适当的时候会根据队列里面的处理函数来处理队列里面的请求。由此我们也可以猜测出来我们的驱动程序里面需要做的事情了:应用程序里面读写块设备的时候会将请求放入块设备队列里面,我们要处理请求的话当然是要在块设备驱动程序里面为块设备分配一个队列,然后初始化它,最主要的不要忘记队列处理函数。
注释1:
q->make_request_fn是默认的函数,我们在内核中搜索过程如下:
首先我们发现
q->make_request_fn在下面这个函数里被设置:
void blk_queue_make_request(request_queue_t * q, make_request_fn * mfn)
{
q->make_request_fn = mfn;
}
那么mfn这个函数是什么东西呢?要想知道需要搜索谁调用了
blk_queue_make_request
blk_init_queue_node(request_fn_proc *rfn, spinlock_t *lock, int node_id)
{
blk_queue_make_request(q, __make_request);
}
终于我们找到了这个默认函数__make_request,也就是说 ret = q->make_request_fn(q, bio);这调代码实际上是执行__make_request这个函数。
注释2:
电梯调度算法:我们先来说一说电梯是怎样工作的。当电梯上的时候,如果在电梯所在层的上层和下层都有请求的话,电梯会先处理上层的请求,同样当电梯下的时候如果上层下层都有请求的话,电梯会先处理下层的请求。那么我们这里电梯调度算法也是采用如此的策略,比如当读数据的时候,如果有读请求也有写请求,那么要先处理读请求。在写数据的时候,如果有读请求也有写请求,那么先处理写请求。