deniz.in

Piyasalar

Hava durumu

Hava durumu yükleniyor

· kaynak Hacker News – Front Page (native)

Epoll ve kqueue açıklaması: yüksek eşzamanlılıklı sunucuların arkasındaki çekirdek API'leri

Hacker News'te öne çıkan The Coding Gopher imzalı derinlemesine bir inceleme, epoll ve kqueue'nun çağ başına descriptor taramalarını tek seferlik çekirdek kaydıyla nasıl değiştirdiğini ve modern eşzamanlı sunucuları nasıl mümkün kıldığını anlatıyor.

Epoll ve kqueue açıklaması: yüksek eşzamanlılıklı sunucuların arkasındaki çekirdek API'leri

The Coding Gopher'ın uzun soluklu denemesi "Epoll and Kqueue: How Operating Systems Learned to Wait Efficiently", Hacker News'in ana sayfasına ulaştı. Yazı, neredeyse her yüksek performanslı sunucunun sessizce dayandığı işletim sistemi mekanizmasını ele alıyor: Linux üzerindeki epoll ve BSD tabanlı sistemler — macOS dahil — üzerindeki kqueue hazırlik-bildirim arayüzleri.

Beklemek pahalı olan kısımdır

Deneme sorunu yeniden çerçeveleyerek başlıyor: ağ I/O'sunun zor kısmı veriyi taşımak değil, üzerinde işlem yapmaya değer bir şeyin olup olmadığını bilmektir. Meşgul bir sunucu binlerce açık bağlantı tutabilir, ancak herhangi bir anda yalnızca bir avuç kadarı aktiftir.

Çekirdek seviyesinde okumalar bloklayıcıdır — verisi olmayan bir socket'ten veri isteyin, çekirdek veri gelene kadar çağırıcıyı bekletir. Bu tek bağlantıda zararsızdır ama on bin bağlantıda sürdürülemez. Bariz çözüm olan bağlantı başına bir thread modeli, thread oluşturma, context switching ve bellek yükünün bileşik ağırlığı altında çöker.

Erken Unix'in yanıtı select, sonra ise poll oldu; bunlar bir programın çekirdeğe bir dosya descriptor listesi verip hangilerinin hazır olduğunu sormasını sağlıyordu. Catch şuydu: her çağrı, tek bir descriptor aktif olsa bile çekirdeğin tüm listeyi baştan sona taramasını zorunlu kılar; böylece maliyet gerçek işe değil bağlantı sayısına göre ölçeklenir. Denemenin ifadesiyle, beklemek hiçbir zaman yavaş olan kısım değildi — kontrol etmekti.

Bir kez kaydet, bildirim al

Epoll ve kqueue aynı tersine çevirme üzerine kuruludur: çekirdeğe neyi önemseyeceğinizi tekrar tekrar sormak yerine, bunu bir kez söylersiniz ve bir şey değiştiğinde çekirdek sizi haberdar eder. Beklemenin maliyeti böylece kapasiteye değil aktiviteye bağlı olur.

Epoll'un içi

Linux'ta epoll_create1 kalıcı bir çekirdek nesnesi kurar — bir epoll instance'ı — ve kendi dosya descriptor'ı ile tanımlanır. Descriptor'lar epoll_ctl ile eklenir, değiştirilir veya kaldırılır; her biri okunabilirlik, yazılabilirlik veya hata koşulları gibi ilgilenilen olaylarla birlikte kaydedilir. Çekirdek tam kümeyi interest list olarak tutar; raporlanacak bir şeyi olan alt küme ise ready list'i oluşturur. epoll_wait çağrısı olaylar varana dek uyur ve yalnızca etkilenen descriptor'ları döndürür; kayıtlı her şeyi taramaz. Bir dosya descriptor'ını kapatmak, onun üyesi olduğu her epoll instance'ından da kaldırır.

Epoll iki teslim modu sunar. Level-triggered modda, koşul sürdüğü sürece olaylar gelmeye devam eder — socket'te veriyi okumadan bırakırsanız size tekrar tekrar hatırlatılır. Edge-triggered modda ise bildirim yalnızca hazır-değil'den hazır'a geçişte tetiklenir; bunu kaçırırsanız veya buffer'ı tamamen boşaltmazsanız o descriptor bir daha asla raporlanmayabilir. Edge triggering daha verimlidir ama disiplinli non-blocking kod gerektirir; yazar bunu ince bug'lar için verimli bir zemin olduğu konusunda uyarıyor.

Kqueue fikri daha ileri taşıyor

BSD sistemler aynı kavramı daha genişletecek şekilde ileri götürür. Bir kqueue, çekirdeğin yönettiği bir olay kuyruğudur ve kevent change istekleri kalıcı ilgi alanları kaydeder — yalnızca socket hazırliği değil, dosya değişiklikleri, süreç yaşam döngüsü olayları, sinyaller ve zamanlayıcılar da tek bir arayüzden. Epoll kasıtlı olarak minimal ve özelleşmişken, kqueue geneldir ve ifade gücü yüksektir; birincil soyutlama olarak yalnız I/O hazırliğini değil, olayların kendisini ele alır.

Pull'dan push'a

Daha derindeki kayma mimariseldir. Select ve poll ile kullanıcı alanı durumu çekirdekten defalarca çeker; epoll ve kqueue ile çekirdek olayları gerçekleştiği anda dışarı iter. Boşta bekleyen bağlantılar neredeyse bedava olur ve işi aktivite yönlendirir. Deneme bunun bir göstergesi olarak Go'nun runtime'ına işaret eder: conn.Read gibi bir çağrı bloklayıcı görünür, ancak runtime socket'i non-blocking moda alır, epoll veya kqueue ile kaydeder ve goroutine'i park eder — veriyi bekleyen hiçbir işletim sistemi thread'i yoktur. Çekirdek hazırliği bildirdiğinde runtime goroutine'i uyandırır ve kaldığı yerden tam olarak devam eder; Go büyük ölçekli eşzamanlılığı programcıya async tesisatı göstermeden böyle sağlar.

Neden önemli

Denemeye göre neredeyse her modern yüksek performanslı sunucu, doğrudan ya da dolaylı olarak bu mekanizmalara dayanır: on binlerce bağlantı tutmayı, I/O'yu küçük bir thread havuzuna çoklamakı, callback-yoğun kod olmadan olay güdümlü runtime'lar kurmayı ve mantıksal eşzamanlılığı fiziksel thread'lerden ayırmayı pratik kılarlar. Bu katmanın çok üstünde çalışan mühendisler için bunu anlamak, hem bugünün sunucularının neden böyle ölçeklendiğini hem de klasik hata modlarının — edge-triggered starvation bunlar arasındadır — nereden geldiğini açıklar. Yazı, ağ servislerinde hata ayıklayan veya runtime tasarlayan herkes için yararlı bir başvuru kaynağı; ayrıca en büyük kazanımların çoğu zaman daha hızlı işlemekten değil, boşta beklemek için ödeme yapmamaktan geldiğinin bir hatırlatıcısı.

  • #linux
  • #bsd
  • #kernel
  • #networking
  • #systems-programming

İlgili yazılar