Co może wyjaśnić tę dziwną rzadką obsługę plików w / w tmpfs?

14

Na mojej ext4partycji systemu plików mogę uruchomić następujący kod:

fs="/mnt/ext4"

#create sparse 100M file on ${fs}
dd if=/dev/zero \
   of=${fs}/sparse100M conv=sparse seek=$((100*2*1024-1)) count=1 2> /dev/null

#show its actual used size before
echo "Before:"
ls ${fs}/sparse100M -s

#setting the sparse file up as loopback and run md5sum on loopback
losetup /dev/loop0 ${fs}/sparse100M 
md5sum /dev/loop0

#show its actual used size afterwards
echo "After:"
ls ${fs}/sparse100M -s

#release loopback and remove file
losetup -d /dev/loop0
rm ${fs}/sparse100M

co daje

Before:
0 sparse100M
2f282b84e7e608d5852449ed940bfc51  /dev/loop0
After:
0 sparse100M

Robi to samo na tmpfs jak w przypadku:

fs="/tmp"

daje

Before:
0 /tmp/sparse100M
2f282b84e7e608d5852449ed940bfc51  /dev/loop0
After:
102400 /tmp/sparse100M

co w zasadzie oznacza, że ​​coś, co spodziewałem się po prostu odczytać dane, spowodowało, że rzadki plik „wysadził się w powietrze jak balon”?

Spodziewam się, że tmpfsdzieje się tak z powodu mniej doskonałej obsługi rzadkich plików w systemie plików, a zwłaszcza z powodu braku ioctl FIEMAP, ale nie jestem pewien, co powoduje takie zachowanie? Czy możesz mi powiedzieć?

ludzkośćANDpeace
źródło
szum. Istnieje wspólna strona zerowania (kopiowanie przy zapisie), z której można korzystać, na przykład, gdy rzadka strona musi być edytowana w mmap (). Nie jestem więc pewien, dlaczego jakikolwiek odczyt z rzadkiego pliku tmpfs wymagałby alokacji prawdziwej pamięci. lwn.net/Articles/517465 . Zastanawiałem się, czy był to jakiś efekt uboczny konwersji pętli na bezpośrednie IO, ale wydaje się, że nie powinno być żadnej różnicy, gdy próbujesz użyć nowego typu pętli na tmpfs. spinics.net/lists/linux-fsdevel/msg60337.html
sourcejedi
może to może uzyskać odpowiedź, jeśli będzie na SO? tylko myśl
1
Dane wyjściowe / tmp mają różne pliki Before / After. Czy to literówka? Przed: 0 / tmp / sparse100 (bez M na końcu) Po: 102400 / tmp / sparse100M (z końcowym M).
YoMismo,
@YoMismo, tak było tylko małą literówkę
humanityANDpeace

Odpowiedzi:

4

Po pierwsze , nie jesteś sam w zagadkach dotyczących tego rodzaju problemów.

Nie ogranicza się to tylko do tmpfsproblemu, o którym wspominano w NFSv4 .

Jeśli aplikacja odczytuje „dziury” w rzadkim pliku, system plików konwertuje puste bloki na „prawdziwe” bloki wypełnione zerami i zwraca je do aplikacji.

Kiedy md5sumpróbuje skanować plik, jawnie decyduje się to zrobić w kolejności sekwencyjnej , co ma sens w oparciu o to, co próbuje zrobić md5sum.

Ponieważ w pliku znajdują się zasadniczo „dziury”, ten sekwencyjny odczyt spowoduje (w niektórych sytuacjach), że operacja kopiowania podczas zapisu spowoduje wypełnienie pliku. To powoduje głębszy problem związany z tym, czy fallocate()implementowane w systemie plików jest obsługiwane FALLOC_FL_PUNCH_HOLE.

Na szczęście nie tylko to tmpfsobsługuje, ale istnieje mechanizm „wykopywania” dziur z powrotem.

Za pomocą narzędzia CLI fallocatemożemy z powodzeniem wykryć i ponownie wykopać te dziury.

Zgodnie z man 1 fallocate:

-d, --dig-holes
      Detect and dig holes.  This makes the file sparse in-place, without
      using extra disk space.  The minimum size of the hole depends on
      filesystem I/O  block size (usually 4096 bytes).  Also, when using
      this option, --keep-size is implied.  If no range is specified by
      --offset and --length, then the entire file is analyzed for holes.

      You can think of this option as doing a "cp --sparse" and then
      renaming the destination file to the original, without the need for
      extra disk space.

      See --punch-hole for a list of supported filesystems.

fallocatedziała jednak na poziomie plików, a kiedy pracujesz md5sum na urządzeniu blokowym (żądając sekwencyjnych odczytów), masz do czynienia z dokładną przerwą między tym, jak fallocate()powinien działać syscall. Widzimy to w akcji:

W działaniu na podstawie Twojego przykładu widzimy:

$ fs=$(mktemp -d)
$ echo ${fs}
/tmp/tmp.ONTGAS8L06
$ dd if=/dev/zero of=${fs}/sparse100M conv=sparse seek=$((100*2*1024-1)) count=1 2>/dev/null
$ echo "Before:" "$(ls ${fs}/sparse100M -s)"
Before: 0 /tmp/tmp.ONTGAS8L06/sparse100M
$ sudo losetup /dev/loop0 ${fs}/sparse100M
$ sudo md5sum /dev/loop0
2f282b84e7e608d5852449ed940bfc51  /dev/loop0
$ echo "After:" "$(ls ${fs}/sparse100M -s)"
After: 102400 /tmp/tmp.ONTGAS8L06/sparse100M
$ fallocate -d ${fs}/sparse100M
$ echo "After:" "$(ls ${fs}/sparse100M -s)"
After: 0 /tmp/tmp.ONTGAS8L06/sparse100M

Teraz ... to odpowiada na twoje podstawowe pytanie. Moje ogólne motto brzmi: „stań się dziwny”, więc kopałem dalej ...

$ fs=$(mktemp -d)
$ echo ${fs}
/tmp/tmp.ZcAxvW32GY
$ dd if=/dev/zero of=${fs}/sparse100M conv=sparse seek=$((100*2*1024-1)) count=1 2>/dev/null
$ echo "Before:" "$(ls ${fs}/sparse100M -s)"
Before: 0 /tmp/tmp.ZcAxvW32GY/sparse100M
$ sudo losetup /dev/loop0 ${fs}/sparse100M
$ echo "After:" "$(ls ${fs}/sparse100M -s)"
After: 1036 /tmp/tmp.ZcAxvW32GY/sparse100M
$ sudo md5sum ${fs}/sparse100M
2f282b84e7e608d5852449ed940bfc51  /tmp/tmp.ZcAxvW32GY/sparse100M
$ echo "After:" "$(ls ${fs}/sparse100M -s)"
After: 1036 /tmp/tmp.ZcAxvW32GY/sparse100M
$ fallocate -d ${fs}/sparse100M
$ echo "After:" "$(ls ${fs}/sparse100M -s)"
After: 520 /tmp/tmp.ZcAxvW32GY/sparse100M
$ sudo md5sum ${fs}/sparse100M
2f282b84e7e608d5852449ed940bfc51  /tmp/tmp.ZcAxvW32GY/sparse100M
$ echo "After:" "$(ls ${fs}/sparse100M -s)"
After: 520 /tmp/tmp.ZcAxvW32GY/sparse100M
$ fallocate -d ${fs}/sparse100M
$ echo "After:" "$(ls ${fs}/sparse100M -s)"
After: 516 /tmp/tmp.ZcAxvW32GY/sparse100M
$ fallocate -d ${fs}/sparse100M
$ sudo md5sum ${fs}/sparse100M
2f282b84e7e608d5852449ed940bfc51  /tmp/tmp.ZcAxvW32GY/sparse100M
$ echo "After:" "$(ls ${fs}/sparse100M -s)"
After: 512 /tmp/tmp.ZcAxvW32GY/sparse100M
$ fallocate -d ${fs}/sparse100M
$ echo "After:" "$(ls ${fs}/sparse100M -s)"
After: 0 /tmp/tmp.ZcAxvW32GY/sparse100M
$ sudo md5sum ${fs}/sparse100M
2f282b84e7e608d5852449ed940bfc51  /tmp/tmp.ZcAxvW32GY/sparse100M
$ echo "After:" "$(ls ${fs}/sparse100M -s)"
After: 0 /tmp/tmp.ZcAxvW32GY/sparse100M

Widać, że tylko akt wykonywanialosetup zmienia rozmiar pliku rozrzedzony. Staje się to więc interesującą kombinacją tmpfsprzecinania się mechanizmu HOLE_PUNCH fallocatei urządzeń blokowych.

Brian Redbeard
źródło
2
Dzięki za odpowiedź. Wiem, że tmpfsobsługuje rzadkie pliki i dziurkacz. To sprawia, że ​​jest to tak mylące - tmpfs obsługuje to, więc po co iść i wypełniać rzadkie dziury podczas czytania przez urządzenie pętli? losetupnie zmienia rozmiaru pliku, ale tworzy urządzenie blokowe, które w większości systemów jest następnie skanowane w poszukiwaniu treści takich jak: czy istnieje tablica partycji? czy istnieje system plików z UUID? powinienem wtedy utworzyć / dev / disk / by-uuid / symlink? A te odczyty już powodują przydzielenie części pliku rzadkiego, ponieważ z jakiegoś tajemniczego powodu tmpfs wypełnia dziury w (niektórych) odczytach.
frostschutz
1
Czy możesz wyjaśnić, że „ odczyt sekwencyjny (w niektórych sytuacjach) spowoduje kopiowanie podczas operacji zapisu ”, proszę? Jestem ciekawy, jak operacja odczytu wyzwalałaby kopiowanie po akcji zapisu. Dzięki!
roaima,
To jest dziwne. W moim systemie wykonałem te same kroki, choć ręcznie i nie w skrypcie. Najpierw zrobiłem plik 100M, podobnie jak OP. Następnie powtórzyłem kroki z plikiem o wielkości 10 MB. Pierwszy wynik: ls -s sparse100M wynosił 102400. Ale ls -s na pliku 10 MB miał tylko 328 bloków. ??
Patrick Taylor,
1
@PatrickTaylor ~ 328K dotyczy tego, co jest używane po pojawieniu się skanerów UUID, ale nie cat / md5sum urządzenia pętli do pełnego odczytu.
frostschutz
1
Przekopałem się przez źródło modułu jądra pętli (wewnątrz loop.c) i zobaczyłem, że istnieją dwie odpowiednie funkcje : lo_read_simplei lo_read_transfer. Istnieją pewne niewielkie różnice w sposobie przydzielania pamięci niskiego poziomu ... w lo_read_transferrzeczywistości żąda nieblokowania io from slab.h( GFP_NOIO) podczas wykonywania alloc_page()połączenia. lo_read_simple()z drugiej strony nie działa alloc_page().
Brian Redbeard,