Linux rendszerhívási bemutató C -vel

Linux System Call Tutorial With C



Utolsó cikkünkben erről Linux rendszerhívások , Meghatároztam egy rendszerhívást, megvitattam azokat az okokat, amelyek miatt ezeket felhasználhatjuk egy programban, és elmélyedtem azok előnyeiben és hátrányaiban. Még egy rövid példát is mondtam a C -n belüli összeszerelésben. Ez szemléltette a lényeget, és leírta, hogyan kell kezdeményezni a hívást, de nem hozott eredményt. Nem éppen izgalmas fejlesztési gyakorlat, de szemléltette a lényeget.

Ebben a cikkben a tényleges rendszerhívásokat fogjuk használni a C programunk valódi munkájához. Először is áttekintjük, hogy szükség van -e rendszerhívásra, majd adunk egy példát a sendfile () hívás használatával, amely jelentősen javíthatja a fájlmásolás teljesítményét. Végül áttekintünk néhány pontot, amelyeket emlékeznünk kell a Linux rendszerhívások használata során.







Bár elkerülhetetlen, hogy a C fejlesztői karrierje egy pontján rendszerhívást fog használni, hacsak nem a nagy teljesítményt vagy egy bizonyos típusú funkciót célozza meg, a glibc könyvtár és a nagy Linux -disztribúciókban található egyéb alapvető könyvtárak gondoskodnak a legtöbb a szükségleteid.



A glibc szabványos könyvtár platformok közötti, jól tesztelt keretet biztosít olyan funkciók végrehajtásához, amelyek egyébként rendszer-specifikus rendszerhívásokat igényelnének. Például elolvashat egy fájlt az fscanf (), fread (), getc () stb. Fájlokkal, vagy használhatja a read () Linux rendszerhívást. A glibc funkciók több szolgáltatást nyújtanak (azaz jobb hibakezelést, formázott IO -t stb.), És minden rendszer glibc -támogatásán működni fognak.



Másrészt vannak esetek, amikor a kompromisszumok nélküli teljesítmény és a pontos végrehajtás kritikus fontosságú. A fread () által biztosított burkolat többletköltséget fog adni, és bár kisebb, nem teljesen átlátszó. Ezenkívül előfordulhat, hogy nem szeretné, vagy szüksége lenne a csomagolás által nyújtott extra szolgáltatásokra. Ebben az esetben a rendszerhívással lehet a legjobban szolgálni.





A rendszerhívásokat olyan funkciók végrehajtására is használhatja, amelyeket a glibc még nem támogat. Ha a glibc példánya naprakész, ez aligha jelent problémát, de a régebbi disztribúciókon történő fejlesztés újabb kernellel igényelheti ezt a technikát.

Most, hogy elolvasta a nyilatkozatokat, a figyelmeztetéseket és a lehetséges kitérőket, most nézzünk néhány gyakorlati példát.



Milyen CPU -n vagyunk?

Egy kérdés, amelyet a legtöbb program valószínűleg nem gondol fel, de mégis érvényes. Ez egy példa a rendszerhívásra, amely nem másolható a glibc programmal, és nincs borítva glibc burkolattal. Ebben a kódban a getcpu () hívást közvetlenül a syscall () függvényen keresztül hívjuk. A syscall funkció a következőképpen működik:

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

Az első argumentum, a SYS_call, egy definíció, amely a rendszerhívás számát képviseli. Ha beveszi a sys/syscall.h -t, akkor ezek is benne vannak. Az első rész a SYS_, a második pedig a rendszerhívás neve.

A hívás érvei a arg1, arg2 fentebb találhatók. Egyes hívások több érvet igényelnek, és sorrendben folytatódnak a kézi oldalukon. Ne feledje, hogy a legtöbb érv, különösen a visszatérések esetében, mutatókat igényel a tömbök vagy a malloc függvényen keresztül kiosztott memória tárolására.

példa1.c

#befoglalni
#befoglalni
#befoglalni
#befoglalni

intfő-() {

aláírás nélküliprocesszor,csomópont;

// Az aktuális CPU mag és a NUMA csomópont beszerzése rendszerhívással
// Ne feledje, hogy ebben nincs glibc -csomagoló, ezért közvetlenül meg kell hívnunk
syscall(SYS_getcpu, &processzor, &csomópont,NULLA);

// Információk megjelenítése
printf ('Ez a program a %u CPU magon és a NUMA %u csomóponton fut. n n',processzor,csomópont);

Visszatérés 0;

}

Összeállítani és futtatni:

gcc példa1.c -o példa1
./példa1

Az érdekesebb eredmények érdekében a pthreads könyvtáron keresztül pörgetheti a szálakat, majd meghívhatja ezt a funkciót, hogy megnézze, melyik processzoron fut a szál.

Küldési fájl: Kiváló teljesítmény

A Sendfile kitűnő példa a teljesítménynövelésre a rendszerhívások révén. A sendfile () függvény adatokat másol az egyik fájlleíróból a másikba. Ahelyett, hogy több fread () és fwrite () függvényt használna, a sendfile elvégzi az átvitelt a kerneltérben, csökkentve a rezsiköltséget és ezáltal növelve a teljesítményt.

Ebben a példában 64 MB adatot másolunk át egyik fájlból a másikba. Az egyik tesztben a szabványos olvasási/írási módszereket fogjuk használni a standard könyvtárban. A másikban a rendszerhívásokat és a sendfile () hívást használjuk, hogy ezeket az adatokat egyik helyről a másikra robbantsuk.

test1.c (glibc)

#befoglalni
#befoglalni
#befoglalni
#befoglalni

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

intfő-() {

FILE*rossz, *vége;

printf (' nI/O teszt hagyományos glibc funkciókkal. n n');

// Fogjon egy BUFFER_SIZE puffert.
// A pufferben véletlenszerű adatok lesznek, de ez nem érdekel minket.
printf ('64 MB puffer kiosztása:');
char *puffer= (char *) malloc (BUFFER_SIZE);
printf ('KÉSZ n');

// Írja be a puffert a fOut -ba
printf ('Adatok írása az első pufferbe:');
rossz= fopen (BUFFER_1, 'wb');
fwrite (puffer, mérete(char),BUFFER_SIZE,rossz);
fclose (rossz);
printf ('KÉSZ n');

printf ('Adatok másolása az első fájlból a másodikba:');
vége= fopen (BUFFER_1, 'rb');
rossz= fopen (PUFF_2, 'wb');
fread (puffer, mérete(char),BUFFER_SIZE,vége);
fwrite (puffer, mérete(char),BUFFER_SIZE,rossz);
fclose (vége);
fclose (rossz);
printf ('KÉSZ n');

printf ('Puffer felszabadítása:');
ingyenes (puffer);
printf ('KÉSZ n');

printf ('Fájlok törlése:');
távolítsa el (BUFFER_1);
távolítsa el (PUFF_2);
printf ('KÉSZ n');

Visszatérés 0;

}

test2.c (rendszerhívások)

#befoglalni
#befoglalni
#befoglalni
#befoglalni
#befoglalni
#befoglalni
#befoglalni
#befoglalni
#befoglalni

#define BUFFER_SIZE 67108864

intfő-() {

introssz,vége;

printf (' nI/O teszt sendfile () -vel és a kapcsolódó rendszerhívásokkal. n n');

// Fogjon egy BUFFER_SIZE puffert.
// A pufferben véletlenszerű adatok lesznek, de ez nem érdekel minket.
printf ('64 MB puffer kiosztása:');
char *puffer= (char *) malloc (BUFFER_SIZE);
printf ('KÉSZ n');


// Írja be a puffert a fOut -ba
printf ('Adatok írása az első pufferbe:');
rossz=nyisd ki('buffer1',O_RDONLY);
ír(rossz, &puffer,BUFFER_SIZE);
Bezárás(rossz);
printf ('KÉSZ n');

printf ('Adatok másolása az első fájlból a másodikba:');
vége=nyisd ki('buffer1',O_RDONLY);
rossz=nyisd ki('buffer2',O_RDONLY);
sendfile(rossz,vége, 0,BUFFER_SIZE);
Bezárás(vége);
Bezárás(rossz);
printf ('KÉSZ n');

printf ('Puffer felszabadítása:');
ingyenes (puffer);
printf ('KÉSZ n');

printf ('Fájlok törlése:');
leválasztás('buffer1');
leválasztás('buffer2');
printf ('KÉSZ n');

Visszatérés 0;

}

1. és 2. teszt összeállítása és futtatása

A példák elkészítéséhez szüksége lesz a disztribúcióra telepített fejlesztőeszközökre. Debian és Ubuntu rendszereken telepítheti ezt:

találótelepítésépítéshez szükséges

Ezután fordítsa le a következővel:

gccteszt1.c-vagyteszt1&& gccteszt2.c-vagyteszt2

Mindkettő futtatásához és a teljesítmény teszteléséhez futtassa:

idő./teszt1&& idő./teszt2

Ilyen eredményeket kell kapnia:

I/O teszt hagyományos glibc funkciókkal.

64 MB puffer kiosztása: KÉSZ
Adatok írása az első pufferbe: KÉSZ
Adatok másolása az első fájlból a másodikba: KÉSZ
Felszabadító puffer: KÉSZ
Fájlok törlése: KÉSZ
valódi 0m0.397s
felhasználó 0m0.000s
sys 0m0,203s
I/O teszt sendfile () -vel és a kapcsolódó rendszerhívásokkal.
64 MB puffer kiosztása: KÉSZ
Adatok írása az első pufferbe: KÉSZ
Adatok másolása az első fájlból a másodikba: KÉSZ
Felszabadító puffer: KÉSZ
Fájlok törlése: KÉSZ
valódi 0m0.019s
felhasználó 0m0.000s
sys 0m0.016s

Mint látható, a rendszerhívásokat használó kód sokkal gyorsabban fut, mint a glibc megfelelője.

Dolgok, amikre emlékezni kell

A rendszerhívások növelhetik a teljesítményt és további funkciókat nyújthatnak, de nem nélkülözik hátrányukat. Mérlegelnie kell a rendszerhívások által nyújtott előnyöket a platformhordozhatóság hiányával és néha a funkcionalitás csökkenésével szemben a könyvtári funkciókhoz képest.

Bizonyos rendszerhívások használatakor ügyelnie kell arra, hogy a rendszerhívásokból visszaadott erőforrásokat használja a könyvtári funkciók helyett. Például a glibc fopen (), fread (), fwrite () és fclose () függvényeihez használt FILE szerkezet nem azonos a open () rendszerhívásból származó fájlleíró számmal (egész számként visszaadva). Ezek keverése problémákat okozhat.

Általában a Linux rendszerhívások kevesebb ütközősávot tartalmaznak, mint a glibc funkciók. Bár igaz, hogy a rendszerhívások némi hibakezeléssel és jelentéssel rendelkeznek, részletesebb funkciókat kap a glibc funkcióból.

És végül néhány szó a biztonságról. A rendszerhívások közvetlenül kapcsolódnak a kernelhez. A Linux kernel kiterjedt védelemmel rendelkezik a felhasználói területről érkező csalások ellen, de vannak felfedezetlen hibák. Ne bízzon abban, hogy a rendszerhívás érvényesíti az Ön bevitelét, vagy elzár a biztonsági problémáktól. Bölcs dolog gondoskodni arról, hogy a rendszerhíváshoz átadott adatok törlésre kerüljenek. Ez természetesen jó tanács minden API híváshoz, de nem lehet óvatos, ha a kernellel dolgozik.

Remélem, élvezte ezt a mélyebb merülést a Linux rendszerhívások országában. A Linux rendszerhívások teljes listáját lásd a mesterlistánkban.