Chamadas sofisticadas do sistema Linux

ls / usr / share / man / man2 /


O que um programador vê ao começar a trabalhar com a linguagem C? Ele vê fopen , printf , scanf e muitas outras funções. Ele vê todos os tipos de mmap open e mmap - ao que parece, por que destacá-los? Mas, diferentemente do primeiro grupo, essas duas funções, quando executadas no kernel Linux, são chamadas de sistema ( na verdade não , quase nunca uma chamada de sistema pode ser chamada simplesmente como uma função e, portanto, libc contém wrappers que reembalam argumentos e, às vezes, como no caso de open , substituindo as chamadas antigas do sistema por outras mais gerais). Em geral, diferentemente das milhares de funções de biblioteca disponíveis em um sistema GNU / Linux típico, a interface do kernel possui um número bastante limitado de pontos de entrada - da ordem de várias centenas, mas para o espaço do usuário, ele falha (por exemplo, acessando uma página ausente), para o kernel - modo padrão de operação.


Neste artigo, vou contar alguns fatos interessantes na minha opinião. Ele não terá futex e outros detalhes de implementação chatos (provavelmente). Será principalmente o que me causou a reação "E o que, poderia ser assim?!?".


Primeiramente, alguns comentários sobre o texto antes do kat: algumas chamadas do sistema têm uma interface opcional na forma de uma função de um objeto compartilhado chamado vDSO , que o kernel coloca no processo. Existem poucas funções desse tipo (algo em torno de quatro, mas o número específico, aparentemente, depende da versão e da arquitetura do kernel) - esses são todos os tipos de time e time gettimeofday , que, por um lado, são frequentemente usados ​​e, por outro, foram implementados sem mudar para contexto do kernel.


Em segundo lugar, o SIGSEGV nem sempre termina com uma falha no processo, mas falaremos sobre isso userfaultfd quando se trata de userfaultfd .


AVISO LEGAL: Lembre - se de que, usando a maioria dos recursos apresentados aqui, você está vinculando seu programa Linux. Isso é normal se, dessa maneira, você fizer a otimização opcional para um tipo específico de sistema ou um recurso adicional que, de outra forma, simplesmente não existiria. Caso contrário, recomendo pensar em como fazer um fallback entre plataformas.


Questões gerais


Para iniciantes, como tudo isso pode ser depurado? Claro que o strace nos ajudará! Como o conjunto de chamadas do sistema é limitado e a maioria dos strace sabe “de vista”, ele mostrará não apenas “o ponteiro 0x12345678 foi passado”, mas também descreverá o que está sendo transferido nessa ou naquela direção nessa estrutura. Se o strace for novo o suficiente, usando a opção -k você poderá solicitar que ele emita uma pilha de chamadas.


Parece algo assim
 $ 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] 

É verdade que os nomes dos arquivos de origem e os números de linha não são exibidos aqui. addr2line irá ajudá- addr2line (se esta informação estiver em princípio presente, é claro).


Há uma segunda pergunta: algumas chamadas do sistema não possuem wrappers na libc . Então você pode usar o wrapper universal chamado syscall :


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

Um arquivo é uma coisa muito estranha ...


As chamadas do sistema não são apenas uma maneira de solicitar ao kernel que acesse o hardware em nome do processo. É também uma API universal que é compreensível para todas as bibliotecas do sistema. Portanto, se a funcionalidade necessária não for suportada na biblioteca, provavelmente funcionará automaticamente se você solicitar o kernel corretamente. Além disso, parte das “configurações” do processo é herdada pelo execve , portanto, você pode tentar fazer isso sem muletas complicadas, simplesmente formando o estado corretamente antes de iniciar o processo (algo como “por que transferir manualmente o stderr para um arquivo, se você pode simplesmente abrir o arquivo e seu FD # 2 para o processo filho ").


Uma vez, eu precisava subtrair uma sequência de pacotes de rede de um arquivo. Em algum momento, o número de muletas excedeu todos os limites razoáveis, e eu decidi que era improvável que a libpcap fosse mais complicada do que eu escrevi; além disso, era o padrão, e geralmente havia ferramentas aceitas para abrir esses arquivos. Aconteceu que usar libpcap para ler dumps é quase tão difícil quanto abrir arquivos: você simplesmente abre o dump com pcap_(f)open_offline e pcap_(f)open_offline pacotes através do pcap_next_ex . Isso é tudo! Ainda vale a pena fechar a lixeira após a conclusão do trabalho ...


Mas aqui está o problema: parece que a libpcap não pode ler da memória. Talvez ele possa, é claro, se você se aprofundar, mas para o nosso "laboratório", imaginaremos que ele não pode.


Então, um exemplo de modelo: estamos esperando no stdin sequência de bytes, após o qual há um dump alinhado por 4 bytes. Entendo que você pode usar entrada em buffer e alguns ungetc (já que a libpcap ainda requer FILE * ), mas no caso geral, podemos descompactá-la em qualquer lugar, por exemplo, ou a biblioteca pode trabalhar diretamente com read / write .


Solução 1: memfd_create


A chamada de sistema memfd_create permite criar um descritor de arquivo "geralmente anônimo". O arquivo está na memória e existe enquanto pelo menos um descritor está aberto nele. No caso mais simples, você apenas obtém um descritor, grava dados para ele através de write , rewind lseek e, com o fdopen informe a libc :


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

O nome do arquivo passado com o primeiro argumento será exibido em um link simbólico em /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]' 

Solução 2: abra com o sinalizador O_TMPFILE


No Linux, começando com uma versão, ao criar um arquivo, você pode O_TMPFILE e o nome do diretório em vez do nome do arquivo. Como resultado, o arquivo, como costumava dizer um caractere literário (aproximadamente), parece estar lá, mas não existe ... não sei se os dados foram gravados no disco, mas provavelmente depende do sistema de arquivos (a propósito, ele deve suportar esse modo) . O arquivo ainda desaparece quando o último link é fechado, mas pode ser anexado à árvore de diretórios usando o 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); } 

Além da oportunidade de não sofrer com a nomeação de arquivos, torna possível preencher o arquivo, configurar os direitos etc., e depois vincular atomicamente à árvore de diretórios.


Exemplo (para ambas as abordagens)
 #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: manipulando erros de memória no espaço do usuário


Eu acho que não haverá algo muito novo em dizer que, em sistemas do tipo UNIX, os descritores de arquivos simplesmente não indicam nada. Por exemplo, no Linux, pode ser um soquete, canal, eventfd ou até um link para um programa ebpf. Mas talvez este exemplo ainda o surpreenda. No começo do artigo, falei sobre o fato de que as falhas de página são comuns para o kernel: swap, copy-on-write, isso é tudo ... Quando o processo do usuário falha, o SIGSEGV é enviado a ele. Até onde eu sei, retornar o controle do manipulador SIGSEGV gerado pelo kernel é um comportamento indefinido e, no entanto, existe a biblioteca GNU libsigsegv que generaliza os recursos de manipulação de erros de acesso à memória em várias plataformas, até Windows (licença ATTENTION: GPL, se não estiver pronto para distribua seu programa, não use libsigsegv) . Há pouco tempo, um método completamente documentado apareceu no Linux, chamado userfaultfd : usando a chamada de sistema com o mesmo nome, você abre um descritor de arquivo, lendo e gravando no qual estruturas especiais são comandos.


Com esse descritor de arquivo, você pode marcar um intervalo de endereços virtuais para o seu processo. Depois disso, no primeiro acesso a cada página marcada da memória, o fluxo adormecerá e a leitura do descritor de arquivo retornará informações sobre o que aconteceu. Depois disso, o manipulador preencherá a estrutura de resposta com um ponteiro para os dados que precisam ser usados ​​para inicializar a página "problem", o kernel o inicializará e ativará o encadeamento que girou. Nesse caso, supõe-se que exista um fluxo separado, cujas tarefas incluem ler comandos do descritor e emitir respostas. De um modo geral, outras informações userfaultfd podem ser obtidas através do userfaultfd , por exemplo, algumas notificações sobre uma alteração do cartão virtual do processo.


Exemplo de uso
 #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 

"A questão-chave da matemática: é tudo a mesma coisa?"


E se você precisar descobrir se esse descritor de arquivo se refere ao stdin ? Parece que if (fd == 0) ... - e é isso. Bem, 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 

Ops ... O identificador é como um, mas os aliases são diferentes. CRIU - Checkpoint / Restore In Userspace nos ajudará. , , . 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/pt475184/


All Articles