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),要么就保存在裸设备里.我们在使用这些数据的时候都是通过文件这个抽象来访问的, ...
随机推荐
- Java创建多线程和线程安全集合Vector
public class Test { public static Vector<String> data = new Vector<String>(); public sta ...
- (转)基于C#的socket编程的TCP异步实现
一.摘要 本篇博文阐述基于TCP通信协议的异步实现. 二.实验平台 Visual Studio 2010 三.异步通信实现原理及常用方法 3.1 建立连接 在同步模式中,在服务器上使用Accept方法 ...
- Setting the Java Class Path
The class path is the path taht Java Runtime Environment(JRE) searches for classes and other resourc ...
- English trip V1 - 12.Belongings 行李 Teacher:Jade Key: ?
In this lesson you will learn to describe your home life and things you own. 在本课中,您将学习如何描述您的家庭生活和您拥有 ...
- 使用Service组件实现简单的音乐播放器功能 --Android基础
1.本例利用Service实现简单的音乐播放功能,下面是效果图.(点击开始播放开启服务,音乐播放,点击“停止播放”关闭服务,音乐停止播放.) 2.核心代码: MusicService.java: pa ...
- OSPF - 3,OSPF区域和LSA
1,四种末端区域骨干区域和标准区域:1,2,3,4,5,包含5类LSA,为了减少某些普通区域的LSA(主要就是4类和5类,有时做绝到连3类也不要了),引入了末梢区域.同时为了确保数据能出去,一般ABR ...
- Lab 3-2
Analyze the malware found in the file Lab03-02.dll using basic dynamic analysis tools. Questions and ...
- NYOJ - 整数划分(四)
题目链接:http://acm.nyist.net/JudgeOnline/problem.php?pid=746 要求对一个n的整数插入m个乘号,求最大结果. 构造dp:dp[i][j]表示枚举至j ...
- Confluence 6 空间
什么是一个空间? Confluence 空间是包含有页面和博客页面的容器.你也可以将空间认为是对你工作中可以使用的 2 中类型的目录. 在 Confluence 中有下面 2 种空间类型: 站点空间( ...
- IntelliJ IDEA的调试方法
快捷键F9 resume programe 恢复程序 Alt+F10 show execution point 显示执行断点 F8 S ...