1一个块IO的一生
从page cache到bio到request
当APP打开一个文件,内核为文件维护一个pagecache(磁盘的一个副本);
读写文件时如果page cache命中,只会读写内存不操作磁盘;没有命中,才操作磁盘。
在内核用file结构体表示,可见其包含一个inode结构体,一个地址空间;
相关的几个结构体在内核对应关系如下:
可见,当多个进程同时打开同一个文件时,不同的file结构体对应同一个inode和同一个地址空间,地址空间是由一颗radix
tree维护(即pagecache),读写文件时,查看对应内存页在page
cache中是否命中,若命中直接从内存空间读写;若不命中,申请一个内存页,从磁盘读入数据,挂到page
cache的radix tree中。
另外,page cache与磁盘的同步由地址空间操作函数readpage/writepage完成
对磁盘访问,有两种方法:
a.裸磁盘直接访问;
b.通过文件系统访问;
它们在内核page cached对应关系如下:
一个address_space对应一个inode。
free命令统计的buffer/cached,只是统计上的区别;
buffer=操作裸分区的地址空间+文件系统的元数据地址空间;
cached=文件系统的地址空间(page cached)缓存;
但是对同一个磁盘,裸磁盘和文件系统两种方式同时操作的时候,同一个数据块会被映射到不同的address_space,会产生同步的问题;
在用dd的时候,最好不要操作文件系统数据。
2 O_DIRECT和O_SYNC
直接操作裸磁盘分区用O_DIRECT,内核没有任何cache,直接操作磁盘;用户可以根据数据特点,在用户空间做cache。O_DIRECT申请内存要用posix_memalign接口;
而O_SYNC依然通过page cache,但是会立即写入同步磁盘;
App通过page cache访问文件和直接操作裸磁盘的模型,与CPU通过cache访问内存和DMA直接访问内存的模型非常类似;
这里page cache是内存,file是磁盘分区数据块;
当有一个进程启用O_DIRECT模式,其他进程最好也用O_DIRECT;
3 BIO 流程blktrace
对于一个pagecache地址空间,指向的是page页,文件系统ext4读取inode,将page转化为磁盘数据块,变成BIO请求;
BIO最终要转化成request,然后request被块设备驱动程序调用完成;
Bio经过三个队列变成request,三进三出
step1:原地蓄势
把bio转化为request,把request放入进程本进程的plug队列;蓄势多个request后,再进行泄洪。
可能合并相邻bio为一个request;
Bio数量>=request数量;
多个进程,泄洪request到电梯;
step2.电梯排序
进程本地的plug队列的request进入到电梯队列,进行再次的合并、排序,执行QoS的排队,之后按照QoS的结果,分发给dispatch队列。电梯内部的实现,可以有各种各样的队列。
比如两个进程需要读邻近的数据块,可以合并为一个request
电梯调度层可以做QoS,设定进程访问IO的优先级;
step3.分发执行dispatch
电梯分发的request,被设备驱动的request_fn()挨个取出来,派发真正的硬件读写命令到硬盘。这个分发的队列,一般就是我们在块设备驱动里面见到的request_queue了。request_queue完成真正的硬件操作;
工具ftrace
do.sh
#!/bin/bash
debugfs=/sys/kernel/debug
echo nop > $debugfs/tracing/current_tracer
echo 0 > $debugfs/tracing/tracing_on
echo `pidof read` > $debugfs/tracing/set_ftrace_pid
echo function_graph > $debugfs/tracing/current_tracer
echo vfs_read > $debugfs/tracing/set_graph_function
echo 1 > $debugfs/tracing/tracing_on
执行./read读文件
查看trace过程
sudo cat /sys/kernel/debug/tracing/trace >
用VIM查看t.txt,用.funcgrahp.vim插件打开可以合并/展开对应函数调用
vim -S ~/.funcgrahp.vim
4 IO调度算法,CFQ和ionice
查看当前系统的IO调度算法
(base) leon\@pc:/sys/block/sda/queue\$ cat scheduler
noop deadline [cfq]
修改调度算法:
sudo echo cfg >
CFQ调度算法:类似进程调度的CFS算法
ionice –help
ionice –c 2 –n 0 dd if=/dev/sda of=/dev/null & //设置nice值=0
ionice –c 2 –n 8 dd if=/dev/sda of=/dev/null & //设置nice值=8
iotop查看IO读写速度,有明显差异
改成deadline策略
echo deadline >
此时尽管优先级不同,但两个进程IO速度相近了;
5 cgroup与IO
cd
a.创建群组A,B
Mkdir A;makedir B
cat blkiolweight //默认是500
分别把两个进程放到A和B执行
cgexec -g blkio:A dd if=/dev/sda of=/dev/null iflag=direct &
cgexec -g blkio:B dd if=/dev/sda of=/dev/null iflag=direct &
查看io占用iotop
默认是相近
b.修改群权重
echo 50 > blkio.weight
两个进程优先级一样,但IO速度差别很大;
c.cgroup还可以控制阀门,限制最大读速度:
8:0磁盘的主次设备号
echo "8:0 1048576" >
带cache读写,这里的限速不起作用;direct方式才生效;
cgroup v2版本支持带cache限速
6 IO性能调试:iotop, iostat
blktrace跟踪硬盘的各个读写点
blktrace –d /dev/sda –o - |blkparse –I –
dd if=main.c of=t.txt oflag=sync
debugfs –R ‘ickeck xxxx’ /dev/sda1
debugfs –R ‘nckeck inode’ /dev/sda1
blkcat /dev/sda1 xxx