Was könnte diese seltsame sparsame Dateibehandlung von / in tmpfs erklären?

14

Auf meiner ext4Dateisystempartition kann ich den folgenden Code ausführen:

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

was ergibt

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

Unter tmpfs dasselbe tun wie unter:

fs="/tmp"

Ausbeuten

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

was bedeutet eigentlich, dass etwas, von dem ich erwartet hatte, dass es nur die Daten liest, dazu führte, dass die dünne Datei "wie ein Ballon explodierte"?

Ich tmpfsgehe davon aus, dass dies an der weniger perfekten Unterstützung für Sparse-Dateien im Dateisystem liegt, insbesondere an der fehlenden FIEMAP ioctl, aber ich bin nicht sicher, was dieses Verhalten verursacht. Kannst du mir sagen?

Mensch und Frieden
quelle
summen. Es gibt eine gemeinsame Nullseite (Copy-on-Write), die beispielsweise verwendet werden kann, wenn eine dünne Seite mit mmap () bearbeitet werden muss. Daher bin ich mir nicht sicher, warum für das Lesen einer spärlichen tmpfs-Datei die Zuweisung von echtem Speicher erforderlich ist. lwn.net/Articles/517465 . Ich habe mich gefragt, ob dies ein Nebeneffekt der Konvertierung von loop in direct io ist, aber es scheint, dass es keinen Unterschied geben sollte, wenn Sie versuchen, den neuen Typ von loop in tmpfs zu verwenden. spinics.net/lists/linux-fsdevel/msg60337.html
sourcejedi
Vielleicht könnte dies eine Antwort bekommen, wenn es auf SO wäre? nur ein Gedanke
1
Die Ausgabe von / tmp hat verschiedene Dateien Vorher / Nachher. Ist das ein Tippfehler? Vorher: 0 / tmp / sparse100 (ohne M am Ende) Nachher: ​​102400 / tmp / sparse100M (mit dem nachgestellten M).
YoMismo
@YoMismo, ja war nur ein kleiner Tippfehler
humanityANDpeace

Antworten:

4

Zunächst einmal sind Sie nicht allein , wenn Sie über diese Art von Problemen rätseln.

Dies ist nicht nur auf NFSv4 beschränkt, tmpfssondern wurde bereits als Problem angeführt .

Wenn eine Anwendung "Löcher" in einer Sparse-Datei liest, konvertiert das Dateisystem leere Blöcke in "echte" Blöcke, die mit Nullen gefüllt sind, und gibt sie an die Anwendung zurück.

Wenn md5sumversucht wird, eine Datei zu scannen, wird dies explizit in sequenzieller Reihenfolge ausgeführt , was sehr sinnvoll ist, wenn man davon ausgeht, was md5sum versucht.

Da die Datei im Grunde genommen "Lücken" aufweist, führt dieses sequentielle Lesen (in einigen Situationen) dazu, dass beim Schreiben eine Kopie die Datei ausfüllt. Dies führt dann zu einer tieferen Frage, ob dies fallocate()vom Dateisystem unterstützt wird oder nicht FALLOC_FL_PUNCH_HOLE.

Glücklicherweise wird dies nicht nur tmpfsunterstützt, sondern es gibt auch einen Mechanismus, um die Löcher wieder auszugraben.

Mit dem CLI-Dienstprogramm können fallocatewir diese Löcher erfolgreich erkennen und neu graben.

Wie pro 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.

fallocatearbeitet jedoch auf Dateiebene und wenn Sie md5sum gegen ein Blockgerät laufen (das sequentielle Lesevorgänge anfordert), stoßen Sie auf die genaue Lücke zwischen der Funktionsweise des fallocate()Syscalls. Wir können dies in Aktion sehen:

In Aktion sehen wir anhand Ihres Beispiels Folgendes:

$ 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

Nun ... das beantwortet Ihre grundlegende Frage. Mein allgemeines Motto ist "werde komisch", also habe ich mich weiter eingegraben ...

$ 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

Sie sehen , dass nur der Akt der Durchführung der losetupdie Größe der Datei mit geringer Dichte ändert. Dies wird zu einer interessanten Kombination aus tmpfsder Schnittstelle zwischen HOLE_PUNCH-Mechanismus fallocateund Blockgeräten.

Brian Redbeard
quelle
2
Danke für deine Antwort. Ich bin mir bewusst, dass tmpfsspärliche Dateien und punch_hole unterstützt werden. Das ist es, was es so verwirrend macht - tmpfs unterstützt dies. Warum sollten Sie also die spärlichen Löcher füllen, wenn Sie durch ein Loop-Gerät lesen? losetupÄndert nicht die Dateigröße, sondern erstellt ein Blockgerät, das dann auf den meisten Systemen nach Inhalten durchsucht wird, z. B .: Gibt es eine Partitionstabelle? Gibt es ein Dateisystem mit UUID? soll ich dann einen / dev / disk / by-uuid / symlink erstellen? Und diese Lesevorgänge führen bereits dazu, dass Teile der Sparse-Datei zugewiesen werden, da tmpfs aus mysteriösen Gründen Lücken bei (einigen) Lesevorgängen füllt.
Frostschutz
1
Können Sie klarstellen, "dass sequentielles Lesen (in einigen Situationen) eine Kopie beim Schreiben verursacht ", bitte? Ich bin gespannt, wie ein Lesevorgang beim Schreiben eine Kopie auslöst. Vielen Dank!
Roaima
Das ist komisch. Auf meinem System habe ich die gleichen Schritte ausgeführt, allerdings manuell und nicht in einem Skript. Zuerst habe ich eine 100M-Datei gemacht, genau wie das OP. Dann habe ich die Schritte mit nur einer 10MB Datei wiederholt. Erstes Ergebnis: ls -s sparse100M war 102400. ls -s in der 10-MB-Datei bestand jedoch nur aus 328 Blöcken. ??
Patrick Taylor
1
Bei @PatrickTaylor ~ 328K geht es um die Verwendung der UUID-Scanner, aber Sie haben das Loop-Gerät nicht für einen vollständigen Lesevorgang verwendet.
Frostschutz
1
Ich habe die Quelle nach dem Loop-Kernel-Modul (in loop.c) durchsucht und festgestellt, dass es zwei relevante Funktionen gibt : lo_read_simple& lo_read_transfer. Es gibt einige kleinere Unterschiede, wie sie geringe Speicherzuweisung tun ... lo_read_transfertatsächlich anfordert non-blocking io aus slab.h( GFP_NOIO) , während ein Durchführen alloc_page()Anruf. lo_read_simple()auf der anderen Seite nicht durchführen alloc_page().
Brian Redbeard