Výukový program Linux System Call s C

Linux System Call Tutorial With C



V našem posledním článku o Systémová volání Linuxu „Definoval jsem systémové volání, diskutoval o důvodech, proč je lze v programu použít, a zabýval se jejich výhodami a nevýhodami. Dokonce jsem dal krátký příklad při montáži v C. Ukázalo to pointu a popsalo, jak uskutečnit hovor, ale neprovedlo nic produktivního. Není to zrovna vzrušující vývojové cvičení, ale ilustrovalo to podstatu.

V tomto článku použijeme skutečná systémová volání k skutečné práci v našem programu C. Nejprve zkontrolujeme, zda potřebujete použít systémové volání, a poté poskytneme příklad pomocí volání sendfile (), které může výrazně zlepšit výkon kopírování souborů. Nakonec si projdeme několik bodů, které je třeba si pamatovat při používání systémových volání Linuxu.







I když je to nevyhnutelné, v určitém okamžiku své vývojové kariéry C použijete systémové volání, pokud necílíte na vysoký výkon nebo na funkce konkrétního typu, o většinu se postará knihovna glibc a další základní knihovny zahrnuté v hlavních distribucích Linuxu tvoje potřeby.



Standardní knihovna glibc poskytuje multiplatformní, dobře testovaný framework pro spouštění funkcí, které by jinak vyžadovaly systémová volání systému. Můžete například přečíst soubor pomocí příkazů fscanf (), fread (), getc () atd. Nebo můžete použít systémové volání read () Linux. Funkce glibc poskytují více funkcí (tj. Lepší zpracování chyb, formátované IO atd.) A budou fungovat na všech systémových podporách glibc.



Na druhou stranu jsou chvíle, kdy je rozhodující nekompromisní výkon a přesné provedení. Obálka, kterou poskytuje fread (), přidá režii, a přestože je malá, není zcela transparentní. Navíc možná nebudete chtít nebo potřebovat další funkce, které wrapper poskytuje. V takovém případě vám nejlépe poslouží systémové volání.





Systémová volání můžete také použít k provádění funkcí, které zatím nejsou podporovány glibc. Pokud je vaše kopie glibc aktuální, sotva to bude problém, ale vývoj na starších distribucích s novějšími jádry může tuto techniku ​​vyžadovat.

Nyní, když jste si přečetli prohlášení o vyloučení odpovědnosti, varování a potenciální objížďky, pojďme se nyní vrhnout na několik praktických příkladů.



Na jakém CPU jsme?

Otázka, kterou si pravděpodobně většina programů nemyslí položit, ale přesto je platná. Toto je příklad systémového volání, které nelze duplikovat pomocí glibc a není pokryto obálkou glibc. V tomto kódu zavoláme volání getcpu () přímo prostřednictvím funkce syscall (). Funkce syscall funguje následovně:

syscall(SYS_call,arg1,arg2,...);

První argument, SYS_call, je definice, která představuje číslo systémového volání. Když zahrnete sys/syscall.h, budou zahrnuty. První část je SYS_ a druhá část je název systémového volání.

Argumenty pro volání jdou do arg1, arg2 výše. Některá volání vyžadují více argumentů a budou pokračovat v pořadí ze své manuálové stránky. Pamatujte, že většina argumentů, zejména pro návraty, bude vyžadovat ukazatele na pole polí nebo paměť přidělenou prostřednictvím funkce malloc.

příklad1.c

#zahrnout
#zahrnout
#zahrnout
#zahrnout

inthlavní() {

nepodepsanýprocesor,uzel;

// Získejte aktuální jádro CPU a uzel NUMA prostřednictvím systémového volání
// Všimněte si, že to nemá žádný glibc wrapper, takže to musíme zavolat přímo
syscall(SYS_getcpu, &procesor, &uzel,NULA);

// Zobrazení informací
printf (`` Tento program běží na jádru CPU %u a uzlu NUMA %u. n n',procesor,uzel);

vrátit se 0;

}

Zkompilovat a spustit:

gcc příklad 1.C -o příklad 1
./příklad 1

Chcete -li získat zajímavější výsledky, můžete roztočit vlákna prostřednictvím knihovny pthreads a poté zavolat tuto funkci a zjistit, na kterém procesoru vaše vlákno běží.

Sendfile: Vynikající výkon

Sendfile poskytuje vynikající příklad zvýšení výkonu prostřednictvím systémových volání. Funkce sendfile () kopíruje data z jednoho deskriptoru souboru do druhého. Sendfile namísto použití více funkcí fread () a fwrite () provádí přenos v prostoru jádra, snižuje režii a tím zvyšuje výkon.

V tomto příkladu zkopírujeme 64 MB dat z jednoho souboru do druhého. V jednom testu použijeme standardní metody čtení/zápisu ve standardní knihovně. V druhém případě použijeme systémová volání a volání sendfile () k přenosu těchto dat z jednoho místa na druhé.

test1.c (glibc)

#zahrnout
#zahrnout
#zahrnout
#zahrnout

#define BUFFER_SIZE 67108864
#define BUFFER_1 'buffer1'
#define BUFFER_2 'buffer2'

inthlavní() {

SOUBOR*špatně, *konec;

printf (' nI/O test s tradičními funkcemi glibc. n n');

// Popadněte vyrovnávací paměť BUFFER_SIZE.
// Vyrovnávací paměť bude obsahovat náhodná data, ale to nás nezajímá.
printf ('Přidělení 64 MB vyrovnávací paměti:');
char *vyrovnávací paměť= (char *) malloc (VELIKOST VYROVNÁVACÍ PAMĚTI);
printf ('HOTOVO n');

// Napište vyrovnávací paměť do fOut
printf ('Zápis dat do první vyrovnávací paměti:');
špatně= otevřít (BUFFER_1, 'wb');
fwrite (vyrovnávací paměť, velikost(char),VELIKOST VYROVNÁVACÍ PAMĚTI,špatně);
fclose (špatně);
printf ('HOTOVO n');

printf ('Kopírování dat z prvního souboru do druhého:');
konec= otevřít (BUFFER_1, 'rb');
špatně= otevřít (BUFFER_2, 'wb');
fread (vyrovnávací paměť, velikost(char),VELIKOST VYROVNÁVACÍ PAMĚTI,konec);
fwrite (vyrovnávací paměť, velikost(char),VELIKOST VYROVNÁVACÍ PAMĚTI,špatně);
fclose (konec);
fclose (špatně);
printf ('HOTOVO n');

printf ('Uvolňovací vyrovnávací paměť:');
volný, uvolnit (vyrovnávací paměť);
printf ('HOTOVO n');

printf ('Mazání souborů:');
odstranit (BUFFER_1);
odstranit (BUFFER_2);
printf ('HOTOVO n');

vrátit se 0;

}

test2.c (systémová volání)

#zahrnout
#zahrnout
#zahrnout
#zahrnout
#zahrnout
#zahrnout
#zahrnout
#zahrnout
#zahrnout

#define BUFFER_SIZE 67108864

inthlavní() {

intšpatně,konec;

printf (' nTest I/O pomocí sendfile () a souvisejících systémových volání. n n');

// Popadněte vyrovnávací paměť BUFFER_SIZE.
// Vyrovnávací paměť bude obsahovat náhodná data, ale to nás nezajímá.
printf ('Přidělení 64 MB vyrovnávací paměti:');
char *vyrovnávací paměť= (char *) malloc (VELIKOST VYROVNÁVACÍ PAMĚTI);
printf ('HOTOVO n');


// Napište vyrovnávací paměť do fOut
printf ('Zápis dat do první vyrovnávací paměti:');
špatně=otevřeno('buffer1',O_RDONLY);
napsat(špatně, &vyrovnávací paměť,VELIKOST VYROVNÁVACÍ PAMĚTI);
zavřít(špatně);
printf ('HOTOVO n');

printf ('Kopírování dat z prvního souboru do druhého:');
konec=otevřeno('buffer1',O_RDONLY);
špatně=otevřeno('buffer2',O_RDONLY);
poslat soubor(špatně,konec, 0,VELIKOST VYROVNÁVACÍ PAMĚTI);
zavřít(konec);
zavřít(špatně);
printf ('HOTOVO n');

printf ('Uvolňovací vyrovnávací paměť:');
volný, uvolnit (vyrovnávací paměť);
printf ('HOTOVO n');

printf ('Mazání souborů:');
odpojit('buffer1');
odpojit('buffer2');
printf ('HOTOVO n');

vrátit se 0;

}

Kompilace a spuštění testů 1 a 2

K vytvoření těchto příkladů budete potřebovat vývojové nástroje nainstalované ve vaší distribuci. Na Debianu a Ubuntu to můžete nainstalovat pomocí:

výstižnýNainstalujtezáklady stavby

Potom zkompilovat s:

gcctest1.c-nebotest 1&& gcctest2.c-nebotest 2

Chcete -li spustit obojí a otestovat výkon, spusťte:

čas./test 1&& čas./test 2

Měli byste získat výsledky takto:

I/O test s tradičními funkcemi glibc.

Přidělení 64 MB vyrovnávací paměti: HOTOVO
Zápis dat do první vyrovnávací paměti: HOTOVO
Kopírování dat z prvního souboru do druhého: HOTOVO
Uvolňovací vyrovnávací paměť: HOTOVO
Mazání souborů: HOTOVO
skutečných 0 mil. 0,397 s
uživatel 0 mil. 000 s
sys 0m0,203s
Test I/O pomocí sendfile () a souvisejících systémových volání.
Přidělení 64 MB vyrovnávací paměti: HOTOVO
Zápis dat do první vyrovnávací paměti: HOTOVO
Kopírování dat z prvního souboru do druhého: HOTOVO
Uvolňovací vyrovnávací paměť: HOTOVO
Mazání souborů: HOTOVO
skutečných 0m0,019s
uživatel 0 mil. 000 s
sys 0m0,016s

Jak vidíte, kód, který používá systémová volání, běží mnohem rychleji než ekvivalent glibc.

Věci k zapamatování

Systémová volání mohou zvýšit výkon a poskytnout další funkce, ale nejsou bez jejich nevýhod. Budete muset zvážit výhody, které systémová volání poskytují, proti nedostatečné přenositelnosti platformy a někdy i snížené funkčnosti ve srovnání s funkcemi knihovny.

Při používání některých systémových volání musíte dávat pozor na používání prostředků vrácených ze systémových volání, nikoli z funkcí knihovny. Například struktura FILE použitá pro funkce glibc fopen (), fread (), fwrite () a fclose () není stejná jako číslo deskriptoru souboru ze systémového volání open () (vrácené jako celé číslo). Jejich míchání může vést k problémům.

Systémová volání Linuxu mají obecně méně nárazníkových pruhů než funkce glibc. I když je pravda, že systémová volání mají určité zpracování chyb a hlášení, z funkce glibc získáte podrobnější funkce.

A na závěr slovo o bezpečnosti. Systém volá přímo s jádrem. Linuxové jádro má rozsáhlou ochranu před shenanigany z uživatelské oblasti, ale neobjevené chyby existují. Nevěřte, že systémové volání ověří váš vstup nebo vás izoluje od bezpečnostních problémů. Je rozumné zajistit, aby data, která předáváte systémovému volání, byla dezinfikována. Přirozeně je to dobrá rada pro jakékoli volání API, ale při práci s jádrem nemůžete být opatrní.

Doufám, že jste si užili tento hlubší ponor do země systémových volání Linuxu. Úplný seznam systémových volání systému Linux naleznete v našem hlavním seznamu.