Panggilan sistem Linux yang mewah

ls / usr / share / man / man2 /


Apa yang dilihat oleh programmer ketika mulai bekerja dengan bahasa C? Dia melihat fopen , printf , scanf dan banyak lagi fungsi lainnya. Dia melihat segala macam open dan mmap - sepertinya, mengapa menyoroti mereka? Tetapi, tidak seperti grup pertama, kedua fungsi ini ketika dijalankan pada kernel Linux adalah pemanggilan sistem ( sebenarnya tidak , hampir tidak pernah pemanggilan sistem hanya dapat disebut sebagai fungsi, dan karenanya libc berisi pembungkus yang mengemas ulang argumen dan terkadang, seperti dalam kasus dengan open , ganti panggilan sistem lama dengan yang baru yang lebih umum). Secara umum, tidak seperti ribuan fungsi pustaka yang tersedia pada sistem GNU / Linux yang khas, antarmuka kernel memiliki jumlah titik masuk yang agak terbatas - dari urutan beberapa ratus, tetapi untuk ruang pengguna itu macet (misalnya, mengakses halaman yang hilang), untuk kernel - mode operasi standar.


Dalam artikel ini saya akan memberi tahu Anda beberapa fakta menarik menurut saya. Itu tidak akan memiliki rincian implementasi futex dan membosankan lainnya (mungkin). Ini akan menjadi penyebab utama reaksi saya, "Dan apa, mungkinkah begitu?!?".


Pertama, beberapa komentar pada teks sebelum kat: beberapa panggilan sistem memiliki antarmuka opsional dalam bentuk fungsi dari objek bersama yang disebut vDSO , yang dimasukkan kernel ke dalam proses. Ada beberapa fungsi seperti itu (sesuatu di sekitar empat, tetapi jumlah spesifik, tampaknya, mungkin tergantung pada versi kernel dan arsitektur) - ini semua jenis time dan time , yang, di satu sisi, sering digunakan, dan di sisi lain, mereka diimplementasikan tanpa beralih ke konteks kernel.


Kedua, SIGSEGV tidak selalu berakhir dengan crash proses, tetapi kita akan membicarakan ini userfaultfd ketika datang ke userfaultfd .


PENOLAKAN: Ingatlah bahwa menggunakan sebagian besar fitur yang disajikan di sini, Anda mengikat program Linux Anda. Ini normal jika dengan cara ini Anda melakukan optimasi opsional untuk jenis sistem tertentu atau fitur tambahan yang jika tidak maka tidak akan ada. Tetapi sebaliknya, saya sarankan untuk berpikir tentang cara membuat mundur lintas platform.


Pertanyaan umum


Sebagai permulaan, bagaimana semua ini bisa di-debug? Tentu saja strace akan membantu kami! Karena rangkaian panggilan sistem terbatas, dan sebagian besar strace tahu "oleh penglihatan", itu akan menunjukkan tidak hanya "pointer 0x12345678 telah dilewati", tetapi akan menjelaskan apa yang sedang ditransfer ke arah ini atau itu dalam struktur ini. Jika strace cukup segar, maka menggunakan opsi -k Anda dapat memintanya untuk mengeluarkan tumpukan panggilan.


Itu terlihat seperti ini
 $ 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] 

Benar, nama file sumber dan nomor baris tidak ditampilkan di sini. addr2line akan membantu addr2line (jika informasi ini pada prinsipnya ada, tentu saja).


Ada pertanyaan kedua: beberapa panggilan sistem tidak memiliki pembungkus di libc . Kemudian Anda dapat menggunakan pembungkus universal yang disebut syscall :


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

File adalah hal yang sangat aneh ...


Panggilan sistem bukan hanya cara untuk meminta kernel mengakses perangkat keras atas nama proses. Ini juga merupakan API universal yang dapat dimengerti oleh semua perpustakaan dalam sistem. Jadi, jika fungsionalitas yang Anda butuhkan tidak didukung di perpustakaan, itu mungkin akan bekerja secara otomatis jika Anda meminta kernel dengan benar. Selain itu, bagian dari "pengaturan" proses diwariskan oleh execve , sehingga Anda dapat mencoba melakukan ini tanpa kruk rumit dengan hanya membentuk negara dengan benar sebelum memulai proses (sesuatu seperti "mengapa mentransfer stderr ke file secara manual, jika Anda bisa membuka file dan melakukan FD # 2 untuk proses anak ").


Suatu kali, saya perlu mengurangi urutan paket jaringan dari sebuah file. Pada titik tertentu, jumlah kruk melebihi semua batas yang masuk akal, dan saya memutuskan bahwa tidak mungkin libpcap akan lebih rumit daripada yang saya tulis, terlebih lagi, itu standar, dan ada alat yang diterima secara umum untuk membuka file-file ini. Ternyata menggunakan libpcap untuk membaca dump hampir sama sulitnya dengan fopen untuk membaca file: Anda cukup membuka dump dengan pcap_(f)open_offline dan mengambil paket melalui pcap_next_ex . Itu saja! Yah, masih layak untuk menutup tempat sampah setelah selesai bekerja ...


Tapi inilah masalahnya: tampaknya libpcap tidak dapat membaca dari memori. Mungkin dia bisa, tentu saja, jika Anda menyelidiki hal itu, tetapi untuk "laboratorium" kami, kami akan membayangkan bahwa ia tidak bisa.


Jadi, contoh model: kita menunggu stdin urutan byte, setelah itu ada dump sejajar dengan 4 byte. Saya mengerti bahwa Anda dapat menggunakan input buffered dan beberapa ungetc (karena libpcap masih memerlukan FILE * ), tetapi dalam kasus umum, kita dapat membukanya saat bepergian, misalnya, atau perpustakaan dapat bekerja secara langsung dengan read / write .


Solusi 1: memfd_create


memfd_create sistem memfd_create memungkinkan memfd_create membuat deskriptor file "umumnya anonim". File ada di memori dan ada sementara setidaknya satu deskriptor terbuka di sana. Dalam kasus yang paling sederhana, Anda hanya mendapatkan deskriptor seperti itu, menulis data ke sana melalui write , mundur lseek , dan dengan fdopen beri tahu libc tentang hal itu:


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

Nama file yang diteruskan dengan argumen pertama akan ditampilkan dalam symlink di /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]' 

Solusi 2: buka dengan bendera O_TMPFILE


Di Linux, dimulai dengan versi, saat membuat file, Anda dapat O_TMPFILE dan nama direktori alih-alih nama file. Akibatnya, file tersebut, seperti yang biasa dikatakan oleh satu karakter sastra (kira-kira), tampaknya ada di sana, tetapi tidak ada ... Saya tidak tahu apakah data ditulis ke disk, tetapi mungkin tergantung pada sistem file (ngomong-ngomong, itu harus mendukung mode ini) . File masih hilang ketika tautan terakhir ditutup, tetapi bisa dilampirkan ke pohon direktori menggunakan 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); } 

Selain kesempatan untuk tidak menderita dengan penamaan file, memungkinkan untuk mengisi file, mengkonfigurasi hak, dll., Dan kemudian secara atom menghubungkan ke pohon direktori.


Contoh (untuk kedua pendekatan)
 #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: menangani kesalahan memori di userspace


Saya pikir tidak akan ada sesuatu yang sangat baru dalam mengatakan bahwa pada sistem seperti UNIX, deskriptor file tidak menunjukkan apa-apa. Misalnya, di Linux dapat berupa soket, pipa, eventfd, atau bahkan tautan ke program ebpf. Tapi mungkin contoh ini masih akan mengejutkan Anda. Pada awal artikel saya berbicara tentang fakta bahwa kesalahan halaman adalah hal yang umum untuk kernel: swap, copy-on-write, itu saja ... Ketika proses pengguna "meleset", SIGSEGV dikirim ke sana. Sejauh yang saya tahu, kontrol kembali dari handler SIGSEGV yang dihasilkan oleh kernel adalah perilaku yang tidak terdefinisi, dan meskipun demikian, ada perpustakaan libsigsegv GNU yang menggeneralisasi fitur penanganan kesalahan akses memori pada berbagai platform, bahkan Windows (PERHATIAN: lisensi GPL, jika tidak siap untuk mendistribusikan program Anda, jangan gunakan libsigsegv) . Belum lama ini, metode yang sepenuhnya didokumentasikan muncul di Linux, yang disebut userfaultfd : menggunakan pemanggilan sistem dengan nama yang sama, Anda membuka deskriptor file, membaca dan menulis yang struktur perintahnya adalah perintah.


Dengan deskriptor file seperti itu, Anda dapat menandai serangkaian alamat virtual untuk proses Anda. Setelah itu, pada akses pertama ke setiap halaman memori yang ditandai, aliran akan tertidur, dan membaca dari deskriptor file akan mengembalikan informasi tentang apa yang terjadi. Setelah itu, pawang akan mengisi struktur respons dengan pointer ke data yang perlu digunakan untuk menginisialisasi halaman "masalah", kernel akan menginisialisasi dan membangunkan aliran yang mengalir. Dalam hal ini, diasumsikan bahwa ada aliran terpisah, yang tugasnya termasuk membaca perintah dari deskriptor dan mengeluarkan jawaban. Secara umum, informasi lain userfaultfd dapat diperoleh melalui userfaultfd , misalnya, beberapa pemberitahuan tentang proses perubahan kartu virtual.


Contoh penggunaan
 #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 

"Pertanyaan kunci matematika: apakah semuanya sama" ©


Bagaimana jika Anda perlu mencari tahu apakah deskriptor file ini merujuk ke stdin ? Tampaknya if (fd == 0) ... - dan hanya itu. Baiklah, 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 

Ups ... Pegangannya agak mirip, tapi alias berbeda. CRIU - Checkpoint / Restore In Userspace akan membantu kami . , , . 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/id475184/


All Articles