Portál AbcLinuxu, 4. května 2025 14:09
Aktuální verze jádra: 3.8-rc5. Citáty týdne: Al Viro, Paul McKenney, Daniel Phillips. Glibc a API jádra pro uživatelský prostor.
Aktuální vývojová verze jádra je 3.8-rc5 vydaná 25. ledna. Jediným oznámením se zdá být tento příspěvek na Google+. Od vydání -rc4 bylo začleněno něco přes 250 oprav; pro podrobnosti čtěte krátký přehled změn.
Stabilní aktualizace: verze 3.7.5, 3.4.28 a 3.0.61 vyšly 27. ledna.
Lidé by opravdu měli být nuceni číst svůj kód do telefonu – to by prudce zlepšilo kvalitu identifikátorů, které používají.
-- Al Viro
A nebylo by snad zajímavé vídět skupinu vozítek, co se předhánějí na Marsu o to, kdo bude mít nejlepší místo, aby omezili dopad zpoždění kvůli rychlosti světla?
Tím skutečným problémem je to, že Mooreův zákon neplatí u disků s pohyblivými částmi. Nikdo opravdu netouží po tom, aby se jejich disk točil rychleji než 7200 ot/min, nebo za to nechtějí platit. Ale hustota stoupá s druhou mocninou velikosti prvku. Takže rychlost přenosu dat roste lineárně, zatímco velikost disku kvadraticky. Dnes trvá přečtení všech terabajtů na disku několik hodin. Fsck je sice obvykle rychlejší, protože mu stačí přečíst jen část disku, ale časem nebude udržitelné ani to. Výsledkem je, že fsck není něco, co bychom si mohli dovolit provozovat jako standardní, pravidelnou proceduru. Nemáme moc na výběr, musíme přejít na inkrementální a online fsck.
Zvykli jsme si vnímat systémová volání jako přímý požadavek jádru. Pravdou ale je, že většina těchto volání probíhá přes wrappery uvnitř knihovny GNU C (glibc). Tyto wrappery zbavují programátory dřiny, kterou by jinak museli kvůli systémovým voláním řešit. Jenže se ukazuje, že glibc neposkytuje wrappery pro všechna volání, a to včetně několika, která se používají vcelku často. Otázka, co by se s tím mělo dělat (nebo jestli se vůbec má něco dělat), se v uplynulých několika měsících na mailing listu libc-alpha objevila hned několikrát a v poslední době se tak stalo ještě jednou.
Systémové volání umožňuje programu požádat jádro o službu – například otevřít soubor nebo vytvořit nový proces. Na úrovni assembleru je pro uskutečnění systémového volání nutné určit číslo volání, nastavit argumenty do příslušných registrů a pak spustit speciální instrukci (např. sysenter, jak tomu je na moderních architekturách x86), jež přepne procesor do režimu jádra, aby v něm byla spuštěna obsluha tohoto volání. Při návratu se nastaví výsledek volání do konkrétního registru a spustí se speciální instrukce (na x86 např. SYSEXIT), která vrátí procesor do uživatelského režimu. Nezáporná návratová hodnota obvykle znamená úspěch, zatímco záporná selhání. Záporný výsledek je negovaným kódem chyby (errno), který označuje důvod.
Všechny podrobnosti spojené s realizací systémových volání jsou před uživatelem ukryté knihovnou C, jež poskytuje odpovídající funkci (wrapper) a deklaraci v hlavičkovém souboru pro většinu systémových volání. Wrapper přijímá argumenty systémového volání jako argumenty (často na stacku), pomocí těchto argumentů inicializuje příslušné registry a spustí příslušnou instrukci pro přepnutí do režimu jádra. Jakmile jádro navrátí řízení zpět do uživatelského režimu, wrapper prozkoumá návratovou hodnotu, přiřadí (negované) číslo chyby do errno v případě chyby a vrátí volající funkci buď -1 pro indikaci chyby, nebo kladný výsledek systémového volání. V mnoha případech je wrapper docela jednoduchý a dělá jen tyto zmiňované kroky. (V těchto případech je wrapper ve skutečnosti automaticky generován ze souborů syscalls.list ve zdrojácích glibc, které popisují typy vracené každým volání a jejich argumenty.) Jsou ale i případy, kdy wrapper dělá dodatečnou práci, jako třeba že přeseskupí argumenty nebo udržuje nějakou stavovou informaci v rámci knihovny C.
Knihovna C se tedy chová jako takový vrátný před přístupem k API nabízených jádrem. Dokud knihovna C neposkytuje příslušný wrapper spolu s deklarací v hlavičkovém souboru, musejí uživatelé dělat některé věci ručně.
Ručně musejí nadeklarovat struktury a konstanty, které jsou pro systémové volání potřeba, a pak se zavolá funkce syscall(), jež se postará o záležitosti spojené s provedením volání – kopírování argumentů do registrů, přepnutí do režimu jádra a nastavení errno po návratu. Takto je možné provést veškerá volání, včetně těch, která svůj wrapper mají. Proto můžeme obejít wrapper pro read() a spustit volání napřímo:
nread = syscall(SYS_read, fd, buf, len);
První argument pro syscall() je číslo volání, které se má spustit; SYS_read je konstanta, jejíž definice je v hlavičkovém souboru <unistd.h>.
Většina linuxových vývojářů samozřejmě používá knihovnu GNU C. Obvykle jsou změny v systémových voláních v glibc pozorně sledovány; nové wrappery a deklarace přibývají krátce poté, co se v jádře nové systémové volání objeví. Proto je ruční volání potřeba jen u naprostých novinek, které se ještě nedostaly v rámci šestiměsíčního vývojového cyklu glibc na svět, případně na systémech, kde je jádro značně novější než glibc.
Některá systémová volání se ale podpory v glibc nedočkala nikdy. Otázka, jak se o podpoře konkrétních volání v glibc rozhoduje, se opět stala námětem debat na mailing listu libc-alpha. Poslední diskuze začla tehdy, když Kees Cook, autor nového systémového volání finit_module(), zaslal hrubý patch pro přidání podpory do glibc. V reakci na to Joseph Myers a Mike Frysinger doplnili určité chybějící drobnosti s tím, že Joseph upozornil na to, že v diskuzi kolem kexec_load někdy v květnu / červnu byly vyjádřeny pochyby, jestli by některá stávající systémová volání měla vůbec mít odpovídající funkce v glibc.
Systémová volání související s moduly – init_module(), delete_module() a tak dále – patří mezi ty, pro které glibc podporu nenabízí. Situace je případě techto konkrétních volání poněkud složitější: glibc je v hlavičkových souborech nenabízí, ale z historických důvodů je jejich wrapper součástí ABI.
Dřívější debata, na kterou se Hoseph odkazoval, se uskutečnila v době, kdy se Maximilian Attems pokusil přidat do glibc hlavičkový soubor pro podporu volání kexec_load() s tím, že jeho cílem bylo vymotat se z bludiště systémových volání v kexec-tools a mít podporu pro toto volání v glibc. Jeden z hlavních správců glibc Roland McGranth měl na nutnost takové změny odlišný pohled: Nejsem doopravdy přesvědčen, že to za to stojí. Volání 'syscall' mi u takto tajemných a výjimečně používaných volání připadá dostatečné. Podpora těchto systémových volání v glibc jinými slovy přidává nepořádek do ABI glibc a vyžaduje (malý objem) dodatečného kódu, jen aby byly uspokojeny potřeby několika málo uživatelů, co by mohli prostě použít syscall().
Andreas Jaeger, který revidoval dřívější verze Maximilianova patche, poznamenal, že linux/syscalls.list už má podobná exotická volání jako create_module bez podpory v hlavičkových souborech. Nebyl bych proti, kdyby to podobně bylo i u kexec_load. Roland souhlasil, že systémové volání kexec_load() je podobný příklad, ale měl pocit, že to není až tak relevantní, protože přidání systémových volání souvisejících s moduly do ABI glibc byl „pochybný“ historický krok, jenž nelze z důvodu kompatibility vzít zpět.
V nedávné diskuzi o finit_module() se Mike Frysinger vyjádřil pro přidání plné podpory systémových volání souvisejících s moduly (jakým je i init_module()) do glibc. Dave Miller použil stejný argument, jen byl ještě stručnější:
Nedává smysl, aby každý nástroj, které chce podporovat dělání různých věci s jadernými moduly, používal syscall(), a šířil tak případné chyby v argumentech na více míst místo toho, aby se to povedlo udělat správně na jediném, kanonickém místě, tedy v libc.
Jinými slovy jde o to, že použití syscall() může být náchylné na chyby: nic nekontroluje typy argumentů a dokonce ani to, jestli jich bylo předáno dost.
Josephu Myersovi dřívější debaty kolem kexec_load nestačily a zajímal se o konkrétní údaje, kolik systémových volání nemá v glibc wrappery. Dá se to zjistit pomocí sekce 2 manuálových stránek. Výsledný seznam je docela dlouhý, jde o téměř 40 volání. Není to ale tak prosté, neboť některá volání jsou zastaralá (např. tkill(), sysctl() a query_module()) a jiná jsou určena jen pro používání jádrem nebo samotným glibc (např. restart_syscall()). Některá jiná sice wrapper mají, ale jmenuje se úplně jinak a přidává dodatečnou funkčnost (např. rt_sigqueueinfo má wrapper v podobě funkce sigqueue()). Pro ně tedy jednoznačně wrapper potřeba není a po jejich vyřazení zbývá nějakých 15 až 20 systémových volání, která by podporu v glibc mohla získat.
Motohiro Kosaki si myslí, že zbylá volání by bylo možné rozdělit do dvou skupin: ta, která jsou používána zcela výjimečně, a ta, která nacházejí širší využití. Motohiro si nebyl jistý, že první zmiňovaná skupina (kam spadají i systémová volání související s moduly) potřebuje wrappery. Podle jeho názoru si ale ta druhá skupina (tedy volání jako ioprio_set(), ioprio_get() a gettid()) plnou podporu v glibc zaslouží.
Neexistující podpora pro gettid(), které vrací jaderné ID volajícího vlákna, stojí obzvláště za pozornost. Dlouhotrvající bug dožadující se podpory se za minulého správce glibc posunu nedočkal. Ignorování tohoto volání je ale poněkud nezvyklé, protože je docela často používáno – jádro toto ID používá v různých rozhraních v /proc a glibc podporuje různá jaderná API, jež mohou ID vláken používat (například sched_setaffinity(), fcntl() a režim notifikací POSIXových časovačů SIGEV_THREAD_ID).
Diskuze v posledních několika dnech utichla, i když se Mike Frysinger snažil diskuzi posunout dále přečtením a shrnutím různých argumentů pro a proti v jediném e-mailu. Jak různí účastníci poznamenali, přidání wrapperů pro některá aktuálně nepodporovaná volání by mělo své výhody. Také by to pomohlo předcházet matoucím situacím, kdy programátoři hledají wrapper a deklarace, které neexistují. Je ale otázkou, jestli tyto argumenty budou stačit na Rolanda, který má obavy ze zaneřádění ABI glibc a přidávání extra kódu, pro který by mělo využití relativně malé množství lidí.
bylo by řádově rychlejší ho napsat znovu než v něm hledat a opravovat chybyPruser je pokud aplikace zaviseji na jeho chybnem chovani.
ISSN 1214-1267, (c) 1999-2007 Stickfish s.r.o.