[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);

 

0 0
原创粉丝点击