IO

块设备IO

介绍块设备的IO栈之前,我们先来了解一下块IO栈的几个基本概念:

  • bio: bio是通用块层IO请求的数据结构,表示上层提交的IO请求,一个bio包含多个page,这些page必须对应磁盘上一段连续的空间。由于文件在磁盘上并不连续存放,文件IO提交到块设备之前,极有可能被拆成多个bio结构。

  • request:表示块设备驱动层I/O请求,经由I/O调度层转换后的I/O请求,将会发到块设备驱动层进行处理。

  • request_queue:维护块设备驱动层I/O请求的队列,所有的request都插入到该队列,每个磁盘设备都只有一个queue(多个分区也只有一个

3个结构的关系如下图示:一个request_queue中包含多个request,每个request可能包含多个bio,请求的合并就是根据各种原则将多个bio加入到同一个requesst中。

块 IO 结构

请求处理方式

块 IO 处理流程

如图所示是块设备的I/O栈,其中的红色文字表示关键I/O路径的函数。对于I/O的读写流程,逻辑比较复杂,这里以写流程简单描述如下:

  • 用户调用系统调用write写一个文件,会调到sys_write函数;

  • 经过VFS虚拟文件系统层,调用vfs_write,如果是缓存写方式,则写入page cache,然后就返回,后续就是刷脏页的流程;如果是Direct I/O的方式,就会走到do_blockdev_direct_IO的流程;

  • 如果操作的设备是逻辑设备如LVMMDRAID设备等,会进入到对应内核模块的处理函数里进行一些处理,否则就直接构造bio请求,调用submit_bio往具体的块设备下发请求,submit_bio函数通过generic_make_request转发biogeneric_make_request是一个循环,其通过每个块设备下注册的q->make_request_fn函数与块设备进行交互;

  • 请求下发到底层的块设备上,调用块设备请求处理函数__make_request进行处理,在这个函数中就会调用blk_queue_bio,这个函数就是合并biorequest中,也就是I/O调度器的具体实现:如果几个bio要读写的区域是连续的,就合并到一个request;否则就创建一个新的request,把自己挂到这个request下。合并bio请求也是有限度的,如果合并后的请求超过阈值(在/sys/block/xxx/queue/max_sectors_kb里设置),就不能再合并成一个request了,而会新分配一个request

  • 接下来的I/O操作就与具体的物理设备有关了,交由相应的块设备驱动程序进行处理,这里以scsi设备为例说明,queue队列的处理函数q->request_fn 对应的scsi驱动的就是 scsi_request_fn 函数,将请求构造成scsi指令下发到scsi设备进行处理,处理完成后就会依次调用各层的回调函数进行完成状态的一些处理,最后返回给上层用户。

request-basedbio-based

在块设备的I/O处理流程中,会涉及到两种不同的处理方式:

  • request-based:这种处理方式下,会进行bio合并到request(即I/O调度合并)的流程,最后才把请求下发到物理设备。目前使用的物理盘都是request-based的设备;

  • bio-based:在逻辑设备自己定义的request处理函数make_request_fn里进行处理,然后调用generic_make_request下发到底层设备。ramdisk设备、大部分Device Mapper设备、virtio-blk都是bio-based

下图从Device Mapper的角度来说明request-basedbio-based处理流程的区别。

一个需要注意的地方是,Device mapper目前只有multipath插件是request-based的,其他的如linearstrip都是bio-based,所以如果是linear DM设备上创建的一个文件系统,对这个文件系统里的文件进行读写,采用缓存I/O时,即使刷脏页时是连续的请求,在DM设备上也不会进行合并,只会到底层的设备(如/dev/sdb)上才进行合并。

上一页
下一页