Vodič za sistemske pozive u sustavu Linux s C

Linux System Call Tutorial With C



U našem posljednjem članku o Sistemski pozivi za Linux , Definirao sam sistemski poziv, raspravljao o razlozima zbog kojih bi se mogli koristiti u programu i ušao u njihove prednosti i nedostatke. Čak sam dao i kratak primjer pri sastavljanju unutar C. To je ilustriralo poantu i opisalo kako uputiti poziv, ali nije učinilo ništa produktivno. Nije baš uzbudljiva vježba razvoja, ali je ilustrirala poantu.

U ovom ćemo članku koristiti stvarne sistemske pozive za obavljanje stvarnog posla u našem C programu. Prvo ćemo pregledati trebate li koristiti sistemski poziv, a zatim navesti primjer pomoću poziva sendfile () koji može dramatično poboljšati performanse kopiranja datoteke. Na kraju ćemo prijeći na neke točke koje treba zapamtiti dok koristite sistemske pozive Linuxa.







Iako je neizbježno da ćete u nekom trenutku svoje razvojne karijere koristiti sistemski poziv, osim ako ne ciljate visoke performanse ili funkcionalnost određene vrste, glibc knjižnica i druge osnovne knjižnice uključene u glavne distribucije Linuxa pobrinut će se za većinu vaše potrebe.



Standardna biblioteka glibc pruža višeplatformski, dobro provjeren okvir za izvršavanje funkcija koje bi inače zahtijevale sistemske pozive specifične za sustav. Na primjer, možete čitati datoteku s fscanf (), fread (), getc (), itd., Ili možete koristiti sistemski poziv read () Linux. Glibc funkcije pružaju više značajki (tj. Bolje rukovanje pogreškama, formatirani IO itd.) I radit će na bilo kojem sustavu koji podržava glibc.



S druge strane, postoje slučajevi u kojima su beskompromisne performanse i točna izvedba kritični. Omotač koji fread () pruža će dodati dodatne troškove, i iako je manji, nije potpuno transparentan. Osim toga, možda nećete htjeti ili vam trebati dodatne značajke koje omot pruža. U tom slučaju najbolje će vam poslužiti sistemski poziv.





Sistemske pozive možete koristiti i za obavljanje funkcija koje glibc još ne podržava. Ako je vaša kopija programa glibc ažurirana, to teško da će biti problem, ali razvoj na starijim distribucijama s novijim jezgrama mogao bi zahtijevati ovu tehniku.

Sada kada ste pročitali odricanja od odgovornosti, upozorenja i potencijalne zaobilaznice, sada ćemo se pozabaviti nekim praktičnim primjerima.



Na kojem smo procesoru?

Pitanje koje većina programa vjerojatno ne misli postaviti, ali je ipak valjano. Ovo je primjer sistemskog poziva koji se ne može duplicirati s glibc -om i nije prekriven omotom glibc. U ovom ćemo kodu nazvati getcpu () poziv izravno putem funkcije syscall (). Funkcija syscall radi na sljedeći način:

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

Prvi argument, SYS_call, je definicija koja predstavlja broj sistemskog poziva. Kada uključite sys/syscall.h, oni su uključeni. Prvi dio je SYS_, a drugi dio je naziv sistemskog poziva.

Argumenti za poziv idu u gore navedene arg1, arg2. Neki pozivi zahtijevaju više argumenata i nastavit će se redom sa svoje stranice za korisnike. Imajte na umu da će većina argumenata, osobito za povrat, zahtijevati pokazivače na char nizove ili memoriju dodijeljenu pomoću funkcije malloc.

primjer1.c

#uključi
#uključi
#uključi
#uključi

intglavni() {

nepotpisanCPU,čvor;

// Dobivanje trenutne jezgre procesora i NUMA čvora putem sistemskog poziva
// Imajte na umu da ovo nema glibc omot pa ga moramo nazvati izravno
syscall(SYS_getcpu, &CPU, &čvor,NULL);

// Prikaz informacija
printf ('Ovaj program radi na jezgri procesora %u i čvoru NUMA %u. n n',CPU,čvor);

povratak 0;

}

Sastaviti i pokrenuti:

gcc primjer1.c -o primjer1
./primjer1

Za zanimljivije rezultate mogli biste vrtjeti niti putem biblioteke pthreads, a zatim pozvati ovu funkciju da vidite na kojem procesoru vaša nit radi.

Sendfile: Vrhunske performanse

Sendfile pruža izvrstan primjer poboljšanja performansi kroz sistemske pozive. Funkcija sendfile () kopira podatke iz jednog deskriptora datoteke u drugi. Umjesto korištenja više funkcija fread () i fwrite (), sendfile vrši prijenos u prostoru jezgre, smanjujući troškove i time povećavajući performanse.

U ovom primjeru kopirat ćemo 64 MB podataka iz jedne datoteke u drugu. U jednom ćemo testu koristiti standardne metode čitanja/pisanja u standardnoj biblioteci. U drugom ćemo koristiti sistemske pozive i poziv sendfile () za prebacivanje ovih podataka s jedne lokacije na drugu.

test1.c (glibc)

#uključi
#uključi
#uključi
#uključi

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

intglavni() {

DATOTEKA*pogrešno, *kraj;

printf (' nI/O test s tradicionalnim glibc funkcijama. n n');

// Zgrabite međuspremnik BUFFER_SIZE.
// Međuspremnik će imati nasumične podatke, ali nas to ne zanima.
printf ('Dodjeljivanje međuspremnika od 64 MB:');
char *pufer= (char *) malloc (BUFFER_SIZE);
printf ('GOTOVO n');

// Zapišite međuspremnik u fOut
printf ('Zapisivanje podataka u prvi međuspremnik:');
pogrešno= fopen (BUFFER_1, 'wb');
pisati (pufer, veličina(char),BUFFER_SIZE,pogrešno);
zbližiti (pogrešno);
printf ('GOTOVO n');

printf ('Kopiranje podataka iz prve datoteke u drugu:');
kraj= fopen (BUFFER_1, 'rb');
pogrešno= fopen (BUFFER_2, 'wb');
fread (pufer, veličina(char),BUFFER_SIZE,kraj);
pisati (pufer, veličina(char),BUFFER_SIZE,pogrešno);
zbližiti (kraj);
zbližiti (pogrešno);
printf ('GOTOVO n');

printf ('Oslobađanje međuspremnika:');
besplatno (pufer);
printf ('GOTOVO n');

printf ('Brisanje datoteka:');
ukloniti (BUFFER_1);
ukloniti (BUFFER_2);
printf ('GOTOVO n');

povratak 0;

}

test2.c (sistemski pozivi)

#uključi
#uključi
#uključi
#uključi
#uključi
#uključi
#uključi
#uključi
#uključi

#define BUFFER_SIZE 67108864

intglavni() {

intpogrešno,kraj;

printf (' nI/O test s sendfile () i povezanim sistemskim pozivima. n n');

// Zgrabite međuspremnik BUFFER_SIZE.
// Međuspremnik će imati nasumične podatke, ali nas to ne zanima.
printf ('Dodjeljivanje međuspremnika od 64 MB:');
char *pufer= (char *) malloc (BUFFER_SIZE);
printf ('GOTOVO n');


// Zapišite međuspremnik u fOut
printf ('Zapisivanje podataka u prvi međuspremnik:');
pogrešno=otvorena('međuspremnik1',O_RDONLY);
pisati(pogrešno, &pufer,BUFFER_SIZE);
Zatvoriti(pogrešno);
printf ('GOTOVO n');

printf ('Kopiranje podataka iz prve datoteke u drugu:');
kraj=otvorena('međuspremnik1',O_RDONLY);
pogrešno=otvorena('međuspremnik2',O_RDONLY);
sendfile(pogrešno,kraj, 0,BUFFER_SIZE);
Zatvoriti(kraj);
Zatvoriti(pogrešno);
printf ('GOTOVO n');

printf ('Oslobađanje međuspremnika:');
besplatno (pufer);
printf ('GOTOVO n');

printf ('Brisanje datoteka:');
prekinuti vezu('međuspremnik1');
prekinuti vezu('međuspremnik2');
printf ('GOTOVO n');

povratak 0;

}

Sastavljanje i pokretanje testova 1 i 2

Za izradu ovih primjera trebat će vam razvojni alati instalirani na vašoj distribuciji. Na Debian i Ubuntu ovo možete instalirati pomoću:

prikladaninstaliratibuild-essentials

Zatim kompajlirajte sa:

gcctest1.c-ilitest1&& gcctest2.c-ilitest2

Da biste pokrenuli oboje i testirali performanse, pokrenite:

vrijeme./test1&& vrijeme./test2

Trebali biste dobiti ovakve rezultate:

I/O test s tradicionalnim glibc funkcijama.

Dodjela međuspremnika od 64 MB: GOTOVO
Zapisivanje podataka u prvi međuspremnik: GOTOVO
Kopiranje podataka iz prve datoteke u drugu: GOTOVO
Oslobađanje međuspremnika: GOTOVO
Brisanje datoteka: GOTOVO
stvarnih 0m0.397s
korisnik 0m0.000s
sys 0m0.203s
I/O test s sendfile () i povezanim sistemskim pozivima.
Dodjela međuspremnika od 64 MB: GOTOVO
Zapisivanje podataka u prvi međuspremnik: GOTOVO
Kopiranje podataka iz prve datoteke u drugu: GOTOVO
Oslobađanje međuspremnika: GOTOVO
Brisanje datoteka: GOTOVO
stvarnih 0m0.019s
korisnik 0m0.000s
sys 0m0.016s

Kao što vidite, kôd koji koristi sistemske pozive radi mnogo brže od ekvivalenta glibc.

Stvari koje treba zapamtiti

Sistemski pozivi mogu povećati performanse i pružiti dodatnu funkcionalnost, ali nisu bez nedostataka. Morat ćete odvagnuti prednosti koje sistemski pozivi pružaju u odnosu na nedostatak prenosivosti platforme, a ponekad i smanjenu funkcionalnost u usporedbi s funkcijama knjižnice.

Kada koristite neke sistemske pozive, morate paziti na korištenje resursa vraćenih iz sistemskih poziva, a ne na funkcije knjižnice. Na primjer, struktura FILE koja se koristi za funkcije glibc fopen (), fread (), fwrite () i fclose () nije isto što i broj deskriptora datoteke iz sistemskog poziva open () (vraćen kao cijeli broj). Njihovo miješanje može dovesti do problema.

Općenito, sistemski pozivi Linuxa imaju manje odbojnih traka od glibc funkcija. Iako je istina da sistemski pozivi imaju izvjesno rukovanje pogreškama i izvješćivanje, detaljnije ćete funkcije dobiti pomoću funkcije glibc.

I za kraj, nekoliko riječi o sigurnosti. Sistemski pozivi izravno se povezuju s jezgrom. Linuxovo jezgro ima opsežnu zaštitu od prijevara iz korisničke zemlje, ali postoje neotkrivene greške. Ne vjerujte da će sistemski poziv potvrditi vaš unos ili vas izolirati od sigurnosnih problema. Mudro je osigurati da su podaci koje predate sistemskom pozivu čisti. Naravno, ovo je dobar savjet za bilo koji API poziv, ali ne možete biti oprezni pri radu s jezgrom.

Nadam se da ste uživali u ovom dubljem zaronu u zemlju poziva sustava Linux. Za potpuni popis Linux sistemskih poziva pogledajte naš glavni popis.