观察内核空间中的硬盘写入(带有驱动程序/模块)


13

如果这篇文章有点密集/混乱,请提前致歉,但是我很难将其表述得更好...基本上,我想研究硬盘写操作时发生的情况,并且我想知道:

  • 我的理解是否正确-如果不正确,我在哪里出问题?
  • 是否有更好的工具来“捕获”磁盘写入期间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_piixsd驱动程序:

$ 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内核模块构建?

对-现在,我正在尝试观察使用ftraceLinux内置函数跟踪器运行程序时发生的情况。

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项目 ”页面建议使用blkin跟踪器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/sdavia的所有访问对ata_piix内核都是透明的(即不可跟踪)-因为所有内核都将看到,只是对内存位置的读/写(不一定是对特定可跟踪内核函数的调用),因此function_graph跟踪器未报告
  • 在引导时,sd驱动程序还将“解析”的分区/dev/sda,使其可用,并可能处理分区<->磁盘设备之间的内存映射。
    • 再次,这应该使访问操作sd对内核透明
  • 由于ata_piixsd都是在内核中编译的,因此即使它们的某些功能最终被捕获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中?

4
嗨,sdaau。这是一个很好的问题,但是这篇文章的篇幅太长了,里面有几个问题。值得称赞的是,您尝试了解事物,而不是仅仅询问服务台问题,而这正是我们在这里遇到的主要问题。这些问题中的每一个都值得一个很长的答案。我建议至少将您的帖子分解为明确定义的部分,并将每个部分放入一个单独的问题中,从而创建一系列问题。
Faheem Mitha 2014年

然后,您可以一起或顺序发布这些问题。我认为,如果您在一个问题中引用另一个(或多个)问题,那就可以了。
Faheem Mitha 2014年

1
如果您希望获得有关清理问题的提示,建议您跳入聊天室并与该处的人交谈。我们已经在这里谈论它了。:-)
Faheem Mitha 2014年

非常感谢@FaheemMitha的评论-我也有类似的疑问,但是我不确定如何解决问题-直到现在我还不知道可以使用聊天功能(而且我也不热衷于使用meta询问此类建议);下次一定可以尝试聊天。值得庆幸的是,这次,它得出了一个可以接受的答案……干杯!
sdaau 2014年

@sdaau,您知道如何监视磁盘访问吗?
2013年

Answers:


10

您在一个问题中问的太多了-嗯,从技术上讲不是这样,因为我想“这种理解正确”可以很快回答:不。但这不是一个有用的答案。

首先,您是正确的,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找到它们)。

基本上,写入磁盘要经过内核子系统的很大一部分。网络是我想到的唯一没有涉及到的主要问题。正确地解释它需要花费大量的精力。我建议寻找一个。


@derobert,您好-非常感谢您的回答;它包含我所缺少的确切信息!我最初是从简单地了解用户空间和内核空间开始的,但是我很快意识到硬盘写并不是我完全了解的,也不是那么简单-感谢您确认它实际上是一本书,长努力!干杯!
sdaau 2014年

小提示:如果在sudo bash...OP 中的脚本中,ftrace内存增加了(echo 8192 > $KDBGPATH/buffer_size_kb),则可以观察到该答案的某些解释(例如,刷新脏页);并sync ;./wtest ;通话后添加。然后我可以看到flush-8kworker(下kthreaddps axf),和sync本身,进程ftrace调用如例如功能ata_bmdma_setup()(这是其一部分libata,它ata_piix建立在),或get_nr_dirty_inodes()
sdaau 2014年

4

所以我的第一个问题是-为什么我不能仅在启动时日志中观察ata_piix模块?是因为ata_piix(和sd)是作为(单片)内核中的内置驱动程序构建的,而不是作为(可加载的).ko内核模块构建的?

您不必猜测您的配置是什么。我的机器上有

$ uname -a
Linux orwell 3.2.0-4-amd64 #1 SMP Debian 3.2.51-1 x86_64 GNU/Linux

该内核的配置文件位于/boot/config-3.2.0-4-amd64

你问了一下ata_piix。搜索上面的.config文件,我们看到了 CONFIG_ATA_PIIX=m。我们可以通过做确认

dlocate ata_piix.ko   

或者

dpkg -S ata_piix.ko

linux-image-3.2.0-4-amd64: /lib/modules/3.2.0-4-amd64/kernel/drivers/ata/ata_piix.ko

因此,至少在我的内核中,它是一个模块。


非常感谢@FaheemMitha-虽然我之前听说过(并使用过)配置文件,但由于某些原因,在本示例中我已经完全忘记了它。很好发现!他说,:)在我的系统上,这可能应该表示在此内核上,是在内核中构建的,而不是作为模块构建的。干杯! grep ATA_PIIX /boot/config-2.6.38-16-genericCONFIG_ATA_PIIX=yata_piix
sdaau 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.