Tutorial Apel sistem Linux cu C

Linux System Call Tutorial With C



În ultimul nostru articol despre Apeluri de sistem Linux Am definit un apel de sistem, am discutat motivele pentru care le-am putea folosi într-un program și am aprofundat avantajele și dezavantajele acestora. Am dat chiar un scurt exemplu de asamblare în cadrul C. A ilustrat ideea și a descris cum să efectuați apelul, dar nu a făcut nimic productiv. Nu tocmai un exercițiu de dezvoltare palpitant, dar a ilustrat ideea.

În acest articol, vom folosi apelurile de sistem efective pentru a face lucrări reale în programul nostru C. Mai întâi, vom examina dacă trebuie să utilizați un apel de sistem, apoi vom oferi un exemplu folosind apelul sendfile () care poate îmbunătăți dramatic performanța copierii fișierelor. În cele din urmă, vom trece în revistă câteva puncte de reținut în timp ce utilizați apeluri de sistem Linux.







Deși este inevitabil, veți utiliza un apel de sistem la un moment dat în cariera dvs. de dezvoltare C, cu excepția cazului în care vizați performanțe ridicate sau un anumit tip de funcționalitate, biblioteca glibc și alte biblioteci de bază incluse în distribuțiile Linux majore vor avea grijă de majoritatea nevoile tale.



Biblioteca standard glibc oferă un cadru pe mai multe platforme, bine testat, pentru a executa funcții care altfel ar necesita apeluri de sistem specifice sistemului. De exemplu, puteți citi un fișier cu fscanf (), fread (), getc () etc. sau puteți utiliza apelul de sistem read () Linux. Funcțiile glibc oferă mai multe caracteristici (adică o mai bună gestionare a erorilor, IO formatat etc.) și vor funcționa pe orice sistem de suport glibc.



Pe de altă parte, există momente în care performanța fără compromisuri și execuția exactă sunt critice. Învelișul pe care îl oferă fread () va adăuga cheltuieli generale și, deși minor, nu este complet transparent. În plus, este posibil să nu doriți sau să aveți nevoie de caracteristicile suplimentare pe care le oferă ambalajul. În acest caz, cel mai bine sunteți servit cu un apel de sistem.





De asemenea, puteți utiliza apeluri de sistem pentru a efectua funcții care nu sunt încă acceptate de glibc. Dacă copia dvs. a glibc este actualizată, aceasta va fi cu greu o problemă, dar dezvoltarea pe distribuții mai vechi cu nuclee mai noi ar putea necesita această tehnică.

Acum, după ce ați citit responsabilitățile, avertismentele și posibilele ocoliri, acum să analizăm câteva exemple practice.



Pe ce CPU suntem?

O întrebare pe care probabil că majoritatea programelor nu se gândesc să o pună, dar totuși una validă. Acesta este un exemplu de apel de sistem care nu poate fi duplicat cu glibc și nu este acoperit cu un glibc wrapper. În acest cod, vom apela apelul getcpu () direct prin funcția syscall (). Funcția syscall funcționează după cum urmează:

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

Primul argument, SYS_call, este o definiție care reprezintă numărul apelului de sistem. Când includeți sys / syscall.h, acestea sunt incluse. Prima parte este SYS_ și a doua parte este numele apelului de sistem.

Argumentele pentru apel intră în arg1, arg2 de mai sus. Unele apeluri necesită mai multe argumente și vor continua în ordine din pagina lor de manual. Amintiți-vă că majoritatea argumentelor, în special pentru returnări, vor necesita pointeri pentru a încărca matricele sau memoria alocată prin funcția malloc.

exemplu1.c

#include
#include
#include
#include

intprincipal() {

nesemnatCPU,nodul;

// Obțineți nucleul procesorului curent și nodul NUMA prin apel de sistem
// Rețineți că acesta nu are nicio împachetare glibc, așa că trebuie să-l sunăm direct
syscall(SYS_getcpu, &CPU, &nodul,NUL);

// Afișați informații
printf ('Acest program rulează pe nucleul procesorului% u și nodul NUMA% u. n n',CPU,nodul);

întoarcere 0;

}

Pentru a compila și a rula:

gcc exemplu1.c -o example1
./exemplu1

Pentru rezultate mai interesante, puteți roti firele prin biblioteca pthreads și apoi puteți apela această funcție pentru a vedea pe ce procesor rulează firul dvs.

Sendfile: Performanță superioară

Sendfile oferă un exemplu excelent de îmbunătățire a performanței prin apeluri de sistem. Funcția sendfile () copiază datele dintr-un descriptor de fișiere în altul. În loc să utilizeze mai multe funcții fread () și fwrite (), sendfile efectuează transferul în spațiul kernel, reducând cheltuielile generale și crescând astfel performanța.

În acest exemplu, vom copia 64 MB de date dintr-un fișier în altul. Într-un test, vom folosi metodele standard de citire / scriere din biblioteca standard. În cealaltă, vom folosi apelurile de sistem și apelul sendfile () pentru a exploda aceste date dintr-o locație în alta.

test1.c (glibc)

#include
#include
#include
#include

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

intprincipal() {

FIŞIER*gresit, *Sfârșit;

printf (' nTest I / O cu funcții tradiționale glibc. n n');

// Prindeți un buffer BUFFER_SIZE.
// Tamponul va conține date aleatorii, dar nu ne pasă de asta.
printf („Alocarea bufferului de 64 MB:”);
char *tampon= (char *) malloc (DIMENSIUNEA MEMORIEI TAMPON);
printf ('TERMINAT n');

// Scrieți tamponul pe fOut
printf ('Scrierea datelor în primul buffer:');
gresit= deschide (BUFFER_1, „wb”);
fwrite (tampon, mărimea(char),DIMENSIUNEA MEMORIEI TAMPON,gresit);
fclose (gresit);
printf ('TERMINAT n');

printf ('Copierea datelor din primul fișier în al doilea:');
Sfârșit= deschide (BUFFER_1, „rb”);
gresit= deschide (BUFFER_2, „wb”);
fread (tampon, mărimea(char),DIMENSIUNEA MEMORIEI TAMPON,Sfârșit);
fwrite (tampon, mărimea(char),DIMENSIUNEA MEMORIEI TAMPON,gresit);
fclose (Sfârșit);
fclose (gresit);
printf ('TERMINAT n');

printf („Eliberare tampon:”);
liber (tampon);
printf ('TERMINAT n');

printf („Ștergerea fișierelor:”);
elimina (BUFFER_1);
elimina (BUFFER_2);
printf ('TERMINAT n');

întoarcere 0;

}

test2.c (apeluri de sistem)

#include
#include
#include
#include
#include
#include
#include
#include
#include

#define BUFFER_SIZE 67108864

intprincipal() {

intgresit,Sfârșit;

printf (' nTest I / O cu sendfile () și apeluri de sistem aferente. n n');

// Prindeți un buffer BUFFER_SIZE.
// Tamponul va conține date aleatorii, dar nu ne pasă de asta.
printf („Alocarea bufferului de 64 MB:”);
char *tampon= (char *) malloc (DIMENSIUNEA MEMORIEI TAMPON);
printf ('TERMINAT n');


// Scrieți tamponul pe fOut
printf ('Scrierea datelor în primul buffer:');
gresit=deschis(„tampon1”,O_RDONLY);
scrie(gresit, &tampon,DIMENSIUNEA MEMORIEI TAMPON);
închide(gresit);
printf ('TERMINAT n');

printf ('Copierea datelor din primul fișier în al doilea:');
Sfârșit=deschis(„tampon1”,O_RDONLY);
gresit=deschis(„tampon2”,O_RDONLY);
Trimite fișier(gresit,Sfârșit, 0,DIMENSIUNEA MEMORIEI TAMPON);
închide(Sfârșit);
închide(gresit);
printf ('TERMINAT n');

printf („Eliberare tampon:”);
liber (tampon);
printf ('TERMINAT n');

printf („Ștergerea fișierelor:”);
deconectați(„tampon1”);
deconectați(„tampon2”);
printf ('TERMINAT n');

întoarcere 0;

}

Compilarea și executarea testelor 1 și 2

Pentru a construi aceste exemple, veți avea nevoie de instrumentele de dezvoltare instalate pe distribuția dvs. Pe Debian și Ubuntu, puteți instala acest lucru cu:

aptinstalareesențial de construcție

Apoi compilați cu:

gcctest1.c-sautest1&& gcctest2.c-sautest2

Pentru a rula ambele și a testa performanța, rulați:

timp./test1&& timp./test2

Ar trebui să obțineți rezultate de acest fel:

Test I / O cu funcții tradiționale glibc.

Alocarea memoriei tampon de 64 MB: Efectuat
Scrierea datelor în primul buffer: Efectuat
Copierea datelor din primul fișier în al doilea: Efectuat
Eliberarea bufferului: Efectuat
Ștergerea fișierelor: Efectuat
0m0.397s real
utilizator 0m0.000s
sys 0m0.203s
Test I / O cu sendfile () și apeluri de sistem aferente.
Alocarea memoriei tampon de 64 MB: Efectuat
Scrierea datelor în primul buffer: Efectuat
Copierea datelor din primul fișier în al doilea: Efectuat
Eliberarea bufferului: Efectuat
Ștergerea fișierelor: Efectuat
0m0.019s real
utilizator 0m0.000s
sys 0m0.016s

După cum puteți vedea, codul care utilizează apelurile de sistem rulează mult mai repede decât echivalentul glibc.

Lucruri de amintit

Apelurile de sistem pot crește performanța și pot oferi funcționalități suplimentare, dar nu sunt lipsite de dezavantaje. Va trebui să cântăriți beneficiile oferite de apelurile de sistem față de lipsa portabilității platformei și uneori funcționalitatea redusă în comparație cu funcțiile bibliotecii.

Când utilizați unele apeluri de sistem, trebuie să aveți grijă să utilizați resursele returnate din apelurile de sistem, mai degrabă decât funcțiile de bibliotecă. De exemplu, structura FILE utilizată pentru funcțiile glibc’s fopen (), fread (), fwrite () și fclose () nu sunt identice cu numărul descriptorului de fișiere din apelul de sistem open () (returnat ca întreg). Amestecarea acestora poate duce la probleme.

În general, apelurile de sistem Linux au mai puține benzi de protecție decât funcțiile glibc. Deși este adevărat că apelurile de sistem au o anumită gestionare și raportare a erorilor, veți obține funcționalități mai detaliate dintr-o funcție glibc.

Și, în sfârșit, un cuvânt despre securitate. Apelurile de sistem interacționează direct cu nucleul. Kernel-ul Linux are protecții extinse împotriva șiretlicurilor de pe teritoriul utilizatorilor, dar există erori nedescoperite. Nu aveți încredere că un apel de sistem vă va valida datele sau vă va izola de problemele de securitate. Este înțelept să vă asigurați că datele pe care le transmiteți unui apel de sistem sunt igienizate. Bineînțeles, acesta este un sfat bun pentru orice apel API, dar nu puteți fi atenți atunci când lucrați cu nucleul.

Sper că v-a plăcut această scufundare mai profundă în țara apelurilor de sistem Linux. Pentru o listă completă a apelurilor de sistem Linux, consultați lista noastră principală.