C Programmazione

Linux System Call Tutorial con C

Linux System Call Tutorial con C
Nel nostro ultimo articolo sulle chiamate di sistema Linux, ho definito una chiamata di sistema, discusso i motivi per cui è possibile utilizzarle in un programma e ho approfondito i loro vantaggi e svantaggi. Ho anche fornito un breve esempio di assemblaggio all'interno di C. Ha illustrato il punto e descritto come effettuare la chiamata, ma non ha fatto nulla di produttivo. Non è esattamente un esercizio di sviluppo entusiasmante, ma ha illustrato il punto.

In questo articolo, useremo le chiamate di sistema effettive per fare un vero lavoro nel nostro programma C. Innanzitutto, esamineremo se è necessario utilizzare una chiamata di sistema, quindi forniremo un esempio utilizzando la chiamata sendfile() che può migliorare notevolmente le prestazioni di copia dei file. Infine, esamineremo alcuni punti da ricordare durante l'utilizzo delle chiamate di sistema Linux.

Hai bisogno di una chiamata di sistema??

Sebbene sia inevitabile che utilizzerai una chiamata di sistema ad un certo punto della tua carriera di sviluppo C, a meno che tu non stia mirando ad alte prestazioni o a una particolare funzionalità di tipo, la libreria glibc e altre librerie di base incluse nelle principali distribuzioni Linux si prenderanno cura della maggior parte di I tuoi bisogni.

La libreria standard glibc fornisce un framework multipiattaforma e ben collaudato per eseguire funzioni che altrimenti richiederebbero chiamate di sistema specifiche del sistema. Ad esempio, puoi leggere un file con fscanf(), fread(), getc(), etc., oppure puoi usare la chiamata di sistema Linux read(). Le funzioni glibc forniscono più funzioni (i.e. migliore gestione degli errori, IO formattato, ecc.) e funzionerà su qualsiasi sistema supportato da glibc.

D'altra parte, ci sono momenti in cui le prestazioni senza compromessi e l'esatta esecuzione sono fondamentali. Il wrapper fornito da fread() aggiungerà un sovraccarico e, sebbene minore, non è del tutto trasparente. Inoltre, potresti non volere o non aver bisogno delle funzionalità extra fornite dal wrapper. In tal caso, ti servirà meglio con una chiamata di sistema.

Puoi anche usare le chiamate di sistema per eseguire funzioni non ancora supportate da glibc. Se la tua copia di glibc è aggiornata, difficilmente questo sarà un problema, ma lo sviluppo su distribuzioni precedenti con kernel più recenti potrebbe richiedere questa tecnica.

Ora che hai letto i disclaimer, gli avvertimenti e le potenziali deviazioni, esaminiamo ora alcuni esempi pratici.

Su quale CPU siamo??

Una domanda che la maggior parte dei programmi probabilmente non pensa di fare, ma comunque valida. Questo è un esempio di una chiamata di sistema che non può essere duplicata con glibc e non è coperta da un wrapper glibc. In questo codice, chiameremo la chiamata getcpu() direttamente tramite la funzione syscall(). La funzione syscall funziona come segue:

syscall(SYS_call, arg1, arg2,… ​​);

Il primo argomento, SYS_call, è una definizione che rappresenta il numero della chiamata di sistema. Quando includi sys/syscall.h, questi sono inclusi. La prima parte è SYS_ e la seconda parte è il nome della chiamata di sistema.

Gli argomenti per la chiamata vanno in arg1, arg2 sopra. Alcune chiamate richiedono più argomenti e continueranno in ordine dalla loro pagina man. Ricorda che la maggior parte degli argomenti, specialmente per i ritorni, richiederanno puntatori a array di caratteri o memoria allocata tramite la funzione malloc.

Esempio 1.c

#includere
#includere
#includere
#includere
 
int main()
 
CPU non firmata, nodo;
 
// Ottieni il core della CPU corrente e il nodo NUMA tramite la chiamata di sistema
// Nota che questo non ha wrapper glibc quindi dobbiamo chiamarlo direttamente
syscall(SYS_getcpu, &cpu, &node, NULL);
 
// Visualizza le informazioni
printf("Questo programmaèin esecuzione sul core della CPU %u e sul nodo NUMA %u.\n\n", cpu, nodo);
 
restituisce 0;
 

 
Per compilare ed eseguire:
 
gcc esempio1.c -o esempio1
./Esempio 1

Per risultati più interessanti, puoi girare i thread tramite la libreria pthreads e quindi chiamare questa funzione per vedere su quale processore è in esecuzione il tuo thread.

Sendfile: prestazioni superiori

Sendfile fornisce un eccellente esempio di miglioramento delle prestazioni tramite chiamate di sistema. La funzione sendfile() copia i dati da un descrittore di file a un altro. Invece di utilizzare più funzioni fread() e fwrite(), sendfile esegue il trasferimento nello spazio del kernel, riducendo il sovraccarico e aumentando così le prestazioni.

In questo esempio, copieremo 64 MB di dati da un file a un altro. In un test, useremo i metodi di lettura/scrittura standard nella libreria standard. Nell'altro, useremo le chiamate di sistema e la chiamata sendfile() per far saltare questi dati da una posizione all'altra.

prova1.c (glibc)

#includere
#includere
#includere
#includere
 
#define BUFFER_SIZE 67108864
#define BUFFER_1 "buffer1"
#define BUFFER_2 "buffer2"
 
int main()
 
FILE *fOut, *fIn;
 
printf("\nTest I/O con le tradizionali funzioni glibc.\n\n");
 
// Prendi un buffer BUFFER_SIZE.
// Il buffer conterrà dati casuali ma non ci interessa.
printf("Allocazione buffer 64 MB:                    ");
char *buffer = (char *) malloc(BUFFER_SIZE);
printf("FATTO\n");
 
// Scrive il buffer su fOut
printf("Scrittura dati nel primo buffer:                ");
fOut = fopen(BUFFER_1, "wb");
fwrite(buffer, sizeof(char), BUFFER_SIZE, fOut);
fclose(fOut);
printf("FATTO\n");
 
printf("Copia dati dal primo file al secondo:      ");
fIn = fopen(BUFFER_1, "rb");
fOut = fopen(BUFFER_2, "wb");
fread(buffer, sizeof(char), BUFFER_SIZE, fIn);
fwrite(buffer, sizeof(char), BUFFER_SIZE, fOut);
fchiudi(fIn);
fclose(fOut);
printf("FATTO\n");
 
printf("Liberazione buffer:                              ");
libero (tampone);
printf("FATTO\n");
 
printf("Eliminazione file:                            ");
remove(BUFFER_1);
remove(BUFFER_2);
printf("FATTO\n");
 
restituisce 0;
 

prova2.c (chiamate di sistema)

#includere
#includere
#includere
#includere
#includere
#includere
#includere
#includere
#includere
 
#define BUFFER_SIZE 67108864
 
int main()
 
int fOut, fIn;
 
printf("\nTest I/O con sendfile() e relative chiamate di sistema.\n\n");
 
// Prendi un buffer BUFFER_SIZE.
// Il buffer conterrà dati casuali ma non ci interessa.
printf("Allocazione buffer 64 MB:                    ");
char *buffer = (char *) malloc(BUFFER_SIZE);
printf("FATTO\n");
 
// Scrive il buffer su fOut
printf("Scrittura dati nel primo buffer:                ");
fOut = open("buffer1", O_RDONLY);
write(fOut, &buffer, BUFFER_SIZE);
chiudi(fOut);
printf("FATTO\n");
 
printf("Copia dati dal primo file al secondo:      ");
fIn = open("buffer1", O_RDONLY);
fOut = open("buffer2", O_RDONLY);
sendfile(fOut, fIn, 0, BUFFER_SIZE);
chiudi(fIn);
chiudi(fOut);
printf("FATTO\n");
 
printf("Liberazione buffer:                              ");
libero (tampone);
printf("FATTO\n");
 
printf("Eliminazione file:                            ");
unlink("buffer1");
unlink("buffer2");
printf("FATTO\n");
 
restituisce 0;
 

Compilazione ed esecuzione dei test 1 e 2

Per creare questi esempi, avrai bisogno degli strumenti di sviluppo installati sulla tua distribuzione. Su Debian e Ubuntu, puoi installarlo con:

apt install build-essentials

Quindi compilare con:

gcc test1.c -o test1 && gcc test2.c -o test2

Per eseguire entrambi e testare le prestazioni, eseguire:

tempo ./test1 && ora ./test2

Dovresti ottenere risultati come questo:

Test I/O con le tradizionali funzioni glibcc.

Allocazione di 64 MB di buffer:                    FATTO
Scrittura dei dati nel primo buffer:                FATTO
Copia dei dati dal primo file al secondo:      FATTO
Buffer di liberazione:                             FATTO
Eliminazione dei file:                             FATTO
reale    0m0.397 secondi
utente    0m0.000s
sis     0m0.203s
Test I/O con sendfile() e relative chiamate di sistema.
Allocazione di 64 MB di buffer:                    FATTO
Scrittura dei dati nel primo buffer:                FATTO
Copia dei dati dal primo file al secondo:      FATTO
Buffer di liberazione:                             FATTO
Eliminazione dei file:                              FATTO
reale    0m0.019s
utente    0m0.000s
sis     0m0.016s

Come puoi vedere, il codice che usa le chiamate di sistema viene eseguito molto più velocemente dell'equivalente glibc.

Cose da ricordare

Le chiamate di sistema possono aumentare le prestazioni e fornire funzionalità aggiuntive, ma non sono prive di svantaggi. Dovrai valutare i vantaggi offerti dalle chiamate di sistema rispetto alla mancanza di portabilità della piattaforma e talvolta funzionalità ridotte rispetto alle funzioni della libreria.

Quando si utilizzano alcune chiamate di sistema, è necessario prestare attenzione a utilizzare le risorse restituite dalle chiamate di sistema piuttosto che le funzioni di libreria. Ad esempio, la struttura FILE utilizzata per le funzioni fopen(), fread(), fwrite() e fclose() di glibc non è la stessa del numero del descrittore di file dalla chiamata di sistema open() (restituita come numero intero). Mescolarli può portare a problemi.

In generale, le chiamate di sistema Linux hanno meno corsie paraurti rispetto alle funzioni glibc. Sebbene sia vero che le chiamate di sistema hanno una gestione e segnalazione degli errori, otterrai funzionalità più dettagliate da una funzione glibc.

E infine, una parola sulla sicurezza. Le chiamate di sistema si interfacciano direttamente con il kernel. Il kernel Linux ha ampie protezioni contro gli imbrogli provenienti dalla terra degli utenti, ma esistono bug non scoperti. Non fidarti che una chiamata di sistema convaliderà il tuo input o ti isolerà dai problemi di sicurezza. È saggio assicurarsi che i dati che si consegnano a una chiamata di sistema siano disinfettati. Naturalmente, questo è un buon consiglio per qualsiasi chiamata API, ma non puoi stare attento quando lavori con il kernel.

Spero che ti sia piaciuto questo tuffo più profondo nella terra delle chiamate di sistema di Linux. Per un elenco completo delle chiamate di sistema Linux, consulta il nostro elenco principale.

Come modificare il puntatore del mouse e le dimensioni, il colore e lo schema del cursore su Windows 10
Il puntatore del mouse e il cursore in Windows 10 sono aspetti molto importanti del sistema operativo. Questo si può dire anche per altri sistemi oper...
Motori di gioco gratuiti e open source per lo sviluppo di giochi Linux
Questo articolo tratterà un elenco di motori di gioco gratuiti e open source che possono essere utilizzati per lo sviluppo di giochi 2D e 3D su Linux....
Tutorial Shadow of the Tomb Raider per Linux
Shadow of the Tomb Raider è la dodicesima aggiunta alla serie Tomb Raider, un franchise di giochi d'azione e avventura creato da Eidos Montreal. Il gi...