Linux-Systemaufruf-Tutorial mit C

Linux System Call Tutorial With C



In unserem letzten Artikel über Linux-Systemaufrufe , definierte ich einen Systemaufruf, diskutierte die Gründe, warum man sie in einem Programm verwenden könnte, und ging auf ihre Vor- und Nachteile ein. Ich habe sogar ein kurzes Beispiel in Assembler in C gegeben. Es illustrierte den Punkt und beschrieb, wie man den Anruf tätigt, aber es ergab sich nichts Produktives. Nicht gerade eine aufregende Entwicklungsübung, aber sie veranschaulichte den Punkt.

In diesem Artikel werden wir tatsächliche Systemaufrufe verwenden, um echte Arbeit in unserem C-Programm zu erledigen. Zuerst überprüfen wir, ob Sie einen Systemaufruf verwenden müssen, und geben dann ein Beispiel mit dem sendfile()-Aufruf, der die Dateikopierleistung drastisch verbessern kann. Schließlich gehen wir auf einige Punkte ein, die Sie bei der Verwendung von Linux-Systemaufrufen beachten sollten.







Es ist zwar unvermeidlich, dass Sie irgendwann in Ihrer C-Entwicklungskarriere einen Systemaufruf verwenden werden, es sei denn, Sie streben eine hohe Leistung oder eine bestimmte Typfunktionalität an, aber die glibc-Bibliothek und andere grundlegende Bibliotheken, die in den wichtigsten Linux-Distributionen enthalten sind, werden den Großteil der Deine Bedürfnisse.



Die Standardbibliothek glibc bietet ein plattformübergreifendes, gut getestetes Framework zum Ausführen von Funktionen, die ansonsten systemspezifische Systemaufrufe erfordern würden. Sie können beispielsweise eine Datei mit fscanf(), fread(), getc() usw. lesen oder den Linux-Systemaufruf read() verwenden. Die glibc-Funktionen bieten mehr Funktionen (d. h. bessere Fehlerbehandlung, formatierte E/A usw.) und funktionieren auf jedem System, das von der glibc unterstützt wird.



Andererseits gibt es Zeiten, in denen kompromisslose Leistung und exakte Ausführung entscheidend sind. Der Wrapper, den fread() bietet, wird Overhead hinzufügen, und obwohl er geringfügig ist, ist er nicht ganz transparent. Darüber hinaus möchten oder benötigen Sie möglicherweise die zusätzlichen Funktionen, die der Wrapper bietet, nicht. In diesem Fall sind Sie am besten mit einem Systemaufruf bedient.





Sie können auch Systemaufrufe verwenden, um Funktionen auszuführen, die von der glibc noch nicht unterstützt werden. Wenn Ihre Kopie von glibc auf dem neuesten Stand ist, wird dies kaum ein Problem darstellen, aber die Entwicklung auf älteren Distributionen mit neueren Kerneln erfordert möglicherweise diese Technik.

Nachdem Sie die Haftungsausschlüsse, Warnungen und möglichen Umwege gelesen haben, wollen wir uns nun einigen praktischen Beispielen widmen.



Auf welcher CPU sind wir?

Eine Frage, die die meisten Programme wahrscheinlich nicht stellen wollen, aber dennoch eine berechtigte. Dies ist ein Beispiel für einen Systemaufruf, der nicht mit glibc dupliziert werden kann und nicht mit einem glibc-Wrapper abgedeckt ist. In diesem Code rufen wir den Aufruf getcpu() direkt über die Funktion syscall() auf. Die syscall-Funktion funktioniert wie folgt:

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

Das erste Argument, SYS_call, ist eine Definition, die die Nummer des Systemaufrufs darstellt. Wenn Sie sys/syscall.h einschließen, sind diese enthalten. Der erste Teil ist SYS_ und der zweite Teil ist der Name des Systemaufrufs.

Argumente für den Aufruf gehen in arg1, arg2 oben. Einige Aufrufe erfordern mehr Argumente und werden in der Reihenfolge ihrer Manpage fortgesetzt. Denken Sie daran, dass die meisten Argumente, insbesondere für Rückgaben, Zeiger auf char-Arrays oder über die malloc-Funktion zugewiesenen Speicher erfordern.

beispiel1.c

#enthalten
#enthalten
#enthalten
#enthalten

inthauptsächlich() {

ohne VorzeichenZentralprozessor,Knoten;

// Aktuellen CPU-Kern und NUMA-Knoten per Systemaufruf abrufen
// Beachten Sie, dass dies keinen glibc-Wrapper hat, also müssen wir ihn direkt aufrufen
Systemaufruf(SYS_getcpu, &Zentralprozessor, &Knoten,NULL);

// Informationen anzeigen
druckenf ('Dieses Programm läuft auf CPU-Kern %u und NUMA-Knoten %u. ',Zentralprozessor,Knoten);

Rückkehr 0;

}

Kompilieren und ausführen:

gcc-Beispiel1.C -o Beispiel1
./Beispiel 1

Für interessantere Ergebnisse könnten Sie Threads über die pthreads-Bibliothek drehen und dann diese Funktion aufrufen, um zu sehen, auf welchem ​​Prozessor Ihr Thread läuft.

Sendfile: Überlegene Leistung

Sendfile bietet ein hervorragendes Beispiel für die Leistungssteigerung durch Systemaufrufe. Die Funktion sendfile() kopiert Daten von einem Dateideskriptor in einen anderen. Anstatt mehrere fread()- und fwrite()-Funktionen zu verwenden, führt sendfile die Übertragung im Kernel-Space durch, wodurch der Overhead reduziert und dadurch die Leistung erhöht wird.

In diesem Beispiel kopieren wir 64 MB Daten von einer Datei in eine andere. In einem Test werden wir die Standard-Lese-/Schreibmethoden in der Standardbibliothek verwenden. Im anderen verwenden wir Systemaufrufe und den Aufruf sendfile(), um diese Daten von einem Ort zum anderen zu senden.

test1.c (glibc)

#enthalten
#enthalten
#enthalten
#enthalten

#define BUFFER_SIZE 67108864
#define BUFFER_1 'puffer1'
#define BUFFER_2 'Puffer2'

inthauptsächlich() {

DATEI*falsch, *Ende;

druckenf (' I/O-Test mit traditionellen glibc-Funktionen. ');

// Holen Sie sich einen BUFFER_SIZE-Puffer.
// Der Puffer enthält zufällige Daten, aber das interessiert uns nicht.
druckenf ('64 MB Puffer zuweisen: ');
verkohlen *Puffer= (verkohlen *) malloc (PUFFERGRÖSSE);
druckenf ('GETAN ');

// Schreibe den Puffer nach fOut
druckenf ('Schreiben von Daten in den ersten Puffer: ');
falsch= fopen (BUFFER_1, 'wb');
fschreiben (Puffer, Größe von(verkohlen),PUFFERGRÖSSE,falsch);
fschließen (falsch);
druckenf ('GETAN ');

druckenf ('Daten von der ersten Datei in die zweite kopieren: ');
Ende= fopen (BUFFER_1, 'rb');
falsch= fopen (BUFFER_2, 'wb');
fread (Puffer, Größe von(verkohlen),PUFFERGRÖSSE,Ende);
fschreiben (Puffer, Größe von(verkohlen),PUFFERGRÖSSE,falsch);
fschließen (Ende);
fschließen (falsch);
druckenf ('GETAN ');

druckenf ('Puffer freigeben: ');
kostenlos (Puffer);
druckenf ('GETAN ');

druckenf ('Dateien löschen: ');
Löschen (BUFFER_1);
Löschen (BUFFER_2);
druckenf ('GETAN ');

Rückkehr 0;

}

test2.c (Systemaufrufe)

#enthalten
#enthalten
#enthalten
#enthalten
#enthalten
#enthalten
#enthalten
#enthalten
#enthalten

#define BUFFER_SIZE 67108864

inthauptsächlich() {

intfalsch,Ende;

druckenf (' E/A-Test mit sendfile() und zugehörigen Systemaufrufen. ');

// Holen Sie sich einen BUFFER_SIZE-Puffer.
// Der Puffer enthält zufällige Daten, aber das interessiert uns nicht.
druckenf ('64 MB Puffer zuweisen: ');
verkohlen *Puffer= (verkohlen *) malloc (PUFFERGRÖSSE);
druckenf ('GETAN ');


// Schreibe den Puffer nach fOut
druckenf ('Schreiben von Daten in den ersten Puffer: ');
falsch=offen('puffer1',O_RDONLY);
schreiben(falsch, &Puffer,PUFFERGRÖSSE);
nah dran(falsch);
druckenf ('GETAN ');

druckenf ('Daten von der ersten Datei in die zweite kopieren: ');
Ende=offen('puffer1',O_RDONLY);
falsch=offen('puffer2',O_RDONLY);
Datei senden(falsch,Ende, 0,PUFFERGRÖSSE);
nah dran(Ende);
nah dran(falsch);
druckenf ('GETAN ');

druckenf ('Puffer freigeben: ');
kostenlos (Puffer);
druckenf ('GETAN ');

druckenf ('Dateien löschen: ');
Verknüpfung aufheben('puffer1');
Verknüpfung aufheben('puffer2');
druckenf ('GETAN ');

Rückkehr 0;

}

Kompilieren und Ausführen von Tests 1 & 2

Um diese Beispiele zu erstellen, benötigen Sie die auf Ihrer Distribution installierten Entwicklungstools. Unter Debian und Ubuntu können Sie dies installieren mit:

geeignetInstallierenBuild-Essentials

Dann kompilieren mit:

gcctest1.c-odertest1&& gcctest2.c-odertest2

Um beide auszuführen und die Leistung zu testen, führen Sie Folgendes aus:

Zeit./test1&& Zeit./test2

Sie sollten Ergebnisse wie diese erhalten:

I/O-Test mit traditionellen glibc-Funktionen.

64 MB Puffer zuweisen: FERTIG
Schreiben von Daten in den ersten Puffer: FERTIG
Kopieren von Daten von der ersten Datei in die zweite: FERTIG
Puffer freigeben: FERTIG
Dateien löschen: FERTIG
echte 0m0.397s
Benutzer 0m0.000s
sys 0m0.203s
E/A-Test mit sendfile() und zugehörigen Systemaufrufen.
64 MB Puffer zuweisen: FERTIG
Schreiben von Daten in den ersten Puffer: FERTIG
Kopieren von Daten von der ersten Datei in die zweite: FERTIG
Puffer freigeben: FERTIG
Dateien löschen: FERTIG
echte 0m0.019s
Benutzer 0m0.000s
sys 0m0.016s

Wie Sie sehen, läuft der Code, der die Systemaufrufe verwendet, viel schneller als das glibc-Äquivalent.

Dinge, die Sie sich merken sollten

Systemaufrufe können die Leistung steigern und zusätzliche Funktionen bereitstellen, sind jedoch nicht ohne Nachteile. Sie müssen die Vorteile, die Systemaufrufe bieten, gegen die fehlende Plattformportabilität und manchmal reduzierte Funktionalität im Vergleich zu Bibliotheksfunktionen abwägen.

Bei einigen Systemaufrufen müssen Sie darauf achten, Ressourcen, die von Systemaufrufen zurückgegeben werden, und nicht Bibliotheksfunktionen zu verwenden. Beispielsweise ist die FILE-Struktur, die für die Funktionen fopen(), fread(), fwrite() und fclose() von glibc verwendet wird, nicht identisch mit der Dateideskriptornummer aus dem open()-Systemaufruf (wird als Ganzzahl zurückgegeben). Diese zu mischen kann zu Problemen führen.

Im Allgemeinen haben Linux-Systemaufrufe weniger Bumper-Lanes als glibc-Funktionen. Es stimmt zwar, dass Systemaufrufe eine gewisse Fehlerbehandlung und Berichterstellung haben, Sie erhalten jedoch detailliertere Funktionen von einer glibc-Funktion.

Und zum Schluss noch ein Wort zur Sicherheit. Systemaufrufe sind direkt mit dem Kernel verbunden. Der Linux-Kernel bietet umfangreiche Schutzmaßnahmen gegen Spielereien aus dem Benutzerland, aber es gibt unentdeckte Fehler. Vertrauen Sie nicht darauf, dass ein Systemaufruf Ihre Eingabe validiert oder Sie von Sicherheitsproblemen isoliert. Es ist ratsam, sicherzustellen, dass die Daten, die Sie einem Systemaufruf übergeben, bereinigt werden. Dies ist natürlich ein guter Rat für jeden API-Aufruf, aber Sie können nicht vorsichtig sein, wenn Sie mit dem Kernel arbeiten.

Ich hoffe, Ihnen hat dieser tiefere Einblick in das Land der Linux-Systemaufrufe gefallen. Eine vollständige Liste der Linux-Systemaufrufe finden Sie in unserer Masterliste.