RSS Amplifier

Kladu odpory · Feb 8, 2026

Kladení odporů IV

0
Sign in to vote or save

This page did not load. You can still read it on the original site — the toolbar below keeps your place in the directory.

Co mě zaujalo z oblasti programování, AI a elektroniky při procházení internetů...

Další várka záložek mi tentokrát přinesla nezvyklou koncentraci čistého C a síťových stacků. Zatímco svět řeší, jak nacpat LLM do prohlížeče, někteří lidé stále píší TCP/IP stacky v čistém C – a já jim rozumím. Pojďme se podívat, co se stane, když odstraníme všechny abstrakce a zůstanou jen registry, bajty a holý křemík. Taky se podíváme na to, jestli bychom nemohli použít optická vlákna jako paměť, emulovat Node v prohlížeči anebo čím nahradit printf v kritických aplikacích.

Síťový stack v holém C?

Začneme tím nejtvrdším jádrem. Autor(ka) Gracia se rozhodl(a) napsat TCP/IP stack v C od nuly. Ne náhodou, ale skutečně rozumět tomu, proč se bajty posílají právě takhle. Rozdíl mezi uživatelem socketového API a autorem stacku je jako rozdíl mezi řidičem a automechanikem, který si vyrobil motor z hlíny.

Strašná práce, past vedle pasti. Nebudu to dělat!

První nástraha přišla hned na začátku: endianness. Když napíšete šestnáctibitovou hodnotu do bufferu a podíváte se na hex dump, je obráceně. Vždycky. Řešení není použít knihovní funkce, ale napsat si vlastní makra: #define htons(x) (((x) << 8) | ((x) >> 8)). To je jen začátek.

IP hlavička má pole délky ve slovech, ne v bajtech. Kontrolní součty (checksums) se počítají jako jedničkový doplněk (one’s complement) s přenosem zpět do sumy. A TCP? To je stavový automat, kde stavy jsou jen návrhy. Poslal jsem SYN, dostal SYN-ACK, poslal ACK – a pak jsem seděl a přemýšlel, co vlastně znamená ESTABLISHED. Znamená to, že máte začít posílat data, ale být připraven na retransmise a řídit posuvné okno (sliding window). Sekvenční čísla jsou uint32_t, která se točí. Porovnávat je jako seq_a > seq_b nefunguje, protože přetečení. Potřebujete makro z RFC: #define TCP_SEQ_LT(a,b) ((int32_t)((a)-(b)) < 0), které využívá signed overflow.

Do produkce to samosebou nenasadíte, ale spoustu věcí pochopíte. Když vidíte v Wiresharku červeně zvýrazněný paket s ACK bitem nastaveným na SYN paketu, víte přesně, co se pokazilo. Chápete, proč to zkolabuje, když je špatný pseudo-header pro TCP checksum. Není to magie, je to jen aritmetika s bajty na úrovni raw socketů (SOCK_RAW), kde musíte ručně skládat Ethernetové hlavičky a počítat offsety v paměti.

Z nuly k bootu

Teď se podíváme pod síťovou vrstvu – na samotný operační systém a jeho boot. Adriksh se pokusil napsat kernel v C, protože chtěl vědět, jaké to je, nabootovat do něčeho, co napsal on sám, v assembleru a kompilátoru C, aniž by tušil, jak se to přesně dělá. To není jako práce s tutoriálem, děti. To je archeologie.

První problém není “kernel v C”, ale předání řízení. Bootloader (GRUB) musí najít váš binární soubor na disku, zkontrolovat multiboot header s magickým číslem 0x1BADB002, a skočit na správnou adresu. Když je číslo špatné, obrazovka zůstane černá. Bez chybové hlášky. Nic. První výstup? Jasně, račte: přímý zápis do VGA paměti na adrese 0xB8000. Každý znak jsou dva bajty: ASCII kód a atribut (barva). Žádný printf, žádná konzole, jen ukazatelová aritmetika: *vga++ = 'H'; *vga++ = 0x0F;.

Pak přichází stack. V normálním programu dostanete zásobník od OS. Tady jste ten OS vy. Musíte rezervovat prostor, nastavit ESP na stack_top, a volat C. Když autor přidal lokální pole, systém triple-faultoval. Proč? Stack narazil do kódu. Není tu žádná ochrana, žádný signál SIGSEGV. Jen tiché přepsání paměti a/nebo tvrdý pád. Interrupt Descriptor Table (IDT) a Programmable Interrupt Controller (8259 PIC) jsou další vrstva – nastavit správné vektory, nezapomenout poslat EOI (End of Interrupt) na port 0x20, jinak systém zdechne. Stránkování (paging) je další kruh pekelný: nastavit CR0 registr, page tables musí být zarovnané, a když to selže, je to okamžitý reboot bez vysvětlení.

Sám jsem si tím před nějakými skoro pětadvaceti lety prošel. Napsal jsem si OS od nuly, se správou procesů, se správou paměti, pro procesory 386 a vyšší, co dokázal nakonec spustit i jednoduchý shell a pár utilit, crosscompilovaných ze zdrojáků pro Linux. Takže mám pochopení.

Když pak bootujete Linux a vidíte hlášky “Loading kernel...”, víte, co se děje. Vidíte handoff od GRUBu, první page tables, první timer tick.

Infrastruktura v jednom souboru

Od kernelu zpátky do userspace, ale stále na úrovni, kde se každý bajt počítá. Tentýž Adriksh napsal TCP load balancer v C, protože chtěl trasovat každý paket bez čtení 50 000 řádků konfigurace nginxu. Výsledek? 300 řádků C, které skutečně fungují v reálném provozu.

Jádro je jednoduché: poll() místo select() (kvůli limitům fd), dva socketové deskriptory (klient a backend), a smyčka, která čte z jednoho a píše do druhého. Ale ďábel je v detailu. Algoritmus round-robin musí přeskakovat mrtvé backendy, jinak každý třetí request visí. Health checky běží v samostatném vlákně, každých 5 sekund se pokouší o connect() s timeoutem 2 sekundy. SO_RCVTIMEO a SO_SNDTIMEO jsou kritické, bez nich visí dead connectiony navždy. A pak je tu stav CLOSE_WAIT, kdy se akumulují spojení, dokud nenastane restart. Výkon? Stovky spojení na vlákno. Thread-per-connection model je líný, ale funkční.

DNS není magie, jen struktury a bajty

Do třetice stejný autor. Zatímco TCP load balancer řeší spojení, DNS řeší jména.

Adriksh postavil resolver od nuly a opět si potvrdil, že DNS není distribuovaná databáze, která nějak “ví”, kde je Google. Je to jen starý svérázný protokol z roku 1987, kde šetříte každý bajt.

UDP socket na portu 53 je jednoduchý. Ale formát paketů? Domain names nejsou řetězce s nulovým terminátorem. Jsou to délkou prefixované labely: 6google3com0. Každý bajt říká, kolik bajtů následuje. A komprese jmen pomocí ukazatelů (0xC0 | offset) znamená rekurzivní parsování s limitem hloubky, abyste se nezacyklili. Hlavička je 12 bajtů: ID, flags (query/response, recursion desired), počty otázek a odpovědí. Bitové pole flags musíte poskládat ručně – 0x8180 znamená standardní odpověď bez chyby. Caching je jen časová matematika: uložíte záznam s time(NULL) + TTL a při lookupu kontrolujete, jestli now < expires.

Event loop pod kapotou

Chcete si postavit event loop v C, protože vaše aplikace v Node.js žere paměť a vy nevíte, proč to dělá? Koukněte na následující článek o vytváření event loopu.

Event loop není složitý, je to jen while(1) s epoll_wait().

Na Linuxu vytvoříte epoll instanci (epoll_create1), nastavíte socket na non-blocking (O_NONBLOCK), a registrujete ho pomocí epoll_ctl s událostí EPOLLIN. Pak voláte epoll_wait s timeoutem -1 (čekání navěky). Kernel uspí celý proces. Když přijdou data na socket nebo stdin, OS probudí váš kód.

To je celé. Žádné vlákna, žádný garbage collector, jen spící proces. Pro časovače se používá timerfd_create – vytvoříte file descriptor pro čas, nastavíte interval pomocí itimerspec, a i ten se chová jako socket, který se stane ready po uplynutí času.

Srovnání s Node.js: libuv dělá přesně tohle, jen s vrstvou abstrakce navíc a obsluhou různých platforem (kqueue na BSD, IOCP na Windows).

Read more

Read on adent.substack.com

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.