文件系统

索引节点和目录项

为了方便管理,Linux 文件系统为每个文件都分配了两个数据结构,即​​索引节点(index node)​​​和​​目录项(directory entry)​​。它们主要用来记录文件的元信息和目录结构。

  • 索引节点(简称 inode):用于记录文件的元数据,比如 inode 编号、文件大小、访问权限、修改日期、数据的位置等。索引节点和文件一一对应,它跟文件呃逆人一样,都会被持久化存储到磁盘中。
  • 目录项(简称 entry):用于记录文件到名字、索引节点指针以及与其它目录项的关联关系。多个关联的目录项,就构成了文件系统的目录结构。
虚拟文件系统层(VFS)

文件系统的访问接口被称为虚拟文件系统层(VFS),这是一套通用的内核接口。VFS 定义了一组文件系统都支持的数据结构和标准接口。这样用户进程和内核中的其它子系统只需要跟 VFS 提供的统一接口进行交互即可,而不需要关注底层的文件系统实现。

Linux 性能优化和内核观测 - 文件系统与磁盘I/O篇(一)_系统调用

文件系统缓存

文件系统缓存包括,​​页缓存​​​、​​inode 缓存​​​和​​目录缓存​

  • 页缓存:页缓存的内容是虚拟内存页,包括文件的内容、被修改过但是还没有被写回到磁盘的内容(脏页),以及 I/O 缓冲的信息。页缓存主要用于提高文件和目录 I/O 访问性能。
  • inode 缓存:inodes 是文件系统用来描述所存储对象的一个数据结构体。VFS 层有一个通用版本的 inode,Linux 维护这个缓存因为检查权限以及读取其它元数据的时候,对这些结构体的读取都会非常的频繁。
  • 目录缓存:dcache 缓存包括目录元素名到 VFS inode 之间的映射信息,这可以提路径名查找的速度。
写回

Linux 支持以写回模式(Write-Back)处理文件系统写操作。该模式先在内存中缓存要修改的页,在一段时间后由内核的工作线程将修改写入磁盘,这样可以避免应用程序阻塞于较慢的磁盘 I/O。

IO 类型
缓冲 I/O 与非缓冲 I/O

根据是否利用标准库缓存,可以把文件 I/O 分为缓冲 I/O 和非缓冲 I/O

  • 缓冲 I/O:利用标准库缓存来加速文件的访问,标准库内部通过系统调用访问文件。缓冲 I/O 可以细分为​​全缓存​​​和​​行缓存​
  • 全缓存:当填满标准I/O缓存后才执行I/O操作。磁盘上的文件通常是全缓存的
  • 行缓存:当输入输出遇到新行符或缓存被填满时,才由标准 I/O 库执行实际 I/O 操作。stdin、stdout 通常是行缓存的
  • 非缓冲 I/O:直接通过系统调用来访问文件,不经过标准库缓存。相当于直接调用 read 和 write,stderr 通常是无缓存的,因为它必须尽快输出

代码示例:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

// 缓冲 I/O 示例
int bufferIO() {
const char *filename = "/tmp/buffer_io.txt";
const char *data = "这是写入测试文件的内容\n";
FILE *fp = fopen(filename, "w+");
fputs(data, fp);

// 将文件指针移动到文件到开始位置, 否则下面到读取动作将什么都读不到
fseek(fp, 0, SEEK_SET);

char buf[1024]={"\0"};
fgets(buf, strlen(data), (FILE*)fp);
printf("%s\n", buf);
fclose(fp);
}

// 非缓冲 I/O 示例(直接使用系统调用操作文件I/O)
int unbufferedIO() {
const char *filename = "/tmp/unbuffered_io.txt";
int fd = open(filename, O_RDWR | O_CREAT, 0644);
if (fd == -1) {
perror("无效的文件描述符, 打开文件错误");
return 1;
}

const char *data = "这是写入测试文件的内容\n";
int wlen = write(fd, data, strlen(data));
printf("写入文件长度: %d\n", wlen);

// 将文件指针移动到文件到开始位置, 否则下面到读取动作将什么都读不到
lseek(fd, 0, SEEK_SET);

char buf[1024]={"\0"};
int rlen = read(fd, buf, strlen(data));
printf("读取文件长度: %d\n", rlen);
printf("%s\n", buf);
close(fd);
return 0;
}


int main() {
printf("\033[31m非缓冲 I/O\n\033[0m");
unbufferedIO();
printf("\033[31m缓冲 I/O\n\033[0m");
bufferIO();
}
直接 I/O 与非直接 I/O

根据是否利用操作系统页缓存,可以把文件 I/O 分为直接 I/O 和非直接 I/O

  • 直接 I/O:跳过操作系统的页缓存,直接跟文件系统交互访问文件
  • 非直接 I/O:文件读写时,先经过系统的页缓存,然后再由内核或者额外的系统调用,真正写入磁盘

要实现直接 I/O,在系统调用中指定 O_DIRECT 标志即可

阻塞 I/O 与非阻塞 I/O

根据应用出现是否阻塞自身运行,可以把文件 I/O 分为阻塞 I/O 和非阻塞 I/O

  • 阻塞 I/O:指应用程序执行 I/O 操作后,如果没有获得响应,就会阻塞当前线程,不能执行其它任务
  • 非阻塞 I/O:指应用程序执行 I/O 操作后,不会阻塞当前线程,可以继续执行其它任务,然后通过轮询或者事件通知的方式获取调用结果

比如,访问管道或者网络套接字时,设置 O_NONBLOCK 标志就表示使用非阻塞方式访问,否则就是阻塞访问

同步 I/O 与异步 I/O

根据是否等待响应结果,可以吧文件 I/O 分为同步 I/O 和异步 I/O

  • 同步 I/O:指应用程序执行 I/O 操作后,一直等待整个 I/O 完成后才能获得 I/O 响应
  • 异步 I/O:指影响程序执行 I/O 操作后,不用等待完成和完成后的响应,而是继续执行其它任务。等到这次 I/O 完成之后,通过事件通知的方式告诉应用程序

比如,在操作文件时,如果设置了 O_SYNC 或者 O_DSYNC 标志就代表使用同步 I/O。如果设置了 O_DSYNC 标志就需要等待文件数据写入磁盘后才返回,而 O_SYNC 则在 O_DSYNC 的基础上要求文件元数据也要写入磁盘后才返回。如果设置了 O_ASYNC 标志就表示使用异步 I/O,这样内核就会通过 SIGIO 或者 SIGPOLL 来通知进程文件是否可以读写

传统工具

工具

介绍

df

显示文件系统用量信息

mount

挂载文件系统到系统上

strace

跟踪系统中的系统调用(不建议在生产环境上使用,这个命令可能会导致应用程序的性能下降 90%)

perf

文件系统相关的跟踪点

fatrace

建议使用 opensnoop bpf 工具代替,相对来说对资源的消耗更少,功能更多

bpf 工具

工具

来源

目标

描述

cachestat

BCC

文件系统缓存

展示文件系统缓存信息

cachetop

BCC

文件系统缓存

以类似于 top 的方式展示文件系统缓存信息

opensnoop

BCC

系统调用

跟踪文件打开信息

statsnoop

BCC

系统调用

跟踪 stat(2) 调用信息以及各种变体

syncsnoop

BCC

系统调用

跟踪 sync(2) 调用信息以及各种变体

filelife

BCC

VFS

跟踪短时文件,按秒记录它们的生命周期

vfsstat

BCC

VFS

统计常见的 VFS 操作

vfscount

BCC

VFS

统计所有的 VFS 操作

fileslower

BCC

VFS

展示较慢的文件读/写操作

filetop

BCC

VFS

按 IOPS 和字节书排序展示文件

writeback

bpftrace

页缓存

展示写回事件和对应的延迟信息

dcstat

BCC

Dcache

目录缓存命中率统计信息

dcsnoop

BCC

Dcache

跟踪目录缓存的查找操作

mountsnoop

BCC

VFS

跟踪系统中的挂载和卸载操作

xfsslower

BCC

XFS

统计过慢的 XFS 操作

xfsdist

BCC

XFS

以直方图统计常见的 XFS 操作延迟

ext4slower

BCC

EXT4

统计过慢的 EXT4 操作

ext4dist

BCC

EXT4

以直方图统计常见的 EXT4 操作延迟

nfsslower

BCC

NFS

统计过慢的 NFS 操作

nfsdist

BCC

NFS

以直方图统计常见的 NFS 操作延迟

btrfsslower

BCC

BTRFS

统计过慢的 BTRFS 操作

btrfsdist

BCC

BTRFS

以直方图统计常见的 BTRFS 操作延迟

cachestat

# 输出文件系统缓存信息
# 每秒输出一次
$ cachestat-bpfcc 1
HITS MISSES DIRTIES HITRATIO BUFFERS_MB CACHED_MB
0 0 0 0.00% 12 1223
192 0 0 100.00% 12 879
291 0 54404 100.00% 12 435
591 16 59466 97.36% 12 668
248 0 68608 100.00% 12 936
246 0 69120 100.00% 12 1206
305 8 4352 97.44% 12 1223
911 0 16 100.00% 12 1223
0 0 0 0.00% 12 1223
0 0 0 0.00% 12 1223

cachetop

# 以 top 方式按进程实时输出文件系统缓存信息
# 每秒刷新一次
$ cachetop-bpfcc 1
17:57:19 Buffers MB: 12 / Cached MB: 1180 / Sort: HITS / Order: descending
PID UID CMD HITS MISSES DIRTIES READ_HIT% WRITE_HIT%
8429 root dd 6529 6154 3072 27.3% 24.3%
8279 root kworker/u8:3 90 0 0 100.0% 0.0%
8327 root cachetop-bpfcc 4 0 0 100.0% 0.0%

opensnoop

# 跟踪文件打开信息
$ opensnoop-bpfcc
PID COMM FD ERR PATH
19029 ls 3 0 /etc/ld.so.cache
19034 cat 3 0 net.bt
19035 cat 3 0 /etc/ld.so.cache

statsnoop

# 跟踪 stat(2) 调用信息以及各种变体
$ statsnoop-bpfcc
PID COMM FD ERR PATH
19039 ls -1 2 /sys/fs/selinux
19039 ls -1 2 /selinux

syncsnoop

# 跟踪 sync(2) 调用信息以及各种变体
$ syncsnoop-bpfcc
TIME(s) CALL
772809.738731000 sync()

filelife

# 跟踪短时文件,按秒记录它们的生命周期
$ filetop-bpfcc
15:31:33 loadavg: 0.00 0.01 0.00 2/184 19059

TID COMM READS WRITES R_Kb W_Kb T FILE
19059 clear 2 0 60 0 R xterm-256color
19053 filetop-bpfcc 2 0 15 0 R loadavg
19059 clear 5 0 2 0 R libc.so.6
19059 clear 1 0 0 0 R libtinfo.so.6.3

vfsstat

# 统计常见的 VFS 操作
$ vfsstat-bpfcc
TIME READ/s WRITE/s FSYNC/s OPEN/s CREATE/s
15:32:23: 1267 4 0 2 0
15:32:24: 6 5 0 2 0
15:32:25: 2 5 0 0 0
15:32:26: 1 4 0 0 0
15:32:27: 1 5 0 0 0

vfscount

# 统计所有的 VFS 操作
$ vfscount-bpfcc
ADDR FUNC COUNT
ffffffffadb93e21 b'vfs_statx' 2
ffffffffadb93431 b'vfs_getattr_nosec' 2
ffffffffadb8b3f1 b'vfs_write' 19
ffffffffadb89171 b'vfs_open' 50
ffffffffadb8b0d1 b'vfs_read' 88

fileslower

# 展示较慢的文件读/写操作
# 展示读写实际超过 1ms(默认为 1ms) 的文件
$ fileslower-bpfcc 1

TIME(s) COMM TID D BYTES LAT(ms) FILENAME
0.142 java 7122 R 4096 25.53 file.out.index

filetop

# 按 IOPS 和字节书排序展示文件
$ filetop-bpfcc
15:42:08 loadavg: 0.00 0.00 0.00 1/182 19085

TID COMM READS WRITES R_Kb W_Kb T FILE
19084 clear 2 0 60 0 R xterm-256color
19081 filetop-bpfcc 2 0 15 0 R loadavg
19084 clear 5 0 2 0 R libc.so.6
19084 clear 1 0 0 0 R libtinfo.so.6.3

writeback

# 展示写回事件和对应的延迟信息
$ writeback.bt
TIME DEVICE PAGES REASON ms
15:43:03 252:0 37647 periodic 0.016
15:43:05 252:0 37650 periodic 0.000
15:43:08 252:0 37650 periodic 0.004
15:43:08 252:0 37650 periodic 0.004
15:43:10 252:0 37650 periodic 0.006

dcstat

# 展示目录缓存的统计信息
$ dcstat-bpfcc
TIME REFS/s SLOW/s MISS/s HIT%
15:50:53: 17 0 0 100.00
15:50:54: 4 0 0 100.00
15:50:55: 6 0 0 100.00
15:50:56: 151 0 0 100.00
15:50:57: 0 0 0 -%
15:50:58: 0 0 0 -%
15:50:59: 0 0 0 -%
15:51:00: 0 0 0 -%
15:51:01: 0 0 0 -%

dcsnoop

# 跟踪目录缓存的查找操作,展示每次查找的详细信息
$ dcsnoop-bpfcc
TIME(s) PID COMM T FILE
24.914044 19151 apt M libapt-private.so.0.0
24.914345 19151 apt M libapt-private.so.0.0.0
24.930057 19152 apt M fd

mountsnoop

# 跟踪挂载和卸载文件系统事件
$ mountsnoop-bpfcc
COMM PID TID MNT_NS CALL
containerd-shim 19832 19840 1970560 umount("", 0x0) = -EINVAL
containerd 589 668 1970560 umount("/run/containerd/....../rootfs", 0x0) = -EINVAL
systemd 1170 1170 1970560 umount("/run/user/0/credentials/run-docker-netns-0f490194376e.mount", MNT_DETACH|UMOUNT_NOFOLLOW) = -ENOENT

xfsslower

# 跟踪常见的 xfs 文件系统操作,对超过阈值的慢操作打印事件的详细信息
$ xfsslower-bpfcc
TIME COMM PID T BYTES OFF_KB LAT(ms) FILENAME
02:04:07 java 5565 R 63559 360237 17.16 shuffle_2_63762_0.data

xfsdist

# 每 10 秒输出 1 次, 常见的 xfs 操作延迟
$ xfsdist-bpfcc 10 1
operation = open
usecs : count distribution
0 -> 1 : 18 |****************************************|
2 -> 3 : 2 |**** |
4 -> 7 : 1 |** |

operation = read
usecs : count distribution
0 -> 1 : 10 |****************************************|
2 -> 3 : 3 |************ |
4 -> 7 : 0 | |
8 -> 15 : 0 | |
16 -> 31 : 1 |**** |

ext4slower

# 和 xfsslower 类似

ext4dist

# 和 xfsdist 类似

nfsslower

# 和 xfsslower 类似

nfsdist

# 和 xfsdist 类似

btrfsslower

# 和 xfsslower 类似

btrfsdist

# 和 xfsdist 类似

磁盘 I/O

I/O 调度器

I/O 在块 I/O 层会进入一个队列,由调度器进行 I/O 调度。传统调度器仅存在于 Linux 5.0 及其以下版本。新版中的调度器采用的是多队列调度器。

传统调度器包括以下几种:

  • Noop:无调度(空操作)
  • Deadline:以截止时间为主要目标进行调度,对实时系统比较有用
  • CFQ:完全公平队列调度器,该调度器和 CPU 调度器类似,将 I/O 时间分片分配给不同的进程

传统调度器存在的问题是,它们使用一个全局共享请求队列,该队列由一个单独的锁来保护,这个全局锁在高 I/O 的情况下会成为性能的瓶颈

多队列驱动程序(blk-mq)针对每个 CPU 使用不同的请求队列,并针对多个设备使用多个分发队列。这样,由于请求可以同步提交,也可以直接在产生 I/O 的 CPU 上直接处理,这样就比传统调度器的性能好上很多,还能降低 I/O 延迟。这对支持基于闪存的存储设备,或其它支持超百万 IOPS 的设备来说是必不可少的

现在可用的多队列调度器包括以下几种:

  • None:无队列
  • BFQ:基于预算的公平队列调度器,与 CFQ 类似,但在考虑 I/O 时间的同时还要考虑带宽因素
  • mq-deadline:多队列版本的 Deadline 调度器
  • Kyber:根据设备性能自动调节读写队列长度的调度器,以便保障目标读写延迟

Linux 5.0 以后删除了传统调度器和传统 I/O 软件栈的代码,现在所有的调度器都默认支持多队列了

传统工具

工具

介绍

iostat

按磁盘分别输出 I/O 统计信息,可提供 IOPS、吞吐量、I/O 请求时长,以及使用率等信息。一般来说此命令是分析磁盘 I/O 时所执行的第一个命令

iotop

top 的 I/O 版本

lsof

列出当前系统中打开的文件

perf

block 相关的跟踪点

btrace

跟踪块 I/O 事件

iostat

$ iostat -dxz 1
Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB/s wrqm/s %wrqm w_await wareq-sz d/s dkB/s drqm/s %drqm d_await dareq-sz f/s f_await aqu-sz %util
loop0 0.00 0.03 0.00 0.00 0.24 32.11 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
loop1 0.00 0.00 0.00 0.00 0.55 8.13 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
loop2 0.00 0.00 0.00 0.00 1.06 17.63 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
vda 0.04 2.34 0.01 25.88 1.18 57.02 0.18 5.88 0.08 32.57 4.32 33.54 0.00 0.00 0.00 0.00 0.00 0.00 0.02 1.85 0.00 0.02

字段

描述

r/s

每秒完成的读请求(合并之后)

rkB/s

每秒从磁盘设备中读取的千字节

rrqm/s

每秒进入队列和被合并的读请求

%rrqm

合并读请求的百分比

r_await

读请求的平均 I/O 请求时长(也就是设备的相应时间),包括在驱动程序队列中等待的时间,以及设备实际响应的时长(单位为 ms)

rareq-sz

发布到设备的读取请求的平均大小(以千字节为单位)

w/s

每秒完成的写请求(合并之后)

wkB/s

每秒写入向磁盘设备中的千字节

wrqm/s

每秒进入队列和被合并的写请求

%wrqm

合并写请求的百分比

w_await

写请求的平均 I/O 请求时长(也就是设备的相应时间),包括在驱动程序队列中等待的时间,以及设备实际响应的时长(单位为 ms)

wareq-sz

发布到设备的写入请求的平均大小(以千字节为单位)

d/s

每秒丢弃的请求数(合并之后)

dkB/s

每秒丢弃设备的千字节

drqm/s

每秒进入队列和被合并的丢弃请求

%drqm

合并丢弃请求的百分比

d_await

丢弃请求的平均 I/O 请求时长(也就是设备的相应时间),包括在驱动程序队列中等待的时间,以及设备实际响应的时长(单位为 ms)

dareq-sz

发给设备的丢弃请求的平均大小(以千字节为单位)

f/s

每秒进入队列和被合并的刷新请求数

f_await

刷新请求的平均 I/O 请求时长(也就是设备的相应时间),包括在驱动程序队列中等待的时间,以及设备实际响应的时长(单位为 ms)

aqu-sz

发给设备请求的平均队列长度,在老版本中此字段为 avgqu-sz

%util

设备忙于处理 I/O 请求的时间百分比

iotop

$ iotop
Total DISK READ: 0.00 B/s | Total DISK WRITE: 0.00 B/s
Current DISK READ: 0.00 B/s | Current DISK WRITE: 0.00 B/s
TID PRIO USER DISK READ DISK WRITE SWAPIN IO> COMMAND
1 be/4 root 0.00 B/s 0.00 B/s ?unavailable? init
2 be/4 root 0.00 B/s 0.00 B/s ?unavailable? [kthreadd]
3 be/4 root 0.00 B/s 0.00 B/s ?unavailable? [rcu_gp]
4 be/4 root 0.00 B/s 0.00 B/s ?unavailable? [rcu_par_gp]
5 be/4 root 0.00 B/s 0.00 B/s ?unavailable? [netns]

lsof
# 列出系统中已打开的所有文件
$ lsof
lsof 20396 root mem REG 252,1 52080 3660 /usr/lib/x86_64-linux-gnu/libkrb5support.so.0.1
lsof 20396 root mem REG 252,1 18504 3946 /usr/lib/x86_64-linux-gnu/libcom_err.so.2.1
lsof 20396 root mem REG 252,1 182928 3654 /usr/lib/x86_64-linux-gnu/libk5crypto.so.3.1
lsof 20396 root mem REG 252,1 828000 3658 /usr/lib/x86_64-linux-gnu/libkrb5.so.3.3
lsof 20396 root mem REG 252,1 613064 4032 /usr/lib/x86_64-linux-gnu/libpcre2-8.so.0.10.4
lsof 20396 root mem REG 252,1 338712 3652 /usr/lib/x86_64-linux-gnu/libgssapi_krb5.so.2.2
lsof 20396 root mem REG 252,1 2216304 4243 /usr/lib/x86_64-linux-gnu/libc.so.6
......
btrace

$ btrace /dev/vda1
252,1 1 1 0.000000000 22255 Q FWS [journal-offline]
252,1 1 2 0.000009689 22255 G FWS [journal-offline]
252,1 1 3 0.000141862 193 D FN [kworker/1:1H]
252,1 1 4 0.001453666 0 C FN 0 [0]

位置

介绍

1

设备类型和分区号,即 major:minor,可以从 /proc/device 查找 252 对应的设备类型

2

CPU ID

3

序列号

4

操作时长吗,单位秒

5

进程 ID

6

操作识别符,Q 表示 Queued(入队)、G 表示 Get Request(分配 Resquest 结构体)、P 表示 Plug、M 表示 Merge(合并)、D 表示 Issued(请求已发送)、C 表示 Complete(请求已完成)

7

RWBS 描述符,R 读取、W 写入、M 元数据、S 同步、A 预读取、F 强制清空缓冲区或者强制从设备直接读取、D 丢弃、E 擦除、N 无。这些字符可以组合使用

bpf 工具

工具

来源

目标

描述

biolatency

BCC

块 I/O

以直方图形式统计块 I/O 延迟

biosnoop

BCC

块 I/O

按 PID 和延迟阈值跟踪块 I/O

biotop

BCC

块 I/O

top 工具的磁盘版:按进程统计块 I/O

bitesize

BCC

块 I/O

按进程统计磁盘 I/O 请求尺寸直方图

biostacks

bpftrace

块 I/O

跟踪磁盘 I/O 相关的初始化软件栈信息

biolatency

# 以直方图形式统计块 I/O 设备的延迟信息。这里的设备延迟指的是从向设备发出请求到请求完成的全部时间,包括在操作系统内部排队的时间
$ biolatency-bpfcc 10 1
usecs : count distribution
0 -> 1 : 0 | |
2 -> 3 : 0 | |
4 -> 7 : 0 | |
8 -> 15 : 0 | |
16 -> 31 : 0 | |
32 -> 63 : 0 | |
64 -> 127 : 0 | |
128 -> 255 : 29 |********************* |
256 -> 511 : 4 |** |
512 -> 1023 : 54 |****************************************|
1024 -> 2047 : 20 |************** |
2048 -> 4095 : 7 |***** |
4096 -> 8191 : 13 |********* |

biosnoop

# 针对每个磁盘 I/O 打印一行信息
$ biosnoop-bpfcc
0.000000 jbd2/vda1-8 300 vda W 2451552 0 1.00
0.001618 ? 0 R 0 0 1.31
0.002238 jbd2/vda1-8 300 vda W 2451568 0 0.47
0.003476 ? 0 R 0 0 1.10

biotop

# top 的磁盘版,按进程统计块 I/O
$ biotop-bpfcc
14:51:22 loadavg: 0.08 0.03 0.01 1/161 23650

PID COMM D MAJ MIN DISK I/O Kbytes AVGms
0 R 252 0 vda 2 0.0 0.97
300 jbd2/vda1-8 W 252 0 vda 2 0.0 0.71

bitesize

# 按进程统计磁盘 I/O 请求尺寸直方图
$ bitesize-bpfcc
Process Name = kworker/0:1H
Kbytes : count distribution
0 -> 1 : 2 |****************************************|
2 -> 3 : 0 | |
4 -> 7 : 1 |******************** |

Process Name = jbd2/vda1-8
Kbytes : count distribution
0 -> 1 : 0 | |
2 -> 3 : 0 | |
4 -> 7 : 0 | |
8 -> 15 : 1 |****************************************|

biostacks

# 跟踪完整的 I/O 延迟(从进入操作系统队列到设备完成 I/O 请求),同时显示初始化该 I/O 请求的调用栈信息
$ biostacks.bt
@usecs[
blk_account_io_start+1
__submit_bio+494
submit_bio_noacct+192
submit_bio+74
submit_bh_wbc+397
submit_bh+19
journal_submit_commit_record.part.0.constprop.0+466
jbd2_journal_commit_transaction+4831
kjournald2+169
kthread+295
ret_from_fork+31
]:
[2M, 4M) 1 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|

@usecs[
blk_account_io_start+1
__submit_bio+494
submit_bio_noacct+192
submit_bio+74
submit_bh_wbc+397
submit_bh+19
jbd2_journal_commit_transaction+2146
kjournald2+169
kthread+295
ret_from_fork+31
]:
[64K, 128K) 1 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|