Lors du développement d'un programme, le programmeur doit garder à l'esprit plusieurs choses, comme le code ne doit pas être complexe, c'est-à-dire qu'il doit être maintenable, la portabilité est un autre domaine à garder à l'esprit. On voit donc qu'il y a quelques bonnes pratiques que le programmeur doit suivre pour produire un bon code. Dans cet article, nous allons nous concentrer sur quelques bonnes pratiques que le programmeur doit suivre lorsqu'il travaille avec des appels système sous Linux.
Qu'est-ce qu'un appel système ?
Un appel système est un appel de fonction spéciale qui est effectué pour demander un service au noyau. Le service demandé peut être de créer un nouveau processus, d'accéder à du matériel tel qu'un disque dur, etc. Lorsqu'un appel système est effectué, l'exécution passe du mode utilisateur au mode noyau et lorsque le service requis est fourni par le noyau, le l'exécution repasse en mode utilisateur. Des exemples d'appels système pourraient être fork(), read(), write() etc.
Traitement des appels système
Les points suivants doivent être gardés à l'esprit lors du traitement des appels système :
- Le programmeur doit avoir une connaissance approfondie de l'appel système. Comme, ce qu'il fait exactement, les ressources système qu'il utilise, quel type d'arguments il attend et surtout dans quels cas il échoue.
- La plupart des appels système Linux renvoient un code d'erreur en cas d'échec. Ces codes d'erreur peuvent varier en fonction du type d'erreur à l'origine de l'échec. Ainsi, une gestion appropriée des erreurs doit être en place afin que chaque type d'erreur soit traité correctement et transmis clairement (soit à l'utilisateur, soit au module parent).
- Pour une connaissance approfondie de l'appel système et des codes d'erreur qu'il renvoie, je vous recommande fortement de consulter la page de manuel de cet appel système spécifique. Les pages de manuel sont les meilleures références pour commencer et développer une bonne compréhension fondamentale de tout appel système sous Linux.
Échecs généraux des appels système
Bien que l'échec d'un appel système puisse dépendre du type d'erreur rencontré lors de l'exécution de l'appel système, voici une liste des raisons qui contribuent principalement aux échecs des appels système :
- Si un appel système tente d'accéder au matériel système et que, pour une raison quelconque, le matériel n'est pas disponible ou suppose que le matériel est défectueux, dans ce cas, l'appel système échouera.
- Lors de l'exécution d'un appel système, si un signal de haute priorité se produit, cela peut également entraîner l'échec de l'exécution de l'appel système.
- Il existe des situations où, via un appel système, un programme tente d'effectuer une tâche spécifique nécessitant des privilèges spéciaux ou root. Si le programme ne dispose pas de ce type de privilèges, l'appel système échouera également.
- La transmission d'arguments non valides est une autre raison très courante d'échec des appels système.
- Supposons qu'un appel système est effectué pour demander de la mémoire au tas et que, pour une raison quelconque, le système n'est pas en mesure d'allouer de la mémoire au processus demandeur qui a effectué l'appel système, dans ce cas également, l'appel système échouera.
La liste ci-dessus n'est pas exhaustive car il peut y avoir de nombreuses autres raisons pour lesquelles un appel système peut échouer.
Travailler avec les codes d'erreur
Comme indiqué précédemment, chaque appel système renvoie un code d'erreur spécifique pour chaque type d'erreur qu'il a rencontré (ce qui a provoqué l'échec de l'appel système). Ainsi, l'identification et la communication des informations d'erreur est une tâche vitale de la programmation. En général, la plupart des appels système renvoient '0' en cas de succès et non zéro en cas d'échec, mais les appels système qui renvoient un pointeur vers une mémoire (comme malloc() ) renvoient '0' ou NULL en cas d'échec et une valeur de pointeur non nulle en cas de succès. .
REMARQUE :l'observation ci-dessus n'est peut-être pas vraie pour tous les appels système. Il pourrait très bien y avoir quelques exceptions.
Ainsi, pour en revenir aux codes d'erreur, comme indiqué, ils peuvent fournir des informations vitales sur la cause de l'échec d'un appel système. Maintenant, puisque chaque code d'erreur est associé à une raison spécifique, le programme peut avoir une carte des codes d'erreur et le texte qui décrit la cause de l'erreur. Mais, c'est très inefficace et non pratique car cela équivaudrait à beaucoup de mappage pour chaque appel système utilisé dans le programme. Alors, maintenant, la question est de savoir quel pourrait être un moyen plus efficace d'y parvenir ?
La variable "errno"
Depuis la page de manuel de cette variable :
Le fichier d'en-tête
définit la variable entière errno, qui est définie par les appels système et certaines fonctions de bibliothèque en cas d'erreur pour indiquer ce qui s'est passé. Sa valeur n'est significative que lorsque la valeur de retour de l'appel indique une erreur (c'est-à-dire, -1 pour la plupart des appels système ; -1 ou NULL pour la plupart des fonctions de la bibliothèque) ; une fonction qui réussit est autorisée à modifier errno.
Les numéros d'erreur valides sont tous différents de zéro ; errno n'est jamais mis à zéro par un appel système ou une fonction de bibliothèque. Pour certains appels système et fonctions de bibliothèque (par exemple, getpriority), -1 est un retour valide en cas de succès. Dans de tels cas, un retour réussi peut être distingué d'un retour d'erreur en définissant errno sur zéro avant l'appel, puis, si l'appel renvoie un état indiquant qu'une erreur peut s'être produite, en vérifiant si errno a un non- valeur nulle. errno est défini par la norme ISO C comme étant une lvalue modifiable de type int, et ne doit pas être explicitement déclaré; errno peut être une macro. errno est local au thread ; le définir dans un thread n'affecte pas sa valeur dans un autre thread.
Alors. d'après la description ci-dessus, il est tout à fait clair que c'est un outil très pratique lorsqu'il s'agit de gérer les erreurs des appels système sous Linux et qu'il peut nous épargner beaucoup de travail. Mais méfiez-vous de l'utilisation de cette variable dans un programme multi-thread car elle est locale à un thread et donc tout changement de valeur de errno dans un thread n'est pas accessible dans un autre thread.
L'API strerror()
Eh bien, un problème avec l'utilisation de errno uniquement est qu'il ne s'agit toujours que d'une valeur entière. Une description est toujours plus utile lors de la journalisation ou lors de la transmission de la cause de l'erreur à l'utilisateur. Il doit donc y avoir une carte des codes d'erreur et la cause à laquelle ils correspondent. Voici l'API 'strerror()'. Cette fonction prend la variable errno comme argument et renvoie un pointeur vers une chaîne contenant la description de la cause à laquelle correspond le code d'erreur.
#include <string.h> char *strerror(int errnum);
D'autres variantes de cette fonction sont également disponibles. Pour plus d'informations, veuillez consulter la page de manuel de cette API.
NOTE :Les lecteurs intéressés peuvent également passer par l'API perror(). Il est utilisé pour imprimer le message d'erreur pour un échec d'appel système sur erreur standard.
Un exemple
Prenons un exemple pour démontrer l'utilisation de errno et strerror()
#include<stdio.h> #include<errno.h> #include<string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main(void) { int fd = -1; // Always Reset errno before use. errno = 0; // Make sure you are opening a file that does not exist fd = open("abcd",O_RDONLY); if(fd == -1) { // Seems like some error occured. Use strerror to print it printf("\nStrerror() says -> [%s]\n",(char*)strerror(errno)); return 1; } return 0; }
Dans le code ci-dessus :
- errno est initialisé à '0' car il n'est pas garanti qu'il soit égal à zéro initialement.
- Ouvrir un fichier inexistant pour que l'appel système open() échoue.
- Maintenant, l'API strerror() est utilisée pour imprimer le message d'erreur basé sur le code errno.
Lorsque le programme ci-dessus est exécuté :
$ ./strerror Strerror() says -> [No such file or directory]
Nous voyons donc que dans la sortie, nous voyons un message d'erreur significatif au lieu d'un code d'erreur.