如果这篇文章有点密集/混乱,请提前致歉,但是我很难将其表述得更好...基本上,我想研究硬盘写操作时发生的情况,并且我想知道:
- 我的理解是否正确-如果不正确,我在哪里出问题?
- 是否有更好的工具来“捕获”磁盘写入期间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
我不得不从较旧的syslog中退出,因为我暂停了很多时间,但以上内容似乎是引导时syslog中正确的代码段,ata_piix
(和sd
)驱动程序首次启动。
我的第一点困惑是,否则我无法观察ata_piix
或sd
驱动程序:
$ 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
(和sd
)是在(整体)内核中构建为内置驱动程序,而不是作为(可加载的).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 | | security_file_permission(){ 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 | | Mutex_lock(){ 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 | | file_update_time(){ ... 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_visible(); 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我们| Mutex_Unlock(); 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()
内核空间导致的用户空间sys_write()
;在中sys_write()
,我观察到与安全相关的功能(例如apparmor_file_permission()
),“通用”写函数(例如generic_file_aio_write()
),ext4
与文件系统相关的功能(例如ext4_journal_start_sb()
)-但是我没有观察到与ata_piix
(或sd
)驱动程序有关的任何东西?
“ 跟踪和概要分析-Yocto项目 ”页面建议使用blk
in跟踪器ftrace
来获取有关块设备操作的更多信息,但是在此示例中,它对我没有任何帮助。另外,Linux Filesystem Drivers-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,确认例如piix_init_sata_map
确实是中的功能ata_piix
。哪个应该告诉我:在内核中编译的模块(因此它们成为整体内核的一部分)“丢失”了有关它们来自哪个模块的信息;但是,作为独立.ko
内核对象构建的可加载模块保留了该信息(例如,[psmouse]
上面方括号中所示)。因此,ftrace
仅对于来自可加载内核模块的功能,也只能显示“源模块”信息。它是否正确?
综合以上考虑,这是我目前对流程的理解:
- 在启动时,
ata_piix
驱动程序在/dev/sda
与硬盘 之间建立DMA(?)内存映射。- 因此,将来对
/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
文件系统根本没有从“自己的” adrresses迈向设备总线地址(DMA)的映射; 因此通过tmpfs
文件系统驱动程序进行的所有访问最终都位于实际的RAM中?