C Programmeren

Linux System Call Tutorial met C

Linux System Call Tutorial met C
In ons laatste artikel over Linux-systeemaanroepen definieerde ik een systeemaanroep, besprak ik de redenen waarom je ze in een programma zou kunnen gebruiken en verdiepte ik me in hun voor- en nadelen. Ik gaf zelfs een kort voorbeeld in assemblage binnen C. Het illustreerde het punt en beschreef hoe je moest bellen, maar deed niets productiefs. Niet bepaald een spannende ontwikkelingsoefening, maar het illustreerde het punt.

In dit artikel gaan we echte systeemaanroepen gebruiken om echt werk te doen in ons C-programma. Eerst bekijken we of u een systeemaanroep moet gebruiken en geven we vervolgens een voorbeeld met de aanroep sendfile() die de kopieerprestaties van bestanden drastisch kan verbeteren. Ten slotte zullen we enkele punten bespreken om te onthouden tijdens het gebruik van Linux-systeemaanroepen.

Heeft u een systeemoproep nodig??

Hoewel het onvermijdelijk is dat je op een bepaald moment in je C-ontwikkelingscarrière een systeemaanroep zult gebruiken, zullen de glibc-bibliotheek en andere basisbibliotheken die zijn opgenomen in grote Linux-distributies, zorgen voor de meerderheid van de je behoeften.

De glibc-standaardbibliotheek biedt een platformonafhankelijk, goed getest raamwerk om functies uit te voeren die anders systeemspecifieke systeemaanroepen zouden vereisen. U kunt bijvoorbeeld een bestand lezen met fscanf(), fread(), getc(), etc., of je kunt de read() Linux-systeemaanroep gebruiken. De glibc-functies bieden meer functies (i.e. betere foutafhandeling, geformatteerde IO, enz.) en werkt op elk systeem dat glibc ondersteunt.

Aan de andere kant zijn er momenten waarop compromisloze prestaties en exacte uitvoering van cruciaal belang zijn. De wrapper die fread() biedt, gaat overhead toevoegen, en hoewel klein, is het niet helemaal transparant. Bovendien wilt of hebt u misschien niet de extra functies die de wrapper biedt. In dat geval ben je het beste geholpen met een systeemoproep.

U kunt systeemaanroepen ook gebruiken om functies uit te voeren die nog niet worden ondersteund door glibc. Als uw exemplaar van glibc up-to-date is, zal dit nauwelijks een probleem zijn, maar ontwikkelen op oudere distributies met nieuwere kernels kan deze techniek vereisen.

Nu u de disclaimers, waarschuwingen en mogelijke omwegen hebt gelezen, gaan we nu in op enkele praktische voorbeelden.

Welke CPU hebben we??

Een vraag die de meeste programma's waarschijnlijk niet denken te stellen, maar toch een geldige vraag. Dit is een voorbeeld van een systeemaanroep die niet kan worden gedupliceerd met glibc en niet is bedekt met een glibc-wrapper. In deze code zullen we de getcpu()-aanroep rechtstreeks aanroepen via de functie syscall(). De syscall-functie werkt als volgt:

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

Het eerste argument, SYS_call, is een definitie die het nummer van de systeemaanroep vertegenwoordigt. Wanneer u sys/syscall . opneemt.h, deze zijn inbegrepen. Het eerste deel is SYS_ en het tweede deel is de naam van de systeemaanroep.

Argumenten voor de oproep gaan naar arg1, arg2 hierboven. Sommige aanroepen vereisen meer argumenten en ze gaan op volgorde verder vanaf hun man-pagina. Onthoud dat de meeste argumenten, vooral voor retouren, verwijzingen nodig hebben naar char-arrays of geheugen toegewezen via de malloc-functie.

voorbeeld 1.c

#include
#include
#include
#include
 
int hoofd()
 
niet-ondertekende cpu, knooppunt;
 
// Haal de huidige CPU-kern en NUMA-knooppunt op via systeemaanroep
// Merk op dat dit geen glibc-wrapper heeft, dus we moeten het direct aanroepen
syscall(SYS_getcpu, &cpu, &node, NULL);
 
// Informatie weergeven
printf("Dit programma draait op CPU core %u en NUMA node %u.\n\n", cpu, knooppunt);
 
retourneer 0;
 

 
Om te compileren en uit te voeren:
 
gcc voorbeeld1.c -o voorbeeld1
./voorbeeld 1

Voor interessantere resultaten kun je threads draaien via de pthreads-bibliotheek en vervolgens deze functie aanroepen om te zien op welke processor je thread draait.

Verzendbestand: Superieure prestaties

Sendfile biedt een uitstekend voorbeeld van het verbeteren van de prestaties door systeemaanroepen. De functie sendfile() kopieert gegevens van de ene bestandsdescriptor naar de andere. In plaats van meerdere functies fread() en fwrite() te gebruiken, voert sendfile de overdracht uit in de kernelruimte, waardoor de overhead wordt verminderd en de prestaties worden verhoogd.

In dit voorbeeld gaan we 64 MB aan gegevens kopiëren van het ene bestand naar het andere. In één test gaan we de standaard lees-/schrijfmethoden in de standaardbibliotheek gebruiken. In de andere gebruiken we systeemaanroepen en de aanroep sendfile() om deze gegevens van de ene locatie naar de andere te blazen.

test1.c (glibc)

#include
#include
#include
#include
 
#define BUFFER_SIZE 67108864
#define BUFFER_1 "buffer1"
#define BUFFER_2 "buffer2"
 
int hoofd()
 
BESTAND *fOut, *fIn;
 
printf("\nI/O-test met traditionele glibc-functies.\n\n");
 
// Pak een BUFFER_SIZE buffer.
// De buffer zal willekeurige gegevens bevatten, maar dat maakt ons niet uit.
printf("Buffer van 64 MB toewijzen:                     ");
char *buffer = (char *) malloc(BUFFER_SIZE);
printf("KLAAR\n");
 
// Schrijf de buffer naar fOut
printf("Gegevens naar eerste buffer schrijven:                ");
fOut = fopen(BUFFER_1, "wb");
fwrite(buffer, sizeof(char), BUFFER_SIZE, fOut);
fclose(fOut);
printf("KLAAR\n");
 
printf("Gegevens kopiëren van eerste bestand naar tweede:      ");
fIn = fopen(BUFFER_1, "rb");
fOut = fopen(BUFFER_2, "wb");
fread(buffer, sizeof(char), BUFFER_SIZE, fIn);
fwrite(buffer, sizeof(char), BUFFER_SIZE, fOut);
fclose(fIn);
fclose(fOut);
printf("KLAAR\n");
 
printf("Buffer vrijmaken:                              ");
gratis (buffer);
printf("KLAAR\n");
 
printf("Bestanden verwijderen:                              ");
verwijderen(BUFFER_1);
verwijderen (BUFFER_2);
printf("KLAAR\n");
 
retourneer 0;
 

test2.c (systeemoproepen)

#include
#include
#include
#include
#include
#include
#include
#include
#include
 
#define BUFFER_SIZE 67108864
 
int hoofd()
 
int fOut, fIn;
 
printf("\nI/O-test met sendfile() en gerelateerde systeemaanroepen.\n\n");
 
// Pak een BUFFER_SIZE buffer.
// De buffer zal willekeurige gegevens bevatten, maar dat maakt ons niet uit.
printf("Buffer van 64 MB toewijzen:                     ");
char *buffer = (char *) malloc(BUFFER_SIZE);
printf("KLAAR\n");
 
// Schrijf de buffer naar fOut
printf("Gegevens naar eerste buffer schrijven:                ");
fOut = open("buffer1", O_RDONLY);
schrijven (fOut, &buffer, BUFFER_SIZE);
sluiten(fOut);
printf("KLAAR\n");
 
printf("Gegevens kopiëren van eerste bestand naar tweede:      ");
fIn = open("buffer1", O_RDONLY);
fOut = open("buffer2", O_RDONLY);
sendfile (fOut, fIn, 0, BUFFER_SIZE);
sluiten (fIn);
sluiten(fOut);
printf("KLAAR\n");
 
printf("Buffer vrijmaken:                              ");
gratis (buffer);
printf("KLAAR\n");
 
printf("Bestanden verwijderen:                              ");
ontkoppelen ("buffer1");
ontkoppelen ("buffer2");
printf("KLAAR\n");
 
retourneer 0;
 

Samenstellen en uitvoeren van tests 1 & 2

Om deze voorbeelden te bouwen, hebt u de ontwikkeltools nodig die op uw distributie zijn geïnstalleerd. Op Debian en Ubuntu kunt u dit installeren met:

apt install build-essentials

Compileer dan met:

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

Om beide uit te voeren en de prestaties te testen, voert u het volgende uit:

tijd ./test1 && tijd ./test2

U zou de volgende resultaten moeten krijgen:

I/O-test met traditionele glibc-functies.

64 MB buffer toewijzen:                     KLAAR
Gegevens naar eerste buffer schrijven:                KLAAR
Gegevens kopiëren van het eerste bestand naar het tweede:      KLAAR
Buffer vrijmaken:                              KLAAR
Bestanden verwijderen:                              KLAAR
echt    0m0.397s
gebruiker    0m0.000s
sys     0m0.203s
I/O-test met sendfile() en gerelateerde systeemaanroepen.
64 MB buffer toewijzen:                     KLAAR
Gegevens naar eerste buffer schrijven:                KLAAR
Gegevens kopiëren van het eerste bestand naar het tweede:      KLAAR
Buffer vrijmaken:                              KLAAR
Bestanden verwijderen:                              KLAAR
echt    0m0.019s
gebruiker    0m0.000s
sys     0m0.016s

Zoals je kunt zien, werkt de code die de systeemaanroepen gebruikt veel sneller dan het glibc-equivalent.

Dingen om te onthouden

Systeemoproepen kunnen de prestaties verhogen en extra functionaliteit bieden, maar ze zijn niet zonder nadelen. U moet de voordelen die systeemaanroepen bieden afwegen tegen het gebrek aan platformportabiliteit en soms verminderde functionaliteit in vergelijking met bibliotheekfuncties.

Wanneer u bepaalde systeemaanroepen gebruikt, moet u ervoor zorgen dat u bronnen gebruikt die worden geretourneerd door systeemaanroepen in plaats van bibliotheekfuncties. De FILE-structuur die wordt gebruikt voor de functies fopen(), fread(), fwrite() en fclose() van glibc is bijvoorbeeld niet hetzelfde als het bestandsdescriptornummer van de systeemaanroep open() (geretourneerd als een geheel getal). Het mengen van deze kan tot problemen leiden.

Over het algemeen hebben Linux-systeemaanroepen minder bumperlanes dan glibc-functies. Hoewel het waar is dat systeemaanroepen enige foutafhandeling en rapportage hebben, krijgt u meer gedetailleerde functionaliteit van een glibc-functie.

En tot slot een woord over veiligheid. Systeemaanroepen hebben een directe interface met de kernel. De Linux-kernel heeft uitgebreide bescherming tegen shenanigans van gebruikersland, maar er bestaan ​​onontdekte bugs. Vertrouw er niet op dat een systeemaanroep uw invoer valideert of u isoleert van beveiligingsproblemen. Het is verstandig om ervoor te zorgen dat de gegevens die u aan een systeemoproep overhandigt, worden opgeschoond. Dit is natuurlijk een goed advies voor elke API-aanroep, maar je kunt niet voorzichtig zijn als je met de kernel werkt.

Ik hoop dat je genoten hebt van deze diepere duik in het land van Linux-systeemaanroepen. Zie onze hoofdlijst voor een volledige lijst met Linux-systeemaanroepen.

De scrollrichting van de muis en touchpads omkeren in Windows 10
Muis en Touchpads maken computergebruik niet alleen eenvoudig, maar ook efficiënter en minder tijdrovend. We kunnen ons een leven zonder deze apparate...
Hoe de muisaanwijzer en cursorgrootte, kleur en schema op Windows 10 te veranderen
De muisaanwijzer en cursor in Windows 10 zijn zeer belangrijke aspecten van het besturingssysteem. Dit geldt ook voor andere besturingssystemen, dus i...
Gratis en open source game-engines voor het ontwikkelen van Linux-games
Dit artikel behandelt een lijst met gratis en open source game-engines die kunnen worden gebruikt voor het ontwikkelen van 2D- en 3D-games op Linux. E...