Portál AbcLinuxu, 6. května 2025 23:13
Aktuální verze jádra: 3.4-rc5. Citáty týdne: Jon Masters, Paul McKenney, Linus Torvalds. Užitečná dokumentace k perf. Opravování TCP spojení. Oprava neopravitelného ABI v autofs.
Aktuální vývojová verze jádra je 3.4-rc5 vydaná 29. dubna. A stejně jako v případě -rc5 docela dost změn dorazilo v pátek (a pár dalších ještě včera). A neuklidňuje se to, spíš naopak. -rc5 má o téměř 50 % commitů víc než -rc4. To není dobré. Tak jako tak přicházejí hlavně opravy, krátký přehled změn najdete v oznámení.
Stabilní aktualizace: verze 3.0.30 a 3.3.4 vyšly 27. dubna s obvyklou hromadou oprav.
Když vám teď povím, že jsem měl včera noční můru, ve které se mě r12 (IP registr na ARM) snažil zabít, tak věřte, že si nevymýšlím. Asi jsem to s hrabáním se v disassembly jádra o víkendu trochu přehnal.
-- Jon Masters
No zajisté, mým cílem bylo, aby to bylo „méně ujeté“, nikoliv „neujeté“. A „neujetý“ popis zůstává dlouhodobou snahou spíše než krátkodobým cílem.
-- přiměřeně ujetý Paul McKenney
Co to s těma Linusama lidé mají? Už i v Googlu jednoho mají. Jednoho dne si vezmu meč, začnu pobíhat okolo a křičet "Může tu být jen jediný Linus!"
Býval jsem něco _extra_. Fňuk.
Pro ty z vás, kteří by se rádi dozvěděli, jak používat subsystém perf, zveřejnil Google rozsáhlý tutorial napsaný Stephanem Eranianem. U zájemců si tento odkaz asi vyslouží zařazení do záložek.
Migrace běžícího kontejneru z jednoho fyzického hostitele na jiného je na mnoha úrovních docela záludnost. Je to ještě složitější, pokud má kontejner aktivní síťová spojení k procesům mimo kontejner. Je přirozené požadovat, aby tato spojení následovala kontejner na nového hostitele, nejlépe, kdyby si vzdálená strana ani nevšimla, že se něco změnilo, jenže síťový stack Linuxu nebyl k tomuto napsán. I tak to ale vypadá, že transparentní přesídlení síťových spojení bude podporováno od jádra 3.5, díky patchům pro opravu TCP spojení od Pavla Emelyanova.
Prvním krokem nezbytným pro přesun TCP spojení je shromáždění všech dostupných informací o aktuálním stavu. Většina těchto informací je už teď dostupná z uživatelského prostoru, z /proc a /sys lze vytáhnout adresu a port vzdálené strany, velikosti přijímacích a odesílacích front, sekvenční čísla TCP a řadu dalších parametrů vyjednaných mezi oběma stranami. Zbývá ještě ale pár věcí, které je nutné zjistit, než je možné provést úkon; k tomu je zapotřebí dodatečná pomoc od jádra.
S Pavlovým patchem je možné tuto pomoc získat, pokud jste dostatečně privilegovaným procesem. Aby bylo možné zkoumat vnitřnosti aktivního síťového spojení, tak musí uživatelský prostor nejprve přepnout soket do nového „opravářského režimu“. To se dá udělat pomocí volání setsockopt() s volbou TCP_REPAIR. Pro nastavení tohoto režimu je třeba mít capability CAP_NET_ADMIN, soket také musí být buď připojený (established) nebo uzavřený (closed). Jakmile je soket v tomto režimu, dá se s ním pracovat několika způsoby.
Jedním způsobem je číst odesílací a přijímací fronty. Odesílací fronta obsahuje data, která ještě nebyla úspěšně odeslána druhé straně; tato data je nutné přesunout spolu se spojením, aby došlo k jejich odeslání z nového místa. Přijímací fronta zase obsahuje data, která ještě nenačetla přesouvaná aplikace; tato data je rovněž nutné přesunout, aby na aplikaci čekala na novém hostiteli. Získání obsahu těchto front je možné pomocí dvou kroků: 1) zavolat setsockopt(TCP_REPAIR_QUEUE) s buď TCP_RECV_QUEUE nebo TCP_SEND_QUEUE a 2) zavolat recvmsg() pro přečtení obsahu zvolené fronty.
Ukázalo se, že je tu už jen jedna další informace, kterou nejde získat z uživatelského prostoru: maximální hodnota MSS (maximální velikost segmentu) vyjednaná mezi oběma konci při vytváření spojení. Aby bylo možné hodnotu získat, tak Pavlův patch mění sémantiku volby soketu TCP_MAXSEG (pro getsockopt()) – když je spojení v opravářském režimu, tak je vracena maximální MSS místo aktuální aktivní hodnoty.
Poslední věcí je to, že pokud je spojení ukončeno v tomto režimu, nejsou druhé straně zaslána žádná upozornění. Žádný RST nebo FIN, takže druhá strana nebude tušit, že se něco děje.
Pak je tu obnova spojení na novém hostiteli. Toho dosáhneme vytvořením nového socketu a okamžitým přepnutím do opravářského režimu. Pak je možné soket navázat na správný port, obvyklé kontroly portů jsou v tomto režimu vypnuty. Opět se použije setsockopt(TCP_REPAIR_QUEUE), ale tentokrát se obsah front obnoví pomocí sendmsg().
Dalším důležitým úkolem je obnovit sekvenční čísla. Tato čísla jsou při připojení obvykle generována náhodně, ale to při přesunu spojení nelze dělat. Tato čísla jde nastavit pomocí dalšího volání setsockopt(), tentokrát s volbou TCP_QUEUE_SEQ. Tato operace se dotýká té fronty, která byla předtím zvolena pomocí TCP_REPAIR_QUEUE, takže obnovu obsahu fronty a nastavení sekvenčního čísla jde udělat najednou.
Pak je třeba obnovit ještě několik dalších sjednaných hodnot – MSS clamp, aktivní maximální velikost segmentu, velikost okénka a používání selective ACK a časových otisků (timestamps). K tomu slouží volání setsockopt(TCP_REPAIR_OPTIONS).
V moment, kdy byl soket obnoven do stavu, v jakém původně byl, je čas jej uvést do běhu. Když je na socketu v opravářském režimu zavoláno connect(), tak je většina kódu kolem nastavování spojení přeskočena a spojení přejde rovnou do stavu připojeno (established). Poslední věcí je to, že při vypnutí údržbářského režimu je poslán paket pro nastavení okénka, aby se obnovil provoz mezi oběma stranami; od této chvíle může být soket používán na novém hostiteli.
Tyto patche v uplynulých měsících prošly několika revizemi; ve verzi 4 je pak správce síťování David Miller přijal do net-next. Z tohoto místa je pak krátká cesta do začleňovacího okna verze 3.5. Patche pro opravu TCP spojení nejsou kompletním řešením problému migrace kontejnerů, ale jsou významným krokem v tomto ohledu.
Jedním ze zásadních pravidel vývoje jádra je to, že rozbíjení ABI pro uživatelský prostor je nepřijatelné. Pokud nějaký kód v uživatelském prostoru závisí na konkrétním chování, tak je nutné toto chování zachovat, i když se to nemusí moc hodit do krámu. Ale co dělat, když dva různé programy závisí na vzájemně nekompatibilním chování, takže je na první pohled nemožné zachovat fungování obou z nich? Odpovědí může být porušení jiného pravidla, tedy dát do jádra ošklivý hack – nebo udělat něco ještě zákeřnějšího.
Protokol „autofs“ je používán pro komunikaci mezi jádrem a démonem pro automatické připojování. Umožňuje automounteru vytvořit speciální virtuální souborový systém, který při použití uživatelem může být nahrazen skutečným. Většina protokolu je implementována pomocí volání ioctl() na speciálním zařízení autofs, ale používají se i roury, a to když se konkrétní souborové systémy připojují.
Tento protokol je rozhodně součástí jaderného ABI, takže jeho komponenty byly navrhovány s určitou mírou péče. Jednou ze zásadních součástí protokolu autofs je struktura autofs_v5_packet, která je z jádra do uživatelského prostoru poslána rourou; mj. se používá k oznámení, že souborový systém už nebyl nějakou dobu používán, takže je možné jej odpojit. Takto struktura vypadá následovně:
struct autofs_v5_packet { struct autofs_packet_hdr hdr; autofs_wqt_t wait_queue_token; __u32 dev; __u64 ino; __u32 uid; __u32 gid; __u32 pid; __u32 tgid; __u32 len; char name[NAME_MAX+1]; };
Velikost každého pole je precizně definovaná, takže by struktura měla vypadat stejně na 32 i 64bitových systémech. A až na jeden malý problém to tak je. Velikost struktury je definovaná na 300 bajtů, což není dělitelné osmi. Takže pokud by se do paměti umístily dvě takové struktury, 64bitová hodnota ino by nemohla být správně zarovnaná. Aby se tomu kompilátor vyhnul, tak na 64bitových systémech zarovná velikost struktury na násobek osmi, takže přidá na konec čtyři extra bajty. sizeof(struct autofs_v5_packet) tedy na 32bitovém systému vrátí 300 a na 64bitovém 304.
Tento nesoulad až na jednu výjimku nepředstavuje problém. Automatické připojování je jedním z mnoha úkolů, které řeší démon systemd. Když systemd čte takovou strukturu z jádra, zkontroluje správnou velikost. Tato kontrola funguje správně do té doby, dokud se jádro a systemd na velikosti shodnou. Pokud je ale systemd 32bitovým procesem na 64bitovém jádře, tak to nedopadne dobře, systemd usoudí, že něco je špatně, a ukončí se.
V únoru začlenil Ian Kent patch, který má problém vyřešit. Je to docela hack: jaderný kód pro automount odečte 4 bajty z velikosti struktury, pokud (a jen pokud) 64bitové jádro komunikuje s 32bitovým procesem. Tento patch zajistí, že systemd funguje; byl začleněn v 3.3-rc5 a byl rychle začleněn do různých stabilních řad. Všichni žili šťastně až do smrti.
...jenže oni šťastně nežili. Ukázalo se, že program automount z balíčku autofs-tools, který se na spoustě systémů stále používá, na tento problém narazil už před léty. Tehdy se vývojáři autofs-tools rozhodli problém obejít v uživatelském prostoru. Takže když automount zjistí, že běží v 32bitovém režimu na 64bitovém jádře, opraví údaj o tom, jak velká by struktura měla být. Když začne jádro na tuto velikost sahat, oprava v automountu přestane fungovat, takže Ianův patch opravuje systemd s tím, že rozbíjí automount.
Takže jsme se dostali do situace, kdy dva různé programy mají jinou představu o tom, jak má protokol autofs vypadat. Je to dosti nešťastná situace. Zachování patche nepotěší jedny, jeho odstranění rozzlobí zase druhé.
Nešťastná, ale ne neřešitelná. Jedním způsobem, jak to opravit, je patch od Michaela Tokareva. Ve zkratce dělá to, že se dívá na název aktuálního příkazu (current->comm) a porovnává to s „automount“. Pokud se aktuální program jmenuje automount, oprava velikosti struktury se nekoná a všechno zase funguje. Takže všechno je opraveno, akorát že tu máme jaderné ABI, které závisí na názvu běžícího programu. To je přinejmenším ne zrovna elegantní. Přinejhorším tu může být jiný program s jiným jménem, který se porouchá stejně jako automount; takový program by ani pak nefungoval.
Linus přesto dospěl k tomu, že to asi budeme muset překousnout. Dával ale přednost robustnějšímu řešení. Jednou možností bylo to, že by se jádro podívalo na velikost operace read(), pomocí které se načítá struktura autofs_v5_packet; pokud by velikost byla 300 nebo 304, tak by jádro dalo volajícímu programu tu velikost, jakou očekává. Problém je v tom, že operace read() je tu ukryta za rourou, takže kód autofs nemá přístup k velikosti tohoto bufferu.
Linus tedy přišel s jiným řešením, jsou jím tzv. paketizované roury. Paketizovaná roura připomíná obyčejnou rouru s pár rozdíly: každé volání write() se uchovává v odděleném bufferu a read() spotřebuje celý buffer, i když je velikost v read menší objem dat v bufferu. S paketizovanou rourou může jádro vždy zapsat větší strukturu a pokud program čte jen 300 bajtů, tak dostane to, co očekává. Takto není potřeba speciálních hacků v jádře, jen se volí jiné chování roury. Na základě návrhu od Alana Coxe zpřístupnil Linus paketizované chování přes volbu O_DIRECT, takže uživatelský prostor může takové roury vytvářet podle potřeby.
Po několika neúspěších Linus patch rozchodil a začlenil jej těsně před vydáním verze 3.4-rc5. Takže jádro 3.4 by mělo dobře fungovat s automount i systemd.
Tentokrát z toho jaderná komunita vybruslila lacino; tentokrát bylo možné, aby chytrý vývojář našel způsob, jak všem programům dát to, co chtějí. Příště už to nemusí být tak snadné. Udržování stability ABI není vždycky sranda, ale je nutné udržovat systém dlouhodobě použitelným.
Kdyby to byla aspoň oprava dvou programů, ale ona je to oprava jen jednoho (systemd). Protože jestli to chápu správně, vývojáři systemd rozbili ABI protože byli líní řešit na straně aplikace různé chování jádra. A místo toho, aby vývojáři zkroušeně připustili, že to někdo podělal a příslušnou změnu revertovali, tak vyrobí hnusný hack.Ne chápeš to zcela špatně - myslím, že zde to Linus vysvětluje.
if (pkt_len % 8) { if (strcmp(un.machine, "alpha") == 0 || strcmp(un.machine, "ia64") == 0 || strcmp(un.machine, "x86_64") == 0 || strcmp(un.machine, "ppc64") == 0) pkt_len += 4; }Až mi někdo řekne, že prasím v C tak mu pošlu tohle.
ISSN 1214-1267, (c) 1999-2007 Stickfish s.r.o.