觀察核心空間中的硬碟寫入(使用驅動程序/模組)
如果這篇文章有點密集/混亂,請提前道歉,但我很難更好地制定它……基本上,我想研究硬碟寫入時會發生什麼,我想知道:
- 我在下面的理解是否正確 - 如果不是,我哪裡錯了?
- 在磁碟寫入期間,是否有更好的工具來“擷取”關於 PC 上發生的所有方面的日誌數據?
更詳細地說 - 首先,我使用的作業系統是:
$ uname -a Linux mypc 2.6.38-16-generic #67-Ubuntu SMP Thu Sep 6 18:00:43 UTC 2012 i686 i686 i386 GNU/Linux
因此,我有以下簡單的(例如,跳過通常的操作失敗檢查)使用者空間 C 程序
wtest.c
:#include <stdio.h> #include <fcntl.h> // O_CREAT, O_WRONLY, S_IRUSR int main(void) { char filename[] = "/tmp/wtest.txt"; char buffer[] = "abcd"; int fd; mode_t perms = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH; fd = open(filename, O_RDWR|O_CREAT, perms); write(fd,buffer,4); close(fd); return 0; }
我用
gcc -g -O0 -o wtest wtest.c
. 現在,由於我正在嘗試寫入/tmp
,我注意到它是根目錄下的目錄/
- 所以我檢查mount
:$ mount /dev/sda5 on / type ext4 (rw,errors=remount-ro,commit=0) ... /dev/sda6 on /media/disk1 type ext4 (rw,uhelper=hal,commit=0) /dev/sda7 on /media/disk2 type ext3 (rw,nosuid,nodev,uhelper=udisks,commit=0,commit=0,commit=0,commit=0,commit=0,commit=0) ...
因此,我的根文件系統
/
是/dev/sda
設備的一個分區(我也將其他分區用作“獨立”磁碟/掛載)。要查找此設備的驅動程序,我使用hwinfo
:$ hwinfo --disk ... 19: IDE 00.0: 10600 Disk ... SysFS ID: /class/block/sda SysFS BusID: 0:0:0:0 ... Hardware Class: disk Model: "FUJITSU MHY225RB" ... Driver: "ata_piix", "sd" Driver Modules: "ata_piix" Device File: /dev/sda ... Device Number: block 8:0-8:15 ...
因此,
/dev/sda
硬碟顯然是由ata_piix
(和sd
)驅動程序處理的。$ grep 'ata_piix\| sd' <(gunzip </var/log/syslog.2.gz) Jan 20 09:28:31 mypc kernel: [ 1.963846] ata_piix 0000:00:1f.2: version 2.13 Jan 20 09:28:31 mypc kernel: [ 1.963901] ata_piix 0000:00:1f.2: PCI INT B -> GSI 19 (level, low) -> IRQ 19 Jan 20 09:28:31 mypc kernel: [ 1.963912] ata_piix 0000:00:1f.2: MAP [ P0 P2 P1 P3 ] Jan 20 09:28:31 mypc kernel: [ 2.116038] ata_piix 0000:00:1f.2: setting latency timer to 64 Jan 20 09:28:31 mypc kernel: [ 2.116817] scsi0 : ata_piix Jan 20 09:28:31 mypc kernel: [ 2.117068] scsi1 : ata_piix Jan 20 09:28:31 mypc kernel: [ 2.529065] sd 0:0:0:0: [sda] 488397168 512-byte logical blocks: (250 GB/232 GiB) Jan 20 09:28:31 mypc kernel: [ 2.529104] sd 0:0:0:0: Attached scsi generic sg0 type 0 Jan 20 09:28:31 mypc kernel: [ 2.529309] sd 0:0:0:0: [sda] Write Protect is off Jan 20 09:28:31 mypc kernel: [ 2.529319] sd 0:0:0:0: [sda] Mode Sense: 00 3a 00 00 Jan 20 09:28:31 mypc kernel: [ 2.529423] sd 0:0:0:0: [sda] Write cache: enabled, read cache: enabled, doesn't support DPO or FUA Jan 20 09:28:31 mypc kernel: [ 2.674783] sda: sda1 sda2 < sda5 sda6 sda7 sda8 sda9 sda10 > Jan 20 09:28:31 mypc kernel: [ 2.676075] sd 0:0:0:0: [sda] Attached SCSI disk Jan 20 09:28:31 mypc kernel: [ 4.145312] sd 2:0:0:0: Attached scsi generic sg1 type 0 Jan 20 09:28:31 mypc kernel: [ 4.150596] sd 2:0:0:0: [sdb] Attached SCSI removable disk
我不得不從舊的系統日誌中提取,因為我暫停了很多,但上面似乎是啟動時系統日誌中的正確片段,其中
ata_piix
(andsd
) 驅動程序第一次啟動。我的第一個困惑是我無法觀察到
ata_piix
orsd
驅動程序:$ lsmod | grep 'ata_piix\| sd' $ $ modinfo sd ERROR: modinfo: could not find module sd $ modinfo ata_piix ERROR: modinfo: could not find module ata_piix
所以我的第一個問題是 - 為什麼我不能在
ata_piix
這裡觀察模組,只能在啟動時日誌中觀察?是因為ata_piix
(andsd
) 是作為(單片)核心中的內置驅動程序建構的,而不是作為(可載入的).ko
核心模組建構的嗎?正確 - 所以現在,我正在嘗試觀察使用
ftrace
Linux 內置函式跟踪器執行程序時會發生什麼。sudo bash -c ' KDBGPATH="/sys/kernel/debug/tracing" echo function_graph > $KDBGPATH/current_tracer echo funcgraph-abstime > $KDBGPATH/trace_options echo funcgraph-proc > $KDBGPATH/trace_options echo 0 > $KDBGPATH/tracing_on echo > $KDBGPATH/trace echo 1 > $KDBGPATH/tracing_on ; ./wtest ; echo 0 > $KDBGPATH/tracing_on cat $KDBGPATH/trace > wtest.ftrace '
…這是
ftrace
有關以下內容的日誌片段write
:4604.352690 | 0) wtest-31632 | | sys_write() { 4604.352690 | 0) wtest-31632 | 0.750 我們 | fget_light(); 4604.352692 | 0) wtest-31632 | | vfs_write() { 4604.352693 | 0) wtest-31632 | | rw_verify_area() { 4604.352693 | 0) wtest-31632 | | 安全文件權限(){ 4604.352694 | 0) wtest-31632 | | apparmor_file_permission() { 4604.352695 | 0) wtest-31632 | 0.811 我們 | common_file_perm(); 4604.352696 | 0) wtest-31632 | 2.198 我們 | } 4604.352697 | 0) wtest-31632 | 3.573 我們 | } 4604.352697 | 0) wtest-31632 | 4.979 我們 | } 4604.352698 | 0) wtest-31632 | | do_sync_write() { 4604.352699 | 0) wtest-31632 | | ext4_file_write() { 4604.352700 | 0) wtest-31632 | | generic_file_aio_write() { 4604.352701 | 0) wtest-31632 | | 互斥鎖(){ 4604.352701 | 0) wtest-31632 | 0.666 我們 | _cond_resched(); 4604.352703 | 0) wtest-31632 | 1.994 我們 | } 4604.352704 | 0) wtest-31632 | | __generic_file_aio_write() { ... 4604.352728 | 0) wtest-31632 | | 文件更新時間(){ ... 4604.352732 | 0) wtest-31632 | 0.756 我們 | mnt_want_write_file(); 4604.352734 | 0) wtest-31632 | | __mark_inode_dirty() { ... 4604.352750 | 0) wtest-31632 | | ext4_mark_inode_dirty() { 4604.352750 | 0) wtest-31632 | 0.679 我們 | _cond_resched(); 4604.352752 | 0) wtest-31632 | | ext4_reserve_inode_write() { ... 4604.352777 | 0) wtest-31632 | | __ext4_journal_get_write_access() { ... 4604.352795 | 0) wtest-31632 | | ext4_mark_iloc_dirty() { ... 4604.352806 | 0) wtest-31632 | | __ext4_journal_stop() { ... 4604.352821 | 0) wtest-31632 | 0.684 我們 | mnt_drop_write(); 4604.352822 | 0) wtest-31632 | + 93.541 我們 | } 4604.352823 | 0) wtest-31632 | | generic_file_buffered_write() { 4604.352824 | 0) wtest-31632 | 0.654 我們 | iov_iter_advance(); 4604.352825 | 0) wtest-31632 | | generic_perform_write() { 4604.352826 | 0) wtest-31632 | 0.709 我們 | iov_iter_fault_in_可讀(); 4604.352828 | 0) wtest-31632 | | ext4_da_write_begin() { 4604.352829 | 0) wtest-31632 | | ext4_journal_start_sb() { ... 4604.352847 | 0) wtest-31632 | 1.453 我們 | __block_write_begin(); 4604.352849 | 0) wtest-31632 | + 21.128 我們 | } 4604.352849 | 0) wtest-31632 | | iov_iter_copy_from_user_atomic() { 4604.352850 | 0) wtest-31632 | | __kmap_atomic() { ... 4604.352863 | 0) wtest-31632 | 0.672 我們 | mark_page_accessed(); 4604.352864 | 0) wtest-31632 | | ext4_da_write_end() { 4604.352865 | 0) wtest-31632 | | generic_write_end() { 4604.352866 | 0) wtest-31632 | | block_write_end() { ... 4604.352893 | 0) wtest-31632 | | __ext4_journal_stop() { ... 4604.352909 | 0) wtest-31632 | 0.655 我們 | 互斥鎖(); 4604.352911 | 0) wtest-31632 | 0.727 我們 | generic_write_sync(); 4604.352912 | 0) wtest-31632 | !212.259 我們 | } 4604.352913 | 0) wtest-31632 | !213.845 我們 | } 4604.352914 | 0) wtest-31632 | !215.286 我們 | } 4604.352914 | 0) wtest-31632 | 0.685 我們 | __fsnotify_parent(); 4604.352916 | 0) wtest-31632 | | fsnotify() { 4604.352916 | 0) wtest-31632 | 0.907 我們 | __srcu_read_lock(); 4604.352918 | 0) wtest-31632 | 0.685 我們 | __srcu_read_unlock(); 4604.352920 | 0) wtest-31632 | 3.958 我們 | } 4604.352920 | 0) wtest-31632 | !228.409 我們 | } 4604.352921 | 0) wtest-31632 | !231.334 我們 | }
這是我的第二個困惑點——正如預期的那樣,我可以觀察到
write()
由 kernel-space 產生的使用者空間sys_write()
;在內部sys_write()
,我觀察到與安全相關的功能(例如apparmor_file_permission()
)、“通用”寫入功能(例如generic_file_aio_write()
)、ext4
文件系統相關的功能(例如ext4_journal_start_sb()
)-但我沒有觀察到與ata_piix
(或sd
)驅動程序相關的任何內容?!Tracing and Profiling - Yocto Project頁面建議使用
blk
跟踪器ftrace
來獲取有關塊設備操作的更多資訊,但在這個範例中它對我沒有任何報告。此外,Linux 文件系統驅動程序 - Annon Inglorion (tutorfs)建議文件系統(可以?)也(可以)實現為核心模組/驅動程序,我猜這也是如此ext4
。最後,我可以發誓,我之前已經在跟踪器顯示的函式旁邊的方括號中觀察到驅動程序名稱
function_graph
,但我想我把事情搞混了——它可能看起來像堆棧(回)跟踪,但不是在函式圖中。此外,我可以檢查/proc/kallsyms
:$ grep 'piix\| sd\|psmouse' /proc/kallsyms ... 00000000 d sd_ctl_dir 00000000 d sd_ctl_root 00000000 d sdev_class 00000000 d sdev_attr_queue_depth_rw 00000000 d sdev_attr_queue_ramp_up_period 00000000 d sdev_attr_queue_type_rw 00000000 d sd_disk_class ... 00000000 t piix_init_sata_map 00000000 t piix_init_sidpr 00000000 t piix_init_one 00000000 t pci_fixup_piix4_acpi ... 00000000 t psmouse_show_int_attr [psmouse] 00000000 t psmouse_protocol_by_type [psmouse] 00000000 r psmouse_protocols [psmouse] 00000000 t psmouse_get_maxproto [psmouse] ...
…並檢查源Linux/drivers/ata/ata_piix.c,確認 eg
piix_init_sata_map
確實是ata_piix
. 這可能會告訴我:在核心中編譯的模組(因此它們成為整體核心的一部分)“失去”了有關它們來自哪個模組的資訊;但是,作為單獨的核心對象建構的可載入模組.ko
保留了該資訊(例如[psmouse]
,上面顯示在方括號中)。因此,也ftrace
只能顯示“原始模組”資訊,僅針對來自可載入核心模組的那些功能。它是否正確?綜合以上考慮,這是我目前對流程的理解:
在啟動時,驅動程序在和硬碟
ata_piix
之間建立一個 DMA (?) 記憶體映射/dev/sda
- 因此,未來對
/dev/sda
via的所有訪問ata_piix
對核心都是透明的(即不可追踪)——因為所有核心都會看到,只是對記憶體位置的讀/寫(不一定呼叫特定的可追踪核心函式),這function_graph
示踪劑不報告在啟動時,
sd
驅動程序將進一步“解析” 的分區/dev/sda
,使它們可用,並可能處理分區 <-> 磁碟設備之間的記憶體映射
- 同樣,這應該使訪問操作
sd
對核心透明由於
ata_piix
和sd
都是在核心中編譯的,即使它們的某些函式最終被 擷取ftrace
,我們也無法獲得這些函式來自哪個模組的資訊(除了與源文件的“手動”關聯)稍後,在分區和相應的文件系統驅動程序(在這種情況下)
mount
之間建立關係/綁定ext4
- 從此時起,所有對掛載文件系統的訪問都將由
ext4
函式處理——核心可跟踪這些函式;但是由於ext4
在核心中編譯,跟踪器無法為我們提供原始模組資訊因此,通過
ext4
函式呼叫的觀察到的“通用”寫入最終將訪問記憶體位置,其映射是由建立的ata_piix
- 但除此之外,ata_piix
不會直接干擾數據傳輸(它可能由 DMA 處理(在處理器之外) (s),因此對它是透明的)。這種理解正確嗎?
一些相關的子問題:
- 在上面的設置中,我可以辨識 PCI 設備驅動程序 (
ata_piix
) 和文件系統驅動程序 (ext4
);但是在“寫入”執行路徑的某處是否使用了字元或塊驅動程序,如果有,它們是什麼?- 這些驅動程序中的哪一個會處理記憶體(因此跳過或優化了不必要的磁碟操作?)
- 我從以前就知道這
/dev/shm
是 RAM 中的文件系統;mount | grep shm
對我來說報告:none on /dev/shm type tmpfs (rw,nosuid,nodev)
。這是否意味著——與此相反/dev/sda
——shm
文件系統只是缺少從“它自己的”地址到匯流排地址到設備的(DMA)映射;因此通過tmpfs
文件系統驅動程序的所有訪問最終都在實際的 RAM 中?
你在一個問題上問的太多了——嗯,從技術上講不是,因為我猜“這種理解是否正確”可以很快得到回答:不。但這不是一個有用的答案。
首先,您是正確的
ata_piix
並且sd_mod
顯然已被編譯到您的核心中。這是您在配置核心時做出的選擇——您可以忽略它、包含它或將它作為一個模組包含在內。(與 ext4 相同)。其次,您假設寫入比實際簡單得多。寫入工作原理的基本概述是文件系統程式碼將要寫入的數據作為緩衝區高速記憶體的一部分放入記憶體中,並將其標記為需要寫入(“臟”)。(除非 RAM 中已經有太多的內容,在這種情況下,它實際上是被迫進行寫入的……)
後來,各種東西(例如
bdflush
核心執行緒)實際上將臟頁刷新到磁碟。這是您通過 sd、scsi、libata、ata_piix、io 調度程序、PCI 等看到呼叫的時候。雖然寫出很可能涉及 DMA,但它是要傳輸的數據,也可能是命令。但是磁碟寫入,至少在 SATA 中,是通過發送基本上意味著“用數據 Y 寫入扇區 X”的命令來處理的。但它絕對不是通過記憶體映射整個磁碟來處理的(考慮一下:您可以在 32 位機器上使用遠大於 4GiB 的磁碟)。記憶體由記憶體管理子系統(不是驅動程序)與文件系統、塊層等一起處理。
tmpfs
很特別,基本上完全是記憶體。它只是永遠不會丟棄或寫回的特殊記憶體(儘管可以換出)。您可以在mm/shmem.c
和其他幾個地方找到程式碼(嘗試ack-grep --cc CONFIG_TMPFS
找到它們)。基本上,寫入磁碟要經過核心子系統的很大一部分。網路是我能想到的唯一一個與您的範例無關的主要網路。正確解釋它需要一本書的努力;我建議找一個。