Appels système Fancy Linux

ls / usr / share / man / man2 /


Que voit un programmeur lorsqu'il commence à travailler avec le langage C? Il voit fopen , printf , scanf et bien d'autres fonctions. Il voit toutes sortes de mmap open et mmap - il semblerait, pourquoi les mettre en évidence? Mais, contrairement au premier groupe, ces deux fonctions lorsqu'elles sont exécutées sur le noyau Linux sont des appels système (en fait pas , presque jamais un appel système ne peut simplement être appelé en tant que fonction, et donc libc contient des wrappers qui reconditionnent les arguments et parfois, comme dans le cas open , remplaçant les anciens appels système par de nouveaux plus généraux). En général, contrairement aux milliers de fonctions de bibliothèque disponibles sur un système GNU / Linux typique, l'interface du noyau a un nombre assez limité de points d'entrée - de l'ordre de plusieurs centaines, mais pour l'espace utilisateur, il se bloque (par exemple, accéder à une page manquante), pour le noyau - mode de fonctionnement par défaut.


Dans cet article, je vais vous dire quelques faits intéressants à mon avis. Il n'aura pas de futex et d'autres détails d'implémentation ennuyeux (probablement). Ce sera principalement ce qui m'a provoqué la réaction "Et quoi, cela pourrait-il en être ainsi?!?".


Tout d'abord, quelques commentaires sur le texte avant kat: certains appels système ont une interface optionnelle sous la forme d'une fonction à partir d'un objet partagé appelé vDSO , que le noyau met dans le processus. Il y a peu de ces fonctions (environ quatre, mais la quantité spécifique, apparemment, peut dépendre de la version et de l'architecture du noyau) - ce sont toutes sortes de time et de gettimeofday , qui, d'une part, sont souvent utilisés, et d'autre part, ils ont été mis en œuvre sans passer à contexte du noyau.


Deuxièmement, SIGSEGV ne se termine pas toujours par un crash de processus, mais nous en parlerons userfaultfd en ce qui concerne userfaultfd .


AVERTISSEMENT: N'oubliez pas qu'en utilisant la plupart des fonctionnalités présentées ici, vous liez votre programme Linux. Ceci est normal si vous effectuez ainsi l'optimisation facultative pour un type de système particulier ou une fonctionnalité supplémentaire qui autrement n'existerait tout simplement pas. Mais sinon, je recommande de réfléchir à la façon de faire un repli multi-plateforme.


Questions d'ordre général


Pour commencer, comment tout cela peut-il être débogué? Bien sûr, strace nous aidera! Étant donné que l'ensemble des appels système est limité et que la plupart des strace savent «à vue», il affichera non seulement «le pointeur 0x12345678 a été passé», mais décrira ce qui est transféré dans telle ou telle direction dans cette structure. Si strace est suffisamment frais, alors en utilisant l'option -k , vous pouvez lui demander d'émettre une pile d'appels.


Cela ressemble Ă  ceci
 $ strace -k sleep 1 execve("/bin/sleep", ["sleep", "1"], 0x7ffe9f9cce30 /* 60 vars */) = 0 > /lib/x86_64-linux-gnu/libc-2.30.so(execve+0xb) [0xe601b] > /usr/bin/strace(+0x0) [0xa279c] > /usr/bin/strace(+0x0) [0xa41d2] > /usr/bin/strace(+0x0) [0x7090b] > /lib/x86_64-linux-gnu/libc-2.30.so(__libc_start_main+0xf3) [0x271e3] > /usr/bin/strace(+0x0) [0x7112a] brk(NULL) = 0x558936ded000 > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_catch_error+0x20b) [0x1ccdb] > /lib/x86_64-linux-gnu/ld-2.30.so(__get_cpu_features+0x1cd2) [0x1b872] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x203c] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x1108] arch_prctl(0x3001 /* ARCH_??? */, 0x7fff593c0070) = -1 EINVAL ( ) > /lib/x86_64-linux-gnu/ld-2.30.so(__get_cpu_features+0x1e25) [0x1b9c5] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x203c] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x1108] access("/etc/ld.so.preload", R_OK) = -1 ENOENT (    ) > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_catch_error+0x10cb) [0x1db9b] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x3c12] > /lib/x86_64-linux-gnu/ld-2.30.so(__get_cpu_features+0x1e7b) [0x1ba1b] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x203c] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x1108] openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_catch_error+0x1238) [0x1dd08] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_debug_state+0x73a) [0x11d4a] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_exception_free+0x908) [0x189c8] > /lib/x86_64-linux-gnu/ld-2.30.so() [0xa362] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_rtld_di_serinfo+0x41b5) [0xeb35] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_catch_exception+0x65) [0x1ca85] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_rtld_di_serinfo+0x4603) [0xef83] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x3c55] > /lib/x86_64-linux-gnu/ld-2.30.so(__get_cpu_features+0x1e7b) [0x1ba1b] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x203c] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x1108] fstat(3, {st_mode=S_IFREG|0644, st_size=254851, ...}) = 0 > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_catch_error+0x1009) [0x1dad9] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_debug_state+0x761) [0x11d71] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_exception_free+0x908) [0x189c8] > /lib/x86_64-linux-gnu/ld-2.30.so() [0xa362] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_rtld_di_serinfo+0x41b5) [0xeb35] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_catch_exception+0x65) [0x1ca85] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_rtld_di_serinfo+0x4603) [0xef83] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x3c55] > /lib/x86_64-linux-gnu/ld-2.30.so(__get_cpu_features+0x1e7b) [0x1ba1b] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x203c] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x1108] mmap(NULL, 254851, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fc49621c000 > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_catch_error+0x1426) [0x1def6] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_debug_state+0x79d) [0x11dad] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_exception_free+0x908) [0x189c8] > /lib/x86_64-linux-gnu/ld-2.30.so() [0xa362] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_rtld_di_serinfo+0x41b5) [0xeb35] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_catch_exception+0x65) [0x1ca85] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_rtld_di_serinfo+0x4603) [0xef83] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x3c55] > /lib/x86_64-linux-gnu/ld-2.30.so(__get_cpu_features+0x1e7b) [0x1ba1b] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x203c] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x1108] close(3) = 0 > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_catch_error+0x10fb) [0x1dbcb] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_debug_state+0x780) [0x11d90] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_exception_free+0x908) [0x189c8] > /lib/x86_64-linux-gnu/ld-2.30.so() [0xa362] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_rtld_di_serinfo+0x41b5) [0xeb35] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_catch_exception+0x65) [0x1ca85] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_rtld_di_serinfo+0x4603) [0xef83] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x3c55] > /lib/x86_64-linux-gnu/ld-2.30.so(__get_cpu_features+0x1e7b) [0x1ba1b] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x203c] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x1108] openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_catch_error+0x1238) [0x1dd08] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x7d40] > /lib/x86_64-linux-gnu/ld-2.30.so() [0xa3a8] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_rtld_di_serinfo+0x41b5) [0xeb35] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_catch_exception+0x65) [0x1ca85] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_rtld_di_serinfo+0x4603) [0xef83] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x3c55] > /lib/x86_64-linux-gnu/ld-2.30.so(__get_cpu_features+0x1e7b) [0x1ba1b] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x203c] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x1108] read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\360r\2\0\0\0\0\0"..., 832) = 832 > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_catch_error+0x12f8) [0x1ddc8] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x7d79] > /lib/x86_64-linux-gnu/ld-2.30.so() [0xa3a8] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_rtld_di_serinfo+0x41b5) [0xeb35] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_catch_exception+0x65) [0x1ca85] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_rtld_di_serinfo+0x4603) [0xef83] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x3c55] > /lib/x86_64-linux-gnu/ld-2.30.so(__get_cpu_features+0x1e7b) [0x1ba1b] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x203c] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x1108] ...    ... brk(NULL) = 0x558936ded000 > /lib/x86_64-linux-gnu/libc-2.30.so(brk+0xb) [0x11755b] > /lib/x86_64-linux-gnu/libc-2.30.so(__sbrk+0x67) [0x117617] > /lib/x86_64-linux-gnu/libc-2.30.so(__default_morecore+0xd) [0x9fd3d] > /lib/x86_64-linux-gnu/libc-2.30.so(thrd_yield+0x2725) [0x9a745] > /lib/x86_64-linux-gnu/libc-2.30.so(thrd_yield+0x3943) [0x9b963] > /lib/x86_64-linux-gnu/libc-2.30.so(thrd_yield+0x3b2b) [0x9bb4b] > /lib/x86_64-linux-gnu/libc-2.30.so(thrd_yield+0x4d9e) [0x9cdbe] > /lib/x86_64-linux-gnu/libc-2.30.so(textdomain+0x740) [0x3be70] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0x1d35) [0x35515] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0xbdf) [0x343bf] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0x215) [0x339f5] > /bin/sleep() [0x25f0] > /lib/x86_64-linux-gnu/libc-2.30.so(__libc_start_main+0xf3) [0x271e3] > /bin/sleep() [0x287e] brk(0x558936e0e000) = 0x558936e0e000 > /lib/x86_64-linux-gnu/libc-2.30.so(brk+0xb) [0x11755b] > /lib/x86_64-linux-gnu/libc-2.30.so(__sbrk+0x91) [0x117641] > /lib/x86_64-linux-gnu/libc-2.30.so(__default_morecore+0xd) [0x9fd3d] > /lib/x86_64-linux-gnu/libc-2.30.so(thrd_yield+0x2725) [0x9a745] > /lib/x86_64-linux-gnu/libc-2.30.so(thrd_yield+0x3943) [0x9b963] > /lib/x86_64-linux-gnu/libc-2.30.so(thrd_yield+0x3b2b) [0x9bb4b] > /lib/x86_64-linux-gnu/libc-2.30.so(thrd_yield+0x4d9e) [0x9cdbe] > /lib/x86_64-linux-gnu/libc-2.30.so(textdomain+0x740) [0x3be70] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0x1d35) [0x35515] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0xbdf) [0x343bf] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0x215) [0x339f5] > /bin/sleep() [0x25f0] > /lib/x86_64-linux-gnu/libc-2.30.so(__libc_start_main+0xf3) [0x271e3] > /bin/sleep() [0x287e] openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3 > /lib/x86_64-linux-gnu/libc-2.30.so(__open64_nocancel+0x4c) [0x11679c] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0x1ce9) [0x354c9] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0xbdf) [0x343bf] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0x215) [0x339f5] > /bin/sleep() [0x25f0] > /lib/x86_64-linux-gnu/libc-2.30.so(__libc_start_main+0xf3) [0x271e3] > /bin/sleep() [0x287e] fstat(3, {st_mode=S_IFREG|0644, st_size=8994080, ...}) = 0 > /lib/x86_64-linux-gnu/libc-2.30.so(__fxstat64+0x19) [0x1107b9] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0x1e33) [0x35613] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0xbdf) [0x343bf] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0x215) [0x339f5] > /bin/sleep() [0x25f0] > /lib/x86_64-linux-gnu/libc-2.30.so(__libc_start_main+0xf3) [0x271e3] > /bin/sleep() [0x287e] mmap(NULL, 8994080, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fc495795000 > /lib/x86_64-linux-gnu/libc-2.30.so(mmap64+0x26) [0x11baf6] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0x1e5d) [0x3563d] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0xbdf) [0x343bf] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0x215) [0x339f5] > /bin/sleep() [0x25f0] > /lib/x86_64-linux-gnu/libc-2.30.so(__libc_start_main+0xf3) [0x271e3] > /bin/sleep() [0x287e] close(3) = 0 > /lib/x86_64-linux-gnu/libc-2.30.so(__close_nocancel+0xb) [0x1165bb] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0x1eab) [0x3568b] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0xbdf) [0x343bf] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0x215) [0x339f5] > /bin/sleep() [0x25f0] > /lib/x86_64-linux-gnu/libc-2.30.so(__libc_start_main+0xf3) [0x271e3] > /bin/sleep() [0x287e] nanosleep({tv_sec=1, tv_nsec=0}, NULL) = 0 > /lib/x86_64-linux-gnu/libc-2.30.so(nanosleep+0x17) [0xe5d17] > /bin/sleep() [0x5827] > /bin/sleep() [0x5600] > /bin/sleep() [0x27b0] > /lib/x86_64-linux-gnu/libc-2.30.so(__libc_start_main+0xf3) [0x271e3] > /bin/sleep() [0x287e] close(1) = 0 > /lib/x86_64-linux-gnu/libc-2.30.so(__close_nocancel+0xb) [0x1165bb] > /lib/x86_64-linux-gnu/libc-2.30.so(_IO_file_close_it+0x70) [0x92fc0] > /lib/x86_64-linux-gnu/libc-2.30.so(fclose+0x166) [0x85006] > /bin/sleep() [0x5881] > /bin/sleep() [0x2d27] > /lib/x86_64-linux-gnu/libc-2.30.so(__libc_secure_getenv+0x127) [0x49ba7] > /lib/x86_64-linux-gnu/libc-2.30.so(exit+0x20) [0x49d60] > /lib/x86_64-linux-gnu/libc-2.30.so(__libc_start_main+0xfa) [0x271ea] > /bin/sleep() [0x287e] close(2) = 0 > /lib/x86_64-linux-gnu/libc-2.30.so(__close_nocancel+0xb) [0x1165bb] > /lib/x86_64-linux-gnu/libc-2.30.so(_IO_file_close_it+0x70) [0x92fc0] > /lib/x86_64-linux-gnu/libc-2.30.so(fclose+0x166) [0x85006] > /bin/sleep() [0x5881] > /bin/sleep() [0x2d4d] > /lib/x86_64-linux-gnu/libc-2.30.so(__libc_secure_getenv+0x127) [0x49ba7] > /lib/x86_64-linux-gnu/libc-2.30.so(exit+0x20) [0x49d60] > /lib/x86_64-linux-gnu/libc-2.30.so(__libc_start_main+0xfa) [0x271ea] > /bin/sleep() [0x287e] exit_group(0) = ? +++ exited with 0 +++ > /lib/x86_64-linux-gnu/libc-2.30.so(_exit+0x36) [0xe5fe6] > /lib/x86_64-linux-gnu/libc-2.30.so(__libc_secure_getenv+0x242) [0x49cc2] > /lib/x86_64-linux-gnu/libc-2.30.so(exit+0x20) [0x49d60] > /lib/x86_64-linux-gnu/libc-2.30.so(__libc_start_main+0xfa) [0x271ea] > /bin/sleep(+0x0) [0x287e] 

Certes, les noms de fichiers source et les numéros de ligne ne sont pas affichés ici. addr2line aidera (si ces informations sont en principe présentes, bien sûr).


Il y a une deuxième question: certains appels système n'ont pas de wrappers dans libc . Ensuite, vous pouvez utiliser le wrapper universel appelé syscall :


  syscall(SYS_kcmp, getpid(), getpid(), KCMP_FILE, 1, fd) 

Un fichier est une chose très étrange ...


Les appels système ne sont pas seulement un moyen de demander au noyau d'accéder au matériel au nom du processus. Il s'agit également d'une API universelle compréhensible par toutes les bibliothèques du système. Donc, si la fonctionnalité dont vous avez besoin n'est pas prise en charge dans la bibliothèque, elle fonctionnera probablement automatiquement si vous demandez correctement le noyau. De plus, une partie des «paramètres» du processus est héritée par execve , vous pouvez donc essayer de le faire sans béquilles compliquées en formant simplement l'état correctement avant de démarrer le processus (quelque chose comme «pourquoi transférer manuellement stderr dans un fichier, si vous pouvez simplement ouvrir le fichier et faire son FD # 2 pour le processus enfant ").


Une fois, j'ai dû soustraire une séquence de paquets réseau d'un fichier. À un moment donné, le nombre de béquilles a dépassé toutes les limites raisonnables, et j'ai décidé qu'il était peu probable que libpcap soit plus compliqué que ce que j'avais écrit, d'ailleurs, c'était la norme, et il y avait des outils généralement acceptés pour ouvrir ces fichiers. Il s'est avéré que l'utilisation de libpcap pour lire les vidages est à peu près aussi difficile que fopen pour lire les fichiers: vous ouvrez simplement le vidage avec pcap_(f)open_offline et récupérez les paquets via pcap_next_ex . C’est tout! Eh bien, cela vaut toujours la peine de fermer le dépotoir à la fin des travaux ...


Mais voici le problème: il semble que libpcap ne puisse pas lire depuis la mémoire. Peut-être qu'il le peut, bien sûr, si vous y plongez, mais pour notre «laboratoire», nous imaginerons qu'il ne peut pas.


Donc, un exemple de modèle: nous attendons sur stdin séquence d'octets, après quoi il y a un vidage aligné de 4 octets. Je comprends que vous pouvez utiliser une entrée mise en mémoire tampon et certains ungetc (car libpcap nécessite toujours FILE * ), mais dans le cas général, nous pouvons le décompresser sur la route, par exemple, ou la bibliothèque peut fonctionner directement avec read / write .


Solution 1: memfd_create


L' memfd_create système memfd_create permet de créer un descripteur de fichier "généralement anonyme". Le fichier est en mémoire et existe alors qu'au moins un descripteur est ouvert dessus. Dans le cas le plus simple, vous obtenez simplement un tel descripteur, y écrivez des données via write , rembobinez lseek et avec fdopen faites-le savoir à libc :


  int fd = memfd_create("pcap-dump-contents", 0); write(fd, buf, length); lseek(fd, 0, SEEK_SET); FILE *file = fdopen(fd, "r"); 

Le nom de fichier transmis avec le premier argument sera affiché dans un lien symbolique dans /proc/<PID>/fd :


 $ ls -l /proc/31747/fd  0 lr-x------ 1 trosinenko trosinenko 64  10 13:12 0 -> /path/to/128test.pcap lrwx------ 1 trosinenko trosinenko 64  10 13:12 1 -> /dev/pts/17 lrwx------ 1 trosinenko trosinenko 64  10 13:12 2 -> /dev/pts/17 lrwx------ 1 trosinenko trosinenko 64  10 13:12 23 -> '/home/trosinenko/.cache/appstream-cache-AH3OA0.mdb (deleted)' lrwx------ 1 trosinenko trosinenko 64  10 13:12 3 -> '/memfd:pcap-dump-contents (deleted)' lrwx------ 1 trosinenko trosinenko 64  10 13:12 57 -> 'socket:[41036]' 

Solution 2: ouvrir avec l'indicateur O_TMPFILE


Sous Linux, à partir d'une version, lors de la création d'un fichier, vous pouvez O_TMPFILE et le nom du répertoire au lieu du nom du fichier. En conséquence, le fichier, comme disait un caractère littéraire (environ), il semble être là, mais il n'existe pas ... Je ne sais pas si les données sont écrites sur le disque, mais cela dépend probablement du système de fichiers (en passant, il devrait prendre en charge ce mode) . Le fichier disparaît toujours lorsque le dernier lien est fermé, mais il peut être attaché à l'arborescence de répertoires à l'aide de linkat :


  int fd = open(".", O_RDWR | O_TMPFILE, S_IRUSR | S_IWUSR); assert(fd != -1); assert(write(fd, buffer + offset, len - offset) == len - offset); assert(lseek(fd, 0, SEEK_SET) == 0); const char *link_to = getenv("LINK_TO"); if (link_to != NULL) { char path[128]; snprintf(path, sizeof(path), "/proc/self/fd/%d", fd); linkat(AT_FDCWD, path, AT_FDCWD, link_to, AT_SYMLINK_FOLLOW); } 

En plus de la possibilité de ne pas souffrir de la dénomination du fichier, il permet de remplir le fichier, de configurer les droits, etc., puis de lier atomiquement à l'arborescence des répertoires.


Exemple (pour les deux approches)
 #define _GNU_SOURCE #ifdef NDEBUG //    assert  -   # undef NDEBUG #endif #include <sys/mman.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <assert.h> #include <signal.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <pcap.h> //     PCAP (   -- . ) static const uint32_t pcap_mgc = 0xA1B2C3D4; char buffer[1 << 20]; int main() { int len = read(0, buffer, sizeof(buffer)); //  -      "", //     pcap_mgc,   //   4  .   ... int offset = -1; for (int i = 0; i < len; i += 4) { if (*(uint32_t *)(buffer + i) == pcap_mgc) { offset = i; break; } } if (offset >= 0) { printf("Found PCAP dump at offset %d\n", offset); } else { fprintf(stderr, "No PCAP dump found.\n"); exit(1); } //   ,  libpcap ,   //   . #if 0 int fd = memfd_create("pcap-dump-contents", 0); assert(fd != -1); assert(write(fd, buffer + offset, len - offset) == len - offset); assert(lseek(fd, 0, SEEK_SET) == 0); #else int fd = open(".", O_RDWR | O_TMPFILE, S_IRUSR | S_IWUSR); assert(fd != -1); assert(write(fd, buffer + offset, len - offset) == len - offset); assert(lseek(fd, 0, SEEK_SET) == 0); const char *link_to = getenv("LINK_TO"); if (link_to != NULL) { char path[128]; snprintf(path, sizeof(path), "/proc/self/fd/%d", fd); linkat(AT_FDCWD, path, AT_FDCWD, link_to, AT_SYMLINK_FOLLOW); } #endif raise(SIGSTOP); //    /proc/PID/fd/ //      - ... FILE *file = fdopen(fd, "r"); char errbuf[PCAP_ERRBUF_SIZE]; pcap_t * dump = pcap_fopen_offline(file, errbuf); assert(dump != NULL); struct pcap_pkthdr *hdr; const uint8_t *data; while (pcap_next_ex(dump, &hdr, &data) == 1) { printf("Read packet: full length = %d bytes, available %d bytes.\n", hdr->len, hdr->caplen); } return 0; } 

 $ fallocate -l 128 zero128 $ cat zero128 test.pcap > 128test.pcap $ ./memfd < 128test.pcap Found PCAP dump at offset 128 Read packet: full length = 105 bytes, available 105 bytes. Read packet: full length = 105 bytes, available 105 bytes. Read packet: full length = 66 bytes, available 66 bytes. Read packet: full length = 385 bytes, available 385 bytes. Read packet: full length = 66 bytes, available 66 bytes. ... 

userfaultfd: gestion des erreurs de mémoire dans l'espace utilisateur


Je pense qu'il n'y aura rien de très nouveau à dire que sur les systèmes de type UNIX, les descripteurs de fichiers n'indiquent rien. Par exemple, sous Linux, il peut s'agir d'un socket, d'un pipe, d'un eventfd ou même d'un lien vers un programme ebpf. Mais cet exemple vous surprendra peut-être encore. Au début de l'article, j'ai parlé du fait que les défauts de page sont une chose courante pour le noyau: échange, copie sur écriture, c'est tout ... Lorsque le processus utilisateur "rate", SIGSEGV lui est envoyé. Pour autant que je sache, le retour du contrôle du gestionnaire SIGSEGV généré par le noyau est un comportement non défini, et néanmoins, il existe la bibliothèque GNU libsigsegv qui généralise les fonctionnalités de gestion des erreurs d'accès à la mémoire sur diverses plates-formes, même Windows (ATTENTION: licence GPL, si elle n'est pas prête pour distribuez votre programme, n'utilisez pas libsigsegv) . Il n'y a pas si longtemps, une méthode complètement documentée est apparue sous Linux, appelée userfaultfd : en utilisant l'appel système du même nom, vous ouvrez un descripteur de fichier, lisant et écrivant dans lequel des structures spéciales sont des commandes.


Avec un tel descripteur de fichier, vous pouvez marquer une plage d'adresses virtuelles pour votre processus. Après cela, au premier accès à chaque page de mémoire marquée, le flux s'endormira et la lecture du descripteur de fichier renverra des informations sur ce qui s'est passé. Après cela, le gestionnaire remplira la structure de réponse avec un pointeur sur les données qui doivent être utilisées pour initialiser la page "problème", le noyau l'initialisera et réveillera le thread qui a tourné. Dans ce cas, il est supposé qu'il existe un flux distinct, dont les fonctions comprennent la lecture des commandes du descripteur et l'émission de réponses. De manière générale, d'autres informations peuvent userfaultfd être obtenues via userfaultfd , par exemple, certaines notifications concernant un changement de carte virtuelle de processus.


Exemple d'utilisation
 #define _GNU_SOURCE #ifdef NDEBUG //    assert  -   # undef NDEBUG #endif #include <linux/userfaultfd.h> #include <syscall.h> #include <sys/mman.h> #include <sys/ioctl.h> #include <unistd.h> #include <pthread.h> #include <assert.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> // -,   sysconf... #define PAGE_SIZE 4096 #define PAGE_MASK (PAGE_SIZE - 1) static void *thread_fn(void * arg) { int uffd = (intptr_t)arg; struct uffd_msg msg; // ,    hugepages... uint8_t *replacement_page = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1 ,0); while(1) { assert(read(uffd, &msg, sizeof msg) > 0); //    ,    if (msg.event == UFFD_EVENT_PAGEFAULT) { uintptr_t addr = msg.arg.pagefault.address; fprintf(stderr, "Fault: addr = 0x%zx\n", addr); //       uint8_t *page_addr = (uint8_t *)((uintptr_t)addr & ~PAGE_MASK); //  "" ,  ""! memset(replacement_page, 0xAB, PAGE_SIZE); //     struct uffdio_copy copy; copy.src = (uintptr_t)replacement_page; copy.dst = (uintptr_t)page_addr; copy.mode = 0; // ,  --   copy.copy = 0; //   --      copy.len = PAGE_SIZE; assert(ioctl(uffd, UFFDIO_COPY, &copy) != -1); } } } static int init_userfaultfd(void) { //   int uffd = syscall(__NR_userfaultfd, 0); // ,       struct uffdio_api api; api.api = UFFD_API; api.features = 0; assert(ioctl(uffd, UFFDIO_API, &api) != -1); fprintf(stderr, "UFFD open\n"); //  - pthread_t thread; memset(&thread, 0, sizeof(thread)); // ...    int  void *? pthread_create(&thread, 0, thread_fn, (void *)(intptr_t)uffd); return uffd; } static void register_region(int uffd, void * aligned_addr, size_t size) { struct uffdio_register reg; memset(&reg, 0, sizeof reg); reg.range.start = (uintptr_t)aligned_addr; reg.range.len = size; reg.mode = UFFDIO_REGISTER_MODE_MISSING; assert (ioctl(uffd, UFFDIO_REGISTER, &reg) != -1); } int main() { void *addr = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); int uffd = init_userfaultfd(); register_region(uffd, addr, PAGE_SIZE); fprintf(stderr, "Before reading\n"); fprintf(stderr, "Data at %p: %x\n", addr, *(volatile int *)addr); return 0; } 

 $ ./userfaultfd UFFD open Before reading Fault: addr = 0x7f46f40d5000 Data at 0x7f46f40d5000: abababab 

"La question clé des mathématiques: est-ce la même chose?" ©


Que faire si vous avez besoin de savoir si ce descripteur de fichier fait référence à stdin ? Il semblerait que if (fd == 0) ... - et c'est tout. Bon, ok ...


 #define _GNU_SOURCE #include <unistd.h> #include <stdio.h> int main() { int fd = dup(0); printf("stdin is fd %d, too\n", fd); if (fd == 0) printf("stdin"); else printf("not stdin"); return 0; } 

 $ gcc kcmp.c -o kcmp $ ./kcmp stdin is fd 3, too not stdin 

Oups ... Le handle est un peu comme un, mais les alias sont différents. CRIU - Checkpoint / Restore In Userspace nous aidera. , , . userspace-, , , kcmp : PID, , , , , :


 #define _GNU_SOURCE #include <linux/kcmp.h> #include <syscall.h> #include <unistd.h> #include <stdio.h> int main() { int fd = dup(0); printf("stdin is fd %d, too\n", fd); int pid = getpid(); if (syscall(SYS_kcmp, pid, pid, KCMP_FILE /*    _FILES! */, 0 /* stdin fd */, fd) == 0) printf("stdin\n"); else printf("not stdin\n"); if (syscall(SYS_kcmp, pid, pid, KCMP_FILE, 1 /* stdout fd */, fd) == 0) printf("stdout\n"); else printf("not stdout\n"); return 0; } 

 $ ./kcmp stdin is fd 3, too stdin stdout 

! , ...


 $ ls -l /proc/self/fd  0 lrwx------ 1 trosinenko trosinenko 64  10 14:45 0 -> /dev/pts/17 lrwx------ 1 trosinenko trosinenko 64  10 14:45 1 -> /dev/pts/17 lrwx------ 1 trosinenko trosinenko 64  10 14:45 2 -> /dev/pts/17 lrwx------ 1 trosinenko trosinenko 64  10 14:45 23 -> '/home/trosinenko/.cache/appstream-cache-AH3OA0.mdb (deleted)' lr-x------ 1 trosinenko trosinenko 64  10 14:45 3 -> /proc/17265/fd lrwx------ 1 trosinenko trosinenko 64  10 14:45 57 -> 'socket:[41036]' 

, , , , . , bash , ls !


 $ ./kcmp < kcmp.c stdin is fd 3, too stdin not stdout 

, — -, best effort - .



, ...



oldolduname ...

Source: https://habr.com/ru/post/fr475184/


All Articles