2.2 Plug合并Plug合并是内核通用块层提供的机制,如果需要开启则在提交IO前执行 blk_start_plug()
函数启动蓄流,IO全部提交完成后执行 blk_finish_plug()
函数进行泄流 。启动蓄流主要是为当前进程( current
宏)分配plug链表 , 提交IO时plug链表存在则IO先缓存到该链表中,执行泄流时将该链表中缓存的IO下发 。执行plug合并的函数为 blk_attempt_plug_merge()
,该函数的主要逻辑为遍历 current->plug->list
中的所有请求,将属于同一个底层设备队列且位置相邻的请求合并在一起,代码逻辑如下:
bool blk_attempt_plug_merge(struct request_queue *q, struct bio *bio,unsigned int *request_count,struct request **same_queue_rq){ struct blk_plug *plug; struct request *rq; bool ret = false; struct list_head *plug_list; plug = current->plug; if (!plug)goto out; *request_count = 0; if (q->mq_ops)plug_list = &plug->mq_list; elseplug_list = &plug->list; list_for_each_entry_reverse(rq, plug_list, queuelist) {int el_ret;/* 给plug_list中同一个设备的请求计数 */if (rq->q == q) {(*request_count)++;/** Only blk-mq multiple hardware queues case checks the* rq in the same queue, there should be only one such* rq in a queue**/if (same_queue_rq)*same_queue_rq = rq;}/* 同一队列,满足合并的才能合并 */if (rq->q != q || !blk_rq_merge_ok(rq, bio))continue;/* 判断能进行合并的位置 */el_ret = blk_try_merge(rq, bio);if (el_ret == ELEVATOR_BACK_MERGE) {ret = bio_attempt_back_merge(q, rq, bio);if (ret)break;} else if (el_ret == ELEVATOR_FRONT_MERGE) {ret = bio_attempt_front_merge(q, rq, bio);if (ret)break;} }out: return ret;}
该逻辑中有两点需要注意 。一是 request_count
计数,该变量统计plug链表中与当前IO属于同一队列的请求个数,当数量超过 BLK_MAX_REQUEST_COUNT
(当前定义为16)时则主动下刷plug中的请求;二是函数 blk_rq_merge_ok()
与函数 blk_try_merge()
,其中 blk_rq_merge_ok()
检查req及bio属性是否支持合并, blk_try_merge()
则是根据req及bio的起始位置与长度来检查是否相邻,以判断能否合并以及合并的类型(前向/后向) , 代码如下:
bool blk_rq_merge_ok(struct request *rq, struct bio *bio){ /* rq_mergeable - 判断rq是否有不允许合并的标记* bio_mergeable - 判断bio是否有不允许合并的标记*/ if (!rq_mergeable(rq) || !bio_mergeable(bio))return false; /* 判断rq和bio是否是特殊的请求,* 如果是特殊请求并且两者相同则不允许合并*/ if (!blk_check_merge_flags(rq->cmd_flags, bio->bi_rw))return false; /* different data direction or already started, don't merge */ /* 请求方向不同,不能合并 */ if (bio_data_dir(bio) != rq_data_dir(rq))return false; /* must be same device and not a special request */ /* 指向的通用磁盘不同或者rq是特殊请求,不能合并 */ if (rq->rq_disk != bio->bi_bdev->bd_disk || req_no_special_merge(rq))return false; /* only merge integrity protected bio into ditto rq */ /* 完整性保护不同的请求和bio,不能合并 */ if (bio_integrity(bio) != blk_integrity_rq(rq))return false; /* must be using the same buffer */ /* REQ_WRITE_SAME请求,来自不同的page,不能合并 */ if (rq->cmd_flags & REQ_WRITE_SAME &&!blk_write_same_mergeable(rq->bio, bio))return false; return true;}int blk_try_merge(struct request *rq, struct bio *bio){ /* rq结束位置等于bio起始位置返回后向合并 */ if (blk_rq_pos(rq) + blk_rq_sectors(rq) == bio->bi_sector)return ELEVATOR_BACK_MERGE; /* bio结束位置等于rq起始位置返回前向合并 */ else if (blk_rq_pos(rq) - bio_sectors(bio) == bio->bi_sector)return ELEVATOR_FRONT_MERGE; return ELEVATOR_NO_MERGE;}
执行泄流操作的函数是 blk_flush_plug_list()
,代码逻辑如下:
void blk_flush_plug_list(struct blk_plug *plug, bool from_schedule){ struct request_queue *q; unsigned long flags; struct request *rq; LIST_HEAD(list); unsigned int depth; BUG_ON(plug->magic != PLUG_MAGIC); flush_plug_callbacks(plug, from_schedule); if (!list_empty(&plug->mq_list))blk_mq_flush_plug_list(plug, from_schedule); if (list_empty(&plug->list))return; list_splice_init(&plug->list, &list); /* 1.根据请求所在的q排序,这样可以优先处理同一设备的请求* 2.同一设备的请求根据请求的起始位置排序,这样可以优化处理性能*/ list_sort(NULL, &list, plug_rq_cmp); q = NULL; depth = 0; /** Save and disable interrupts here, to avoid doing it for every* queue lock we have to take.*/ local_irq_save(flags); while (!list_empty(&list)) {rq = list_entry_rq(list.next);list_del_init(&rq->queuelist);BUG_ON(!rq->q);/* 当 rq->q != q 时,说明一个队列的请求都已经取出,排查第一次,* 当q为真时调用q->request_fn处理请求 , q->request_fn会调用* 具体的调度算法取出请求进行处理*/if (rq->q != q) {/** This drops the queue lock*/if (q)queue_unplugged(q, depth, from_schedule);q = rq->q;depth = 0;spin_lock(q->queue_lock);}/** Short-circuit if @q is dead*/if (unlikely(blk_queue_dying(q))) {__blk_end_request_all(rq, -ENODEV);continue;}/** rq is already accounted, so use raw insert** 在这里处理同一个队列的请求时,先将请求插入到调度队列和电梯* 哈希中,同一队列全部插入完成后,在上边会一次性处理*/if (rq->cmd_flags & (REQ_FLUSH | REQ_FUA))__elv_add_request(q, rq, ELEVATOR_INSERT_FLUSH);else__elv_add_request(q, rq, ELEVATOR_INSERT_SORT_MERGE);depth++; } /** This drops the queue lock* 处理最后一个设备的请求*/ if (q)queue_unplugged(q, depth, from_schedule); local_irq_restore(flags);}
推荐阅读
- Linux Block模块之deadline调度算法代码解析
- Sentinel安装教程【Linux+windows】
- Linux 下模拟制作块设备并挂载
- CentOS6/7开机启动配置
- LinkedBlockingQueue详解
- 简析 Linux 的 CPU 时间
- springboot H2 linux下搭建使用
- 驱动开发:内核取ntoskrnl模块基地址
- 已验证 Linux安装中文字体
- 聊聊Linux中CPU上下文切换