一、块I/O基本概念
字符设备:按照字符流的方式被有序访问的设备。如串口、键盘等。
块设备:系统中不能随机(不需要按顺序)访问固定大小的数据片(chunk 块)的设备。
如:硬盘、软盘、CD-ROM驱动器、闪存等。都是通过以安装文件系统的方式使用。
块设备的组成:
扇区:是块设备中最小的可寻址单元(常见大小512字节);是块设备的基本寻址和操作单元。
块:是文件系统最小逻辑可寻址单元,文件系统的抽象,只能通过块访问文件系统。通常包含多个扇区。
当一个块被调入内存时(读入后或等待写出时),它要存储在一个缓冲区中;每个缓冲区与一个块对应,缓冲区相当于是磁盘块在内存中的表示;块大小不超过一个页面,一个页可以容纳一个或多个内存中的块。
缓冲区:是内核操作块设备的逻辑单元,每个缓冲区需要一个描述符来表示块的控相关制信息。
数据结构:缓冲区头buffer_head,虽然不用于I/O操作,但是它负责描述磁盘块到页面的映射;
内核操作I/O块基本容器是:bio。操作内核中所有的缓冲区对应的I/O块。
请求队列:块设备将他们挂起的块I/O请求保存在请求队列中。
二、I/O调度机制
简单的以内核产生I/O请求的次序直接将请求发向块设备,造成性能将难以接受。因为磁盘寻址是整个计算机中最慢的操作之一,每一次寻址定位硬盘磁头到特定块上某个位置需要花费不少时间;要提高I/O操作性能,尽量缩短磁盘寻址时间。
在提交请求到块设备前,内核需要对请求进行处理:先执行合并与排序的预操作——I/O调度机制子系统,负责I/O请求的提交。
I/O调度程序管理块设备的请求队列,决定队列中的请求排列顺序,何时派发请求到设备。以减少磁盘寻址时间,提高全局吞吐量。
其实现的方法是合并与排序:
合并:将两个或多个请求结合成一个新的请求,比如访问磁盘扇区相邻时,合并为一个对单个和多个相邻磁盘扇区操作的新请求。合并后仅需要一次请求一条寻址命令。
排序:没有相邻操作扇区请求时,但可能是比较接近的;将整个请求队列按扇区增长方向有序排列,操作时保持磁头以直线一个方向移动,缩短请求磁盘寻址时间。
三、调度程序实现
1、Linus 电梯
在Linux2.4或更早的版本的调度程序,那时只有这一种I/O调度算法。
当一个请求加入到队列时:
如果队列已存在一个对相邻磁盘扇区操作的请求,将新请求和这个已存在的请求合并成一个请求。
如果队列中存在一个驻留时间过长的请求,将新请求插入到队列尾部,防止请求发生饥饿。
如果队列中以扇区方向为序存在合适插入位置,将新请求插入到该位置,与被访问磁盘物理位置为序排列。
如果队列不存在合适位置插入,将请求插入到队列尾部。
2、最终期限I/O调度程序
Linus电梯调度程序存在使请求发生饥饿的情况:
对某个磁盘区域繁重操作,使得磁盘其他位置上的操作请求得不到运行;
同一位置顺序上的请求流可以造成较远位置请求得不到运行;
写操作和提交应用程序是异步执行,读操作和提交应用程序是同步执行会阻塞,读操作响应时间影响性能。要在提高全局吞吐量和使请求得到公平处理之间进行平衡。
最终期限I/O调度程序中:每个请求都有一个超时时间,读请求默认500毫秒,写请求5秒。
提交请求时:
一个请求递交给排序队列,按照合并和排序插入队列;
将读请求按次序插入到读FIFO队列中;
将写请求按次序插入到写FIFO队列中;
派发请求时:
通常从排序队列中取队首请求加入到派发队列中;
如果写FIFO队列首或读FIFO队列首请求超时,调度程序从FIFO队列中提取队首请求加入到派发队列中。
如下图所示:
此方式能尽量保证:
请求超时前得到执行,防止请求发生饥饿;
读请求超时时间比写请求短很多,保证写请求不会因为堵塞读请求而使读请求发生饥饿。
3、预测I/O调度程序
最终期限调度程序降低请求发生饥饿的概率,同时降低了系统吞吐量。预测I/O调度程序的目标就是在保持良好读响应同时提供良好的全局吞吐量。
预测I/O调度程序与最终期限调度程序不同之处:
请求提交后并不直接放回处理其他请求,而是会空闲片刻(6毫秒),使应用程序有提交其他请求的机会——任何对相邻磁盘位置的操作请求都会立刻得到处理,
等待结束后,预测I/O调度程序重新返回原来的位置,继续执行以前的剩下请求。
预测I/O调度程序所能带来的优势取决于能否正确预测应用程序和文件系统的行为,需要启发和统计工作,预测准确能够减少寻址开销,提高系统响应,提高吞吐量。
4、完全公正的排队I/O调度
完全公正的排队(Complete Fair Queuing, CFQ)I/O调度是为专有工作负荷设计的,它和之前提到的I/O调度有根本的不同。
CFQ I/O调度算法中,每个进程都有自己的I/O队列,
CFQ I/O调度程序以时间片轮转调度队列,从每个队列中选取一定的请求数(默认4个),然后进行下一轮调度。
CFQ I/O调度在进程级提供了公平。
CFQ试图均匀地分布对I/O带宽的访问,避免进程被饿死并实现较低的延迟,是deadline和as调度器的折中.
CFQ对于多媒体应用(video,audio)和桌面系统是最好的选择。
在最新的内核版本和发行版中,都选择CFQ做为默认的I/O调度器,对于通用的服务器也是最好的选择。
5、空操作的I/O调度
空操作(Noop)I/O调度几乎不做什么事情,这也是它这样命名的原因。
空操作I/O调度只做一件事情,当有新的请求到来时,把它与任一相邻的请求合并。
空操作I/O调度主要用于闪存卡之类的块设备,这类设备没有磁头,没有寻址的负担。
最后,作为默认方式,块设备使用完全公平的I/O调度程序。在启动时,可以通过命令行选项elevator=xxx覆盖默认设置。
elevator选项参数如下:
参数 |
I/O调度程序 |
as | 预测 |
cfq | 完全公正排队 |
deadline | 最终期限 |
noop | 空操作 |
如果启动预测I/O调度,启动的命令行参数中加上 elevator=as 。
参考资料:
《Linux内核设计与实现》原书第三版
https://www.cnblogs.com/tdyizhen1314/p/5316893.html