The Block I/O Layer

http://sylab-srv.cs.fiu.edu/lib/exe/fetch.php?media=paperclub:lkd3ch14.pdf
这里mark一些要点跟总结等。

Anatomy of a Block Device

  • 快设备最小寻址单位是sector, 多数是512bytes
  • kernel寻址单位block, 是sector大小的power-of-two倍数,不大于page size, 常见512B, 1KB, 4KB
  • 机械硬盘的扇区这些概念是对特定block设备的属性,kernel在sector上抽象block

Buffers and Buffer Heads

  • block在内存里变现为一个buffer
  • 每个buffer有一个buffer head 对应这个buffer的信息(哪个device, block等)
struct buffer_head {
  unsigned long b_state; /* buffer state flags */
  struct buffer_head *b_this_page; /* list of page’s buffers */
  struct page *b_page; /* associated page */
  sector_t b_blocknr; /* starting block number */
  size_t b_size; /* size of mapping */
  char *b_data; /* pointer to data within the page */
  struct block_device *b_bdev; /* associated block device */
  bh_end_io_t *b_end_io; /* I/O completion */
  void *b_private; /* reserved for b_end_io */
  struct list_head b_assoc_buffers; /* associated mappings */
  struct address_space *b_assoc_map; /* associated address space */
  atomic_t b_count; /* use count */
};

The bio Structure

struct bio {
    sector_t bi_sector; /* associated sector on disk */
    struct bio *bi_next; /* list of requests */
    struct block_device *bi_bdev; /* associated block device */
    unsigned long bi_flags; /* status and command flags */
    unsigned long bi_rw; /* read or write? */
    unsigned short bi_vcnt; /* number of bio_vecs off */
    unsigned short bi_idx; /* current index in bi_io_vec */
    unsigned short bi_phys_segments; /* number of segments */
    unsigned int bi_size; /* I/O count */
    unsigned int bi_seg_front_size; /* size of first segment */
    unsigned int bi_seg_back_size; /* size of last segment */
    unsigned int bi_max_vecs; /* maximum bio_vecs possible */
    unsigned int bi_comp_cpu; /* completion CPU */
    atomic_t bi_cnt; /* usage counter */
    struct bio_vec *bi_io_vec; /* bio_vec list */
    bio_end_io_t *bi_end_io; /* I/O completion method */
    void *bi_private; /* owner-private method */
    bio_destructor_t *bi_destructor; /* destructor method */
    struct bio_vec bi_inline_vecs[0]; /* inline bio vectors */
};
The Block I/O Layer_第1张图片
Figure 14.2
  • bio 替代buffer_head表示一次io 操作,buffer只跟block对应
  • The basic container for block I/O within the kernel is the bio structure。

The Old Versus the New

  • bio 可以容易表示high memory, bio处理对应物理page,不是pointer
  • 可以同时表示 normal page I/O 跟direct I/O
  • 容易处理涉及多个物理页的操作
  • 相比buffer head更轻量, 只包含一个block I/O操作需要的信息

buffer head的概念仍然需要,但只表示block到buffer的对应,bio表示in-flight I/O.

Request Queues

  • 设备维护一个request queues存储pending的block I/O request, 有定义在里的request_queue structure表示,包含一个请求的双向链表跟相关信息。
  • request由高层的代码如文件系统添加。
  • queue非空快设备驱动就从queue里队首获取request提交到对应块设备。
  • 一个request由里的struct request表示,可以包含多个bio, 因为一个request可以操作多个连续的disk blocks.

I/O Schedulers

如果kernel需要io request的时候就丢到queue的那么性能会很差(考虑磁盘seek操作)。所以kernel会有mergingsorting的操作来提升性能,提供这些操作的子系统就叫做 I/O scheduler

The Job of an I/O Scheduler

一个I/O scheduler管理块设备的request queue.它通过决定request被分发到块设备的顺序跟时间来提高整体的吞吐。

  • merging
    两个或多个request合并成一个。比如文件系统提交一个request,但queue里已经有一个request读取相邻的section,可以合并减少overhead&seed
  • sorting
    没有相邻的request不能合并,但有相近的section请求可以调整顺序。比如把读第3个sector的放在读第1个sector的请求后面。(想下电梯调度)

The Linus Elevator

Linus Elevator 是第一个I/Oscheduler, 2.4默认,2.6后被其它替代。

Linus Elevator会执行merging和sorting操作,但添加一个request是,会在queue里检查每个request看有没相邻(前或后相邻)的request合并。

如果不能merging会找一个sectorwise的合适位置插入,不然插入的队列末尾。另外如果有request在队列里超过了一定时间也会把当前request放到队列而不是插入到合适位置,这个为了比较某个位置的大量请求饿死其它位置的请求。

总的说当添加一个request有4个操作按序可能:

  • 队列里有相邻的request, 合并.
  • 队列里有相当old的request.没处理了, 把新的插到队尾防止饿死其它更老的requests.
  • 有合适的sector-wise位置在队列里插入到对应位置,让队列保持按磁盘物理顺序排序。
  • 没合适位置,简单插到队尾。

The Deadline I/O Scheduler

Deadline I/O Scheduler用来解决Linux Elevator产生的饥饿问题。为了减少seeks的时间,大量同一区域的磁盘操作容易饿死距离较远的request, 这不公平。

更糟糕的是,上面的request 饥饿问题会产生write starving reads.写request当丢到队列就可以当提交了,对应用异步。读当应用提交request, 应用会block到reqeust处理完拿到数据。这样read lantency对上层应用很重要,虽然希望对于写 lantency也不能太大。

读请求更加趋向于互相依赖。例如读取大量文件,每个读在一个很小的buffered chunks。应用不会读取下个chunk(活着说下个文件),知道前一个chunk已经读取并返回到应用,更糟糕的是写也要读(文件系统读取元数据如inode)。读取这些block会串行化I/O。因此,每个读请求都饥饿的化对于应用操作的lantency会很大。Deadline I/O scheduler实现若干特性来确保读饥饿最小化。

The Block I/O Layer_第2张图片

要知道减少读饥饿会带来全局吞吐下降的问题。Linux Elevator也是这样,Linux Elevator提供更好的吞吐(通过更大力度的减小 seeks)。Deadline I/O scheduler通过努力限制饥饿同时提供好的全局吞吐。

在Deadline I/O scheduler每个request会有一个超时时间。默认读500ms, 写5s。如图Deadline scheduler会维护一个类似the Linux Elevator的队列,这个队列按磁盘物理排序,同样会执行mergingsorting的操作。同时根据读或者写的类型插入对另一对应队列(FIFO, 即按时间排序)。Deadline scheduler从sorted queue垃取request到dispatch queue给磁盘驱动消费,这个最小化seeks次数时间。

当在read queue或write queue的request超时了的时候,Deadline scheduler 就从这些FIFO的队列拿取request而不是sorted queue。通过如此,Deadline scheduler尝试确保没有请求远大于它的超时时间。

所以不确保在超时时间内处理,但通过给读请求一个相当小的超时时间这可以防止write starve read, 读会提供更好的lantency。

The Anticipatory I/O Scheduler

The Complete Fair Queuing I/O Scheduler

CFQ类似the Linux Elevator,但是每个进程维护一个queue, 分别merge跟sort, round robin处理每个进程(默认每次获取4个request)。
为多媒体workload设计,但对于多数情景也很好。
(应该是2.6的默认scheduler)

The Noop I/O Scheduler

会做merging但不sorted,基本没什么操作,对于random-access的设备比较好。

Conclusion

  • bio 表示 in-flight I/O
  • buffer_head 表示一个block-to-page mapping
  • request structure 表示一个特定的I/O请求。
    request最后到scheduler处理调度,由driver处理。

一般来说 NOOP 调度器最适合于固态硬盘,DeadLine 调度器适用于写入较多的文件服务器,比如Web服务器,数据库应用等,而CFQ 调度器适合于桌面多任务及媒体应用。


看最新linux(4.12)代码下有的I/O scheduler

➜  linux git:(master) ✗ ll block/*iosched.c
-rw-r--r--  1 huangjiahao  staff   162K May 23  2017 block/bfq-iosched.c
-rw-r--r--  1 huangjiahao  staff   127K May 23  2017 block/cfq-iosched.c
-rw-r--r--  1 huangjiahao  staff    11K May 23  2017 block/deadline-iosched.c
-rw-r--r--  1 huangjiahao  staff    21K May 23  2017 block/kyber-iosched.c
-rw-r--r--  1 huangjiahao  staff   2.6K May 23  2017 block/noop-iosched.c

bfq跟kyber应该是4.12才加入的,参见:https://lwn.net/Articles/720675/

看完可能想看下当前硬盘上用什么调度器了

➜  ~ cat /sys/block/sda/queue/scheduler
noop deadline [cfq]

括号起来的cfq就是当前用的,当前有3种可以配置使用。

你可能感兴趣的:(The Block I/O Layer)