向通用块层提交一个bio请求
当上层构建后io操作对应的bio请求后,通过submit_bio函数向通用块层提交一个 bio请求。
void submit_bio(int rw, struct bio *bio)
{
bio->bi_rw |= rw;//io操作是读操作还是写操作
...
generic_make_request(bio);//通用块层的主要入口点
}
void generic_make_request(struct bio *bio)
{
struct bio_list bio_list_on_stack;
/*对bio进行检查,查看bio的各种参数是否合理,如bio->bi_sector没有超过块设备的扇区数*/
if (!generic_make_request_checks(bio))
return;
/*在一个进程内,同一时刻内核希望只有一个bio正在被处理,内核使用current->bio_list
来维护当前进程提交的bio请求,如果链表不为空,则说明前面有bio正在被处理,只需要将新
bio挂入链表即可返回(令我疑惑的是并未见加锁操作,是怎么避免竞态的呢?)*/
if (current->bio_list) {
bio_list_add(current->bio_list, bio);
return;
}
//能走到这说明前面没有bio请求等待处理
BUG_ON(bio->bi_next);
bio_list_init(&bio_list_on_stack);//初始化链表,表头就是函数开头定义的那个局部变量
//将表头赋给current->bio_list,后面来的bio都将挂在这个链表上
current->bio_list = &bio_list_on_stack;
do {
//获取bio要发送的设备对应的请求队列
struct request_queue *q = bdev_get_queue(bio->bi_bdev);
//将请求插入队列,所谓的io调度都是在该函数完成
q->make_request_fn(q, bio);
bio = bio_list_pop(current->bio_list);//从链表移除
} while (bio);
current->bio_list = NULL; /* deactivate */
//该bio链表已经处理完了,下次再进入这个函数将创建新的链表
}
最终,调用q->make_request_fn方法进入块设备的I/O调度层,将bio请求插入请求队列q中。
大家一定很好奇q->make_request_fn函数到底是哪个函数
void blk_queue_make_request(struct request_queue *q, make_request_fn *mfn)
{
...
q->make_request_fn = mfn;
...
}
struct request_queue * blk_init_allocated_queue(struct request_queue *q, request_fn_proc *rfn, spinlock_t *lock)
{
q->request_fn = rfn; //请求处理函数,负责处理请求队列中的每一个请求
q->prep_rq_fn = NULL;
...
blk_queue_make_request(q, blk_queue_bio); //设置q->make_request_fn
...
if (elevator_init(q, NULL)) {
mutex_unlock(&q->sysfs_lock);
return NULL;
}//初始化调度算法
...
}
由上可知一般默认的q->make_request_fn就是blk_queue_bio函数。如果被访问的设备是一个有queue的块设备,那么系统会调用blk_queue_bio函数进行bio的调度合并。我们将在io调度层具体分析该函数。