Donc, je pensais avoir une bonne compréhension de cela, mais je viens de faire un test (en réponse à une conversation où je n'étais pas d'accord avec quelqu'un) et j'ai constaté que ma compréhension était erronée…
Avec le plus de détails possible que se passe-t-il exactement lorsque j'exécute un fichier dans mon shell ? Ce que je veux dire, c'est que si je tape :./somefile some arguments
dans mon shell et appuyez sur retour (et somefile
existe dans le cwd, et j'ai des autorisations de lecture + exécution sur somefile
) alors que se passe-t-il sous le capot ?
J'ai pensé la réponse était :
- Le shell fait un appel système à
exec
, en passant le chemin verssomefile
- Le noyau examine
somefile
et regarde le nombre magique du fichier pour déterminer s'il s'agit d'un format que le processeur peut gérer - Si le nombre magique indique que le fichier est dans un format que le processeur peut exécuter, alors
- un nouveau processus est créé (avec une entrée dans la table des processus)
somefile
est lu/mappé en mémoire. Une pile est créée et l'exécution saute au point d'entrée du code desomefile
, avecARGV
initialisé à un tableau de paramètres (unchar**
,["some","arguments"]
)
- Si le nombre magique est un shebang alors
exec()
génère un nouveau processus comme ci-dessus, mais l'exécutable utilisé est l'interpréteur référencé par le shebang (par exemple/bin/bash
ou/bin/perl
) etsomefile
est passé àSTDIN
- Si le fichier n'a pas de numéro magique valide, une erreur du type "fichier invalide (mauvais numéro magique) :erreur de format d'exécution" se produit
Cependant, quelqu'un m'a dit que si le fichier est en texte brut, le shell essaie d'exécuter les commandes (comme si j'avais tapé bash somefile
). Je n'y croyais pas, mais je viens d'essayer, et c'était correct. J'ai donc clairement des idées fausses sur ce qui se passe réellement ici, et j'aimerais comprendre les mécanismes.
Que se passe-t-il exactement lorsque j'exécute un fichier dans mon shell ? (avec autant de détails c'est raisonnable...)
Réponse acceptée :
La réponse définitive à « comment les programmes sont exécutés » sur Linux est la paire d'articles sur LWN.net intitulé, assez étonnamment, Comment les programmes sont exécutés et Comment les programmes sont exécutés :les binaires ELF. Le premier article traite brièvement des scripts. (Strictement parlant, la réponse définitive se trouve dans le code source, mais ces articles sont plus faciles à lire et fournissent des liens vers le code source.)
Une petite expérimentation montre que vous avez à peu près bien compris et que l'exécution d'un fichier contenant une simple liste de commandes, sans artifice, doit être gérée par le shell. La page de manuel execve(2) contient le code source d'un programme de test, execve; nous allons l'utiliser pour voir ce qui se passe sans shell. Tout d'abord, écrivez un script de test, testscr1
, contenant
#!/bin/sh
pstree
et un autre, testscr2
, ne contenant que
pstree
Rendez-les tous les deux exécutables et vérifiez qu'ils s'exécutent tous les deux à partir d'un shell :
chmod u+x testscr[12]
./testscr1 | less
./testscr2 | less
Maintenant réessayez en utilisant execve
(en supposant que vous l'ayez construit dans le répertoire courant) :
./execve ./testscr1
./execve ./testscr2
testscr1
fonctionne toujours, mais testscr2
produit
execve: Exec format error
Cela montre que le shell gère testscr2
différemment. Cependant, il ne traite pas le script lui-même, il utilise toujours /bin/sh
pour faire ça; cela peut être vérifié en reliant testscr2
à less
:
./testscr2 | less -ppstree
Sur mon système, j'obtiens
|-gnome-terminal--+-4*[zsh]
| |-zsh-+-less
| | `-sh---pstree
Comme vous pouvez le voir, il y a le shell que j'utilisais, zsh
, qui a commencé less
, et un deuxième shell, simple sh
(dash
sur mon système), pour exécuter le script, qui a exécuté pstree
. En zsh
ceci est géré par zexecve
dans Src/exec.c
:le shell utilise execve(2)
pour essayer d'exécuter la commande, et si cela échoue, il lit le fichier pour voir s'il a un shebang, le traitant en conséquence (ce que le noyau aura également fait), et si cela échoue, il essaie d'exécuter le fichier avec sh
, tant qu'il n'a lu aucun octet zéro du fichier :
for (t0 = 0; t0 != ct; t0++)
if (!execvebuf[t0])
break;
if (t0 == ct) {
argv[-1] = "sh";
winch_unblock();
execve("/bin/sh", argv - 1, newenvp);
}
bash
a le même comportement, implémenté dans execute_cmd.c
avec un commentaire utile (comme souligné par taliezin):
Exécutez une commande simple qui, espérons-le, est définie dans un fichier disque
quelque part.
fork ()
- connecter les tuyaux
- recherchez la commande
- faire des redirections
execve ()
- Si le
execve
a échoué, voyez si le fichier est en mode exécutable.
Si c'est le cas, et qu'il ne s'agit pas d'un répertoire, exécutez son contenu comme
un script shell.
POSIX définit un ensemble de fonctions, connu sous le nom de exec(3)
fonctions, qui enveloppent execve(2)
et fournir également cette fonctionnalité ; voir la réponse de muru pour plus de détails. Sous Linux, au moins ces fonctions sont implémentées par la bibliothèque C, pas par le noyau.