¿Qué podría explicar este extraño manejo de archivos dispersos de / en tmpfs?

14

En mi ext4partición del sistema de archivos puedo ejecutar el siguiente 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

cuyos rendimientos

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

Haciendo lo mismo en tmpfs que con:

fs="/tmp"

rendimientos

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

lo que básicamente significa que algo que esperaba leer simplemente los datos, hizo que el archivo disperso "explotara como un globo".

Supongo que se debe a un soporte menos perfecto para archivos dispersos en el tmpfssistema de archivos, y en particular debido a la falta de FIEMAP ioctl, pero no estoy seguro de qué causa este comportamiento. ¿Usted pude decirme?

humanidad y paz
fuente
tararear. Hay una página cero compartida (copia en escritura), que podría usarse cuando una página dispersa necesita ser editada por mmap (), por ejemplo. Por lo tanto, no estoy seguro de por qué cualquier tipo de lectura de un archivo tmpfs disperso requeriría asignar memoria real. lwn.net/Articles/517465 . Me preguntaba si esto era algún efecto secundario de la conversión del bucle para usar io directo, pero parece que no debería haber ninguna diferencia cuando intentas usar el nuevo tipo de bucle en tmpfs. spinics.net/lists/linux-fsdevel/msg60337.html
sourcejedi
tal vez esto podría obtener una respuesta si estuviera en SO? solo un pensamiento
1
La salida de / tmp tiene diferentes archivos Antes / Después. ¿Es eso un error tipográfico? Antes: 0 / tmp / sparse100 (sin M al final) Después: 102400 / tmp / sparse100M (con la M final).
YoMismo
@YoMismo, sí, fue solo un pequeño error tipográfico
humanityANDpeace el

Respuestas:

4

En primer lugar, no estás solo en el enigma sobre este tipo de problemas.

Esto no solo se limita, tmpfssino que ha sido una preocupación citada con NFSv4 .

Si una aplicación lee 'agujeros' en un archivo disperso, el sistema de archivos convierte los bloques vacíos en bloques "reales" llenos de ceros y los devuelve a la aplicación.

Cuando md5sumintenta escanear un archivo, elige explícitamente hacerlo en orden secuencial , lo que tiene mucho sentido en función de lo que md5sum intenta hacer.

Como existen fundamentalmente "agujeros" en el archivo, esta lectura secuencial va a (en algunas situaciones) causar una copia en la operación de escritura para completar el archivo. Esto luego entra en un problema más profundo sobre si es fallocate()compatible o no con la implementación del sistema de archivos FALLOC_FL_PUNCH_HOLE.

Afortunadamente, no solo es tmpfscompatible con esto, sino que hay un mecanismo para "cavar" los agujeros de nuevo.

Usando la utilidad CLI fallocatepodemos detectar con éxito y volver a excavar estos agujeros.

Según 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.

fallocateSin embargo, opera en el nivel de archivo y cuando se está ejecutando md5sum contra un dispositivo de bloque (solicitando lecturas secuenciales) está tropezando con la brecha exacta entre cómo fallocate()debería funcionar la llamada al sistema. Podemos ver esto en acción:

En acción, usando su ejemplo, vemos lo siguiente:

$ 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

Ahora ... eso responde a tu pregunta básica. Mi lema general es "ponte raro", así que profundicé más ...

$ 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

Verá que el simple hecho de realizar los losetupcambios cambia el tamaño del archivo disperso. Por lo tanto, esto se convierte en una combinación interesante de dónde tmpfs, el mecanismo HOLE_PUNCH fallocatey los dispositivos de bloque se cruzan.

Brian Barba Roja
fuente
2
Gracias por tu respuesta. Sé que tmpfsadmite archivos dispersos y punch_hole. Eso es lo que lo hace tan confuso: esto es tmpfs compatible , así que ¿por qué ir y llenar los agujeros escasos al leer a través de un dispositivo de bucle? losetupno cambia el tamaño del archivo, pero crea un dispositivo de bloque, que en la mayoría de los sistemas se escanea en busca de contenido como: ¿hay una tabla de partición? ¿Hay un sistema de archivos con UUID? ¿Debería crear un / dev / disk / by-uuid / symlink entonces? Y esas lecturas ya hacen que se asignen partes del archivo disperso, porque por alguna razón misteriosa , tmpfs llena agujeros en (algunas) lecturas.
frostschutz
1
¿Puede aclarar que "la lectura secuencial va a (en algunas situaciones) causar una copia en escritura como operación ", por favor? Tengo curiosidad por entender cómo una operación de lectura desencadenaría una copia en la acción de escritura. ¡Gracias!
roaima
Esto es raro. En mi sistema seguí los mismos pasos, aunque manualmente y no en un script. Primero hice un archivo de 100M al igual que el OP. Luego repetí los pasos con solo un archivo de 10 MB. Primer resultado: ls -s sparse100M fue 102400. Pero ls -s en el archivo de 10MB tenía solo 328 bloques. ??
Patrick Taylor el
1
@PatrickTaylor ~ 328K trata sobre lo que se usó después de que aparecieron los escáneres UUID, pero no captó / md5sum el dispositivo de bucle para una lectura completa.
frostschutz
1
Estaba cavando a través de la fuente del módulo del núcleo del bucle (in loop.c) y vi que hay dos funciones relevantes : lo_read_simple& lo_read_transfer. Hay algunas diferencias menores en la forma en que realizan la asignación de memoria de bajo nivel ... en lo_read_transferrealidad está solicitando io sin bloqueo desde slab.h( GFP_NOIO) mientras realiza una alloc_page()llamada. lo_read_simple()por otro lado no está realizando alloc_page().
Brian Redbeard