[IO系统]16 IO调度器-NOOP
来源:互联网 发布:java表格代码怎么写 编辑:程序博客网 时间:2024/05/18 01:43
Noop调度算法是内核中最简单的IO调度算法。
1.1 原理
Noop调度算法也叫作电梯调度算法,它将IO请求放入到一个FIFO队列中,然后逐个执行这些IO请求,当然对于一些在磁盘上连续的IO请求,Noop算法会适当做一些合并。这个调度算法特别适合那些不希望调度器重新组织IO请求顺序的应用。
1.2 优势
这种调度算法在以下场景中优势比较明显:
1)在IO调度器下方有更加智能的IO调度设备。如果您的Block Device Drivers是Raid,或者SAN,NAS等存储设备,这些设备会更好地组织IO请求,不用IO调度器去做额外的调度工作;
2)上层的应用程序比IO调度器更懂底层设备。或者说上层应用程序到达IO调度器的IO请求已经是它经过精心优化的,那么IO调度器就不需要画蛇添足,只需要按序执行上层传达下来的IO请求即可。
3)对于一些非旋转磁头氏的存储设备,使用Noop的效果更好。因为对于旋转磁头式的磁盘来说,IO调度器的请求重组要花费一定的CPU时间,但是对于SSD磁盘来说,这些重组IO请求的CPU时间可以节省下来,因为SSD提供了更智能的请求调度算法,不需要内核去画蛇添足。
1.3 调度器源码分析
首先要了解描述elevator的数据结构。和elevator相关的数据结构有个,一个是elevator_type,一个是elevator_queue,前者对应一个调度器类型,后者对应一个调度器实例,也就说如果内核中只有上述四种类型的调度器,则只有四个elevator_type,但是多个块设备(分区)可拥有多个相应分配器的实例,也就是elevator_queue。两个数据结构中最关键的元素都是struct elevator_ops,该结构定义了一组操作函数,用来描述请求队列的相关算法,实现对请求的处理
struct elevator_type{ structlist_head list; structelevator_ops ops; structelv_fs_entry *elevator_attrs; charelevator_name[ELV_NAME_MAX]; structmodule *elevator_owner;};
及:
struct elevator_queue{ structelevator_ops *ops; void*elevator_data; structkobject kobj; structelevator_type *elevator_type; structmutex sysfs_lock; structhlist_head *hash;};
函数elevator_init()用来为请求队列分配一个I/O调度器的实例
int elevator_init(struct request_queue *q, char*name){ structelevator_type *e = NULL; structelevator_queue *eq; int ret =0; void*data; /*初始化请求队列的相关元素*/ INIT_LIST_HEAD(&q->queue_head); q->last_merge= NULL; q->end_sector= 0; q->boundary_rq= NULL; /*下面根据情况在elevator全局链表中来寻找适合的调度器分配给请求队列*/ if (name){//如果指定了name,则寻找与name匹配的调度器 e =elevator_get(name); if(!e) return-EINVAL; } //如果没有指定io调度器,并且chosen_elevator存在,则寻找其指定的调度器 if (!e&& *chosen_elevator) { e =elevator_get(chosen_elevator); if(!e) printk(KERN_ERR"I/O scheduler %s not found\n", chosen_elevator); } //依然没获取到调度器的话则使用默认配置的调度器 if (!e) { e =elevator_get(CONFIG_DEFAULT_IOSCHED); if(!e) {//获取失败则使用最简单的noop调度器 printk(KERN_ERR "DefaultI/O scheduler not found. " \ "Usingnoop.\n"); e= elevator_get("noop"); } } //分配并初始化elevator_queue eq =elevator_alloc(q, e); if (!eq) return-ENOMEM; //调用ops中的elevator_init_fn函数,针对调度器的队列进行初始化 data =elevator_init_queue(q, eq); if (!data){ kobject_put(&eq->kobj); return-ENOMEM; } //建立数据结构的关系 elevator_attach(q,eq, data); returnret;}
所有的I/O调度器类型都会通过链表链接起来(通过struct elevator_type中的list元素),elevator_get()函数便是通过给定的name,在链表中寻找与name匹配的调度器类型。当确定了I/O调度器的类型后,便要通过elevator_alloc()为等待队列分配一个调度器的实例--struct elevator_queue,并进行初始化;其后,由于每个调度器根据自身算法的不同,都会拥有不同的队列结构,在elevator_init_queue()中会调用特定于调度器的初始化函数针对这些队列进行初始化,并且返回特定于调度器的数据结构,最后再elevator_attach()中建立相关结构的关系。
static struct elevator_queue *elevator_alloc(structrequest_queue *q, struct elevator_type *e){ structelevator_queue *eq; int i; //为eq分配内存 eq =kmalloc_node(sizeof(*eq), GFP_KERNEL | __GFP_ZERO, q->node); if(unlikely(!eq)) gotoerr; //根据之前确定的elevator_type初始化eq eq->ops= &e->ops; eq->elevator_type= e; kobject_init(&eq->kobj,&elv_ktype); mutex_init(&eq->sysfs_lock); //分配elevator的哈希表内存 eq->hash= kmalloc_node(sizeof(struct hlist_head) * ELV_HASH_ENTRIES, GFP_KERNEL,q->node); if(!eq->hash) gotoerr; //初始化哈希表 for (i =0; i < ELV_HASH_ENTRIES; i++) INIT_HLIST_HEAD(&eq->hash[i]); return eq;err: kfree(eq); elevator_put(e); returnNULL;}
及
static void *elevator_init_queue(structrequest_queue *q, struct elevator_queue *eq){ returneq->ops->elevator_init_fn(q);}
再:
static void elevator_attach(struct request_queue*q, struct elevator_queue *eq, void *data){ q->elevator= eq; eq->elevator_data= data;}
下面就来看一下elevator_ops中定义了哪些操作:
struct elevator_ops{ elevator_merge_fn *elevator_merge_fn; elevator_merged_fn *elevator_merged_fn; elevator_merge_req_fn *elevator_merge_req_fn; elevator_allow_merge_fn *elevator_allow_merge_fn; elevator_dispatch_fn *elevator_dispatch_fn; elevator_add_req_fn *elevator_add_req_fn; elevator_activate_req_fn *elevator_activate_req_fn; elevator_deactivate_req_fn *elevator_deactivate_req_fn; elevator_queue_empty_fn *elevator_queue_empty_fn; elevator_completed_req_fn *elevator_completed_req_fn; elevator_request_list_fn *elevator_former_req_fn; elevator_request_list_fn *elevator_latter_req_fn; elevator_set_req_fn *elevator_set_req_fn; elevator_put_req_fn *elevator_put_req_fn; elevator_may_queue_fn *elevator_may_queue_fn; elevator_init_fn *elevator_init_fn; elevator_exit_fn *elevator_exit_fn; void(*trim)(struct io_context *);};
这里只关注几个主要的操作函数,其中前面加了*号的表示这些函数是每个调度器都必须实现的
elevator_merge_fn查询一个request,用于将bio并入
elevator_merge_req_fn将两个合并后的请求中多余的那个给删除
*elevator_dispatch_fn将调度器的队列最前面的元素取出,分派给request_queue中的请求队列以等候响应*
*elevator_add_req_fn将一个新的request添加进调度器的队列
elevator_queue_empty_fn检查调度器的队列是否为空
elevator_set_req_fn和elevator_put_req_fn分别在创建新请求和将请求所占的空间释放到内存时调用
*elevator_init_fn用于初始化调度器实例
一个请求在创建到销毁的过程遵循下面三种流程
set_req_fn->
i. add_req_fn -> (merged_fn ->)* -> dispatch_fn -> activate_req_fn-> (deactivate_req_fn -> activate_req_fn ->)* -> completed_req_fn
ii. add_req_fn-> (merged_fn ->)* -> merge_req_fn iii. [none] ->put_req_fn
在分析调度器的实现时,不妨也以此为依据,选择i或者ii来作为分析的流程。
1.4 NOOP源码分析
Noop调度器的实现非常简单,其主要完成了一个elevator request queue,这个request queue没有进行任何的分类处理,只是对输入的request进行简单的队列操作。但是,需要注意的是,虽然Noop没有做什么事情,但是elevator还是对bio进行了后向合并,从而最大限度的保证相邻的bio得到合并处理。Noop调度器实现了elevator的基本接口函数,并将这些函数注册到linux系统的elevator子系统中。
需要注册到elevator子系统中的基本接口函数声明如下:
static struct elevator_type elevator_noop = {.ops = {/* 合并两个request */.elevator_merge_req_fn = noop_merged_requests,/* 调度一个合适的request进行发送处理 */.elevator_dispatch_fn = noop_dispatch,/* 将request放入调度器的queue中*/.elevator_add_req_fn = noop_add_request,/* 获取前一个request */.elevator_former_req_fn = noop_former_request,/* 获取后一个request */.elevator_latter_req_fn = noop_latter_request,.elevator_init_fn = noop_init_queue,.elevator_exit_fn = noop_exit_queue,},.elevator_name = "noop",.elevator_owner = THIS_MODULE,};
Noop调度器使用的管理数据:
struct noop_data { structlist_head queue;};
noop_init_queue如下:
static void *noop_init_queue(struct request_queue*q){ structnoop_data *nd; //为noop调度器使用的数据结构分配内存 nd = kmalloc_node(sizeof(*nd),GFP_KERNEL, q->node); if (!nd) returnNULL; //初始化noop调度器使用的队列 INIT_LIST_HEAD(&nd->queue); return nd;}
由于 Noop 调度器没有对 request 进行任何的分类处理、调度,因此上述这些函数的实现都很简单。例如,当调度器需要发送 request 时,会调用 noop_dispatch 。该函数会直接从调度器所管理的 request queue 中获取一个 request ,然后调用 elv_dispatch_sort 函数将请求加入到设备所在的request queue 中。 Noop dispatch 函数实现如下:
static int noop_dispatch(struct request_queue *q,int force){ struct noop_data *nd =q->elevator->elevator_data; if (!list_empty(&nd->queue)) { struct request *rq; /* 从调度器的队列头中获取一个request */ rq = list_entry(nd->queue.next, struct request,queuelist); list_del_init(&rq->queuelist); /* 将获取的request放入到设备所属的request queue中 */ elv_dispatch_sort(q, rq); return 1; } return 0;}
当需要往 noop 调度器中放入 request 时,可以调用 noop_add_request ,该函数的实现及其简单,就是将 request 挂入调度器所维护的 request queue 中。 Noop_add_request 函数实现如下:
static void noop_add_request(struct request_queue*q, struct request *rq){ struct noop_data *nd =q->elevator->elevator_data; /* 将request挂入noop调度器的request queue */ list_add_tail(&rq->queuelist,&nd->queue);}
由此可见, noop 调度器的实现是很简单的,仅仅实现了一个调度器的框架,用一条链表把所有输入的 request 管理起来。
1.5 简单调度器实现
通过 noop 调度器的例子,我们可以了解到实现一个调度器所需要的基本结构:
/* 包含基本的头文件 */#include <linux/blkdev.h>#include <linux/elevator.h>#include <linux/bio.h>#include <linux/module.h>#include <linux/slab.h>#include <linux/init.h>/* 定义调度器所需要的数据结构,一条管理request的队列是必须的 */struct noop_data { structlist_head queue;};/* 实现调度器的接口函数 */static struct elevator_type elevator_noop = { .ops = { /* 调度器的功能函数 */ .elevator_merge_req_fn = noop_merged_requests, …… /* 初始化/注销调度器,通常在下面这些函数初始化调度器内部的一些数据结构,例如noop_data */ .elevator_init_fn = noop_init_queue, .elevator_exit_fn = noop_exit_queue, }, .elevator_name = "noop", .elevator_owner = THIS_MODULE,};/* 注册调度器 */static int __init noop_init(void){ elv_register(&elevator_noop); return 0;}/* 销毁调度器 */static void __exit noop_exit(void){ elv_unregister(&elevator_noop);}/* 模块加载时调用noop_init */module_init(noop_init);/* 模块退出时调用noop_exit */module_exit(noop_exit);
- [IO系统]16 IO调度器-NOOP
- Linux noop io 调度算法分析
- [IO系统]17 IO调度器-DEADLINE
- [IO系统]18 IO调度器
- btrfs cfq, noop, deadline三种IO调度策略下的IO性能表现(gp针对grup.conf配置)
- Linux IO Scheduler -- NOOP(no operation)
- [IO系统]14 IO调度层
- linux IO调度器
- IO调度器
- IO调度器
- Linux IO调度器
- linux IO调度器
- IO调度器原理介绍
- IO调度器原理介绍
- 修改Linux IO调度器
- IO队列和IO调度
- IO队列和IO调度
- IO队列和IO调度
- Linux系统下的常用命令
- robot framework读取Excel文件,并保存为list
- RatingBar流泪、有脚、显示不全问题
- (C++基础)浅谈继承与派生
- MYSQL(5)触发器
- [IO系统]16 IO调度器-NOOP
- Java读取文件byte转化String问题
- 【项目实战】从抽奖算法感受算法奥妙
- codeforces contest 785 a题
- 指针极简教程
- OA的项目
- xpath提取网页内容
- 蓝桥杯 小朋友排队
- 登陆验证