[LINUX] L'histoire du "trou" dans le fichier

L'histoire du "trou" dans le fichier

$ dd if=/dev/zero of=testfile1 bs=1 seek=104857599 count=1 ; ls -ls testfile1
1+0 records in
1+0 records out
1 bytes transferred in 0.000114 secs (8775 bytes/sec)
8 -rw-r--r--  1 user  staff  104857600  5 25 13:24 testfile
$ 

Vous pouvez créer un fichier dont la taille est de 100 Mo mais qui ne consomme que 8 blocs. Vous pouvez faire quelque chose de similaire avec la commande Linux (coreutils) truncate ou la commande de gestion qemu qemu-img.

$ truncate -s 100M testfile2
$ qemu-img create -f raw testfile3 100M
$ 

dd utilise deux appels système, lseek (2) et write (2), et truncate et qemu-img utilisent un appel système appelé ftruncate (2), mais les résultats sont presque les mêmes [^ 1].

[^ 1]: Puisqu'un octet est écrit à la fin de dd, le bloc disque de cette partie est alloué. D'un autre côté, truncate et qemu-img peuvent ne pas être alloués du tout. Le montant réellement alloué dépend de l'implémentation du système de fichiers.

Lorsque ces fichiers sont lus, les données remplies de "0" sont lues, et lorsqu'elles sont écrites, le bloc de disque est alloué à ce stade et les informations écrites sont sauvegardées. La partie à laquelle le bloc de disque n'est pas alloué est appelée "trou" ou "trou", et le fichier ayant une telle partie est appelé "fichier perforé" ou "fichier fragmenté". Depuis les temps anciens, des fichiers littéralement clairsemés (taille de fichier importante par rapport à la quantité d'informations) tels que les fichiers DB ont souvent été perforés, et récemment l'image du disque virtuel de la machine virtuelle Qemu / KVM a été perforée. Peut être.

Détection de trous et perçage

Le trou dans ce fichier concerne l'utilisation de l'espace disque dans le système de fichiers et n'apparaît pas traditionnellement dans l'API. En plus de l'API POSIX normale, il y avait aussi un DMAPI, mais il semble que ce n'était pas très populaire.

Depuis les temps anciens, Linux a été capable de détecter les trous dans les fichiers en ouvrant le fichier et en lançant ioctl (2). Un ioctl appelé FIBMAP renvoie le numéro du bloc de disque où les données du fichier sont stockées. La partie trou renvoie 0 comme numéro de bloc de disque, afin qu'elle puisse être détectée. Le problème est que vous avez besoin des privilèges root pour exécuter cet ioctl, et vous devez lancer ioctl (2) une fois pour rechercher un bloc.

Linux a également un ioctl (2) appelé FS_IOC_FIEMAP. Il s'agit d'une version améliorée de FIBM AP, qui vous permet d'obtenir des informations sur une plage spécifiée de fichiers à la fois et ne nécessite pas de privilèges root. FIEMAP octobre 2008, en cours de développement du 2.6.28 Introduit dans.

FIBMAP et FS_IOC_FIEMAP étaient des méthodes permettant de connaître l'emplacement spécifique sur le disque où les données du fichier étaient stockées, et le trou a été trouvé comme effet secondaire. En tant qu'API dédiée à la détection des trous, il existe les options SEEK_HOLE et SEEK_DATA dans l'appel système lseek (2). Il a été initialement implémenté sur Solaris et plus tard sur FreeBSD et [Linux](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=982d816581eeeacfe5b2b7c6d47d13a157616eff Depuis sa mise en œuvre, il semble bon de penser qu'il n'est pas standard mais qu'il a une certaine portabilité.

SEEK_HOLE se déplace vers le premier trou après le décalage spécifié. SEEK_DATA se déplace vers un autre trou après le décalage spécifié. Avec la bonne combinaison, vous pouvez énumérer correctement les trous dans tout le fichier.

D'un autre côté, que diriez-vous de faire un trou? Les méthodes lseek (2) + write (2) et ftruncate (2) mentionnées au début ne peuvent ajouter qu'un trou à la fin du fichier. En d'autres termes, il n'est pas possible de faire un trou dans la pièce où le bloc de données a déjà été alloué.

Là encore, Solaris précède et une commande appelée F_FREESP est ajoutée à fcntl (2). Vous pouvez faire un trou dans une zone spécifiée du fichier. Cette API n'est pas suivie par d'autres OS [^ 2], et sous Linux, un appel système appelé fallocate (2) a été marqué comme FALLOC_FL_PUNCH_HOLE. scm / linux / kernel / git / torvalds / linux.git / commit /? id = 79124f18b335172e1916075c633745e12dae1dac), vous pouvez également faire un trou dans la zone spécifiée. Dans les deux implémentations, les données enregistrées dans la zone spécifiée sont perdues (la lecture renvoie des données remplies de 0).

[^ 2]: Cependant, XFS a un ioctl appelé XFS_IOC_FREESP depuis longtemps, et il peut faire la même chose que fcntl (F_FREESP).

Enregistrer et copier des trous

Avant même l'ajout d'API pouvant être utilisées pour détecter les trous (sans privilèges root), la commande coreutils cp et GNU tar étaient capables de détecter les fichiers perforés et de les copier et archiver efficacement. Comment cela a-t-il été détecté?

Selon l'historique de mise à jour de coreutils, la détection de trous par FS_IOC_FIEMAP a été implémentée en mai 2010. //git.savannah.gnu.org/gitweb/?p=coreutils.git; a = commit; h = dff2b95e4fb22f3e6f3360da0774c784d2f50ff9). Voir le [code] précédent (https://git.savannah.gnu.org/gitweb/?p=coreutils.git;a=blob;f=src/copy.c;h=9a014ad5aa672c78b7e3cd69de1e684c73563e1f;hb=e1aaf851903db) Essayez-le.

En gros, une copie d'un fichier régulier est [copy_reg ()](https://git.savannah.gnu.org/gitweb/?p=coreutils.git;a=blob;f=src/copy.c;h= 9a014ad5aa672c78b7e3cd69de1e684c73563e1f; hb = e1aaf8903db97f3240b1551fd6936ccdc652dfc8 # l458). Ligne 707 La copie est en cours d'exécution dans la boucle while de.

L'attention est ligne 746, Comptez le nombre de 0 et cherchez autant. En d'autres termes, si les données source de la copie sont égales à 0, elles n'écrivent pas 0 dans la destination de la copie, mais recherchent simplement, de sorte qu'elles peuvent devenir un trou.

Même avec les derniers coreutils, les trous sont traités de cette manière lorsque FIEMAP ne peut pas être utilisé.

Trous de fichier et commandes TRIM (UNMAP)

Sur une note différente, quelques années avant la généralisation des SSD, le terme de commande TRIM est devenu un sujet brûlant. TRIM résout le problème que l'écriture des données est relativement lente car la mémoire flash NAND, qui est le support de stockage du SSD, nécessite une opération d'effacement avant la réécriture. Le SSD peut être effacé à l'avance en notifiant au SSD les blocs inutilisés ou libérés du système d'exploitation (système de fichiers). De plus, il est également avantageux dans le nivellement d'usure d'égaliser la fréquence d'écriture pour chaque bloc de flash NAND, qui a une limite supérieure sur le nombre d'écritures.

TRIM est une commande SATA, mais SCSI a également une commande similaire appelée UNMAP. Il existe des SSD avec connexion SCSI, mais ils sont également efficaces pour le provisionnement léger avec SAN. Étant donné que la zone allouée peut être renvoyée au stockage lorsqu'un fichier est supprimé, la zone utilisée n'augmente pas seulement de manière monotone comme par le passé.

En parlant de Thin Provisioning, les disques virtuels pour les machines virtuelles peuvent être fournis par Thin Provisioning. Par exemple, le fichier Qcow2 de Qemu est un format à provisionnement léger, et si vous créez une image brute sous forme de fichier perforé, elle sera provisionnée de manière dynamique. Alors je suis revenu au trou.

Commandes Qemu et TRIM / UNMAP

Les disques SATA et SCSI émulés de Qemu comprennent la commande TRIM (UNMAP) pour manipuler correctement l'image du disque principal (en option).

Premièrement, SATA a commencé à interpréter TRIM dans commit en mai 2011. Le disque SCSI est août 2012. Le périphérique de bloc VirtIO est beaucoup plus récent février 2019. En termes de versions de Qemu, elles correspondent aux cycles de publication de 0.15, 1.2.0 et 4.0, respectivement.

D'autre part, parmi les pilotes pour les fichiers d'image virtuelle back-end, Qcow2 est janvier 2011, TRIM (UNMAP) Relâchez maintenant la pièce. Cependant, la taille du fichier n'est pas réduite et la zone libérée peut être perforée par fallocate. De plus, au format brut, la zone TRIM (UNMAP) de fallocate () est ajoutée à janvier 2013. Il est venu pour être ouvert. Il semble que XFS ait été publié par ioctl () avant cela.

Désormais, lorsque vous créez réellement une machine virtuelle dans l'environnement d'Ubuntu 20.04 LTS (focal, série Linux-5.4, série Qemu-4.2), le disque virtuel (tous les exemples de VirtIO SCSI) devient un OS invité en tant que disque «thin provisionable». Il est visible depuis. Par exemple, lorsque vous démarrez l'invité Linux, les fichiers thin_provisioning et provisioning_mode sont créés dans l'entrée de disque sysfs comme indiqué ci-dessous, qui sont les suivants.

$ cat /sys/bus/scsi/devices/0\:0\:0\:0/scsi_disk/0\:0\:0\:0/thin_provisioning
1
$ cat /sys/bus/scsi/devices/0\:0\:0\:0/scsi_disk/0\:0\:0\:0/provisioning_mode
unmap
$ 

Pour les invités Windows, si vous regardez l'outil «Défragmentation et optimisation du lecteur» (Démarrer-> Sous Outils d'administration Windows), la section «Type de support» est «Lecteur compatible Virtual Provisioning» et sélectionnez «Optimiser». Et TRIM sont exécutés (Z: dans la figure est un disque iSCSI).

スクリーンショット 2020-05-29 16.39.54.png

Du côté de l'hôte avec un disque normal attaché, si vous regardez le même fichier que celui que vous avez vu dans l'invité Linux,

$ cat /sys/bus/scsi/devices/0\:0\:0\:0/scsi_disk/0\:0\:0\:0/thin_provisioning
0
$ cat /sys/bus/scsi/devices/0\:0\:0\:0/scsi_disk/0\:0\:0\:0/provisioning_mode
full
$ 

On constate qu'il ne s'agit pas de provisionnement fin (provisionnement épais).

En fait, lorsqu'un invité émet une commande UNMAP (parce que c'est SCSI), le périphérique de blocage a une option de suppression. Si tel est le cas, fallocate (2) libère de l'espace. Si vous utilisez un noyau Linux 4.9 ou supérieur tel qu'Ubuntu 20.04, [fallocate (2) fonctionne] non seulement lorsque le disque virtuel est un fichier, mais également lorsqu'il s'agit d'un périphérique hôte (tel qu'un volume LVM) (https :: //git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=25f4c41415e513f0e9fb1f3fce2ce98fcba8d263), TRIM (UNMAP) est transmis.

J'ai en fait expérimenté avec une image brute.

host# qemu-img create -f raw vol.img 20G
Formatting 'vol.img', fmt=raw size=21474836480
host# qemu-img info vol.img ; ls -lsh vol.img
image: vol.img
file format: raw
virtual size: 20 GiB (21474836480 bytes)
disk size: 4 KiB
4.0K -rw-r--r-- 1 libvirt-qemu kvm 20G May 30 13:42 vol.img
host# 

Attachez ce disque au sdb invité. Avec libvirt pour ajouter l'option de suppression

      <driver name='qemu' type='raw'/>

Où ça ressemble

      <driver name='qemu' type='raw' discard='unmap'/>

Faire. Après avoir lancé l'invité et identifié le disque cible, formatez-le. J'ai choisi XFS ici.

guest:~# mkfs.xfs /dev/sdb
meta-data=/dev/sdb               isize=512    agcount=4, agsize=1310720 blks
         =                       sectsz=512   attr=2, projid32bit=1
         =                       crc=1        finobt=1, sparse=0, rmapbt=0, reflink=0
data     =                       bsize=4096   blocks=5242880, imaxpct=25
         =                       sunit=0      swidth=0 blks
naming   =version 2              bsize=4096   ascii-ci=0 ftype=1
log      =internal log           bsize=4096   blocks=2560, version=2
         =                       sectsz=512   sunit=0 blks, lazy-count=1
realtime =none                   extsz=4096   blocks=0, rtextents=0
guest:~# 

Si vous examinez l'image côté hôte, vous pouvez voir qu'elle est plus grande par la quantité de métadonnées.

host# qemu-img info vol.img ; ls -lsh vol.img
image: vol.img
file format: raw
virtual size: 20 GiB (21474836480 bytes)
disk size: 10.3 MiB
11M -rw-r--r-- 1 libvirt-qemu kvm 20G May 30 13:51 vol.img
host# 

Montez-le et créez un fichier de 512 Mo.

guest:~# mount /dev/sdb /mnt
guest:~# dd if=/dev/urandom of=/mnt/randomfile bs=1M count=512
512+0 records in
512+0 records out
536870912 bytes (537 MB, 512 MiB) copied, 2.95154 s, 182 MB/s
guest:~# 

L'examen de l'image côté hôte révèle qu'elle est d'environ 512 Mo plus grande.

host# qemu-img info vol.img ; ls -lsh vol.img
image: vol.img
file format: raw
virtual size: 20 GiB (21474836480 bytes)
disk size: 522 MiB
523M -rw-r--r-- 1 libvirt-qemu kvm 20G May 30 13:52 vol.img
host# 

Ensuite, essayez de supprimer ce fichier. TRIM est émis lorsque discard est ajouté à l'option de montage ou lorsque fstrim (8) est exécuté, alors exécutez fstrim (-v est verbeux).

guest:~# rm /mnt/randomfile 
guest:~# fstrim -v /mnt
/mnt: 20 GiB (21464170496 bytes) trimmed
guest:~# 

En examinant l'image, vous pouvez voir qu'elle est revenue à peu près à la même taille qu'immédiatement après mkfs.

host# qemu-img info vol.img ; ls -lsh vol.img
image: vol.img
file format: raw
virtual size: 20 GiB (21474836480 bytes)
disk size: 10.1 MiB
11M -rw-r--r-- 1 libvirt-qemu kvm 20G May 30 13:53 vol.img
host# 

Debian et ubuntu récents ont une commande appelée zerofree, qui semble remplir à zéro l'espace libre de ext3 et ext4. Combiner cela avec l'option [detect-zeros = unmap] de qemu (https://www.qemu.org/docs/master/system/invocation.html#hxtool-1) créera une zone allouée de l'image disque. Il peut être possible de le réduire.

Recommended Posts

L'histoire du "trou" dans le fichier
L'histoire de la participation à AtCoder
L'histoire d'une erreur dans PyOCR
L'histoire de sys.path.append ()
L'histoire de la lecture des données HSPICE en Python
L'histoire de l'affichage des fichiers multimédias dans Django
L'histoire de la construction de Zabbix 4.4
L'histoire de FileNotFound en Python open () mode = 'w'
L'histoire de Python et l'histoire de NaN
Enregistrez le fichier binaire en Python
Google recherche la chaîne sur la dernière ligne du fichier en Python
L'histoire du remontage du serveur d'application
L'histoire de l'exportation d'un programme
L'histoire de la rétrogradation de la version de tensorflow dans la démo de Mask R-CNN.
Traitez le contenu du fichier dans l'ordre avec un script shell
L'histoire de la sortie du maître de planétarium au format pdf avec Pycairo
Utilisation du fichier de contraintes ajouté dans pip 7.1
L'histoire d'essayer de reconnecter le client
[Comprendre en 3 minutes] Le début de Linux
Vérifiez le comportement du destroyer en Python
Gestion des codes de caractères des fichiers en IronPython
L'histoire de la mise en place de MeCab dans Ubuntu 16.04
Implémenter une partie du processus en C ++
Vérifier l'existence du fichier avec python
L'histoire de la fabrication d'un moule immuable
Le résultat de l'installation de python sur Anaconda
Lisez le fichier ligne par ligne avec Python
Lisez le fichier ligne par ligne avec Python
L'histoire de la manipulation des variables globales Python
Principes de base pour exécuter NoxPlayer en Python
L'histoire d'essayer deep3d et de perdre
Décodage du modèle LSTM de Keras.
À la recherche du FizzBuzz le plus rapide en Python
[Python] Récupère le code de caractère du fichier
L'histoire du traitement A du blackjack (python)
L'histoire du changement de pep8 en pycodestyle
[Python] Lire la ligne spécifiée dans le fichier
[Python3] Comprendre les bases des opérations sur les fichiers
L'histoire d'un capteur de stationnement en 10 minutes avec le kit de démarrage GrovePi +
Afficher le chemin complet (chemin absolu) d'un fichier dans un répertoire sous Linux Bash
Ouvrez un fichier Excel en Python et coloriez la carte du Japon
J'ai fait un programme pour vérifier la taille d'un fichier avec Python
Sortie du nombre de cœurs de processeur en Python
L'histoire de l'apprentissage profond avec TPU
Signification de {numéro de version} dans le package mysql rpm
L'histoire selon laquelle le coût d'apprentissage de Python est faible
[Python] Trier la liste de pathlib.Path dans l'ordre naturel
Extraire uniquement le nom du fichier à l'exclusion du répertoire dans le répertoire
Changer la taille de police de la légende dans df.plot
Récupérer l'appelant d'une fonction en Python
Faites correspondre la distribution de chaque groupe en Python
Afficher le résultat du traitement de la géométrie en Python
L'histoire de la création du Mel Icon Generator version 2
Copiez la liste en Python