C-Programmierung

Linux-Systemaufruf-Tutorial mit C

Linux-Systemaufruf-Tutorial mit C
In unserem letzten Artikel über Linux-Systemaufrufe habe ich einen Systemaufruf definiert, die Gründe diskutiert, warum man sie in einem Programm verwenden könnte, und mich mit ihren Vor- und Nachteilen befasst. Ich habe sogar ein kurzes Beispiel in der Montage in C . gegeben. Es veranschaulichte den Punkt und beschrieb, wie man den Anruf tätigt, aber es hat nichts produktives getan. Nicht gerade eine spannende Entwicklungsübung, aber es hat den Punkt verdeutlicht illustrated.

In diesem Artikel werden wir echte 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 Aufruf sendfile() an, der die Leistung beim Kopieren von Dateien erheblich verbessern kann. Schließlich werden wir einige Punkte durchgehen, die Sie bei der Verwendung von Linux-Systemaufrufen beachten sollten.

Benötigen Sie einen Systemaufruf??

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 glibc-Standardbibliothek bietet ein plattformübergreifendes, gut getestetes Framework, um Funktionen auszuführen, die sonst systemspezifische Systemaufrufe erfordern würden. Sie können beispielsweise eine Datei mit fscanf(), fread(), getc() usw. lesen., oder Sie können den Linux-Systemaufruf read() verwenden. Die glibc-Funktionen bieten weitere Funktionen (i.e. bessere Fehlerbehandlung, formatierte E/A, etc.) und funktioniert auf jedem System, das glibc unterstützt.

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 nicht die zusätzlichen Funktionen, die der Wrapper bietet not. In diesem Fall sind Sie am besten mit einem Systemaufruf bedient.

Sie können auch Systemaufrufe verwenden, um Funktionen auszuführen, die von 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 nun die Haftungsausschlüsse, Warnungen und möglichen Umwege gelesen haben, werfen wir nun einen Blick auf einige praktische Beispiele.

Welche CPU haben wir??

Eine Frage, die die meisten Programme wahrscheinlich nicht stellen wollen, aber dennoch eine berechtigte Frage. 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 getcpu()-Aufruf direkt über die syscall()-Funktion auf. Die syscall-Funktion funktioniert wie folgt:

syscall(SYS_call, arg1, arg2,…);

Das erste Argument, SYS_call, ist eine Definition, die die Nummer des Systemaufrufs darstellt. Wenn Sie sys/syscall . einschließen.h, diese sind 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.

Beispiel 1.c

#einschließen
#einschließen
#einschließen
#einschließen
 
int main()
 
unsignierte CPU, 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
syscall(SYS_getcpu, &cpu, &node, NULL);
 
// Informationen anzeigen
printf("Dieses Programm läuft auf CPU-Kern %u und NUMA-Knoten %u.\n\n", CPU, Knoten);
 
0 zurückgeben;
 

 
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 verwenden wir die Standard-Lese-/Schreibmethoden in der Standardbibliothek. Im anderen verwenden wir Systemaufrufe und den Aufruf sendfile(), um diese Daten von einem Ort zum anderen zu senden.

test1.c (glibc)

#einschließen
#einschließen
#einschließen
#einschließen
 
#define BUFFER_SIZE 67108864
#define BUFFER_1 "puffer1"
#define BUFFER_2 "puffer2"
 
int main()
 
DATEI *fOut, *fIn;
 
printf("\nE/A-Test mit traditionellen glibc-Funktionen.\n\n");
 
// Nimm einen BUFFER_SIZE-Puffer.
// Der Puffer enthält zufällige Daten, aber das interessiert uns nicht.
printf("64 MB Puffer zuweisen:                     ");
char *buffer = (char *) malloc(BUFFER_SIZE);
printf("FERTIG\n");
 
// Schreibe den Puffer nach fOut
printf("Schreiben von Daten in den ersten Puffer:                ");
fOut = fopen(BUFFER_1, "wb");
fwrite (Puffer, Größe von (Zeichen), BUFFER_SIZE, fOut);
fclose(fOut);
printf("FERTIG\n");
 
printf("Daten von der ersten Datei in die zweite kopieren:      ");
fIn = fopen(BUFFER_1, "rb");
fOut = fopen(BUFFER_2, "wb");
fread(Puffer, sizeof(char), BUFFER_SIZE, fIn);
fwrite (Puffer, Größe von (Zeichen), BUFFER_SIZE, fOut);
fclose(fIn);
fclose(fOut);
printf("FERTIG\n");
 
printf("Puffer freigeben:                             ");
frei(Puffer);
printf("FERTIG\n");
 
printf("Dateien löschen:                             ");
entfernen(BUFFER_1);
entfernen (BUFFER_2);
printf("FERTIG\n");
 
0 zurückgeben;
 

test2.c (Systemaufrufe)

#einschließen
#einschließen
#einschließen
#einschließen
#einschließen
#einschließen
#einschließen
#einschließen
#einschließen
 
#define BUFFER_SIZE 67108864
 
int main()
 
int fOut, fIn;
 
printf("\nE/A-Test mit sendfile() und zugehörigen Systemaufrufen.\n\n");
 
// Nimm einen BUFFER_SIZE-Puffer.
// Der Puffer enthält zufällige Daten, aber das interessiert uns nicht.
printf("64 MB Puffer zuweisen:                     ");
char *buffer = (char *) malloc(BUFFER_SIZE);
printf("FERTIG\n");
 
// Schreibe den Puffer nach fOut
printf("Schreiben von Daten in den ersten Puffer:                ");
fOut = open("buffer1", O_RDONLY);
write(fOut, &buffer, BUFFER_SIZE);
schließen (fOut);
printf("FERTIG\n");
 
printf("Daten von der ersten Datei in die zweite kopieren:      ");
fIn = open("buffer1", O_RDONLY);
fOut = open("buffer2", O_RDONLY);
sendfile(fOut, fIn, 0, BUFFER_SIZE);
schließen (fIn);
schließen (fOut);
printf("FERTIG\n");
 
printf("Puffer freigeben:                             ");
frei(Puffer);
printf("FERTIG\n");
 
printf("Dateien löschen:                             ");
unlink("puffer1");
unlink("puffer2");
printf("FERTIG\n");
 
0 zurückgeben;
 

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:

apt install build-essentials

Dann kompilieren mit:

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

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
Daten in den ersten Puffer schreiben:                FERTIG
Daten von der ersten Datei in die zweite kopieren:      FERTIG
Puffer freigeben:                              FERTIG
Dateien löschen:                              FERTIG
echt    0m0.397s
Benutzer    0m0.000s
sys     0m0.203s
I/O-Test mit sendfile() und zugehörigen Systemaufrufen.
64 MB Puffer zuweisen:                     FERTIG
Daten in den ersten Puffer schreiben:                FERTIG
Daten von der ersten Datei in die zweite kopieren:      FERTIG
Puffer freigeben:                              FERTIG
Dateien löschen:                              FERTIG
echt    0m0.019s
Benutzer    0m0.000s
sys     0m0.016s

Wie Sie sehen können, 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 den Mangel an Plattformportabilität und manchmal reduzierte Funktionalität im Vergleich zu Bibliotheksfunktionen abwägen.

Bei einigen Systemaufrufen müssen Sie darauf achten, von Systemaufrufen zurückgegebene Ressourcen 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 verbinden sich direkt mit dem Kernel. Der Linux-Kernel bietet einen umfassenden Schutz 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.

Weisen Sie Ihre Maustasten mit der X-Mouse Button Control für verschiedene Software unterschiedlich zu
Vielleicht benötigen Sie ein Tool, mit dem sich die Steuerung Ihrer Maus bei jeder von Ihnen verwendeten Anwendung ändern kann. In diesem Fall können ...
Microsoft Sculpt Touch Wireless-Maus Bewertung
Ich habe vor kurzem gelesen, dass Microsoft Sculpt Touch kabellose Maus und beschloss, sie zu kaufen. Nachdem ich es eine Weile benutzt hatte, beschlo...
AppyMouse On-Screen Trackpad und Mauszeiger für Windows Tablets
Tablet-Benutzer vermissen oft den Mauszeiger, insbesondere wenn sie die Laptops gewohnt sind. Die Touchscreen-Smartphones und -Tablets bieten viele Vo...