Linux3.10.0块IO子系统流程(2)-- 构造、排序、合并请求
类型 | make_request_fn | request_fn | 备注 |
SCSI 设备等 | 从bio构造request(经过合并和排序),返回0 | 逐个处理request | 调用blk_init_queue,使用默认的__make_request,提供策略例程 |
SSD等 | 直接处理bio,返回0 | 无 | 调用blk_alloc_queue,提供make_request_fn |
RAID或Device Mapper设备 | 重定向bio,返回非零值 | 无 | 调用blk_alloc_queue,提供make_request_fn |
- struct request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
- {
- return blk_init_queue_node(rfn, lock, NUMA_NO_NODE);
- }
- EXPORT_SYMBOL(blk_init_queue);
- struct request_queue *
- blk_init_queue_node(request_fn_proc *rfn, spinlock_t *lock, int node_id)
- {
- struct request_queue *uninit_q, *q;
- uninit_q = blk_alloc_queue_node(GFP_KERNEL, node_id);
- if (!uninit_q)
- return NULL;
- q = blk_init_allocated_queue(uninit_q, rfn, lock);
- if (!q)
- blk_cleanup_queue(uninit_q);
- return q;
- }
- EXPORT_SYMBOL(blk_init_queue_node);
- struct request_queue *
- blk_init_allocated_queue(struct request_queue *q, request_fn_proc *rfn,
- spinlock_t *lock)
- {
- if (!q)
- return NULL;
- if (blk_init_rl(&q->root_rl, q, GFP_KERNEL))
- return NULL;
- q->request_fn = rfn;
- q->prep_rq_fn = NULL;
- q->unprep_rq_fn = NULL;
- q->queue_flags |= QUEUE_FLAG_DEFAULT;
- /* Override internal queue lock with supplied lock pointer */
- if (lock)
- q->queue_lock = lock;
- /*
- * This also sets hw/phys segments, boundary and size
- */
- blk_queue_make_request(q, blk_queue_bio); //使用blk_init_queue会默认绑定blk_queue_bio来处理IO
- q->sg_reserved_size = INT_MAX;
- /* init elevator */
- if (elevator_init(q, NULL)) // 初始化IO调度
- return NULL;
- return q;
- }
- EXPORT_SYMBOL(blk_init_allocated_queue);
下面来跟踪blk_queue_bio函数:
- void blk_queue_bio(struct request_queue *q, struct bio *bio)
- {
- const bool sync = !!(bio->bi_rw & REQ_SYNC);
- struct blk_plug *plug;
- int el_ret, rw_flags, where = ELEVATOR_INSERT_SORT;
- struct request *req;
- unsigned int request_count = ;
- /*
- * low level driver can indicate that it wants pages above a
- * certain limit bounced to low memory (ie for highmem, or even
- * ISA dma in theory)
- */
- blk_queue_bounce(q, &bio); // 如果需要,创建反弹缓冲区
- if (bio_integrity_enabled(bio) && bio_integrity_prep(bio)) {
- bio_endio(bio, -EIO);
- return;
- }
- if (bio->bi_rw & (REQ_FLUSH | REQ_FUA)) {
- spin_lock_irq(q->queue_lock);
- where = ELEVATOR_INSERT_FLUSH;
- goto get_rq;
- }
- /*
- * Check if we can merge with the plugged list before grabbing any locks
- * 首先尝试请求合并
- */
- if (attempt_plug_merge(q, bio, &request_count))
- return;
- spin_lock_irq(q->queue_lock);
- el_ret = elv_merge(q, &req, bio); // 判断是否bio是否可以合并
- // 如果可以合并的话,分为向前和向后合并
- if (el_ret == ELEVATOR_BACK_MERGE) {
- if (bio_attempt_back_merge(q, req, bio)) {
- elv_bio_merged(q, req, bio); // 请求如果在硬件上允许,则进行合并
- if (!attempt_back_merge(q, req)) // 合并之后可能两个request可以合并
- elv_merged_request(q, req, el_ret);
- goto out_unlock;
- }
- } else if (el_ret == ELEVATOR_FRONT_MERGE) {
- if (bio_attempt_front_merge(q, req, bio)) {
- elv_bio_merged(q, req, bio);
- if (!attempt_front_merge(q, req))
- elv_merged_request(q, req, el_ret);
- goto out_unlock;
- }
- }
- // 不能合并就根据bio构造request
- get_rq:
- /*
- * This sync check and mask will be re-done in init_request_from_bio(),
- * but we need to set it earlier to expose the sync flag to the
- * rq allocator and io schedulers.
- */
- rw_flags = bio_data_dir(bio);
- if (sync)
- rw_flags |= REQ_SYNC;
- /*
- * Grab a free request. This is might sleep but can not fail.
- * Returns with the queue unlocked.
- */
- req = get_request(q, rw_flags, bio, GFP_NOIO); // 获取一个request
- if (unlikely(!req)) {
- bio_endio(bio, -ENODEV); /* @q is dead */
- goto out_unlock;
- }
- /*
- * After dropping the lock and possibly sleeping here, our request
- * may now be mergeable after it had proven unmergeable (above).
- * We don't worry about that case for efficiency. It won't happen
- * often, and the elevators are able to handle it.
- */
- init_request_from_bio(req, bio); // 根据bio构造一个request,并添加到IO调度器队列
- if (test_bit(QUEUE_FLAG_SAME_COMP, &q->queue_flags))
- req->cpu = raw_smp_processor_id();
- plug = current->plug;
- // 接下来是蓄流/泄流策略
- if (plug) {
- /*
- * If this is the first request added after a plug, fire
- * of a plug trace. If others have been added before, check
- * if we have multiple devices in this plug. If so, make a
- * note to sort the list before dispatch.
- */
- if (list_empty(&plug->list))
- trace_block_plug(q);
- else {
- if (request_count >= BLK_MAX_REQUEST_COUNT) {
- blk_flush_plug_list(plug, false);
- trace_block_plug(q);
- }
- }
- list_add_tail(&req->queuelist, &plug->list);
- drive_stat_acct(req, );
- } else {
- spin_lock_irq(q->queue_lock);
- add_acct_request(q, req, where); // 将请求添加到IO调度队列或请求队列,主要被用来处理屏障请求
- __blk_run_queue(q);
- out_unlock:
- spin_unlock_irq(q->queue_lock);
- }
- }
- EXPORT_SYMBOL_GPL(blk_queue_bio); /* for device mapper only */
第13行,blk_queue_bounce创建一个反弹缓冲区。通常是在驱动尝试在外围设备不可达到的地址。例如高端内存上执行DMA等。创建反弹缓冲区后,数据要在原缓冲区和反弹缓冲区之间进行与读写方向对应的复制。毫无疑问,使用反弹缓冲区会降低性能,但也没有其他办法。
所谓反弹,实际上是分配一个新的bio描述符,它和原始bio的segment一一对应。如果原始bio的segment使用的页面在DMA内存范围外,则分配一个在DMA范围内的页面,赋给新的bio对应的segment。对于写操作,需要将旧bio页面的内容复制到新的bio中。如果原始的bio的segment使用的页面在DMA范围内,则将新的bio指向同一地方。
最后将原始bio保存在新的bio的bi_private域中,并设置新bio的完成回调函数。
接下来交给IO调度器,由它负责合并和排序请求。合并是指将对磁盘上连续位置的请求合并为一个,通过一次SCSI命令完成。排序是将多个请求对磁盘上的访问位置顺序重新排列,使得磁头尽可能向一个方向移动。请求的合并和排序是在SCSI设备的请求队列描述符上进行的。
- int elv_merge(struct request_queue *q, struct request **req, struct bio *bio)
- {
- struct elevator_queue *e = q->elevator;
- struct request *__rq;
- int ret;
- /*
- * Levels of merges:
- * nomerges: No merges at all attempted
- * noxmerges: Only simple one-hit cache try
- * merges: All merge tries attempted
- */
- if (blk_queue_nomerges(q)) // 如果设置了QUEUE_FLAG_NOMERGES的标志位,就直接返回不合并
- return ELEVATOR_NO_MERGE;
- /*
- * First try one-hit cache.
- */
- // 如果请求队列的last_merge有缓存下来的request,调用blk_try_merge来进行尝试和它进行合并,如果可以合并,通过参数输出这个req
- if (q->last_merge && elv_rq_merge_ok(q->last_merge, bio)) {
- ret = blk_try_merge(q->last_merge, bio);
- if (ret != ELEVATOR_NO_MERGE) {
- *req = q->last_merge;
- return ret;
- }
- }
- // 如果设置了QUEUE_FLAG_NOXMERGES的标志位,表明不要进行“扩展”的合并尝试
- if (blk_queue_noxmerges(q))
- return ELEVATOR_NO_MERGE;
- /*
- * See if our hash lookup can find a potential backmerge.
- * 后面的代码就是所谓的“扩展”合并尝试,它包含两方面的内容:
- * 第一部分是各种IO调度算法全都适用的,而第二部分则是各种IO调度算法特定的
- */
- __rq = elv_rqhash_find(q, bio->bi_sector);
- if (__rq && elv_rq_merge_ok(__rq, bio)) {
- *req = __rq;
- return ELEVATOR_BACK_MERGE;
- }
- /*
- * IO调度特定的合并算法是通过电梯队列操作表的elevator_merge_fn回调实现的
- */
- if (e->type->ops.elevator_merge_fn)
- return e->type->ops.elevator_merge_fn(q, req, bio);
- return ELEVATOR_NO_MERGE;
- }
如果我们的请求不能合并到现有的request中,那么就要新申请request描述符了,根据bio对它初始化,并添加到IO调度器队列
最后Linux块设备层采用蓄流/泄流技术来改进吞吐量,蓄流是为了将请求合并和排序,然后一起泄流,泄流函数为__blk_run_queue(q)
- /**
- * __blk_run_queue - run a single device queue
- * @q: The queue to run
- *
- * Description:
- * See @blk_run_queue. This variant must be called with the queue lock
- * held and interrupts disabled.
- */
- void __blk_run_queue(struct request_queue *q)
- {
- if (unlikely(blk_queue_stopped(q)))
- return;
- __blk_run_queue_uncond(q);
- }
- /**
- * __blk_run_queue_uncond - run a queue whether or not it has been stopped
- * @q: The queue to run
- *
- * Description:
- * Invoke request handling on a queue if there are any pending requests.
- * May be used to restart request handling after a request has completed.
- * This variant runs the queue whether or not the queue has been
- * stopped. Must be called with the queue lock held and interrupts
- * disabled. See also @blk_run_queue.
- */
- inline void __blk_run_queue_uncond(struct request_queue *q)
- {
- if (unlikely(blk_queue_dead(q)))
- return;
- /*
- * Some request_fn implementations, e.g. scsi_request_fn(), unlock
- * the queue lock internally. As a result multiple threads may be
- * running such a request function concurrently. Keep track of the
- * number of active request_fn invocations such that blk_drain_queue()
- * can wait until all these request_fn calls have finished.
- */
- q->request_fn_active++;
- q->request_fn(q); // 回调函数实例化为scsi_request_fn,也就是通常所说的SCSI策略例程
- q->request_fn_active--;
- }
__blk_run_queue
对于SCSI设备,在为它分配请求队列时,将请求队列的request_fn回调函数实例化为scsi_request_fn,也就是通常所说的SCSI策略例程。
Linux3.10.0块IO子系统流程(2)-- 构造、排序、合并请求的更多相关文章
- Linux3.10.0块IO子系统流程(4)-- 为请求构造SCSI命令
首先来看scsi_prep_fn int scsi_prep_fn(struct request_queue *q, struct request *req) { struct scsi_device ...
- Linux3.10.0块IO子系统流程(0)-- 块IO子系统概述
前言:这个系列主要是记录自己学习Linux块IO子系统的过程,其中代码分析皆基于Linux3.10.0版本,如有描述错误或不妥之处,敬请指出! 参考书籍:存储技术原理分析--基于Linux 2.6内核 ...
- Linux3.10.0块IO子系统流程(7)-- 请求处理完成
和提交请求相反,完成请求的过程是从低层驱动开始的.请求处理完成分为两个部分:上半部和下半部.开始时,请求处理完成总是处在中断上下文,在这里的主要任务是将已完成的请求放到某个队列中,然后引发软终端让中断 ...
- Linux3.10.0块IO子系统流程(3)-- SCSI策略例程
很长时间以来,Linux块设备使用了一种称为“蓄流/泄流”(plugging/unplugging)的技术来改进吞吐率.简单而言,这种工作方式类似浴盆排水系统的塞子.当IO被提交时,它被储存在一个队列 ...
- Linux3.10.0块IO子系统流程(6)-- 派发SCSI命令到低层驱动
在SCSI策略例程中最后调用scsi_dispatch_cmd将SCSI命令描述符派发给低层驱动进行处理 /** * scsi_dispatch_command - Dispatch a comman ...
- Linux3.10.0块IO子系统流程(5)-- 为SCSI命令准备聚散列表
SCSI数据缓冲区组织成聚散列表的形式.Linux内核中表示聚散列表的基本数据结构是scatterlist,虽然名字中有list,但它只对应一个内存缓冲区,聚散列表就是多个scatterlist的组合 ...
- Linux3.10.0块IO子系统流程(1)-- 上层提交请求
Linux通用块层提供给上层的接口函数是submit_bio.上层在构造好bio之后,调用submit_bio提交给通用块层处理. submit_bio函数如下: void submit_bi ...
- DPA 9.1.85 升级到DPA 10.0.352流程
SolarWinds DPA的升级其实是一件非常简单的事情,这里介绍一下从DPA 9.1.95升级到 DPA 10.0.352版本的流程.为什么要升级呢? DPA给用户发的邮件已经写的非常清楚了(如下 ...
- 转 -- linux IO子系统和文件系统读写流程
我们含有分析的,是基于2.6.32及其后的内核. 我们在linux上总是要保存数据,数据要么保存在文件系统里(如ext3),要么就保存在裸设备里.我们在使用这些数据的时候都是通过文件这个抽象来访问的, ...
随机推荐
- C#内存压缩zip文件
C#中我们使用比较多的文件压缩第三方DLL就是Ionic.Utils.Zip.dll.但是这个DLL只支持对现有文件进行压缩,而不支持内存压缩,如果需要使用内存压缩,那么有第三方DLL ICSharp ...
- HeadFirst Ruby 第十章总结 Comparable & Enumerable
导言 这一章的标题是 Ready-Made Mixes, 也就是 Ruby 已经准备好的用于 Mix-in 的 Modules, 它们是: Comparable 和 Enumerable, Compa ...
- android --------学习流程图
如何快速入门和进阶安卓开发,是很多技术小白的疑问. 大家都知道,Android开发要学的技能非常多,技术更新速度还快,但是总的来说:掌握最核心的技术,最规范的开发流程,成为专业.出色的安卓开发工程师也 ...
- Artem and Array CodeForces - 442C (贪心)
大意: 给定序列$a$, 每次任选$a_i$删除, 得分$min(a_{i-1},a_{i+1})$(无前驱后继时不得分), 求最大得分. 若一个数$x$的两边都比$x$大直接将$x$删除, 最后剩余 ...
- 『计算机视觉』Mask-RCNN_推断网络其六:Mask生成
一.Mask生成概览 上一节的末尾,我们已经获取了待检测图片的分类回归信息,我们将回归信息(即待检测目标的边框信息)单独提取出来,结合金字塔特征mrcnn_feature_maps,进行Mask生成工 ...
- thinkphp3.1.3导入
1.首先我们做导入一定要在我们的项目中导入一个 PHPExcel 下载地址:http://phpexcel.codeplex.com/ 2.下载之后就解压我们就可以看到像这样子的文件 3.就 ...
- SpringMVC的底层实现
SpringMVC的底层实现流程: SpringMVC的核心是DispatchServlet,它负责接收HTTP的请求和协调SpringMVC中各个组件来完成请求处理的任务,一个请求被截获后,Disp ...
- 安卓——Handler延迟跳转
//声明控制对象 Handler handler =new Handler(){ @Override public void handleMessage(Message msg) { super.ha ...
- Leetcode 1020. 将数组分成和相等的三个部分
1020. 将数组分成和相等的三个部分 显示英文描述 我的提交返回竞赛 用户通过次数321 用户尝试次数401 通过次数324 提交次数883 题目难度Easy 给定一个整数数组 A,只有我们可 ...
- sql语句的各种模糊查询语句
一般模糊语句如下: SELECT 字段 FROM 表 WHERE 某字段 Like 条件 其中关于条件,SQL提供了四种匹配模式: 1.%:表示任意0个或多个字符.可匹配任意类型和长度的字符,有些情况 ...