块设备I/O调度程序

块设备I/O调度程序,如电梯算法,通过排序请求减少磁头寻道,优化性能。Linux内核中包括四种调度算法:预期、最后期限、CFQ和Noop。I/O调度涉及请求队列管理,如blk_plug_device()和blk_remove_plug()用于控制数据流。系统管理员可以更改特定设备的调度程序,选择如CFQ以实现I/O带宽公平分配。
摘要由CSDN通过智能技术生成

1.5.5 块设备I/O调度程序

我们建立请求队列建的目录是,当向请求队列增加一条新的请求,即产生一个request数据结构时,通用块层会调用I/O调度程序来确定该新request将在请求队列中的确切位置。I/O调度程序试图通过扇区将请求队列排序。如果顺序地从链表中提取要处理的请求,那么就会明显减少磁头寻道的次数,因为磁头是按照直线的方式从内磁道移向外磁道(反之亦然),而不是随意地从一个磁道跳跃到另一个磁道。

 

这就是著名的电梯算法,回想一下,电梯算法处理来自不同层的上下请求。电梯是往一个方向移动的;当朝一个方向上的最后一个预定层到达时,电梯就会改变方向而开始向相反的方向移动。因此,块设备I/O调度程序也被称为电梯算法(elevator)。

 

在重负载和磁盘操作频繁的情况下,固定数目的动态空闲内存将成为进程想要把新请求加入请求队列q的瓶颈。为了解决这种问题,每个request_queue描述符包含一个request_list数据结构:

 

struct request_list {

       int count[2]; //两个计数器,分别用于记录分配给READWRITE请求的请求描述符数

       int starved[2];  //两个标志,分别用于标记为读或写请求的分配是否失败

       int elvpriv;     //

       mempool_t *rq_pool;        //一个指针,指向请求描述符的内存池

       wait_queue_head_t wait[2];   //两个等待队列,

//分别存放了为获得空闲的读和写请求描述符而睡眠的进程。

};

 

我们可以通过blk_get_request()函数从一个特定请求队列的内存池中获得一个空闲的请求描述符;如果内存区不足并且内存池已经用完,则要么挂起当前进程,要么返回NULL(如果不能阻塞内核控制路径)。如果分配成功,则将请求队列的request_list数据结构的地址存放在请求描述符的r1字段中。blk_put_request()函数则释放一个请求描述符;如果该描述符的引用计数器的值为0,则将描述符归还回它原来所在的内存池。

 

每个请求队列都有一个允许处理的最大请求数。请求队列描述符的nr_requests字段存放了每个数据传送方向所允许处理的最大请求数。缺省情况下,一个队列至多有128个待处理读请求和128个待处理写请求。如果待处理的读(写)请求数超过了nr_requests,那么通过设置请求队列描述符request_queue数据结构的queue_flags字段的QUEUE_FLAG_READFULLQUEUE_FLAG_WRITEFULL)标志将该队列标记为已满,试图把请求加入到某个传送方向的可阻塞进程被放置到request_list结构所对应的等待队列中睡眠。

 

一个填满的请求队列对系统性能有负面影响,因为它会强制许多进程去睡眠以等待I/O数据传送的完成。因此,如果给定传送方向上的待处理请求数超过了存放在请求描述符的nr_congestion_on字段中的值(缺省值为113),那么内核认为该队列是拥塞的,并试图降低新请求的创建速率。当待处理请求数小于nr_congestion_off的值(缺省值为111)时,拥塞的请求队列才变为不拥塞。blk_congestion_wait()函数挂起当前进程直到所有请求队列都变为不拥塞或超时已到。

 

我们前面谈到,延迟激活块设备驱动程序有利于把相邻块的请求进行集中。这种延迟是通过所谓的设备插入和设备拔出技术来实现的,其实,我们更愿意用激活-不激活来描述。在块设备驱动程序被插入时,该驱动程序并不被激活,即使在驱动程序队列中有待处理的请求。

 

blk_plug_device()函数的功能是插入一个块设备——更准确地说,插入到某个块备驱动程序处理的请求队列中。本质上,该函数接收一个请求队列描述符的地址q作为其参数。它设置q->queue_flags字段中的QUEUE_FLAG_PLUGGED位;然后,重新启动q->unplug_timer字段中的内嵌动态定时器。

 

blk_remove_plug()则拔去一个请求队列q:清除QUEUE_FLAG_PLUGGED标志并取消q->unplug_timer动态定时器的执行。当“视线中”所有可合并的请求都被加入请求队列时,内核就会显式地调用该函数。此外,如果请求队列中待处理的请求数超过过了请求队列描述符的unplug_thresh字段中存放的值(缺省值为4),那么I/O调度程序也会去掉该请求队列。

 

如果一个设备保持插入的时间间隔为q->unplug_delay(通常为3ms),那么说明blk_plug_device()函数激活的动态定时器的时间已用完,因此就会执行blk_unplug_timeout()函数。因而,唤醒内核线程kblockd所操作的工作队列kblockd_workqueuekblockd执行blk_unplug_work()函数,其地址存放在q->unplug_work结构中。接着,该函数会调用请求队列中的q->unplug_fn方法,通常该方法是由generic_unplug_device()函数实现的。generic_unplug_device()函数的功能是拔出块设备:首先,检查请求队列是否仍然活跃;然后,调用blk_remove_plug()函数;最后,执行策略例程request_fn方法来开始处理请求队列中的下一个请求。

 

总结一下就是:

1. blk_plug_device()负责戒严。

2. blk_remove_plug()负责解禁。

3. 但是戒严这东西吧,也是有时间限制的,所以在戒严的时候,设了一个定时器,unplug_timer,一旦时间到了就自动执行blk_remove_plug去解禁。

4. 而在解禁的时候就不要忘记把这个定时器给关掉.(del_timer)

5. 解禁之后调用request_fn()开始处理队列中的下一个请求,或者说车流开始恢复前行。

 

这样我们就算是明白这两个戒严与解禁的函数了。最后,题外话,关于unplugplug,我觉得更贴切的单词是activate

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值