Ich dachte also, ich hätte ein gutes Verständnis davon, aber ich habe gerade einen Test durchgeführt (als Antwort auf ein Gespräch, in dem ich mit jemandem nicht einverstanden war) und festgestellt, dass mein Verständnis fehlerhaft ist …
So detailliert wie möglich Was genau passiert, wenn ich eine Datei in meiner Shell ausführe? Was ich meine ist, wenn ich Folgendes eingebe:./somefile some arguments
in meine Shell und drücken Sie die Eingabetaste (und somefile
existiert in cwd, und ich habe Lese- und Ausführungsberechtigungen für somefile
) was passiert dann unter der Haube?
Ich dachte Die Antwort war:
- Die Shell führt einen Syscall zu
exec
durch , wobei der Pfad zusomefile
übergeben wird - Der Kernel untersucht
somefile
und sieht sich die magische Nummer der Datei an, um festzustellen, ob es sich um ein Format handelt, das der Prozessor verarbeiten kann - Wenn die magische Zahl anzeigt, dass die Datei in einem Format vorliegt, das der Prozessor ausführen kann, dann
- ein neuer Prozess wird erstellt (mit einem Eintrag in der Prozesstabelle)
somefile
wird gelesen/auf den Speicher abgebildet. Ein Stack wird erstellt und die Ausführung springt zum Einstiegspunkt des Codes vonsomefile
, mitARGV
mit einem Array der Parameter initialisiert (einchar**
,["some","arguments"]
)
- Wenn die magische Zahl ein Shebang ist, dann
exec()
erzeugt einen neuen Prozess wie oben, aber die verwendete ausführbare Datei ist der Interpreter, auf den der Shebang verweist (z. B./bin/bash
oder/bin/perl
) undsomefile
wird anSTDIN
übergeben - Wenn die Datei keine gültige magische Nummer hat, tritt ein Fehler wie „ungültige Datei (schlechte magische Nummer):Exec-Formatfehler“ auf
Jemand sagte mir jedoch, dass, wenn die Datei aus reinem Text besteht, die Shell versucht, die Befehle auszuführen (als ob ich bash somefile
eingegeben hätte ). Ich habe es nicht geglaubt, aber ich habe es einfach versucht, und es war richtig. Ich habe also eindeutig einige Missverständnisse darüber, was hier tatsächlich passiert, und würde gerne die Mechanik verstehen.
Was genau passiert, wenn ich eine Datei in meiner Shell ausführe? (so detailliert wie möglich…)
Akzeptierte Antwort:
Die endgültige Antwort auf „Wie Programme ausgeführt werden“ unter Linux ist das Artikelpaar auf LWN.net mit den überraschenderweise betitelten „Wie Programme ausgeführt werden“ und „Wie Programme ausgeführt werden:ELF-Binärdateien“. Der erste Artikel befasst sich kurz mit Skripten. (Genau genommen liegt die endgültige Antwort im Quellcode, aber diese Artikel sind einfacher zu lesen und enthalten Links zum Quellcode.)
Ein wenig Experimentieren zeigt, dass Sie es ziemlich richtig gemacht haben und dass die Ausführung einer Datei, die eine einfache Liste von Befehlen enthält, ohne einen Kram von der Shell gehandhabt werden muss. Die Manpage execve(2) enthält Quellcode für ein Testprogramm, execve; Wir werden das verwenden, um zu sehen, was ohne Shell passiert. Schreiben Sie zuerst ein Testskript, testscr1
, enthält
#!/bin/sh
pstree
und ein weiterer, testscr2
, die nur
pstree
Machen Sie beide ausführbar und vergewissern Sie sich, dass sie beide von einer Shell ausgeführt werden:
chmod u+x testscr[12]
./testscr1 | less
./testscr2 | less
Versuchen Sie es jetzt erneut mit execve
(vorausgesetzt, Sie haben es im aktuellen Verzeichnis erstellt):
./execve ./testscr1
./execve ./testscr2
testscr1
läuft noch, aber testscr2
produziert
execve: Exec format error
Dies zeigt, dass die Shell testscr2
handhabt anders. Es verarbeitet jedoch nicht das Skript selbst, sondern verwendet weiterhin /bin/sh
das zu tun; Dies kann durch Weiterleiten von testscr2
überprüft werden zu less
:
./testscr2 | less -ppstree
Auf meinem System bekomme ich
|-gnome-terminal--+-4*[zsh]
| |-zsh-+-less
| | `-sh---pstree
Wie Sie sehen können, gibt es die Shell, die ich verwendet habe, zsh
, die less
gestartet hat , und eine zweite Shell, einfach sh
(dash
auf meinem System), um das Skript auszuführen, das pstree
ausgeführt hat . In zsh
Dies wird von zexecve
gehandhabt in Src/exec.c
:Die Shell verwendet execve(2)
um zu versuchen, den Befehl auszuführen, und wenn das fehlschlägt, liest es die Datei, um zu sehen, ob sie einen Shebang hat, verarbeitet es entsprechend (was der Kernel auch getan haben wird), und wenn das fehlschlägt, versucht es, die Datei mit sh
, solange es kein Nullbyte aus der Datei gelesen hat:
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
hat das gleiche Verhalten, implementiert in execute_cmd.c
mit einem hilfreichen Kommentar (wie von taliezin hervorgehoben):
Führen Sie einen einfachen Befehl aus, der hoffentlich irgendwo in einer Plattendatei
definiert ist.
fork ()
- Rohre verbinden
- Befehl nachschlagen
- Weiterleitungen durchführen
execve ()
- Falls die
execve
fehlgeschlagen ist, überprüfen Sie, ob für die Datei der Ausführungsmodus eingestellt ist.
Wenn dies der Fall ist und es sich nicht um ein Verzeichnis handelt, führen Sie dessen Inhalt als
Shell-Skript aus.
POSIX definiert eine Reihe von Funktionen, die als exec(3)
bekannt sind Funktionen, die execve(2)
umschließen und bieten diese Funktionalität auch an; Einzelheiten finden Sie in der Antwort von Muru. Zumindest unter Linux werden diese Funktionen von der C-Bibliothek implementiert, nicht vom Kernel.