Cosa potrebbe spiegare questa strana gestione dei file sparsi di / in tmpfs?


14

Sulla mia ext4partizione di filesystem posso eseguire il seguente codice:

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

che cede

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

Fare la stessa cosa su tmpfs con:

fs="/tmp"

i rendimenti

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

il che significa sostanzialmente che qualcosa che mi aspettavo di leggere semplicemente i dati, ha causato l'esplosione del file sparso come un palloncino?

Mi aspetto che ciò sia dovuto al supporto meno perfetto per i file sparsi nel tmpfsfilesystem, e in particolare a causa della mancanza di ioctl FIEMAP, ma non sono sicuro di cosa causi questo comportamento? Puoi dirmi?


ronzio. Esiste una pagina zero condivisa (copia su scrittura), che potrebbe essere utilizzata quando, ad esempio, una pagina sparsa doveva essere mmap () ed. Quindi non sono sicuro del motivo per cui qualsiasi tipo di lettura da un file tmpfs sparso richiederebbe l'allocazione della memoria reale. lwn.net/Articles/517465 . Mi chiedevo se questo fosse un effetto collaterale della conversione di loop per usare direct io, ma sembra che non ci dovrebbe essere alcuna differenza quando si tenta di utilizzare il nuovo tipo di loop su tmpfs. spinics.net/lists/linux-fsdevel/msg60337.html
sourcejedi

forse questo potrebbe ottenere una risposta se fosse su SO? solo un pensiero

1
L'output di / tmp ha file diversi prima / dopo. È un errore di battitura? Prima: 0 / tmp / sparse100 (senza M alla fine) Dopo: 102400 / tmp / sparse100M (con la M finale).
YoMismo,

@YoMismo, sì, era solo un piccolo errore di battitura
umanità e

Risposte:


4

Prima di tutto, non sei il solo a preoccuparti di questo tipo di problemi.

Questo non è solo limitato a, tmpfsma è stato un problema citato con NFSv4 .

Se un'applicazione legge "buchi" in un file sparse, il file system converte i blocchi vuoti in blocchi "reali" pieni di zeri e li restituisce all'applicazione.

Quando md5sumtenta di scansionare un file, sceglie esplicitamente di farlo in ordine sequenziale , il che ha molto senso in base a ciò che md5sum sta tentando di fare.

Poiché ci sono fondamentalmente "buchi" nel file, questa lettura sequenziale causerà (in alcune situazioni) una copia in scrittura come un'operazione per riempire il file. Questo quindi entra in un problema più profondo fallocate()riguardo all'implementazione o meno del supporto del filesystem FALLOC_FL_PUNCH_HOLE.

Fortunatamente, non solo tmpfssupporta questo, ma esiste un meccanismo per "scavare" i fori.

Usando l'utilità CLI fallocatepossiamo rilevare e scavare nuovamente questi buchi con successo.

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

fallocatefunziona a livello di file , tuttavia, e quando si esegue md5sum su un dispositivo a blocchi (richiedendo letture sequenziali) si inciampa nello spazio esatto tra come fallocate()dovrebbe funzionare la syscall. Possiamo vederlo in azione:

In azione, usando il tuo esempio vediamo quanto segue:

$ 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

Ora ... questo risponde alla tua domanda di base. Il mio motto generale è "diventare strano", quindi ho approfondito ...

$ 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

Si vede che semplicemente l'atto di eseguire le losetupmodifiche modifica la dimensione del file sparse. Quindi questa diventa un'interessante combinazione di dove tmpfs, il meccanismo HOLE_PUNCH fallocatee i dispositivi di blocco si intersecano.


2
Grazie per la tua risposta. So che tmpfssupporta file sparsi e punch_hole. Questo è ciò che lo rende così confuso: tmpfs supporta questo, quindi perché andare a riempire i fori sparsi quando si legge attraverso un dispositivo ad anello? losetupnon modifica le dimensioni del file, ma crea un dispositivo a blocchi, che sulla maggior parte dei sistemi viene quindi scansionato per contenuti come: esiste una tabella delle partizioni? c'è un filesystem con UUID? dovrei creare un / dev / disk / by-uuid / symlink quindi? E quelle letture già causano l'allocazione di parti del file sparse, perché per qualche motivo misterioso , tmpfs riempie buchi su (alcune) letture.
frostschutz,

1
Potete chiarire "la lettura sequenziale (in alcune situazioni) causerà una copia in scrittura come un'operazione ", per favore? Sono curioso di capire come un'operazione di lettura innescherebbe una copia in scrittura. Grazie!
roaima,

Questo è strano. Sul mio sistema ho seguito gli stessi passaggi, anche se manualmente e non in uno script. Per prima cosa ho fatto un file da 100 M proprio come l'OP. Quindi ho ripetuto i passaggi con solo un file da 10 MB. Primo risultato: ls -s sparse100M era 102400. Ma ls -s nel file da 10 MB era solo 328 blocchi. ??
Patrick Taylor,

1
@PatrickTaylor ~ 328K riguarda ciò che viene utilizzato dopo che sono arrivati ​​gli scanner UUID, ma non hai cat / md5sum il dispositivo loop per una lettura completa.
frostschutz,

1
Stavo scavando nella fonte per il modulo del kernel del ciclo (in loop.c) e ho visto che ci sono due funzioni rilevanti : lo_read_simple& lo_read_transfer. Ci sono alcune piccole differenze nel modo in cui eseguono l'allocazione di memoria di basso livello ... in lo_read_transferrealtà sta richiedendo io non bloccante da slab.h( GFP_NOIO) durante l'esecuzione di una alloc_page()chiamata. lo_read_simple()d'altra parte non si esibisce alloc_page().
Brian Redbeard,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.