Linux 塊設備驅動 (4)

1. 背景

讓我們梳理一下本系列文章整體脈絡。

  • 首先,Linux Block Driver - 1 介紹了一個只有 200 行源碼的 Sampleblk 塊驅動的實現。
  • 然後,在 Linux Block Driver - 2 中,我們在 Sampleblk 驅動創建了 Ext4 文件系統,並做了一個 fio 順序寫測試。測試中我們利用 Linux 的各種跟蹤工具,對這個 fio 測試做了一個性能個性化分析。
  • 而在 Linux Block Driver - 3 中,我們利用 Linux 跟蹤工具和 Flamegraph 來對文件系統層面上的文件 IO 內部實現,有了一個概括性的瞭解。

本文將繼續之前的實驗,圍繞這個簡單的 fio 測試,探究 Linux 塊設備驅動的運作機制。除非特別指明,本文中所有 Linux 內核源碼引用都基於 4.6.0。其它內核版本可能會有較大差異。

2. 準備

閱讀本文前,可能需要如下準備工作,

本文將在與前文完全相同 fio 測試負載下,在塊設備層面對該測試做進一步的分析。

3. Block IO Pattern 分析

3.1 寫請求大小

Linux 4.6 內核的塊設備層的預定義了 19 個通用塊層的 tracepoints。這些 tracepoints,可以通過如下 perf 命令來列出來,

$ sudo perf list  block:*

List of pre-defined events (to be used in -e):

  block:block_bio_backmerge                          [Tracepoint event]
  block:block_bio_bounce                             [Tracepoint event]
  block:block_bio_complete                           [Tracepoint event]
  block:block_bio_frontmerge                         [Tracepoint event]
  block:block_bio_queue                              [Tracepoint event]
  block:block_bio_remap                              [Tracepoint event]
  block:block_dirty_buffer                           [Tracepoint event]
  block:block_getrq                                  [Tracepoint event]
  block:block_plug                                   [Tracepoint event]
  block:block_rq_abort                               [Tracepoint event]
  block:block_rq_complete                            [Tracepoint event]
  block:block_rq_insert                              [Tracepoint event]
  block:block_rq_issue                               [Tracepoint event]
  block:block_rq_remap                               [Tracepoint event]
  block:block_rq_requeue                             [Tracepoint event]
  block:block_sleeprq                                [Tracepoint event]
  block:block_split                                  [Tracepoint event]
  block:block_touch_buffer                           [Tracepoint event]
  block:block_unplug                                 [Tracepoint event]

我們可以利用 block:block_rq_insert 來跟蹤獲取 fio 測試時,該進程寫往塊設備 /dev/sampleblk1 IO 請求的起始扇區地址和扇區數量,

$ sudo perf record -a -g --call-graph dwarf -e block:block_rq_insert sleep 10

因爲我們指定了記錄調用棧的信息,所以,perf script 可以獲取 fio 從用戶態到內核 block:block_rq_insert tracepoint 的完整調用棧的信息。並且,給出了主次設備號,相關操作,及起始扇區和扇區數,

$ sudo perf script | head -n 20
fio 73790 [000] 1011438.379090: block:block_rq_insert: 253,1 W 0 () 3510 + 255 [fio]
              5111e1 __elv_add_request (/lib/modules/4.6.0-rc3+/build/vmlinux)
              518e64 blk_flush_plug_list (/lib/modules/4.6.0-rc3+/build/vmlinux)
              51910b blk_queue_bio (/lib/modules/4.6.0-rc3+/build/vmlinux)
              517453 generic_make_request (/lib/modules/4.6.0-rc3+/build/vmlinux)
              517597 submit_bio (/lib/modules/4.6.0-rc3+/build/vmlinux)
               107de ext4_io_submit ([ext4])
                c6bc ext4_writepages ([ext4])
              39cd3e do_writepages (/lib/modules/4.6.0-rc3+/build/vmlinux)
              390b66 __filemap_fdatawrite_range (/lib/modules/4.6.0-rc3+/build/vmlinux)
              3d5d96 sys_fadvise64 (/lib/modules/4.6.0-rc3+/build/vmlinux)
              203c12 do_syscall_64 (/lib/modules/4.6.0-rc3+/build/vmlinux)
              8bb721 return_from_SYSCALL_64 (/lib/modules/4.6.0-rc3+/build/vmlinux)
        7fd1e61d7d4d posix_fadvise64 (/usr/lib64/libc-2.17.so)
              4303b3 file_invalidate_cache (/usr/local/bin/fio)
              41a79b td_io_open_file (/usr/local/bin/fio)
              43f40d get_io_u (/usr/local/bin/fio)
              45ad89 thread_main (/usr/local/bin/fio)
              45cffc run_threads (/usr/local/bin/fio)
[...snipped...]

使用簡單的處理,我們即可發現這個測試在通用塊層的 IO Pattern,

$ sudo perf script | grep lock_rq_insert | head -n 20
fio 71005 [000] 977641.575503: block:block_rq_insert: 253,1 W 0 () 3510 + 255 [fio]
fio 71005 [000] 977641.575566: block:block_rq_insert: 253,1 W 0 () 3765 + 255 [fio]
fio 71005 [000] 977641.575568: block:block_rq_insert: 253,1 W 0 () 4020 + 255 [fio]
fio 71005 [000] 977641.575568: block:block_rq_insert: 253,1 W 0 () 4275 + 255 [fio]
fio 71005 [000] 977641.575569: block:block_rq_insert: 253,1 W 0 () 4530 + 255 [fio]
fio 71005 [000] 977641.575570: block:block_rq_insert: 253,1 W 0 () 4785 + 255 [fio]
fio 71005 [000] 977641.575570: block:block_rq_insert: 253,1 W 0 () 5040 + 255 [fio]
fio 71005 [000] 977641.575571: block:block_rq_insert: 253,1 W 0 () 5295 + 255 [fio]
fio 71005 [000] 977641.575572: block:block_rq_insert: 253,1 W 0 () 5550 + 8 [fio]
fio 71005 [000] 977641.575572: block:block_rq_insert: 253,1 W 0 () 5558 + 255 [fio]
fio 71005 [000] 977641.575573: block:block_rq_insert: 253,1 W 0 () 5813 + 255 [fio]
fio 71005 [000] 977641.575574: block:block_rq_insert: 253,1 W 0 () 6068 + 255 [fio]
fio 71005 [000] 977641.575574: block:block_rq_insert: 253,1 W 0 () 6323 + 255 [fio]
fio 71005 [000] 977641.575575: block:block_rq_insert: 253,1 W 0 () 6578 + 255 [fio]
fio 71005 [000] 977641.575576: block:block_rq_insert: 253,1 W 0 () 6833 + 255 [fio]
fio 71005 [000] 977641.575577: block:block_rq_insert: 253,1 W 0 () 7088 + 255 [fio]
fio 71005 [000] 977641.575779: block:block_rq_insert: 253,1 W 0 () 7343 + 255 [fio]
fio 71005 [000] 977641.575781: block:block_rq_insert: 253,1 W 0 () 7598 + 8 [fio]
fio 71005 [000] 977641.577234: block:block_rq_insert: 253,1 W 0 () 3510 + 255 [fio]
fio 71005 [000] 977641.577236: block:block_rq_insert: 253,1 W 0 () 3765 + 255 [fio]

bitesize-nd.stp 是 Systemtap 寫的統計塊 IO 的字節數大小分佈的工具。
基於該工具,簡單修改後,即可按照 Block IO 請求扇區數來統計,請參考 bio_sectors.stp 的源碼。

$ sudo ./bio_sectors.stp
Tracing block I/O... Hit Ctrl-C to end.
^C
I/O size (sectors):

[...snipped...]

process name: fio
value |-------------------------------------------------- count
    0 |                                                       0
    1 |                                                      17
    2 |                                                      26
    4 |                                                      63
    8 |@@@                                                 2807
   16 |                                                     398
   32 |                                                     661
   64 |@@                                                  1625
  128 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@  38490
  256 |                                                       0
  512 |                                                       0

可以看到,128 ~ 255 扇區的分佈佔了絕大多數,本例中,實際上這個區間的 IO 請求都是 255 個扇區,與之前 perf 查看的結果一致。

3.2 寫延遲分析

iosnoop 不但可以瞭解塊設備上的 IO 請求大小,更有從 IO 請求發起到完成的延遲時間的信息。下面我們在運行 fio 測試時,使用 iosnoop 來獲得的相關信息。

首先,我們需要得到 fio 測試所用的塊設備的主次設備號,

$ mount | grep sample
/dev/sampleblk1 on /mnt type ext4 (rw,relatime,seclabel,data=ordered)
[yango@localhost ~]$ ls -l /dev/sampleblk1
brw-rw----. 1 root disk 253, 1 Aug 12 22:10 /dev/sampleblk1

然後,運行 iosnoop 來獲取所有在 /dev/sampleblk1 上的 IO 請求,

$ sudo iosnoop -d 253,1 -s -t
Tracing block I/O. Ctrl-C to end
STARTs          ENDs            COMM         PID    TYPE DEV      BLOCK        BYTES     LATms
11165.028153    11165.028194    fio          11425  W    253,1    4534         130560     0.04
11165.028196    11165.028210    fio          11425  W    253,1    4789         130560     0.01
11165.028211    11165.028224    fio          11425  W    253,1    5044         130560     0.01
11165.028227    11165.028241    fio          11425  W    253,1    5299         130560     0.01
11165.028244    11165.028258    fio          11425  W    253,1    5554         130560     0.01
11165.028261    11165.028274    fio          11425  W    253,1    5809         130560     0.01
11165.028276    11165.028290    fio          11425  W    253,1    6064         130560     0.01
11165.028295    11165.028309    fio          11425  W    253,1    6319         130560     0.01
11165.028311    11165.028312    fio          11425  W    253,1    6574         4096       0.00
11165.029896    11165.029937    fio          11425  W    253,1    2486         130560     0.04
11165.029939    11165.029951    fio          11425  W    253,1    2741         130560     0.01
11165.029952    11165.029965    fio          11425  W    253,1    2996         130560     0.01
11165.029968    11165.029981    fio          11425  W    253,1    3251         130560     0.01
11165.029982    11165.029995    fio          11425  W    253,1    3506         130560     0.01
11165.029998    11165.030012    fio          11425  W    253,1    3761         130560     0.01
11165.030012    11165.030026    fio          11425  W    253,1    4016         130560     0.01
11165.030029    11165.030042    fio          11425  W    253,1    4271         130560     0.01
11165.030044    11165.030045    fio          11425  W    253,1    4526         4096       0.00
11165.030095    11165.030135    fio          11425  W    253,1    4534         130560     0.04

可以看到,該輸出不但包含了 IO 請求的大小,而且還有 IO 延遲時間。如,130560 字節正好就是 255 扇區,4096 字節,恰好就是 8 個扇區。因此,IO 大小和之前其它工具得到時類似的。
而在發出 255 扇區的 IO 請求延遲是有變化的,大致是 0.01 毫秒或者 0.04 毫秒,大概是百納秒級別的延遲。

iosnoop 在短時間內會產生大量的輸出,每個 IO 請求的 IO 延遲時間都可能有很大差異,如何能對 fio 測試的延遲有沒有更好的數據呈現方式呢?

Heatmap 就是一個這樣的工具,其具體使用方法如下,

$ sudo ./iosnoop -d 253,1 -s -t >  iosnoop.log
$ grep '^[0-9]'  iosnoop.log | awk '{ print $1, $9 }' | sed  's/\.//g' | sed 's/$/0/g' > trace.txt
$ ./trace2heatmap.pl --unitstime=us --unitslatency=us --maxlat=200 --grid trace.txt> heatmap.svg

於是,基於 iosnoop 工具得到的數據,我們生成了下面的熱點圖 (Heatmap),

右擊該圖片,在新窗口打開,在圖片範圍內移動鼠標,即可看到不同的延遲時間所佔 IO 請求數據採樣的百分比。
例如,顏色最紅的那一行代表採樣最多的 IO 延遲,在橫軸時間是 40 秒時,延遲範圍大概是 8 ~ 12 微妙,具有這樣延遲的 IO 請求站了全部採樣的 76%。

3.3 文件和塊 IO 延遲的比較

Linux Block Driver - 2 中,我們介紹過 fio 的輸出中自帶 IO 延遲的計算和數值分佈的統計。
例如,下面的輸出就是這個 fio 測試的一個結果,

job1: (groupid=0, jobs=1): err= 0: pid=22977: Thu Jul 21 22:10:28 2016
  write: io=1134.8GB, bw=1038.2MB/s, iops=265983, runt=1118309msec
    clat (usec): min=0, max=66777, avg= 1.63, stdev=21.57
     lat (usec): min=0, max=66777, avg= 1.68, stdev=21.89
    clat percentiles (usec):
     |  1.00th=[    0],  5.00th=[    1], 10.00th=[    1], 20.00th=[    1],
     | 30.00th=[    1], 40.00th=[    1], 50.00th=[    2], 60.00th=[    2],
     | 70.00th=[    2], 80.00th=[    2], 90.00th=[    2], 95.00th=[    3],
     | 99.00th=[    4], 99.50th=[    7], 99.90th=[   18], 99.95th=[   25],
     | 99.99th=[  111]
    lat (usec) : 2=49.79%, 4=49.08%, 10=0.71%, 20=0.34%, 50=0.06%
    lat (usec) : 100=0.01%, 250=0.01%, 500=0.01%, 750=0.01%, 1000=0.01%
    lat (msec) : 2=0.01%, 4=0.01%, 10=0.01%, 20=0.01%, 50=0.01%
    lat (msec) : 100=0.01%

如果仔細分析上面的結果,可以發現,其中 clat 和 lat 的分佈要明顯好於 iosnoop 的結果。這是爲什麼呢?

其實這很好解釋:因爲 fio 的 clat 和 lat 是文件同步 IO 的延遲,該 IO 模式是 buffer IO,即文件的讀寫是基於文件的 page cache 的,是內存的讀寫。因此 clat 和 lat 的延遲要小很多。

而本章中,iosnoop 的 IO 延遲是塊 IO 的延遲。文件系統 buffer IO 的讀寫並不會直接觸發塊設備的讀寫,因此,iosnoop 的 IO 請求和 fio 的 IO 請求根本不是同一個 IO 請求。

如果還記得 Linux Block Driver - 3 裏的分析,我們知道,
這裏的 iosnoop 的 IO 請求,都是 fio 通過調用 fadvise64,使用 POSIX_FADV_DONTNEED 把 /mnt/test 在 page cache 裏的數據 flush 到磁盤引發的。

3.4 塊 IO 吞吐量和 IOPS

運行 fio 測試時,我們可以利用 iostat(1) 命令來獲取指定塊設備在測試中的吞吐量 (throughput) 和 IOPS。

$ iostat /dev/sampleblk1  -xmdz 1
Linux 4.6.0-rc3+ (localhost.localdomain)    08/25/2016  _x86_64_    (2 CPU)

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sampleblk1        0.00     0.37    0.00   59.13     0.00     6.50   225.31     0.01    0.15    0.00    0.15   0.02   0.12

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sampleblk1        0.00   168.00    0.00 8501.00     0.00   932.89   224.74     0.77    0.10    0.00    0.10   0.02  14.40

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sampleblk1        0.00    63.00    0.00 8352.00     0.00   909.64   223.05     0.89    0.11    0.00    0.11   0.02  16.30

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sampleblk1        0.00    59.00    0.00 8305.00     0.00   908.45   224.02     0.98    0.13    0.00    0.13   0.02  17.50

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sampleblk1        0.00    36.00    0.00 8536.00     0.00   936.51   224.69     1.06    0.13    0.00    0.13   0.02  19.00

[...snipped...]

其中,rMB/s 和 wMB/s 就是讀寫的吞吐量,而 r/s 和 w/s 就是 IOPS。
本例中的輸出可以得到如下結論,

  • sampleblk1 塊設備的寫吞吐量 (wMB/s) 是 908 ~ 932 MB/s。
  • IOPS (w/s) 大概在 8300 ~ 8500。
  • 平均的 IO 請求大小 (avgrq-sz) 爲 220 左右
  • 平均寫等待時間 (w_await) 是 0.10 ~ 0.15 毫秒

需要說明的是,此處的的吞吐量和 IOPS 與如下所示的 fio 返回的輸出裏的有很大不同,

write: io=1134.8GB, bw=1038.2MB/s, iops=265983, runt=1118309msec

本例的測試中,fio 返回的是應用程序的 IO 吞吐量和 IOPS,而 iostat 返回的是底層一個塊設備層面的吞吐量和 IOPS。

4. 小結

本文通過使用 Linux 下的各種追蹤工具 Systemtap,Perf,iosnoop (基於 ftrace 和 tracepoint),及 iostat 來分析 fio 測試時,底層塊設備的運行情況。
我們掌握了本文中塊設備 IO 在 fio 測試的主要特徵,塊 IO size,IO 延遲分佈。這是性能分析裏 resource analysis 方法的一部分。

關於 Linux 動態追蹤工具的更多信息,請參考延伸閱讀章節裏的鏈接。

5. 延伸閱讀

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章