Spíš je otázkou, jaký programátorský nebo programovací styl byste na ten socket ještě chtěl navěsit, pokud Vám nestačí poll() nebo blokující recv() ? Jako že prostě zaregistrovat callback?
Pak je třeba si uvědomit, co by znamenalo takový callback zavolat. Buď by Libc musela na pozadí mít nějaké vlákno, které ty callbacky bude obsluhovat. Nebo pro každé "probuzení callbacku" nějaké to vlákno odštípnout. A pokud ne plnotučné vlákno, tak aspoň nějaký ten tasklet... ale o těch jsem slyšel jenom v kernelu. Nebo by ty callbacky prováděl přímo scheduler? V zásadě pokud by se to mělo dělat nějak "lehkotonážně", jako že ten "callback prostě systém zavolá v nějakém svém kontextu", tak je třeba ten kontext napřed nějak vymyslet/zavést a zřejmě by pak z toho plynula nějaká omezení, co ten callback smí v takovém kontextu dělat.
Vlastně mě jedna analogie napadá: POSIXové signály v UNIXu. Pro signály se taky registrují callbacky. Ale stejně je zase logika taková, že signálů je velmi omezený počet a signál se doručuje vláknu jako celku. Dál tam tuším není jak předat nějaký další argument (uživatelský kontext) - jediným argumentem handleru je číslo signálu. Takže třeba pokud by Váš program v jednom vlákně obsluhoval víc socketů tím způsobem, že by si nechal posílat signály (a třeba by v hlavní smyčce prostě chrápal), tak by při příchodu signálu neměl jak zjistit, který socket zrovna dostal data - aniž by je všechny postupně olíznul. To už mi přijde přímočařejší, použít sockets API a jeho funkci poll().
Nebyl náhodou původní Winsock (ten bez čísla, před Winsock2.dll) řešený tak, že člověk dostával od socketů v zásadě Window Messages? Jak to řekl Linus o event-driven programování? "... It feels good, but it doesn't actually get anything done." ?
Ona i ta obsluha přerušení má svá omezení, podobně jako callback zvaný "signal handler", který si můžete zaregistrovat v user space.
Pravda v souvislosti se sockety se signály zřejmě nepoužívají, přinejmenším nikoli pro "užitečnou práci" socketu = lifrování dat. Pokud se někde mluví o signálech v souvislosti se sockety, tak obvykle v tom smyslu, že funkce recv() spinkající na socketu může být "mimořádně probuzena" signálem, pokud si pár věcí kolem takto nastavíte. Čili ne že by ten signál přišel od socketu jako upozornění na příchozí data. Spíš se to používá na mimořádné věci, jako třeba timeout ("už chrápeš nějak moc dlouho, dlouho nepřišla žádná data") nebo při ukončení programu, pokud se snažíte o graceful shutdown.
=> to už mi přijde víc košer, uloupnout si pro obsluhu každého socketu svůj vlastní plnotučný user-space thread, kde můžu většinu času sladce spinkat v recv() a pokud nějaká data přijdou, tak se nemusím omezovat v paletě "vyjadřovacích prostředků". A pokud potřebuju odvést nějakou práci a přitom zase rychle znovu čekat na další data, tak to roztrhnout na více vláken, přidat nějakého konzumenta, data předávat skrz frontu s mutexem apod. Režie vláken není nijak hrozná.
Ohledně Vaší otázky "jak to dělá process scheduler"... no obecně řadí procesy do dvou kategorií: "běžící" a "spící". Process scheduler žije v kernelu a má nějaké tabulky nebo fronty procesů/vláken/tasků, se kterými žongluje. Když se scheduler rozhoduje, kterému procesu dá veslo tentokrát, vybere si logicky z těch, které jsou cinknuté jako "runnable" (a na ten výběr je nějaký dost chytrý algoritmus, navíc laditelný). Jestli má CPU scheduler nějaký vychytaný rychlý index (hash/btree) kde klíčem je sledovaný "systémový zdroj" (třeba socket) a hodnotou pointer na "struct task", to se mě moc ptáte :-) Poměrně blízko u "pramene" je třeba funkce schedule() = usínající proces ji zavolá, a ona se vrátí v jiném procesu, kterému dal CPU scheduler zrovna "veslo" :-) Je vošajstlich vymýšlet moc složité rozhodovací chytristiky zrovna v tak exponované funkci jako je scheduler - spouští se hodně často (tuším spíš při každém přišedším IRQ, než jenom pravidelně podle timeru) takže třeba balancovat v scheduleru nějaký strom mi přijde možná za hranou.
Našel jsem
jedno online čtení které trochu popisuje reálie v Linuxu - bohužel odpověď zrovna na Vaši otázku tam možná přímo nebude. Ale vidím tam pár nápadů, odkud začít číst zdrojáky kernelu :-)