Qu’est-ce qui limite les appels fwrite () à un disque complet sous Linux?

Il semble que si mon programme tente d’écrire sur un système de fichiers complet, il s’annonce initialement avec «Pas d’espace disponible sur le périphérique» presque instantanément, mais si je le laisse fonctionner pendant une minute, il ralentit de quelques ordres de grandeur. .

Notez que ceci est un cas de test minimum, ce comportement a été remarqué pour la première fois dans une structure de journalisation Java, d’où les petits blocs de sortie (512 octets) et beaucoup d’entre eux.

main.c

 #include  #include  #include  #include  #include  #include  long microTime() { struct timeval time; gettimeofday(&time, NULL); return time.tv_sec * 1000 * 1000 + time.tv_usec; } int main(int argc, const char * argv[]) { char *payload ; payload = (char *)malloc(512 * sizeof(char)); if (payload == NULL) { printf("Failed to alloc memory\n"); exit(1); } FILE *fp; fp = fopen(argv[1], "a+"); printf("opened [%s]\n", argv[1]); if (fp == NULL) { printf("Failed to open [%s]\n", argv[1]); exit(1); } for (;;) { int batchSize = 100000; bool errored = false; long runStart = microTime(); for (int i = 0; i < batchSize; i ++) { size_t result = fwrite(payload, 512 * sizeof(char), 1, fp); if (result == 0 && !errored) { perror("Failed to write to disk"); errored = true; } } long runEnd = microTime(); printf("total elapsed %dms\n", (int)(runEnd - runStart) / 1000); } return 0; } 

(excusez mon C, c’est probablement le premier programme C que j’ai écrit depuis près de 20 ans)

Fonctionnement:

gcc -std=c99 main.c && ./a.out /path/to/somewhere/file1.bin

Attention ce programme remplira votre partition de disque

sortie:

 total elapsed 42ms total elapsed 105ms total elapsed 104ms total elapsed 125ms ... skip until the disk fills Failed to write to disk: No space left on device total elapsed 104ms Failed to write to disk: No space left on device total elapsed 76ms Failed to write to disk: No space left on device total elapsed 84ms Failed to write to disk: No space left on device ... then skip a little more about one minute total elapsed 8096ms Failed to write to disk: No space left on device total elapsed 43245ms Failed to write to disk: No space left on device total elapsed 48670ms Failed to write to disk: No space left on device total elapsed 45929ms Failed to write to disk: No space left on device 

Je m’attends à ce que ce programme fonctionne à jamais avec un temps assez faible pour écrire sur le disque.

J’ai couru ceci sur une boîte locale de 6.4 centos, un linux d’Amazon et des boîtes d’ubuntu 14.04 ec2 avec exactement les mêmes résultats. Il est intéressant de noter que cela ne semble pas se produire sous OSX 10.9.5 en essayant de remplir une image disque.

Donc ma question est vraiment, qu’est-ce qui cause cet étranglement apparent?

update: Exécuter avec strace -t -T

 10:27:38 write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 4096  10:27:38 write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 4096  10:27:38 write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = -1 ENOSPC (No space left on device)  10:27:38 dup(2) = 4  10:27:38 fcntl(4, F_GETFL) = 0x8002 (flags O_RDWR|O_LARGEFILE)  10:27:38 fstat(4, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0  10:27:38 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa8e97f2000  10:27:38 lseek(4, 0, SEEK_CUR) = -1 ESPIPE (Illegal seek)  10:27:38 write(4, "Failed to write to disk: No spac"..., 49Failed to write to disk: No space left on device ) = 49  10:27:38 close(4) = 0  10:27:38 munmap(0x7fa8e97f2000, 4096) = 0  10:27:38 write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = -1 ENOSPC (No space left on device)  10:27:38 write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = -1 ENOSPC (No space left on device)  10:27:38 write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = -1 ENOSPC (No space left on device)  10:27:38 write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = -1 ENOSPC (No space left on device)  10:27:38 write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = -1 ENOSPC (No space left on device)  10:27:38 write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = -1 ENOSPC (No space left on device)  10:27:38 write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = -1 ENOSPC (No space left on device)  10:27:38 write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = -1 ENOSPC (No space left on device)  10:27:38 write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = -1 ENOSPC (No space left on device)  10:27:38 write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = -1 ENOSPC (No space left on device)  ... skipping to the end 10:30:02 write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = -1 ENOSPC (No space left on device)  10:30:02 write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = -1 ENOSPC (No space left on device)  10:30:02 write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = -1 ENOSPC (No space left on device)  10:30:02 write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = -1 ENOSPC (No space left on device)  10:30:02 write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = -1 ENOSPC (No space left on device)  10:30:02 write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = -1 ENOSPC (No space left on device)  

pas les durées d’appel à write() vont de ~ 0,00001s => ~ 0,005s à la fin du cycle.

Le run.log complet est de 250 Mo

update 2: Ajout des détails d’utilisation du processeur pour les différentes phases:

Regarder la course, c’est-à-dire remplir le disque

 top - 11:00:56 up 2:52, 2 users, load average: 1.79, 0.99, 0.48 Tasks: 75 total, 3 running, 72 sleeping, 0 stopped, 0 zombie Cpu(s): 7.3%us, 71.8%sy, 0.0%ni, 0.0%id, 0.0%wa, 14.6%hi, 6.3%si, 0.0%st Mem: 603764k total, 561868k used, 41896k free, 134976k buffers Swap: 1254392k total, 0k used, 1254392k free, 359020k cached PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 9861 vagrant 20 0 4056 500 412 R 54.5 0.1 0:01.88 a.out 766 root 20 0 0 0 0 R 36.9 0.0 0:28.51 flush-8:0 28 root 20 0 0 0 0 S 5.6 0.0 0:05.14 kswapd0 16 root 20 0 0 0 0 S 2.3 0.0 4:51.07 kblockd/0 

Disque plein, erreur rapide

 top - 11:01:11 up 2:52, 2 users, load average: 1.68, 1.01, 0.49 Tasks: 75 total, 2 running, 73 sleeping, 0 stopped, 0 zombie Cpu(s): 9.0%us, 91.0%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Mem: 603764k total, 555424k used, 48340k free, 134976k buffers Swap: 1254392k total, 0k used, 1254392k free, 352224k cached PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 9861 vagrant 20 0 4056 552 464 R 99.5 0.1 0:12.91 a.out 988 root 20 0 215m 1572 860 S 0.3 0.3 0:05.05 VBoxService 1 root 20 0 19228 1348 1072 S 0.0 0.2 0:00.24 init 2 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kthreadd 

Erreur lente

 top - 11:03:03 up 2:54, 2 users, load average: 1.63, 1.14, 0.59 Tasks: 74 total, 3 running, 71 sleeping, 0 stopped, 0 zombie Cpu(s): 0.0%us, 0.4%sy, 0.0%ni, 0.0%id, 98.8%wa, 0.4%hi, 0.4%si, 0.0%st Mem: 603764k total, 555284k used, 48480k free, 134976k buffers Swap: 1254392k total, 0k used, 1254392k free, 352308k cached PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 216 root 20 0 0 0 0 R 3.7 0.0 4:50.72 jbd2/sda1-8 16 root 20 0 0 0 0 R 3.3 0.0 4:53.04 kblockd/0 9861 vagrant 20 0 4056 552 464 D 1.3 0.1 1:17.47 a.out 1 root 20 0 19228 1348 1072 S 0.0 0.2 0:00.24 init 

Vous pouvez observer les effets de la mémoire virtuelle Linux lorsque les appels système en write bloqués et deviennent synchrones. En effet, votre application génère en permanence des données à écrire sur le disque et le disque ne peut pas stocker les données aussi rapidement.

Alternativement, le vidage d’arrière-plan intervient quelque temps après le remplissage du disque, de sorte que les appels système en attente de concurrencer pour l’access au kernel videront le thread dans les structures internes du système de fichiers.

Voir Amélioration de la mise en cache et des performances des disques Linux avec vm.dirty_ratio & vm.dirty_background_ratio :

vm.dirty_background_ratio est le pourcentage de mémoire système pouvant être rempli avec des pages “sales” – des pages de mémoire qui doivent encore être écrites sur le disque – avant que les processus d’arrière-plan pdflush / flush / kdmflush ne les écrivent sur le disque. Mon exemple est 10%, donc si mon serveur virtuel dispose de 32 Go de mémoire, 3,2 Go de données peuvent être stockés dans la RAM avant que quelque chose ne soit fait.

vm.dirty_ratio correspond à la quantité maximale de mémoire système pouvant être remplie avec des pages modifiées avant que tout ne soit validé sur le disque. Lorsque le système arrive à ce point, tous les nouveaux blocs d’E / S jusqu’à ce que les pages incorrects aient été écrites sur le disque. C’est souvent la source de longues pauses d’E / S , mais c’est une protection contre trop de données mises en cache dans la mémoire.