Qu'est-ce qui pourrait expliquer cette étrange gestion de fichiers clairsemés de / dans tmpfs?

14

Sur ma ext4partition de système de fichiers, je peux exécuter le code suivant:

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

qui donne

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

Faire la même chose sur tmpfs qu'avec:

fs="/tmp"

les rendements

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

ce qui signifie essentiellement que quelque chose que je m'attendais à lire simplement les données, a fait "exploser le fichier" comme un ballon "?

Je m'attends à ce que cela soit dû à une prise en charge moins parfaite du fichier clairsemé dans le tmpfssystème de fichiers, et en particulier à cause de l'ioctl FIEMAP manquant, mais je ne sais pas ce qui cause ce comportement? Peux tu me dire?

l'humanité et la paix
la source
fredonner. Il existe une page zéro partagée (copie sur écriture), qui peut être utilisée lorsqu'une page fragmentée doit être mmap () éditée, par exemple. Je ne sais donc pas pourquoi tout type de lecture à partir d'un fichier tmpfs clairsemé nécessiterait d'allouer de la mémoire réelle. lwn.net/Articles/517465 . Je me demandais si c'était un effet secondaire de la conversion de boucle pour utiliser io direct, mais il semble qu'il ne devrait pas y avoir de différence lorsque vous essayez d'utiliser le nouveau type de boucle sur tmpfs. spinics.net/lists/linux-fsdevel/msg60337.html
sourcejedi
peut-être que cela pourrait obtenir une réponse si c'était sur SO? juste une pensée
1
La sortie de / tmp a différents fichiers avant / après. Est-ce une faute de frappe? Avant: 0 / tmp / sparse100 (sans M à la fin) Après: 102400 / tmp / sparse100M (avec le M de fin).
YoMismo
@YoMismo, oui était une petite faute de frappe seulement
humanityANDpeace

Réponses:

4

Tout d'abord, vous n'êtes pas seul à vous interroger sur ce genre de problèmes.

Cela ne se limite pas à, tmpfsmais a été une préoccupation citée avec NFSv4 .

Si une application lit des "trous" dans un fichier clairsemé, le système de fichiers convertit les blocs vides en "vrais" blocs remplis de zéros et les renvoie à l'application.

Lorsque md5sumtente d'analyser un fichier, il choisit explicitement de le faire dans un ordre séquentiel , ce qui a beaucoup de sens en fonction de ce que md5sum tente de faire.

Comme il y a fondamentalement des "trous" dans le fichier, cette lecture séquentielle va (dans certaines situations) entraîner une copie lors d'une opération d'écriture pour remplir le fichier. Cela entre alors dans un problème plus profond quant à savoir si ou non fallocate()implémenté dans le système de fichiers prend en charge FALLOC_FL_PUNCH_HOLE.

Heureusement, non seulement cela prend en tmpfscharge mais il existe un mécanisme pour "creuser" les trous.

En utilisant l'utilitaire CLI, fallocatenous pouvons réussir à détecter et refaire ces trous.

Selon 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.

fallocatefonctionne au niveau du fichier et lorsque vous exécutez md5sum sur un périphérique de bloc (demandant des lectures séquentielles), vous trébuchez sur l'écart exact entre la façon dont le fallocate()syscall doit fonctionner. Nous pouvons le voir en action:

En action, en utilisant votre exemple, nous voyons ce qui suit:

$ 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

Maintenant ... cela répond à votre question de base. Ma devise générale est "devenir bizarre" alors j'ai creusé plus loin ...

$ 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

Vous voyez que le simple fait d' effectuer les losetupmodifications modifie la taille du fichier clairsemé. Cela devient donc une combinaison intéressante de l'endroit où tmpfsle mécanisme HOLE_PUNCH fallocateet les périphériques de bloc se croisent.

Brian Redbeard
la source
2
Merci pour votre réponse. Je sais que tmpfsprend en charge les fichiers clairsemés et punch_hole. C'est ce qui le rend si déroutant - tmpfs prend en charge cela, alors pourquoi aller remplir les trous clairsemés lors de la lecture à travers un périphérique en boucle? losetupne change pas la taille du fichier, mais il crée un périphérique de bloc, qui sur la plupart des systèmes est ensuite analysé pour le contenu comme: y a-t-il une table de partition? existe-t-il un système de fichiers avec UUID? dois-je alors créer un / dev / disk / by-uuid / symlink? Et ces lectures provoquent déjà l'allocation de parties du fichier clairsemé, car pour une raison mystérieuse , tmpfs remplit des trous sur (certaines) lectures.
frostschutz
1
Pouvez-vous clarifier "la lecture séquentielle va (dans certaines situations) provoquer une copie lors de l'opération d'écriture ", s'il vous plaît? Je suis curieux de comprendre comment une opération de lecture déclencherait une copie sur l'action d'écriture. Merci!
roaima
C'est étrange. Sur mon système, j'ai suivi les mêmes étapes, bien que manuellement et non dans un script. J'ai d'abord fait un fichier de 100M comme l'OP. Ensuite, j'ai répété les étapes avec seulement un fichier de 10 Mo. Premier résultat: ls -s sparse100M était 102400. Mais ls -s sur le fichier 10MB n'était que de 328 blocs. ??
Patrick Taylor
1
@PatrickTaylor ~ 328K concerne ce qui a été utilisé après l'arrivée des scanners UUID, mais vous n'avez pas cat / md5sum le périphérique de boucle pour une lecture complète.
frostschutz
1
Je parcourais la source du module du noyau de boucle (in loop.c) et j'ai vu qu'il y avait deux fonctions pertinentes : lo_read_simple& lo_read_transfer. Il y a quelques différences mineures dans la façon dont ils font l'allocation de mémoire de bas niveau ... lo_read_transferdemande en fait io non bloquant à slab.h( GFP_NOIO) lors d'un alloc_page()appel. lo_read_simple()d'autre part ne fonctionne pas alloc_page().
Brian Redbeard,