Linux: 块设备的读写与 IO scheduler


 

上一篇文章所说的读和写请求并没有直接发送到disk driver 去处理,中间还有一个重要的IO scheduler 的过程。

块设备最悲剧的地方就是磁盘转动,这个过程会很耗时间。IO scheduler 的作用主要是为了减少磁盘转动的需求。主要通过2中方式实现: 1.合并  2. 排序

每个设备都会自己对应请求队列,所有的请求在被处理之前都会在请求队列上。 在新来一个请求的时候如果发现这个请求和前面的某个请求请求的位置是相邻的,那么就可以合并为一个请求。如果不能找到合并的,就会按照磁盘的转动方向进行排序。通常IO scheduler 的作用就是为了在进行合并和排序的同时,也不会太影响单个请求的处理时间。

Linux 现在有5种IO scheduler.

1 Linus Elevator
在2.4 的内核中,它还是默认的IO scheduler. 2.6 以后被替换。
它的主要作用是为每个设备维护一个request queue. 
当来一个新请求时,
1 如果能合并就合并
2 如果不能合并,就会尝试排序。 如果队列上的请求都已经很老了,这个新的请求就不能插队,只能放到最后面。否则就插到合适的位置
3 如果既不能合并,有没有合适的位置插入,就放到请求队列的最后。

2 DeadLine IO
Deadline io scheduler 实际上是对Elevator 的一种改进, 1 避免有些请求太长时间不能被处理。2 区分对待读操作和写操作。
deadline IO 维护3个队列。第一个队列和Elevator 一样, 尽量按照物理位置排序。 第二个队列和第三个队列都是按照时间排序,不同的是一个是读操作一个是写操作。
deadline IO 之所以区分读和写是因为设计者认为如果应用程序发了一个读请求,一般就会阻塞到那里,一直等到的结果返回。 而写请求则不是通常是应用请求写到内存即可,由后台进程再写回磁盘。应用程序一般不等写完成就继续往下走。 所以读请求应该比写请求有更高的优先级。

在这种设计下,每个新增请求都会先放到第一个队列,算法和Elevator的方式一样,同时也会增加到读或者写队列的尾端。这样首先处理一些第一队列的请求,同时检测第二/三队列前几个请求是否等了太长时间,如果已经超过一个阀值,就会去处理一下。 这个阀值对于读请求时 5ms, 对于写请求时5s.

个人认为对于记录数据库变更日志的分区,例如oracle 的online log, mysql 的binlog 等等,最好不要使用这种分区。 因为这类写请求通常是调用fsync 的。 如果写完不成,也会很影响应用性能的。

3 anticipatory IO
 这是基于deadline IO 的。 deadline IO 的一个缺点是本身处理第一队列上的请求蛮好的,但有可能处理了一下第二三队列的请求把磁盘转的很远。这样就影响了性能。anticipatory 的改进就是当已经处理了一个读请求后(磁盘已经移位), 并不急于处理其他请求,而是等一段时间(默认6ms,可控),看有没有附近的请求,如果有,就立即处理。没有这段时间就会浪费。

4 complete fair queruing IO

完全公平的IO scheduler. 公平是对于同一优先级的各个进程而言。 前面的3中算法没有区分任何进程。所以cfq 是和别的有较大的区别。 cfq 为每一个进程维护一个队列。 请求在单个队列的位置和Elevator 类似。 每次处理一个队列上的4个(默认)请求,然后就去处理别的请求。cfq 是2.6 以后的默认io scheduler.

每个进程还有自己有的io优先级,可以通过ionice 去查看和修改。 优先级越高,被处理的越早,用于这个进程的时间片也越多,一次处理的请求数也会越多。
时间片的概念,如果在时间片内请求数还没有被处理完,就会停止处理。如果在时间片内要处理的请求已经处理完,也会停止处理该进程。


 
5 noop
  什么都不做,请求来一个处理一个。这种方式事实起来简单,也更有效。问题就是disk seek 太多,对于传统磁盘,这是不能接受的。 但对于SSD 磁盘就可以,因为SSD 磁盘不需要转动。
 
在新的内核里面似乎找不到Elevator 了。所以只剩下了4种。

相关内容