Tutoriel d'appel du système Linux avec C

Tutoriel d'appel du système Linux avec C
Dans notre dernier article sur les appels du système Linux, j'ai défini un appel du système, discuté des raisons que l'on pourrait les utiliser dans un programme et j'ai plongé dans leurs avantages et leurs inconvénients. J'ai même donné un bref exemple dans l'assemblage dans C. Il a illustré le point et a décrit comment passer l'appel, mais n'a rien fait de productif. Pas exactement un exercice de développement passionnant, mais il a illustré le point.

Dans cet article, nous allons utiliser des appels système réels pour faire un réel travail dans notre programme C. Tout d'abord, nous allons consulter si vous avez besoin d'utiliser un appel système, puis fournir un exemple à l'aide de l'appel SendFile () qui peut améliorer considérablement les performances de copie de fichiers. Enfin, nous passerons en revue quelques points à retenir lors de l'utilisation des appels du système Linux.

Avez-vous besoin d'un appel système?

Bien qu'il soit inévitable, vous utiliserez un appel système à un moment donné de votre carrière de développement C, à moins que vous ne cibliez des performances élevées ou une fonctionnalité de type particulier, la bibliothèque GLIBC et d'autres bibliothèques de base incluses dans les distributions linux majeures prendront en charge la majorité des vos besoins.

La bibliothèque standard GLIBC fournit un cadre multiplateforme et bien testé pour exécuter des fonctions qui nécessiteraient autrement les appels système spécifiques au système. Par exemple, vous pouvez lire un fichier avec fscanf (), fread (), getc (), etc., ou vous pouvez utiliser l'appel Système Read () Linux. Les fonctions GLIBC offrent plus de fonctionnalités (i.e. meilleure gestion des erreurs, IO formaté, etc.) et travaillera sur tout système de supports GLIBC.

D'un autre côté, il y a des moments où les performances sans compromis et l'exécution exacte sont essentielles. L'emballage que Fread () fournit va ajouter des frais généraux, et bien que mineur, n'est pas entièrement transparent. De plus, vous ne voudrez peut-être pas ou avez peut-être besoin des fonctionnalités supplémentaires que fournit l'emballage. Dans ce cas, vous êtes mieux servi avec un appel système.

Vous pouvez également utiliser des appels système pour effectuer des fonctions non encore prises en charge par GLIBC. Si votre copie de GLIBC est à jour, ce ne sera guère un problème, mais se développer sur des distributions plus anciennes avec des noyaux plus récents pourrait nécessiter cette technique.

Maintenant que vous avez lu les clauses de non-responsabilité, les avertissements et les détours potentiels, découvrons maintenant quelques exemples pratiques.

Sur quel processez-nous?

Une question que la plupart des programmes ne pensent probablement pas à poser, mais une valide. Ceci est un exemple d'un appel système qui ne peut pas être dupliqué avec GLIBC et n'est pas recouvert d'un wrapper GLIBC. Dans ce code, nous appellerons l'appel getcpu () directement via la fonction syscall (). La fonction syscall fonctionne comme suit:

syscall (sys_call, arg1, arg2,…);

Le premier argument, SYS_CALL, est une définition qui représente le nombre de l'appel système. Lorsque vous incluez Sys / Syscall.H, ceux-ci sont inclus. La première partie est SYS_ et la deuxième partie est le nom de l'appel système.

Les arguments pour l'appel vont dans Arg1, Arg2 ci-dessus. Certains appels nécessitent plus d'arguments, et ils continueront par leur page d'homme. N'oubliez pas que la plupart des arguments, en particulier pour les rendements, nécessiteront des pointeurs sur des tableaux de char ou une mémoire alloués via la fonction malloc.

Exemple 1.c

#inclure
#inclure
#inclure
#inclure
int main()
CPU non signé, nœud;
// Obtenir le nœud CPU CPU et NUMA actuel via l'appel système
// Notez que cela n'a pas d'emballage GLIBC, nous devons donc l'appeler directement
syscall (sys_getcpu, & cpu, & node, null);
// Afficher les informations
printf ("Ce programme fonctionne sur CPU Core% U et NUMA NODE% U.\ n \ n ", cpu, nœud);
retour 0;

Pour compiler et courir:
Exemple GCC1.C -o Exemple1
./Exemple 1

Pour des résultats plus intéressants, vous pouvez faire tourner les threads via la bibliothèque Pthreads, puis appeler cette fonction pour voir sur quel processeur votre thread est en cours d'exécution.

SendFile: performance supérieure

SendFile fournit un excellent exemple d'amélioration des performances via les appels système. La fonction SendFile () copie les données d'un descripteur de fichier à un autre. Plutôt que d'utiliser plusieurs fonctions Fread () et FWrite (), SendFile effectue le transfert dans l'espace du noyau, réduisant les frais généraux et augmentant ainsi les performances.

Dans cet exemple, nous allons copier 64 Mo de données d'un fichier à un autre. Dans un test, nous allons utiliser les méthodes de lecture / écriture standard dans la bibliothèque standard. Dans l'autre, nous utiliserons les appels système et l'appel SendFile () pour faire exploser ces données d'un endroit à un autre.

test1.C (GLIBC)

#inclure
#inclure
#inclure
#inclure
#define buffer_size 67108864
#define buffer_1 "buffer1"
#define buffer_2 "buffer2"
int main()
Fichier * fout, * fin;
printf ("\ ni / o Tester avec les fonctions GLIBC traditionnelles.\ n \ n ");
// Prenez un tampon tampon_size.
// Le tampon aura des données aléatoires, mais nous ne nous soucions pas de cela.
printf ("allouant du tampon 64 MB:");
char * buffer = (char *) malloc (buffer_size);
printf ("Done \ n");
// Écrivez le tampon à FOT
printf ("Écriture de données dans le premier tampon:");
fout = fopen (buffer_1, "wb");
fwrite (tampon, sizeof (char), buffer_size, fout);
fclose (fout);
printf ("Done \ n");
printf ("Copie des données du premier fichier au deuxième:");
fin = fopen (buffer_1, "rb");
fout = fopen (buffer_2, "wb");
Fread (tampon, sizeof (char), buffer_size, fin);
fwrite (tampon, sizeof (char), buffer_size, fout);
fclose (fin);
fclose (fout);
printf ("Done \ n");
printf ("Tampon de libération:");
gratuit (tampon);
printf ("Done \ n");
printf ("Suppression de fichiers:");
retirer (buffer_1);
retirer (buffer_2);
printf ("Done \ n");
retour 0;

test2.C (appels système)

#inclure
#inclure
#inclure
#inclure
#inclure
#inclure
#inclure
#inclure
#inclure
#define buffer_size 67108864
int main()
int fout, fin;
Test printf ("\ ni / o avec SendFile () et appels système connexes.\ n \ n ");
// Prenez un tampon tampon_size.
// Le tampon aura des données aléatoires, mais nous ne nous soucions pas de cela.
printf ("allouant du tampon 64 MB:");
char * buffer = (char *) malloc (buffer_size);
printf ("Done \ n");
// Écrivez le tampon à FOT
printf ("Écriture de données dans le premier tampon:");
fout = open ("buffer1", o_rdonly);
écrire (fout, & buffer, buffer_size);
Close (fout);
printf ("Done \ n");
printf ("Copie des données du premier fichier au deuxième:");
fin = open ("buffer1", o_rdonly);
fout = open ("buffer2", o_rdonly);
sendFile (fout, fin, 0, buffer_size);
Close (Fin);
Close (fout);
printf ("Done \ n");
printf ("Tampon de libération:");
gratuit (tampon);
printf ("Done \ n");
printf ("Suppression de fichiers:");
Unlink ("Buffer1");
Unlink ("Buffer2");
printf ("Done \ n");
retour 0;

Compilation et exécution des tests 1 et 2

Pour créer ces exemples, vous aurez besoin des outils de développement installés sur votre distribution. Sur Debian et Ubuntu, vous pouvez l'installer avec:

APT INSTALLATION BUILD-Essentials

Puis compilez avec:

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

Pour exécuter les deux et tester les performances, exécutez:

temps ./ test1 && heure ./ test2

Vous devriez obtenir des résultats comme ceci:

Test d'E / S avec des fonctions GLIBC traditionnelles.

Attribution du tampon de 64 Mb: Terminé
Écrire des données dans le premier tampon: fait
Copie de données du premier fichier au deuxième: Terminé
Tampon de libération: fait
Suppression de fichiers: fait
réel 0m0.397S
utilisateur 0m0.000
sys 0m0.203
Test d'E / S avec SendFile () et les appels système connexes.
Attribution du tampon de 64 Mb: Terminé
Écrire des données dans le premier tampon: fait
Copie de données du premier fichier au deuxième: Terminé
Tampon de libération: fait
Suppression de fichiers: fait
réel 0m0.019
utilisateur 0m0.000
sys 0m0.016

Comme vous pouvez le voir, le code qui utilise les appels système fonctionne beaucoup plus rapidement que l'équivalent GLIBC.

Choses dont il faut se rappeler

Les appels système peuvent augmenter les performances et fournir des fonctionnalités supplémentaires, mais elles ne sont pas sans leurs inconvénients. Vous devrez peser les avantages des appels du système pour le manque de portabilité de la plate-forme et des fonctionnalités parfois réduites par rapport aux fonctions de la bibliothèque.

Lorsque vous utilisez certains appels système, vous devez prendre soin d'utiliser des ressources renvoyées des appels système plutôt que des fonctions de bibliothèque. Par exemple, la structure de fichier utilisée pour les fonctions FOpen (), Fread (), FWrite () et Fclose () de GLIBC ne sont pas les mêmes que le numéro de descripteur de fichier à partir de l'appel système Open () (renvoyé en entier). Les mélanger peuvent entraîner des problèmes.

En général, les appels du système Linux ont moins de voies pare-chocs que les fonctions GLIBC. S'il est vrai que les appels système ont une gestion des erreurs et des rapports, vous obtiendrez des fonctionnalités plus détaillées d'une fonction GLIBC.

Et enfin, un mot sur la sécurité. Les appels système interfacent directement avec le noyau. Le noyau Linux a des protections étendues contre les manigances des terres utilisateur, mais des bogues non découverts existent. Ne croyez pas qu'un appel système validera vos contributions ou vous isolera des problèmes de sécurité. Il est sage de s'assurer que les données que vous remettez à un appel système sont désinfectées. Naturellement, c'est un bon conseil pour tout appel API, mais vous ne pouvez pas être prudent lorsque vous travaillez avec le noyau.

J'espère que vous avez apprécié cette plongée plus profonde dans le pays des appels du système Linux. Pour une liste complète des appels du système Linux, consultez notre liste principale.