O que poderia explicar esse estranho tratamento de arquivos esparsos de / in tmpfs?

14

Na minha ext4partição do sistema de arquivos, posso executar o seguinte código:

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

que produz

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

Fazendo a mesma coisa no tmpfs como em:

fs="/tmp"

rendimentos

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

o que basicamente significa que algo que eu esperava apenas ler os dados fez com que o arquivo esparso "explodisse como um balão"?

Espero que seja por causa do suporte menos perfeito para arquivos esparsos no tmpfssistema de arquivos e, em particular, devido à falta do FIEMAP ioctl, mas não sei ao certo o que causa esse comportamento? Você pode me dizer?

humanandpeace
fonte
cantarolar. Há uma página zero compartilhada (cópia na gravação), que poderia ser usada quando uma página esparsa precisasse ser mmap () ed, por exemplo. Portanto, não sei por que qualquer tipo de leitura de um arquivo tmpfs esparso exigiria a alocação de memória real. lwn.net/Articles/517465 . Gostaria de saber se isso era algum efeito colateral da conversão do loop para usar o io direto, mas parece que não deve haver diferença quando você tenta usar o novo tipo de loop no tmpfs. spinics.net/lists/linux-fsdevel/msg60337.html
sourcejedi
talvez isso possa obter uma resposta se estiver no SO? apenas um pensamento
1
A saída de / tmp possui arquivos diferentes Antes / Depois. Isso é um erro de digitação? Antes: 0 / tmp / sparse100 (sem M no final) Depois: 102400 / tmp / sparse100M (com o M final).
YoMismo 19/09/18
@YoMismo, sim era um apenas um pouco erro de digitação
humanityANDpeace

Respostas:

4

Primeiro, você não está sozinho em intrigas sobre esse tipo de problema.

Isso não se limita apenas a, tmpfsmas tem sido uma preocupação citada com o NFSv4 .

Se um aplicativo lê 'buracos' em um arquivo esparso, o sistema de arquivos converte blocos vazios em blocos "reais" preenchidos com zeros e os retorna ao aplicativo.

Quando md5sumestá tentando digitalizar um arquivo, ele decide explicitamente fazer isso em ordem seqüencial , o que faz muito sentido com base no que o md5sum está tentando fazer.

Como existem fundamentalmente "brechas" no arquivo, essa leitura seqüencial (em algumas situações) causa uma operação de cópia em gravação para preencher o arquivo. Isso então entra em uma questão mais profunda sobre a fallocate()implementação ou não do suporte ao sistema de arquivos FALLOC_FL_PUNCH_HOLE.

Felizmente, não apenas tmpfssuporta isso, mas também existe um mecanismo para "cavar" os buracos.

Usando o utilitário CLI fallocate, podemos detectar e refazer esses furos com êxito.

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

fallocateopera no nível do arquivo e, quando você está executando md5sum em um dispositivo de bloco (solicitando leituras seqüenciais), está observando o espaço exato entre como o fallocate()syscall deve operar. Podemos ver isso em ação:

Em ação, usando o seu exemplo, vemos o seguinte:

$ 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

Agora ... isso responde à sua pergunta básica. Meu lema geral é "ficar estranho", então eu cavei mais ...

$ 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

Você vê que apenas o ato de executar as losetupalterações altera o tamanho do arquivo esparso. Portanto, isso se torna uma combinação interessante de onde tmpfs, o mecanismo HOLE_PUNCH fallocatee os dispositivos de bloco se cruzam.

Brian Redbeard
fonte
2
Obrigado pela sua resposta. Estou ciente de que tmpfssuporta arquivos esparsos e punch_hole. Isso é o que a torna tão confusa - tmpfs suporta isso, então por que preencher os buracos esparsos ao ler um dispositivo de loop? losetupnão altera o tamanho do arquivo, mas cria um dispositivo de bloco, que na maioria dos sistemas é verificado em busca de conteúdo como: existe uma tabela de partição? existe um sistema de arquivos com UUID? devo criar um / dev / disk / by-uuid / symlink então? E essas leituras já fazem com que partes do arquivo esparso sejam alocadas, porque, por algum motivo misterioso , o tmpfs preenche lacunas (algumas) leituras.
frostschutz 20/09/19
1
Você pode esclarecer que "a leitura seqüencial vai (em algumas situações) causar uma cópia na operação de gravação ", por favor? Estou curioso para entender como uma operação de leitura acionaria uma cópia na ação de gravação. Obrigado!
roaima 20/09/18
Isso é estranho. No meu sistema, segui as mesmas etapas, embora manualmente e não em um script. Primeiro, criei um arquivo de 100 milhões como o OP. Depois, repeti as etapas com apenas um arquivo de 10 MB. Primeiro resultado: ls -s sparse100M foi 102400. Mas ls -s no arquivo 10MB foi de apenas 328 blocos. ??
Patrick Taylor
1
O @PatrickTaylor ~ 328K é sobre o que é usado após a chegada dos scanners UUID, mas você não cat / md5sum no dispositivo de loop para uma leitura completa.
Frostschutz 21/09/19
1
Eu estava procurando na fonte o módulo do kernel de loop (in loop.c) e vi que há duas funções relevantes : lo_read_simple& lo_read_transfer. Existem algumas pequenas diferenças em como eles fazem a alocação de memória de baixo nível ... lo_read_transferna verdade, está solicitando io non-blocking de slab.h( GFP_NOIO) durante a realização de uma alloc_page()chamada. lo_read_simple()por outro lado, não está funcionando alloc_page().
Brian Redbeard