当前位置: 首页 > news >正文

一个IO的传奇一生(8) -- elevator子系统

Elevator子系统介绍

Elevator子系统是IO 路径上非常重要的组成部分,前面已经分析过,elevator中实现了多种类型的调度器,用于满足不同应用的需求。那么,从整个IO路径的角度来看,elevator这层主要解决IOQoS问题,通常需要解决如下两大问题:

1)Bio的合并问题。主要考虑bio是否可以和scheduler中的某个request进行合并。因为从磁盘的角度来看,临近的请求需要合并,所有的IO需要顺序化处理,这样磁头才能往一个方向运行,避免无规则的乱序运行。

2)Request的调度问题。request在何时可以从scheduler中取出,并且送入底层驱动程序继续进行处理?不同的应用可能需要不同的带宽资源,读写请求的带宽、延迟控制也可以不一样,因此,需要解决request的调度处理,从而可以更好的控制IOQoS

通过上面分析,一个IO在经过块设备层处理之后,终于来到了elevator层。我们熟知,一个request在送往设备之前会被放入到每个设备所对应的request queue。其实,通过分析一个IOelevator层其实会经过很多request queue,不同的request queue会有不同的作用。如下图所示,一个IO在经历很多层queue的调度处理之后,最后才能达到每个设备的request queueLinux中各个request queue之间的关系如下图所示:

wKioL1NBGWWgPZRaAAKZ3MZCwYc482.jpg

Linux-3.2中,已经采用新的unplug机制对请求进行批量unplug处理,相对于2.6.23 kernel这是新的一层。在老Kernel中,没有这层unplug机制,request请求可以直接进入elevator,然后通过内核中的unplug定时器对elevator中的request进行unplug调度处理。在新kernel中,每个线程可以对自己的request进行unplug调度处理。例如,ext3文件系统的writeback线程可以主动unplug自己的request,这种application awareness的方法可以最大限度的减少请求处理的延迟时间。

从上图可以看出,一个IO请求首先进入每个线程域所在的unplug请求队列。如果这个线程没有unplug请求队列,那么IO request直接被送入elevator。在unplug请求队列中等待的request会在请求unplug的过程中被送入elevator的请求队列。每个设备可以采用不同类型的IO调度方法,因此,在elevator中的IO分类方法会有所不同。这里Elevator的类型也就是我们通常所说的Noopdeadline以及CFQ方法。最后,Elevator中的request会在一定的策略控制下被送入每个设备的request queue。从这个结构中,我们可以看出,只要控制住了elevator的调度器,那么我们就可以控制每个设备IO的优先级,从而达到IO QoS的目的。

通过分析,我们已经知道Request在三类request queue中被调度处理,其主要处理时机点可以描述如下:

wKiom1NBGbWQgs3bAAEe9L_1Ykk561.jpg

在一般的请求处理过程中,request被创建并且会被挂载到unplug request queue中,然后通过flush request方法将requestunplug request queue中移至elevator request queue中。当一个新的BIO需要被处理时,其可以在unplug request queue或者elevator request queue中进行合并。当需要将请求发送到底层设备时,可以通过调用run_queue的方法将elevator分类处理过的request转移至device request queue中,最后调用scsi_dispatch_cmd方法将请求发送到HBA。在这个过程有一些问题需要处理:底层设备可能存在故障;HBA的处理队列是有长度限制的。因此,如何连续调度device request queue及重新调度request成了一个需要考虑的问题。在Linux中,如果scsi层需要重新调度一个request,可以通过blk_requeue_request接口来完成。通过该接口,可以把request重新放回到device request queue中。另外,在一个request结束之后的回调函数中,需要通过scsi_run_queue函数来再次调度处理device request queue中的剩余请求,从而可以保证批量处理device request queue中的请求,HBA也一直运行在最大的queue depth深度。

Elevator层关键函数分析

Elv_merge

当一个IO离开块设备层,需要发送到底层设备时,首先需要判断该IO是否可以和正在等待处理的request进行合并。这一步主要是通过elv_merge()函数来实现的,需要注意的是,在调用elv_merge进行合并操作之前,首先需要判断unplug request queue是否可以进行合并,如果不能合并,那么才调用elv_merge进行elevator request queue的合并操作。一旦bio找到了可以合并的request,那么,这个IO就会合并放入对应的request中,否则需要创建一个新的request,并且放入到unplug request queue中。

Elevator层提供的bio合并函数分析如下:

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))
return ELEVATOR_NO_MERGE;
/*
* First try one-hit cache.
* 尝试和最近的request进行合并
*/
if (q->last_merge) {
ret = elv_try_merge(q->last_merge, bio);
if (ret != ELEVATOR_NO_MERGE) {
/* 可以和last_merge进行合并 */
*req = q->last_merge;
return ret;
}
}
if (blk_queue_noxmerges(q))
return ELEVATOR_NO_MERGE;
/*
* See if our hash lookup can find a potential backmerge.
* 查找elevator中的后向合并的hash table,获取可以合并的request
*/
__rq = elv_rqhash_find(q, bio->bi_sector);
if (__rq && elv_rq_merge_ok(__rq, bio)) {
*req = __rq;
return ELEVATOR_BACK_MERGE;
}
/* 查找scheduler检查是否可以进行前向合并,如果可以,那么进行前向合并 */
if (e->ops->elevator_merge_fn)
return e->ops->elevator_merge_fn(q, req, bio);
return ELEVATOR_NO_MERGE;
}

__elv_add_request

需要将一个request加入到request queue中时,可以调用__elv_add_request函数。通过该函数可以将request加入到elevator request queue或者device request queue中。该函数的实现如下:

void __elv_add_request(struct request_queue *q, struct request *rq, int where)
{
trace_block_rq_insert(q, rq);
rq->q = q;
if (rq->cmd_flags & REQ_SOFTBARRIER) {
/* barriers are scheduling boundary, update end_sector */
if (rq->cmd_type == REQ_TYPE_FS ||
(rq->cmd_flags & REQ_DISCARD)) {
q->end_sector = rq_end_sector(rq);
q->boundary_rq = rq;
}
} else if (!(rq->cmd_flags & REQ_ELVPRIV) &&
(where == ELEVATOR_INSERT_SORT ||
where == ELEVATOR_INSERT_SORT_MERGE))
where = ELEVATOR_INSERT_BACK;
switch (where) {
case ELEVATOR_INSERT_REQUEUE:
case ELEVATOR_INSERT_FRONT:
/* 将request加入到device request queue的队列前 */
rq->cmd_flags |= REQ_SOFTBARRIER;
list_add(&rq->queuelist, &q->queue_head);
break;
case ELEVATOR_INSERT_BACK:
/* 将request 加入到device request queue的队列尾 */
rq->cmd_flags |= REQ_SOFTBARRIER;
elv_drain_elevator(q);
list_add_tail(&rq->queuelist, &q->queue_head);
/*
* We kick the queue here for the following reasons.
* - The elevator might have returned NULL previously
*   to delay requests and returned them now.  As the
*   queue wasn't empty before this request, ll_rw_blk
*   won't run the queue on return, resulting in hang.
* - Usually, back inserted requests won't be merged
*   with anything.  There's no point in delaying queue
*   processing.
*/
__blk_run_queue(q);
break;
case ELEVATOR_INSERT_SORT_MERGE:
/* 尝试对request进行合并操作,如果无法合并将request加入到elevator request queue中 */
/*
* If we succeed in merging this request with one in the
* queue already, we are done - rq has now been freed,
* so no need to do anything further.
*/
if (elv_attempt_insert_merge(q, rq))
break;
case ELEVATOR_INSERT_SORT:
/* 将request加入到elevator request queue中 */
BUG_ON(rq->cmd_type != REQ_TYPE_FS &&
!(rq->cmd_flags & REQ_DISCARD));
rq->cmd_flags |= REQ_SORTED;
q->nr_sorted++;
if (rq_mergeable(rq)) {
elv_rqhash_add(q, rq);
if (!q->last_merge)
q->last_merge = rq;
}
/*
* Some ioscheds (cfq) run q->request_fn directly, so
* rq cannot be accessed after calling
* elevator_add_req_fn.
*/
q->elevator->ops->elevator_add_req_fn(q, rq);
break;
case ELEVATOR_INSERT_FLUSH:
rq->cmd_flags |= REQ_SOFTBARRIER;
blk_insert_flush(rq);
break;
default:
printk(KERN_ERR "%s: bad insertion point %d\n",
__func__, where);
BUG();
}
}

Elv_dispatch_sort

elevator request queue中的request需要发送到device request queue中时,可以调用elv_dispatch_sort函数,通过该函数可以对request进行排序,插入到合适的位置。Elv_dispatch_sort函数的实现如下:

void elv_dispatch_sort(struct request_queue *q, struct request *rq)
{
sector_t boundary;
struct list_head *entry;
int stop_flags;
if (q->last_merge == rq)
q->last_merge = NULL;
elv_rqhash_del(q, rq);
q->nr_sorted--;
boundary = q->end_sector;
stop_flags = REQ_SOFTBARRIER | REQ_STARTED;
list_for_each_prev(entry, &q->queue_head) {
struct request *pos = list_entry_rq(entry);
if ((rq->cmd_flags & REQ_DISCARD) !=
(pos->cmd_flags & REQ_DISCARD))
break;
if (rq_data_dir(rq) != rq_data_dir(pos))
break;
if (pos->cmd_flags & stop_flags)
break;
if (blk_rq_pos(rq) >= boundary) {
if (blk_rq_pos(pos) < boundary)
continue;
} else {
if (blk_rq_pos(pos) >= boundary)
break;
}
if (blk_rq_pos(rq) >= blk_rq_pos(pos))
break;
}
list_add(&rq->queuelist, entry);
}

Elevator子系统小结

Elevator子系统是实现IO调度处理的框架,功能不同的scheduler可以做为一种elevator type加入到这个框架中来。所以,如果需要设计实现一个自定义的scheduler,那么首先必须需要了解elevator子系统。

 

相关文章:

  • linux:shell脚本格式
  • CSS自定义select下拉选择框(不用其他标签模拟)
  • 关于一级指针和二级指针作为参数的探究
  • 2014年4月15日星期二java学习历程
  • Amazon Workspace DaaS服务快速导读
  • http 错误
  • Unity3d 屏幕截图。并保存。iOS
  • quota .1
  • 34、最简单的mvc框架tiny,总结分析V2版思路
  • linux ----系统下各个文件夹的作用及系统启动顺序
  • 妙趣横生的算法--二叉树
  • 在 Cacti 下实现监控 IIS 服务器
  • asp.net 页面实践执行顺序
  • First First
  • 从凭证反查日记账
  • 2017-08-04 前端日报
  • JavaScript DOM 10 - 滚动
  • java小心机(3)| 浅析finalize()
  • jQuery(一)
  • js学习笔记
  • leetcode378. Kth Smallest Element in a Sorted Matrix
  • leetcode98. Validate Binary Search Tree
  • MySQL几个简单SQL的优化
  • TCP拥塞控制
  • vue 配置sass、scss全局变量
  • 阿里云容器服务区块链解决方案全新升级 支持Hyperledger Fabric v1.1
  • 百度地图API标注+时间轴组件
  • 从@property说起(二)当我们写下@property (nonatomic, weak) id obj时,我们究竟写了什么...
  • 分布式任务队列Celery
  • 计算机在识别图像时“看到”了什么?
  • 解决jsp引用其他项目时出现的 cannot be resolved to a type错误
  • 聊聊directory traversal attack
  • 聊一聊前端的监控
  • 深入浅出Node.js
  • 使用权重正则化较少模型过拟合
  • 我的业余项目总结
  • 云大使推广中的常见热门问题
  • 运行时添加log4j2的appender
  • #{} 和 ${}区别
  • #{}和${}的区别是什么 -- java面试
  • #LLM入门|Prompt#1.8_聊天机器人_Chatbot
  • (2021|NIPS,扩散,无条件分数估计,条件分数估计)无分类器引导扩散
  • (6)设计一个TimeMap
  • (C#)Windows Shell 外壳编程系列9 - QueryInfo 扩展提示
  • (zz)子曾经曰过:先有司,赦小过,举贤才
  • (二)换源+apt-get基础配置+搜狗拼音
  • (四) 虚拟摄像头vivi体验
  • (已更新)关于Visual Studio 2019安装时VS installer无法下载文件,进度条为0,显示网络有问题的解决办法
  • (译)2019年前端性能优化清单 — 下篇
  • (转)编辑寄语:因为爱心,所以美丽
  • .apk 成为历史!
  • .NET Conf 2023 回顾 – 庆祝社区、创新和 .NET 8 的发布
  • .net core使用ef 6
  • .NET 设计模式—适配器模式(Adapter Pattern)
  • .Net接口调试与案例