Ebben a cikkben a tényleges rendszerhívásokat fogjuk használni, hogy valós munkát végezzünk a C programunkban. Először megvizsgáljuk, ha rendszerszintű hívást kell használnia, majd adjon egy példát a sendfile () hívással, amely drámai módon javíthatja a fájlmásolás teljesítményét. Végül áttekintünk néhány szempontot, amelyet emlékeznünk kell a Linux rendszerhívások használata közben.
Szüksége van egy rendszerhívásra?
Bár elkerülhetetlen, hogy a C fejlesztői karrierje során egy rendszerhívást használjon, hacsak nem a nagy teljesítményre vagy egy adott típusú funkcionalitásra irányul, a glibc könyvtár és más, a főbb Linux disztribúciókba tartozó egyéb alapkönyvtárak gondoskodnak a legtöbb a szükségleteid.
A glibc szabványkönyvtár platformokon átívelő, jól tesztelt keretrendszert biztosít olyan funkciók végrehajtásához, amelyek egyébként rendszerspecifikus rendszerhívásokat igényelnének. Például elolvashat egy fájlt az fscanf (), fread (), getc () stb., vagy használhatja a read () Linux rendszerhívást. A glibc függvények több funkciót nyújtanak (azaz.e. jobb hibakezelés, formázott IO stb.), és működni fog a rendszer bármely glibc-támogatásán.
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 növelni fogja a rezsit, és bár kisebb, mégsem teljesen átlátszó. Ezenkívül előfordulhat, hogy nem szeretné, vagy szüksége lenne a burkoló által biztosított extra szolgáltatásokra. Ebben az esetben a rendszerhívás szolgál a legjobban.
Rendszerhívásokat is használhat olyan funkciók végrehajtására, amelyeket a glibc még nem támogat. Ha a glibc példánya naprakész, akkor ez aligha lesz probléma, de a régebbi disztribúciókkal történő fejlesztésnél újabb kernelekkel lehet szükség erre a technikára.
Most, hogy elolvasta a korlátozásokat, figyelmeztetéseket és lehetséges kitérőket, most ássunk be néhány gyakorlati példát.
Milyen CPU-n vagyunk?
Olyan kérdés, amelyet valószínűleg a legtöbb program nem gondol, de mégis érvényes. Ez egy példa egy olyan rendszerhívásra, amelyet nem lehet megismételni a glibc-vel, és amelyet nem takar egy glibc-csomagoló. Ebben a kódban a getcpu () hívást közvetlenül a syscall () függvényen keresztül hívjuk meg. 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 jelöli. Amikor felveszi a sys / syscall szót.h, ezeket tartalmazza. Az első rész SYS_, a második rész a rendszerhívás neve.
A hívás argumentumai a fenti arg1, arg2 pontokra mennek. Egyes hívásokhoz több érv szükséges, és a sorrendben folytatódnak a man oldalukon. Ne feledje, hogy a legtöbb argumentumhoz, különösen a visszatérésekhez, a tömb tömbökre vagy a malloc függvény által lefoglalt memóriára lesz szükség.
példa1.c
#include#include
#include
#include
int main ()
aláíratlan cpu, csomópont;
// A jelenlegi CPU mag és a NUMA csomópont lekérése rendszerhívás útján
// Megjegyezzük, hogy ennek nincs glibc burkolója, ezért közvetlenül hívnunk kell
syscall (SYS_getcpu, & cpu, & node, NULL);
// Információk megjelenítése
printf ("Ez a program a% u CPU magon és a NUMA csomóponton fut% u.\ n \ n ", cpu, csomópont);
visszatér 0;
Összeállítás és futtatás:
gcc példa1.c -o példa1
./ példa1
Érdekesebb eredmények érdekében szálakat pörgethet a pthreads könyvtáron keresztül, majd felhívhatja ezt a funkciót, hogy megnézze, melyik processzoron fut a szál.
Sendfile: Kiváló teljesítmény
A Sendfile kiváló példát nyújt a teljesítmény növelésére a rendszerhívások révén. A sendfile () függvény átmásolja az adatokat az egyik fájlleíróból a másikba. A sendfile több fread () és fwrite () függvény használata helyett a kerneltérben hajtja végre az átvitelt, csökkentve a rezsit és ezáltal növelve a teljesítményt.
Ebben a példában 64 MB adatot másolunk egyik fájlból a másikba. Egy teszt során 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 fogjuk felhasználni az adatok egyik helyről a másikra történő robbantására.
teszt1.c (glibc)
#include#include
#include
#include
#define BUFFER_SIZE 67108864
#define BUFFER_1 "buffer1"
#define BUFFER_2 "buffer2"
int main ()
FÁJL * fOut, * fIn;
printf ("\ nI / O teszt hagyományos glibc függvényekkel.\ n \ n ");
// Fogj egy BUFFER_SIZE puffert.
// A pufferben véletlenszerű adatok lesznek, de ez nem érdekel.
printf ("64 MB puffer kiosztása:");
char * puffer = (char *) malloc (BUFFER_SIZE);
printf ("KÉSZ \ n");
// Írja a puffert az fOut-ba
printf ("Adatok írása az első pufferbe:");
fOut = fopen (BUFFER_1, "wb");
fwrite (puffer, sizeof (char), BUFFER_SIZE, fOut);
fclose (fOut);
printf ("KÉSZ \ n");
printf ("Adatok másolása az első fájlból a másodikba:");
fIn = fopen (BUFFER_1, "rb");
fOut = fopen (BUFFER_2, "wb");
fread (puffer, sizeof (char), BUFFER_SIZE, fIn);
fwrite (puffer, sizeof (char), BUFFER_SIZE, fOut);
fclose (fIn);
fclose (fOut);
printf ("KÉSZ \ n");
printf ("Szabadító puffer:");
szabad (puffer);
printf ("KÉSZ \ n");
printf ("Fájlok törlése:");
eltávolítás (BUFFER_1);
eltávolítás (BUFFER_2);
printf ("KÉSZ \ n");
visszatér 0;
teszt2.c (rendszerhívások)
#include#include
#include
#include
#include
#include
#include
#include
#include
#define BUFFER_SIZE 67108864
int main ()
int fOut, fIn;
printf ("\ nI / O teszt sendfile () és a kapcsolódó rendszerhívásokkal.\ n \ n ");
// Fogj egy BUFFER_SIZE puffert.
// A pufferben véletlenszerű adatok lesznek, de ez nem érdekel.
printf ("64 MB puffer kiosztása:");
char * puffer = (char *) malloc (BUFFER_SIZE);
printf ("KÉSZ \ n");
// Írja a puffert az fOut-ba
printf ("Adatok írása az első pufferbe:");
fOut = nyitott ("buffer1", O_RDONLY);
write (fOut, & buffer, BUFFER_SIZE);
bezár (fOut);
printf ("KÉSZ \ n");
printf ("Adatok másolása az első fájlból a másodikba:");
fIn = nyitott ("buffer1", O_RDONLY);
fOut = nyitott ("buffer2", O_RDONLY);
sendfile (fOut, fIn, 0, BUFFER_SIZE);
bezár (fIn);
bezár (fOut);
printf ("KÉSZ \ n");
printf ("Szabadító puffer:");
szabad (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 0;
1. és 2. teszt összeállítása és futtatása
Ezeknek a példáknak az elkészítéséhez telepítenie kell a disztribúcióra fejlesztő eszközöket. A Debianon és az Ubuntuban ezt telepítheti:
apt install build-essentialsEzután állítsa össze:
gcc teszt1.c -o teszt1 && gcc teszt2.c -o teszt2Mindkettő futtatásához és a teljesítmény teszteléséhez futtassa:
idő ./ test1 && time ./ teszt2Ilyen eredményeket kell elérnie:
I / O teszt hagyományos glibc funkciókkal.
64 MB puffer kiosztása: DONEAdatok írása az első pufferbe: DONE
Adatok másolása az első fájlból a másodikba: DONE
Szabadító puffer: KÉSZ
Fájlok törlése: DONE
valós 0m0.397-es évek
felhasználó 0m0.000-es évek
sys 0m0.203-as évek
I / O teszt sendfile () és a kapcsolódó rendszerhívásokkal.
64 MB puffer kiosztása: DONE
Adatok írása az első pufferbe: DONE
Adatok másolása az első fájlból a másodikba: DONE
Szabadító puffer: KÉSZ
Fájlok törlése: DONE
valós 0m0.019-es évek
felhasználó 0m0.000-es évek
sys 0m0.016-os évek
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 funkcionalitást nyújthatnak, de nem hátrányosak. Mérlegelnie kell a rendszerhívások előnyeit a platform hordozhatóságának hiányával és a könyvtári funkciókhoz képest néha csökkent funkcionalitással.
Egyes rendszerhívások használatakor ügyelnie kell arra, hogy a könyvtárhívások helyett a rendszerhívásokból visszakapott erőforrásokat használja. Például a FILE struktúra, amelyet a glibc fopen (), fread (), fwrite () és fclose () függvényei használnak, nem azonos a open () rendszerhívás fájlleíró számával (egész számként adják meg). Ezek keverése problémákhoz vezethet.
Általánosságban elmondható, hogy a Linux rendszerhívások kevesebb lökhárítóval rendelkeznek, mint a glibc funkciók. Bár igaz, hogy a rendszerhívások tartalmaznak hibakezelést és jelentéseket, a glibc funkcióval részletesebb funkciókat kap.
És végül egy szó a biztonságról. A rendszerhívások közvetlenül kapcsolódnak a kernellel. A Linux kernel kiterjedt védelemmel rendelkezik a felhasználói földterületről érkező shenanigans ellen, de felfedezetlen hibák vannak. Ne bízzon abban, hogy egy rendszerhívás érvényesíti a bemenetet, vagy elkülöníti a biztonsági problémáktól. Bölcs dolog annak biztosítása, hogy a rendszerhívásnak átadott adatokat megtisztítsák. Természetesen ez jó tanács bármilyen API-híváshoz, de nem lehet óvatos, amikor a kernellel dolgozik.
Remélem, élvezte ezt a mélyebb merülést a Linux rendszerhívások földjén. A Linux rendszerhívások teljes listáját lásd mesterlistánkban.