Obserwacja zapisu na dysku twardym w przestrzeni jądra (ze sterownikami / modułami)

13

Z góry przepraszam, jeśli ten post jest trochę gęsty / niechlujny, ale mam trudności z jego lepszym sformułowaniem ... Zasadniczo chciałbym przestudiować, co dzieje się podczas zapisu na dysku twardym i chciałbym wiedzieć:

  • Czy moje rozumienie poniżej jest prawidłowe - a jeśli nie, to gdzie się mylę?
  • Czy istnieje lepsze narzędzie do „przechwytywania” danych dziennika o wszystkich aspektach zachodzących na komputerze podczas zapisu na dysku?

Bardziej szczegółowo - po pierwsze, używam systemu operacyjnego:

$ 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

Mam więc następujący prosty (np. Zwykłe kontrole niepowodzenia operacji) program C w przestrzeni użytkownika 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;
}

Buduję to za pomocą gcc -g -O0 -o wtest wtest.c. Teraz, skoro próbuję pisać /tmp, zauważam, że jest to katalog pod rootem /- więc sprawdzam 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)
...

Tak więc mój główny system plików /to jedna partycja /dev/sdaurządzenia (i używam innych partycji jako „samodzielnych” dysków / montowań). Aby znaleźć sterownik dla tego urządzenia, używam 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
...

Tak więc /dev/sdadysk twardy jest najwyraźniej obsługiwany przez sterownik ata_piix(i 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

Muszę pobierać ze starszego sysloga, ponieważ dużo zawieszam, ale powyższe wydaje się być właściwym fragmentem sysloga z sysloga w czasie uruchamiania, w którym sterownik ata_piix(i sd) uruchamia się po raz pierwszy.

Moim pierwszym zamieszaniem jest to, że nie mogę inaczej obserwować sterowników ata_piixlub sdsterowników:

$ 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

Moje pierwsze pytanie brzmi: dlaczego nie mogę obserwować ata_piixmodułu tutaj, tylko w dziennikach rozruchu? Czy to dlatego, że ata_piix(i sd) są wbudowane jako wbudowane sterowniki w (monolitycznym) jądrze, a nie jako (ładowane) .komoduły jądra?

Tak - teraz próbuję obserwować, co dzieje się po uruchomieniu programu z ftracewbudowanym modułem śledzenia funkcji w systemie 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
'

... a oto fragment ftracedziennika dotyczący write:

4604,352690 | 0) wtest-31632 | | sys_write () {
 4604,352690 | 0) wtest-31632 | 0,750 us | 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 us | common_file_perm ();
 4604,352696 | 0) wtest-31632 | 2.198 us | }
 4604,352697 | 0) wtest-31632 | 3.573 us | }
 4604,352697 | 0) wtest-31632 | 4,979 us | }
 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 us | _cond_resched ();
 4604,352703 | 0) wtest-31632 | 1.994 us | }
 4604,352704 | 0) wtest-31632 | | __generic_file_aio_write () {
...
 4604,352728 | 0) wtest-31632 | | file_update_time () {
...
 4604,352732 | 0) wtest-31632 | 0,756 us | 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 us | _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 us | mnt_drop_write ();
 4604,352822 | 0) wtest-31632 | + 93.541 us | }
 4604,352823 | 0) wtest-31632 | | generic_file_buffered_write () {
 4604,352824 | 0) wtest-31632 | 0,654 us | iov_iter_advance ();
 4604,352825 | 0) wtest-31632 | | generic_perform_write () {
 4604,352826 | 0) wtest-31632 | 0,709 us | 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 us | __block_write_begin ();
 4604,352849 | 0) wtest-31632 | + 21.128 us | }
 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 us | 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 us | mutex_unlock ();
 4604,352911 | 0) wtest-31632 | 0,727 us | generic_write_sync ();
 4604,352912 | 0) wtest-31632 | ! 212.259 us | }
 4604,352913 | 0) wtest-31632 | ! 213,845 us | }
 4604,352914 | 0) wtest-31632 | ! 215.286 us | }
 4604,352914 | 0) wtest-31632 | 0,685 us | __fsnotify_parent ();
 4604,352916 | 0) wtest-31632 | | fsnotify () {
 4604,352916 | 0) wtest-31632 | 0,907 us | __srcu_read_lock ();
 4604,352918 | 0) wtest-31632 | 0,685 us | __srcu_read_unlock ();
 4604,352920 | 0) wtest-31632 | 3,958 us | }
 4604,352920 | 0) wtest-31632 | ! 228,409 us | }
 4604,352921 | 0) wtest-31632 | ! 231,334 us | }

To mój drugi punkt pomyłki - zgodnie z oczekiwaniami mogę obserwować przestrzeń użytkownika write()wynikającą z przestrzeni jądra sys_write(); a na terenie sys_write(), ja obserwować funkcje związane z bezpieczeństwem (np apparmor_file_permission()), „rodzajowe” funkcje zapisu (np generic_file_aio_write()) ext4filesystem funkcje towarzyszące (np ext4_journal_start_sb()) - ale ja nie wszystko związane obserwować ata_piix(lub sd) kierowców?

Strona Śledzenie i profilowanie - Projekt Yocto sugeruje użycie modułu blkśledzącego, ftraceaby uzyskać więcej informacji o działaniu urządzenia blokowego, ale w tym przykładzie nic mi nie zgłasza. Również sterowniki systemu plików Linux - Annon Inglorion (tutorfs) sugerują, że systemy plików są (mogą?) Również być (być) implementowane jako moduły / sterowniki jądra, i przypuszczam, że ext4tak też jest.

Na koniec mogłem przysiąc, że wcześniej obserwowałem nazwę sterownika w nawiasach kwadratowych obok funkcji pokazanej przez function_graphznacznik, ale wydaje mi się, że pomieszałem rzeczy - prawdopodobnie może to wyglądać tak w śladach stosu (z tyłu), ale nie na wykresie funkcji. Ponadto mogę sprawdzić /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]
...

... i sprawdzając za pomocą źródłowego Linux / drivers / ata / ata_piix.c , potwierdź, że np. piix_init_sata_maprzeczywiście jest funkcją w ata_piix. Co prawdopodobnie powinno mi powiedzieć, że: moduły skompilowane w jądrze (aby stały się częścią jądra monolitycznego) „tracą” informacje o tym, z którego modułu pochodzą; jednak moduły ładowalne, które są zbudowane jako osobne .koobiekty jądra, zachowują tę informację (np. [psmouse]pokazano powyżej w nawiasach kwadratowych). Dlatego też ftracemógł pokazywać tylko informacje o „module inicjującym”, tylko dla funkcji pochodzących z ładowalnych modułów jądra. Czy to jest poprawne?

Biorąc powyższe pod uwagę, rozumiem obecnie proces:

  • W czasie uruchamiania ata_piixsterownik ustanawia mapowanie pamięci DMA (?) Pomiędzy /dev/sdai dyskiem twardym
    • z tego powodu wszystkie przyszłe dostępy do /dev/sdavia ata_piixbędą przezroczyste dla jądra (to znaczy, że nie można go śledzić) - ponieważ wszystkie jądra by to zobaczyły, są to tylko odczyty / zapisy do miejsc w pamięci (niekoniecznie wywołania określonych funkcji śledzenia jądra), które nie są zgłaszane przez function_graphtracer
  • W czasie uruchamiania sdsterownik „parsuje” partycje /dev/sda, udostępnia je i ewentualnie obsługuje mapowania pamięci między partycjami <-> urządzenia dyskowego
    • znowu powinno to uczynić operacje dostępu sdprzejrzystymi dla jądra
  • Ponieważ oba ata_piixi sdsą kompilowane w jądra, nawet jeśli niektóre z ich funkcji nie kończy się rejestrowany przez ftracenie możemy uzyskać informacje, z których te funkcje modułu będzie pochodzić z (oprócz „manual” korelacja z plików źródłowych)
  • Później mountustanawia relację / powiązanie między partycją a odpowiednim sterownikiem systemu plików (w tym przypadku ext4)
    • od tego momentu wszystkie dostępy do zamontowanego systemu plików będą obsługiwane przez ext4funkcje - które są identyfikowalne przez jądro; ale jak ext4skompilowano w jądrze, moduł śledzący nie może przekazać nam informacji o module źródłowym
  • Tak więc zaobserwowane „ogólne” zapisy, wywoływane przez ext4funkcje, ostatecznie uzyskałyby dostęp do lokalizacji pamięci, których mapowanie jest ustanawiane przez ata_piix- ale poza tym, ata_piixnie zakłóca bezpośrednio transferu danych (prawdopodobnie jest obsługiwany przez DMA (poza procesorem) (s), a zatem przejrzyste dla niego).

Czy to zrozumienie jest prawidłowe?

Niektóre powiązane pytania:

  • W powyższej konfiguracji mogę zidentyfikować sterownik urządzenia PCI ( ata_piix) i sterownik systemu plików ( ext4); ale czy są jakieś sterowniki znaków lub bloków używane gdzieś na ścieżce wykonania „zapisu”, a jeśli tak, to jakie?
  • Który ze sterowników obsłużyłby buforowanie (więc niepotrzebne operacje na dysku są pomijane lub optymalizowane?)
  • Wiem wcześniej, że /dev/shmjest to system plików w pamięci RAM; mount | grep shmdla mnie raporty: none on /dev/shm type tmpfs (rw,nosuid,nodev). Czy to oznacza, że - w przeciwieństwie do /dev/sda- w shmsystemie plików po prostu brakuje (DMA) mapowanie z „własnymi” adrresses do adresów autobusowego w kierunku urządzenia; a zatem wszystkie dostępy za pośrednictwem tmpfssterownika systemu plików kończą się w rzeczywistej pamięci RAM?
sdaau
źródło
4
Cześć sdaau. To dobre pytania, ale długość tego postu jest zbyt długa i jest tam kilka pytań. Godne pochwały jest to, że próbujesz zrozumieć rzeczy, a nie tylko zadajesz pytania do działu pomocy technicznej, co najczęściej dostajemy tutaj. Każde z tych pytań samo w sobie zasługiwałoby na długą odpowiedź. Polecam przynajmniej rozbicie posta na jasno określone części i umieszczenie każdego z nich w osobnym pytaniu, tworząc w ten sposób serię pytań.
Faheem Mitha
Następnie możesz opublikować te pytania razem lub sekwencyjnie. Myślę, że jest OK, jeśli odwołujesz się do innego pytania (lub pytań) w obrębie pytania.
Faheem Mitha
1
Jeśli potrzebujesz wskazówek na temat czyszczenia swojego pytania, sugeruję, aby wskoczyć do pokoju rozmów i porozmawiać z tamtejszymi ludźmi. Rozmawialiśmy już o tym tutaj. :-)
Faheem Mitha
Bardzo dziękuję za komentarz, @FaheemMitha - miałem również podobne wątpliwości, ale tak naprawdę nie byłem pewien, jak pokroić pytania - i do tej pory nie wiedziałem, że mogę używać do tego czatu (i nie byłem zainteresowany używanie meta do pytania o tego rodzaju porady); na pewno spróbuje porozmawiać następnym razem. Na szczęście tym razem udało się uzyskać bardzo akceptowalną odpowiedź ... Pozdrawiam!
sdaau
@ sdaau, czy wymyśliłeś sposób monitorowania dostępu do dysku?
ransh

Odpowiedzi:

10

Za dużo zadałeś w jednym pytaniu - cóż, technicznie nie, jak sądzę „czy to zrozumienie jest prawidłowe” można szybko odpowiedzieć: nie. Ale to nie jest przydatna odpowiedź.

Po pierwsze, masz rację ata_piixi sd_modprawdopodobnie jesteś wkompilowany w jądro. Jest to wybór dokonywany podczas konfigurowania jądra - możesz go pominąć, dołączyć lub włączyć jako moduł. (To samo z ext4).

Po drugie, założyłeś, że zapisy są znacznie prostsze niż w rzeczywistości. Podstawowy zarys działania zapisu polega na tym, że kod systemu plików umieszcza dane do zapisania w pamięci jako część bufora pamięci podręcznej i oznacza je jako wymagające zapisania („brudne”). (Chyba że jest już zbyt dużo tego w pamięci RAM, w takim przypadku jest on faktycznie zmuszony do zapisu ...)

Później różne rzeczy (takie jak bdflushwątek jądra) faktycznie opróżniają brudne strony na dysk. To wtedy zobaczysz wywołania przez sd, scsi, libata, ata_piix, harmonogramy IO, PCI itp. Chociaż jest bardzo prawdopodobne, że DMA jest zaangażowany w ten zapis, to dane, które mają być przesłane, a może polecenie. Ale zapisy na dysku, przynajmniej w SATA, są obsługiwane przez wysyłanie poleceń, które w zasadzie oznaczają „zapis sektora X z danymi Y”. Ale zdecydowanie nie jest to obsługiwane przez mapowanie pamięci całego dysku (rozważ: możesz używać dysków znacznie większych niż 4GiB na komputerach 32-bitowych).

Buforowanie jest obsługiwane przez podsystem zarządzania pamięcią (nie sterownik), w połączeniu z systemem plików, warstwą bloków itp.

tmpfsjest wyjątkowy, to w zasadzie całkowicie pamięć podręczna. Jest to po prostu specjalna pamięć podręczna, której nigdy nie odrzuca się ani nie zapisuje (choć można ją wymienić). Możesz znaleźć kod w mm/shmem.ckilku innych miejscach (spróbuj ack-grep --cc CONFIG_TMPFSje znaleźć).

Zasadniczo zapis na dysk przebiega przez sporą część podsystemów jądra; sieciowanie jest jedyną ważną, o której myślę, że nie jest uwzględnione w twoim przykładzie. Właściwe wyjaśnienie tego wymaga długiego wysiłku; Polecam poszukać jednego.

derobert
źródło
Cześć @derobert - wielkie, wielkie dzięki za odpowiedź; zawiera dokładny rodzaj informacji, których mi brakowało! Zacząłem od szukania prostej ilustracji przestrzeni użytkownika i jądra, ale wkrótce zdałem sobie sprawę, że zapis na dysku twardym nie jest czymś, co w pełni rozumiem, i nie jest tak trywialny - dziękuję za potwierdzenie, że jest to książka wysiłek długości! Twoje zdrowie!
sdaau
Mała uwaga: niektóre wyjaśnienia w tej odpowiedzi (np. sudo bash...Opróżnianie brudnych stron) są obserwowalne, jeśli w skrypcie w OP: pamięć ftrace jest zwiększona ( echo 8192 > $KDBGPATH/buffer_size_kb); i sync ;jest dodawany po zakończeniu ./wtest ;połączenia. Wtedy widzę flush-8, kworker(zgodnie kthreaddz ps axf), a syncsam, jak w procesach ftracewywoływania funkcji, takich jak na przykład ata_bmdma_setup()(który jest częścią libata, która ata_piixopiera się na), lub get_nr_dirty_inodes().
sdaau
4

Moje pierwsze pytanie brzmi: dlaczego nie mogę obserwować modułu ata_piix tutaj, tylko w dziennikach rozruchu? Czy to dlatego, że ata_piix (i sd) są wbudowane jako wbudowane sterowniki w (monolitycznym) jądrze, w przeciwieństwie do budowania jako (ładowalne) moduły jądra .ko?

Nie musisz zgadywać, jaka jest twoja konfiguracja. Na mojej maszynie mam

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

Plik konfiguracyjny dla tego jądra znajduje się w /boot/config-3.2.0-4-amd64.

Zapytałeś o ata_piix. Przeszukując powyższy .configplik, widzimy CONFIG_ATA_PIIX=m. możemy to potwierdzić, robiąc

dlocate ata_piix.ko   

alternatywnie

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

Więc przynajmniej w moim jądrze jest to moduł.

Faheem Mitha
źródło
Wielkie dzięki za to, @FaheemMitha - chociaż wcześniej słyszałem (i korzystałem) z pliku konfiguracyjnego, z jakiegoś powodu całkowicie zapomniałem o nim w tym przykładzie; ładnie zauważony! Mówi :), grep ATA_PIIX /boot/config-2.6.38-16-genericże w moim systemie, CONFIG_ATA_PIIX=yco prawdopodobnie powinno oznaczać to jądro, ata_piixjest wbudowane „w jądrze”, a nie jako moduł. Twoje zdrowie!
sdaau