Seluruh kebenaran tentang linux epoll

Ya, atau hampir semua ...



Saya percaya bahwa masalah di Internet modern adalah meluap-luapnya informasi dengan kualitas yang berbeda. Menemukan materi tentang topik yang menarik bukanlah masalah, masalahnya adalah membedakan materi yang baik dari materi yang buruk jika Anda memiliki sedikit pengalaman di bidang ini. Saya mengamati gambar ketika ada banyak informasi ikhtisar "di atas" (hampir pada tingkat penghitungan sederhana), sangat sedikit artikel mendalam dan tidak ada artikel transisi dari sederhana ke kompleks. Namun demikian, pengetahuan tentang fitur-fitur mekanisme tertentu yang memungkinkan kita untuk membuat pilihan berdasarkan informasi selama pengembangan.


Dalam artikel ini saya akan mencoba mengungkap apa perbedaan mendasar antara epoll dan mekanisme lainnya, apa yang membuatnya unik, serta mengutip artikel yang hanya perlu Anda baca untuk lebih memahami kemungkinan dan masalah epoll .


Siapa pun dapat menggunakan kapak, tetapi dibutuhkan pejuang sejati untuk membuatnya melodi melee.

Saya menganggap pembaca sudah terbiasa dengan epoll , setidaknya membaca halaman manual. Cukup telah ditulis tentang epoll , polling , pilih sehingga setiap orang yang berkembang di Linux telah mendengarnya setidaknya sekali.


Banyak sekali


Ketika orang berbicara tentang epoll pada dasarnya saya mendengar tesis bahwa "kinerjanya lebih baik ketika ada banyak deskriptor file".


Hanya ingin mengajukan pertanyaan - berapa banyak? Berapa banyak koneksi yang dibutuhkan, dan yang paling penting, dalam kondisi apa epoll akan mulai memberikan keuntungan kinerja yang nyata?


Bagi mereka yang mempelajari epoll (ada cukup banyak bahan termasuk artikel ilmiah), jawabannya jelas - lebih baik jika dan hanya jika jumlah senyawa "menunggu suatu peristiwa" secara signifikan melebihi jumlah "siap untuk diproses". Tanda kuantitas, ketika keuntungan menjadi sangat signifikan sehingga tidak ada urin untuk mengabaikan fakta ini, senyawa 10k dianggap [4].


Asumsi bahwa sebagian besar koneksi akan ditangguhkan berasal dari logika suara dan pemantauan beban server yang sedang digunakan aktif.


Jika jumlah senyawa aktif berusaha untuk jumlah total, tidak akan ada keuntungan tidak akan ada perolehan yang signifikan, keuntungan yang signifikan disebabkan dan hanya karena epoll mengembalikan hanya deskriptor yang membutuhkan perhatian, dan polling mengembalikan semua deskriptor yang ditambahkan untuk observasi.


Jelas, dalam kasus terakhir, kami menghabiskan waktu melintasi semua deskriptor + overhead menyalin berbagai peristiwa dari kernel.


Memang, dalam pengukuran kinerja awal, yang dilampirkan pada tambalan [9], titik ini tidak ditekankan dan orang hanya bisa menebak dengan adanya utilitas deadcon yang disebutkan dalam artikel (sayangnya, kode utilitas pipetest.c hilang). Di sisi lain, dalam sumber lain [6, 8] sangat sulit untuk tidak memperhatikan, karena fakta ini praktis mencuat.


Pertanyaan segera muncul, tetapi bagaimana sekarang jika tidak direncanakan untuk melayani sejumlah deskriptor file epoll , seolah-olah, dan tidak diperlukan?


Terlepas dari kenyataan bahwa epoll awalnya dibuat khusus untuk situasi seperti itu [5, 8, 9], ini jauh dari satu-satunya perbedaan antara epoll .


EPOLLET


Untuk memulainya, kita akan melihat perbedaan antara pemicu tepi dan pemicu level. Ada pernyataan yang sangat bagus tentang topik ini dalam artikel Edge Triggered Vs Level Pemicu terputus - Venkatesh Yadav :


Gangguan di level, itu seperti anak kecil. Jika bayi menangis, Anda harus menyerahkan semua yang Anda lakukan dan lari ke bayi untuk memberinya makan. Lalu Anda memasukkan bayi kembali ke boks. Jika dia menangis lagi, Anda tidak akan meninggalkannya di mana pun, tetapi Anda akan mencoba menenangkannya. Dan sementara anak itu menangis, Anda tidak akan meninggalkannya sejenak, dan akan kembali bekerja hanya ketika dia tenang. Tetapi katakanlah kita pergi ke kebun (gangguan berhenti) ketika anak mulai menangis, lalu ketika Anda kembali ke rumah (gangguan dihidupkan) hal pertama yang Anda lakukan adalah pergi memeriksa anak. Tetapi Anda tidak akan pernah tahu bahwa dia menangis saat Anda berada di taman.

Gangguan di bagian depan seperti pengasuh elektronik untuk orang tua tuli. Segera setelah anak mulai menangis pada perangkat, lampu merah menyala dan menyala sampai Anda menekan tombol. Sekalipun anak mulai menangis, tetapi dengan cepat berhenti dan tertidur, Anda akan tetap tahu bahwa anak itu sedang menangis. Tetapi jika dia mulai menangis, dan Anda menekan tombol (konfirmasi gangguan), cahaya tidak akan menyala bahkan jika dia terus menangis. Level suara di ruangan harus turun dan kemudian naik lagi sehingga cahaya menyala.

Jika epoll (serta jajak pendapat / pilih ) tidak dikunci dalam perilaku yang dipicu tingkat jika deskriptor dalam keadaan yang ditentukan dan akan dianggap aktif sampai keadaan ini dihapus, tepi-dipicu dibuka hanya dengan mengubah keadaan saat ini yang diperintahkan.


Hal ini memungkinkan Anda untuk menangani acara nanti, dan tidak segera setelah diterima (hampir analogi langsung dengan bagian atas dan bawah setengah dari penangan interrupt).


Contoh spesifik dengan epoll:


Level dipicu


  • handle ditambahkan ke epoll dengan flag EPOLLIN
  • epoll_wait () mencekal sambil menunggu acara
  • menulis ke deskriptor file 19 byte
  • epoll_wait () buka dengan acara EPOLLIN
  • kami tidak melakukan apa pun dengan data yang datang
  • epoll_wait () membuka lagi dengan acara EPOLLIN

Dan ini akan berlanjut sampai kita benar-benar menghitung atau mereset data dari deskriptor.


Edge dipicu


  • handle ditambahkan ke epoll dengan flag EPOLLIN | EPOLLET
  • epoll_wait () mencekal sambil menunggu acara
  • menulis ke deskriptor file 19 byte
  • epoll_wait () buka dengan acara EPOLLIN
  • kami tidak melakukan apa pun dengan data yang datang
  • epoll_wait () diblokir menunggu acara baru
  • tulis 19 byte lagi ke deskriptor file
  • epoll_wait () membuka dengan acara EPOLLIN baru
  • epoll_wait () diblokir menunggu acara baru

contoh sederhana: epollet_socket.c


Mekanisme ini dirancang untuk mencegah kembalinya epoll_wait () karena suatu peristiwa yang sudah diproses.


Jika, dalam kasus level, saat memanggil epoll_wait (), kernel memeriksa untuk melihat apakah fd dalam keadaan ini, kemudian edge melewatkan pemeriksaan ini dan segera memasukkan proses panggilan ke kondisi tidur.


EPOLLET sendiri adalah yang membuat epoll O (1) multiplexer untuk berbagai acara.


Penting untuk mengklarifikasi tentang EAGAIN dan EPOLLET - rekomendasi dengan EAGAIN bukan untuk memperlakukan byte-stream, bahaya dalam kasus yang terakhir hanya muncul jika Anda tidak membaca deskriptor hingga akhir, dan data baru tidak datang. Maka ekor akan menggantung di deskriptor, tetapi Anda tidak akan menerima pemberitahuan baru. Dengan accept (), situasinya hanya berbeda, di sana Anda harus melanjutkan sampai accept () mengembalikan EAGAIN , hanya dalam hal ini operasi yang benar dijamin.


// TCP socket (byte stream) //  fd    EPOLLIN      int len = read(fd, buffer, BUFFER_LEN); if(len < BUFFER_LEN) { //   } else { //         //  -       epoll_wait, //      } 

  // accept //  listenfd    EPOLLIN      event.events = EPOLLIN | EPOLLERR; epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event); sleep(5); //       >1  //   while(epoll_wait()) { newfd = accept(listenfd, ...); //      //        //  epoll_wait    listenfd    } //   while(epoll_wait()) { while((newfd = accept(...)) > 0) { //  -  } if(newfd == -1 && errno = EAGAIN) { //       //       } } 

Dengan properti ini, cukup kelaparan saja sudah cukup:


  • paket datang ke deskriptor
  • baca paket ke buffer
  • paket lain datang
  • baca paket ke buffer
  • datang sebagian kecil
  • ...

Dengan demikian , kami tidak akan segera menerima EAGAIN , tetapi kami mungkin tidak akan menerima sama sekali.


Dengan demikian, deskriptor file lainnya tidak menerima waktu untuk diproses, dan kami sibuk membaca data kecil yang datang secara tiba-tiba.


guntur kutu buku kawanan


Untuk menuju ke bendera terakhir, Anda perlu memahami mengapa itu benar-benar dibuat dan salah satu masalah apa yang muncul bagi pengembang dengan evolusi teknologi dan perangkat lunak.


Gemuruh masalah kawanan


Guntur masalah kawanan

Bayangkan sejumlah besar proses menunggu acara. Jika suatu peristiwa terjadi, mereka akan dibangunkan dan perjuangan untuk sumber daya akan dimulai, meskipun hanya satu proses yang diperlukan yang akan menangani proses lebih lanjut dari peristiwa tersebut. Sisa proses akan tidur lagi.

Terminologi IT - Vasily Alekseenko

Dalam hal ini, kami tertarik dengan masalah accept () dan read () yang didistribusikan melalui stream bersama dengan epoll .


terima


Sebenarnya, dengan panggilan pemblokiran untuk menerima (), tidak ada masalah untuk waktu yang lama. Kernel akan berhati-hati bahwa hanya satu proses telah dibuka untuk acara ini, dan semua koneksi masuk serial.


Tetapi dengan epoll, trik seperti itu tidak akan berhasil. Jika kami telah mendengarkan () dibuat pada soket non-pemblokiran, ketika koneksi dibuat, semua epoll_wait () akan menunggu acara dari deskriptor ini.


Tentu saja, accept () hanya dapat melakukan satu utas, sisanya akan menerima EAGAIN , tetapi ini adalah pemborosan sumber daya.


Selain itu, EPOLLET juga tidak membantu kami, karena kami tidak tahu persis berapa banyak koneksi yang ada dalam antrian koneksi ( backlog ). Seperti yang kita ingat, ketika menggunakan EPOLLET , pemrosesan soket harus dilanjutkan sampai kembali dengan kode kesalahan EAGAIN , sehingga ada kemungkinan semua menerima () akan diproses oleh satu utas dan sisanya tidak akan berfungsi.


Dan ini sekali lagi membawa kita ke situasi di mana aliran tetangga terbangun dengan sia-sia.


Kami juga bisa mendapatkan jenis kelaparan yang berbeda - kami hanya akan memiliki satu utas dimuat, dan sisanya tidak akan menerima koneksi untuk diproses.


EPOLLONESHOT


Sebelum versi 4.5, satu-satunya cara yang benar untuk memproses epoll yang didistribusikan menjadi deskriptor listen () yang tidak menghalangi dengan panggilan accept () berikutnya adalah dengan mengatur flag EPOLLONESHOT , yang sekali lagi membuat kami menerima () hanya diproses dalam satu utas pada satu waktu.


Singkatnya - jika EPOLLONESHOT digunakan , peristiwa yang terkait dengan deskriptor tertentu akan diaktifkan hanya sekali, setelah itu perlu untuk mengokang ulang bendera menggunakan epoll_ctl () .


EPOLLEXKLUSIF


Di sini EPOLLEXCLUSIVE dan level-triggered datang untuk membantu kami.


EPOLLEXCLUSIVE membuka satu epoll_wait () yang tertunda pada satu waktu untuk satu acara.


Skema ini cukup sederhana (sebenarnya tidak):


  • Kami memiliki N utas menunggu acara koneksi
  • Klien pertama terhubung ke kami
  • Thread 0 akan tersebar dan mulai memproses, utas lainnya akan tetap diblokir
  • Klien kedua terhubung ke kami, jika utas 0 masih sibuk dengan pemrosesan, maka utas 1 tidak dikunci
  • Kami melanjutkan lebih jauh sampai kumpulan utas habis (tidak ada yang mengharapkan acara di epoll_wait () )
  • Klien lain terhubung ke kami
  • Dan prosesnya akan menerima utas pertama, yang akan memanggil epoll_wait ()
  • Utas kedua akan menerima klien kedua, yang akan memanggil epoll_wait ()

Dengan demikian, semua perawatan didistribusikan secara merata di seluruh aliran.


 $ ./epollexclusive --help -i, --ip=ADDR specify ip address -p, --port=PORT specify port -n, --threads=NUM specify number of threads to use #    -  n*8 -t, --thunder not adding EPOLLEXCLUSIVE #     thunder herd -h, --help prints this message $ sudo taskset -c 0-7 ./epollexclusive -i 10.56.75.201 -p 40000 -n 8 2>&1 

contoh kode: epollexclusive.c (hanya akan berfungsi dengan versi kernel dari 4.5)


Kami mendapatkan model pre-fork di epoll. Skema ini berlaku untuk koneksi TCP jangka pendek .


baca


Tetapi dengan read () dalam hal byte-streaming, EPOLLEXCLUSIVE , seperti EPOLLET, tidak akan membantu kami.


Untuk alasan yang jelas, tanpa EPOLLEXCLUSIVE kita tidak dapat menggunakan pemicu level sama sekali. Dengan EPOLLEXCLUSIVE, semuanya tidak lebih baik, karena kita bisa mendapatkan paket yang tersebar di stream, selain itu dengan urutan byte yang tidak diketahui telah tiba.


Dengan EPOLLET, situasinya sama.


Dan di sini EPOLLONESHOT dengan reinitialization setelah menyelesaikan pekerjaan akan menjadi jalan keluar. Jadi, segera setelah satu utas akan berfungsi dengan deskriptor dan buffer file ini:


  • handle ditambahkan ke epoll dengan flag EPOLLONESHOT | EPOLLET
  • menunggu di epoll_wait ()
  • baca dari socket ke buffer sampai read () mengembalikan EAGAIN
  • menginisialisasi ulang dengan flag EPOLLONESHOT | EPOLLET

struct epoll_event


 typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t; struct epoll_event { uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ }; 

Item ini mungkin satu-satunya di artikel saya IMHO pribadi saya. Kemampuan untuk menggunakan pointer atau angka sangat membantu. Misalnya, menggunakan pointer saat menggunakan epoll memungkinkan Anda melakukan trik seperti ini:


 #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) struct epoll_client { /** some usefull associated data...*/ struct epoll_event event; }; struct epoll_client* to_epoll_client(struct epoll_event* event) { return container_of(event, struct epoll_client, event); } struct epoll_client ec; ... epoll_ctl(efd, EPOLL_CTL_ADD, fd, &ec.e); ... epoll_wait (efd, events, 1, -1); struct epoll_client* ec_ = to_epoll_client(events[0].data.ptr); 

Saya pikir semua orang tahu dari mana teknik ini berasal.


Kesimpulan


Saya harap kami bisa membuka topik epoll . Mereka yang ingin menggunakan mekanisme ini secara sadar, hanya perlu membaca artikel dalam daftar referensi [1, 2, 3, 5].


Berdasarkan materi ini (dan bahkan lebih baik membaca materi dari daftar referensi), Anda dapat membuat server pre-fork (pembuatan proses awal) multi-threaded (tanpa proses pemblokiran) atau merevisi strategi yang ada berdasarkan sifat khusus epoll () ).


epoll adalah salah satu mekanisme unik yang perlu diketahui oleh orang-orang yang memilih jalur pemrograman Linux, karena mereka memberikan keuntungan serius dibandingkan sistem operasi lain), dan, mungkin, akan menolak cross-platforming untuk kasus tertentu (biarkan bekerja hanya di Linux tetapi akan melakukannya dengan baik).


Penalaran tentang "kekhususan" masalah


Sebelum seseorang berbicara tentang kekhasan bendera dan pola penggunaan ini, saya ingin bertanya:


"Tapi bukankah kita mencoba membahas spesifisitas untuk mekanisme yang diciptakan untuk tugas-tugas spesifik pada awalnya [9, 11]? Atau apakah kita bahkan melayani koneksi 1k adalah tugas harian bagi seorang programmer?"


Saya tidak mengerti konsep "tugas khusus", ini mengingatkan saya pada semua jenis tangisan tentang kegunaan dan kesia-siaan dari berbagai disiplin ilmu yang diajarkan. Membiarkan diri kita untuk bernalar dengan cara ini, kita memberikan kepada diri kita sendiri hak untuk memutuskan bagi orang lain informasi mana yang berguna bagi mereka dan mana yang tidak berguna, sementara, ingatlah, tidak berpartisipasi dalam proses pendidikan secara keseluruhan.


Untuk skeptis, beberapa tautan:


Meningkatkan kinerja dengan SO_REUSEPORT di NGINX 1.9.1 - VBart
Belajar dari Unicorn: kawanan accept () bergemuruh tidak bermasalah - Chris Siebenmann
Serializing accept (), AKA Thundering Herd, AKA the Zeeg Problem - Roberto De Ioris
Bagaimana cara mode EPOLLEXCLUSIVE epoll berinteraksi dengan pemicu level?


Referensi


  1. Pilih rusak secara fundamental - Marek
  2. Epoll pada dasarnya rusak 1/2 - Marek
  3. Epoll secara fundamental rusak 2/2 - Marek
  4. Masalah C10K - Dan Kegel
  5. Poll vs Epoll, sekali lagi - Jacques Mattheij
  6. epoll - Fasilitas notifikasi acara I / O - The Mann
  7. Metode untuk kegilaan epoll - Cindy Sridharan

Tingkatan yang dicapai


  1. https://www.kernel.org/doc/ols/2004/ols2004v1-pages-215-226.pdf
  2. http://lse.sourceforge.net/epoll/index.html
  3. https://mvitolin.wordpress.com/2015/12/12/endurox-testing-epollexclusive-flag/

Evolusi epoll


  1. https://lwn.net/Articles/13918/
  2. https://lwn.net/Articles/520012/
  3. https://lwn.net/Articles/520198/
  4. https://lwn.net/Articles/542629/
  5. https://lwn.net/Articles/633422/
  6. https://lwn.net/Articles/637435/

Catatan tambahan


Banyak terima kasih kepada Sergey ( dlinyj ) dan Peter Ovchenkov untuk diskusi, komentar, dan bantuan yang berharga!

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


All Articles