Спостереження за записом на жорсткий диск у просторі ядра (з драйверами / модулями)


13

Вибачте заздалегідь, якщо ця публікація трохи щільна / безладна, але мені важко сформулювати її краще ... В основному, я хотів би вивчити, що відбувається під час запису на жорсткий диск, і я хотів би знати:

  • Чи правильно моє розуміння нижче - а якщо ні, то де я помиляюся?
  • Чи є кращий інструмент для "захоплення" даних журналу про всі аспекти, що відбуваються на ПК під час запису на диск?

Більш детально - по-перше, ОС, яку я використовую, це:

$ 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_piixsd) драйвером.

$ 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_piixsd) запускається вперше.

Перший мій плутанина полягає в тому, що я інакше не можу спостерігати за драйверами 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_piixsd) вбудовані як вбудовані драйвери в (монолітне) ядро, на відміну від їх побудови як (завантажуваних) .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,997 нас | }
 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_readable ();
 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відстеження, 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 / драйвери / ata / ata_piix.c , підтвердьте, що, наприклад piix_init_sata_map, дійсно є функцією в ata_piix. Що, мабуть, повинно мені сказати, що: модулі, складені в ядрі (так вони стають частиною монолітного ядра), "втрачають" інформацію про те, з якого модуля вони походять; однак завантажувані модулі, побудовані як окремі .koоб'єкти ядра, зберігають цю інформацію (наприклад, [psmouse]показану вище у квадратних дужках). Таким чином, також ftraceможна було показати лише інформацію про "вихідний модуль" лише для тих функцій, що надходять із завантажуваних модулів ядра. Це правильно?

Вище взяте до уваги, це розуміння того процесу, який я маю зараз:

  • Під час завантаження ata_piixдрайвер встановлює відображення пам'яті DMA (?) Між /dev/sdaжорстким диском та жорстким диском
    • через це всі майбутні звернення до /dev/sdavia ata_piixбудуть прозорими до ядра (тобто не відстежуються) - оскільки всі ядра бачать, просто читаються / записуються в місця пам'яті (не обов'язково викликає певні функції відстежуваних ядер), які не повідомляється function_graphтрасером
  • Під час завантаження sdдрайвер ще більше "розбере" розділи /dev/sda, зробить їх доступними та, можливо, обробляє відображення пам'яті між розділами <-> дискового пристрою
    • знову ж таки, це повинно зробити операції доступу через sdпрозорі до ядра
  • Оскільки обидва ata_piixі sdкомпілюються в ядрі, навіть якщо деякі їх функції в кінцевому підсумку захоплені ftrace, ми не можемо отримати інформацію, з якого модуля ці функції беруться (крім "ручної" кореляції з вихідними файлами)
  • Пізніше mountвстановлюється зв'язок / прив'язка між розділом та відповідним драйвером файлової системи (у цьому випадку ext4)
    • з цього моменту всі звернення до змонтованої файлової системи оброблялися б ext4функціями, які простежуються ядром; але, як ext4складено в ядрі, трекер не може дати нам інформацію про вихідний модуль
  • Таким чином, спостережуване "загальне" записування, викликане через ext4функції, в кінцевому рахунку отримає доступ до місць пам'яті, відображення яких встановлюється ata_piix- але крім цього, ata_piixне перешкоджає безпосередньо передачі даних (цим, ймовірно, керує DMA (поза процесором) (і), і таким чином прозорі до цього).

Чи правильно це розуміння?

Деякі пов'язані з цим питання

  • Під час налаштування я можу визначити драйвер пристрою PCI ( ata_piix) та драйвер файлової системи ( ext4); але чи є драйвери символів або блоків, які використовуються десь на шляху виконання "запису", і якщо так, то які вони?
  • Хто з цих драйверів оброблятиме кешування (настільки непотрібні дискові операції пропускаються чи оптимізуються?)
  • Я знаю з того, що /dev/shmце файлова система в оперативній пам'яті; mount | grep shmдля мене повідомляє: none on /dev/shm type tmpfs (rw,nosuid,nodev). Чи означає це, що - на відміну від /dev/sda- у shmфайловій системі просто не вистачає (DMA) відображення від "власних" адрес до адрес шини до пристрою; і, отже, всі доступ через tmpfsдрайвер файлової системи закінчується фактичною оперативною пам'яттю?

4
Привіт sdaau. Це хороші запитання, але тривалість цієї публікації надмірна, і тут є кілька питань. Похвально, що ви намагаєтесь зрозуміти речі, а не просто ставити запитання довідкової служби, саме це ми здебільшого отримуємо тут. Кожне з цих питань заслуговувало б на довгу відповідь. Я рекомендую принаймні розбити свою публікацію на чітко визначені фрагменти і поставити кожну частину окремим запитанням, таким чином створивши ряд запитань.
Faheem Mitha

Потім ви можете опублікувати ці питання разом або послідовно. Я думаю, що це нормально, якщо ви посилаєтесь на інше запитання (або питання) в межах питання.
Faheem Mitha

1
Якщо ви хочете поради щодо прибирання свого питання, я пропоную вам зайти в чат і поговорити з людьми там. Ми вже говорили про це тут. :-)
Faheem Mitha

Велике спасибі за коментар, @FaheemMitha - у мене також були подібні сумніви, але я не був дуже впевнений, як вирішити питання - і не знав до цих пір, я можу використовувати для цього чат (і я не захоплювався використання мета для запитання про таку пораду); обов'язково спробую чат спробувати наступного разу. На щастя, цього разу це вийшло з дуже прийнятною відповіддю ... Ура!
sdaau

@sdaau, ви зрозуміли, як слідкувати за доступом до диска?
ransh

Відповіді:


10

Ви занадто багато задали одне питання - ну, технічно це не так, як я думаю, "чи це розуміння правильне", можна швидко відповісти: ні. Але це не корисна відповідь.

По-перше, ви маєте рацію ata_piixі, sd_modмабуть , збираєтеся до свого ядра. Це вибір, який ви робите для налаштування ядра - ви можете його опустити, включити або включити як модуль. (Те саме з ext4).

По-друге, ви припустили, що записи набагато простіші, ніж вони є насправді. Основний конфігурація того, як працює запис, полягає в тому, що код файлової системи містить дані, що записуються в пам'ять, як частину буфера кешу, і позначає їх як необхідні для запису ("брудні"). (Якщо тільки в оперативній пам'яті вже не так багато, і в такому випадку вона насправді змушена робити запис ...)

Пізніше різні речі (наприклад, bdflushнитка ядра) фактично видаляють брудні сторінки на диск. Це коли ви побачите дзвінки через sd, scsi, libata, ata_piix, io планувальники, PCI тощо. Хоча DMA в цьому виписуванні є дуже ймовірним, це дані, що передаються, а може бути і команда. Але записи з диска, принаймні в SATA, обробляються надсиланням команд, які в основному означають "записувати сектор X з даними Y". Але це точно не обробляється картою пам'яті всього диска (врахуйте: ви можете використовувати диски набагато більше 4GiB на 32-бітних машинах).

Кешування обробляється підсистемою управління пам’яттю (не драйвером) спільно з файловою системою, блоковим шаром тощо.

tmpfsє особливим, це в основному цілком кеш-пам'ять. Його просто спеціальний кеш, який ніколи не відкидається і не записується назад (хоча його можна замінити). Ви можете знайти код у mm/shmem.cкількох місцях (спробуйте ack-grep --cc CONFIG_TMPFSїх знайти).

В основному, запис на диск проходить через хорошу частину підсистем ядра; мережа - це єдине головне, про що я можу придумати, і це не стосується вашого прикладу. Для правильного пояснення цього потрібні великі зусилля; Я рекомендую шукати його.


Привіт @derobert - багато, велике спасибі за вашу відповідь; вона містить точну інформацію, яку мені не вистачало! Спочатку я почав шукати просту ілюстрацію простору користувача проти ядра, але незабаром зрозумів, що запис на жорсткому диску - це не те, що я розумію повністю, і це не банально - дякую за підтвердження, що це насправді книга, довжина зусиль! Ура!
sdaau

Невелика примітка: деякі пояснення у цій відповіді (наприклад, промивання брудних сторінок) можна помітити, якщо в sudo bash...сценарії в ОП: пам'ять ftrace збільшена ( echo 8192 > $KDBGPATH/buffer_size_kb); і sync ;додається після ./wtest ;виклику. Тоді я можу бачити flush-8, kworker(під kthreaddв ps axf), а syncсам, як процеси в ftraceвиконанні функції , як , наприклад , ata_bmdma_setup()(який є частиною libata, яка ata_piixспирається на), або get_nr_dirty_inodes().
sdaau

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-genericкаже CONFIG_ATA_PIIX=y, що, мабуть, має означати це ядро, ata_piixбудується "in-kernel", а не як модуль. Ура!
sdaau
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.