2. execve

execve() est un appel système qui remplace l'image mémoire du processus en cours par un nouveau processus spécifié en argument. En d'autres termes, cela permet à votre programme d'exécuter un autre programme. Or justement, si notre greffon est un programme, cette instruction nous intéresse au plus haut point !

2.1. Les arguments

execve() s'appelle rarement directement. On préfère habituellement utilise un de ses frontaux à la place, pour plus de convivialité. Ces frontaux sont execl(), execlp(), execle(), execv(), execvp().

Les trois premiers se distinguent des deux derniers par leurs arguments : on spécifie à partir du second argument la liste (liste terninée par un élément NULL) des arguments à fournir au programme à exécuter. Les deux derniers frontaux ne prennent au contraire que deux arguments, et le second est un tableau de chaînes de caractères terminé par un élément NULL. Les trois premiers frontaux sont préférés lorsque vous voulez exécuter un programme dont le nombre d'arguments est connu à l'avance. Les deux derniers s'utilisent au contraire lorsque vous ne savez rien du nombre d'arguments ou qu'il est variable. Dans ce cas, vous allouez dynamiquement la place pour un tableau, vous remplissez ce tableau et vous le donnez en second argument au frontal d'execve(). Notez enfin que dans tous les cas, le premier élément donné comme argument n'est pas le premier argument mais le nom du programme à exécuter.

J'ai parlé des arguments des frontaux à l'exception du premier argument. Celui-ci est le chemin dans lequel se trouve le programme à exécuter pour les frontaux execl, execle, execv. C'est un fichier pour les frontaux execlp et execvp.

Le frontal execle a cette particularité que vous pouvez aussi spécifier à la suite des arguments une liste indiquant l'environnement. Référez-vous à la page de manuel pour l'utiliser si vous en avez vraiment besoin un jour.

2.2. Utilisation

Execve() s'utilise quasiment toujours en association avec fork() car elle ne revient jamais. En effet, si vous voulez exécuter un programme à l'intérieur du votre, vous devez d'abord créer un nouveau processus, dont vous allez envoyer l'exécution sur le programe que vous voulez lancer.

Techniquement parlant, vous utiliserez fork(). Dans le fils, vous pouvez lancer le programme à appeler directement via un appel à exec*(). Pensez tout de même à mettre un message d'erreur après, suivi d'un exit(). Ce message d'erreur apparaîtra dans le cas où le exec*() ne se lance pas pour une raison quelconque.

Dans le processus père, vous avez deux possibilités suivant ce que vous voulez faire. L'une est d'attendre que le programme que vous avez lancé se finisse pour reprendre l'exécution du père. Dans d'autres langages comme le Perl, ceci est simulé par l'appel system(). Dans ce cas, en C, vous n'avez qu'à faire un appel à wait4(pid,NULL,0,NULL). Cet appel est bloquant et s'occupe d'attendre la fin du fils, dont le PID est contenu dans la variable pid spécifiée en premier argument à wait4(). Cette variable contient la valeur de retour de l'appel fork() puisque fork() retourne le numéro de PID du processus fils pour le processus père.

L'autre possibilité est de ne pas attendre la fin du processus fils pour continuer l'exécution du processus père. Dans ce cas, pour le père, faites comme si de rien n'était et enchaînez les lignes de code. Cependant, tout père doit attendre la fin de son fils avec un appel à wait3(), wait4() ou waitpid(). En effet, lorsque le processus fils finit, il ne meurt pas tout à fait pour le système: il devient un processus zombie. Un appel à wait*() est le moyen pour le père de mettre effectivement fin au processus fils, et d'enlever ce dernier de la table des processus. Si cette table qui n'est pas infinie déborde, il vous devient impossible de faire de nouveaux fork() ou même de lancer de nouveaux programmes depuis le shell.

L'appel à wait*() peut se placer à deux endroits stratégiques. Le premier, le plus intuitif, est de le placer dans la boucle principale du processus père. Seulement cet appel doit être non bloquant. Il faut utiliser WNOHANG comme option. Cela donne par exemple wait4(pid,status,WNOHANG,NULL). Alors, l'appel est non bloquant et si le fils n'est pas encore mort, le père ne reste pas bloqué. Notez que le second argument est un pointeur vers un entier, et que cet entier n'est rien d'autre que le status de sortie. Vous pouvez connaître le code retour avec WEXITSTATUS(*status). Nous utiliserons cela plus tard.

L'autre endroit stratégique est à l'intérieur d'une fonction de traitement de signal. En effet, lorsqu'un fils finit son exécution, il le signale à son père via un signal SIGCHLD sans que vous n'ayez rien à programmer de spécifique. Il vous suffit de capter ce signal, et dans la fonction de traitement de ce signal, un appel à wait*() supprimera le zombie de la table des processus.

création est mise à disposition sous un contrat Creative Commons