Ulli Horlacher <
fram...@rus.uni-stuttgart.de>:
> framstag@juhu:~: echo $BASH_VERSION
> 4.4.20(1)-release
>
> framstag@juhu:~: set -o|grep export
> allexport off
>
> framstag@juhu:~: unset x; x=X;(echo $x)
> X
>
> Wieso ist x in der subshell (neuer Prozess!) vorhanden?
> Ich haette erwartet, dass es dazu ein export bedarf?
Die traditionelle Implementierung des Subshell‐Environments («(…)»)
wird mit dem Systemaufruf «fork» gemacht (zu diesem Systemaufruf
siehe die Handbuchseite «fork(2)», das heißt, der Shell erzeugt
eine Prozesskopie, also einen Doppelgänger, von sich selbst. Eine
Prozesskopie zu sein, bedeutet, dass der neu erzeugte Prozess eine
(nahezu[1] gleiche) Kopie des Virtuellspeicherinhalts des
erzeugenden Prozesses (den gleichen Programmcode, die gleichen
Daten, die gleiche File‐Descriptor‐Tabelle und weiteres) erhält.
Dadurch erhält er den kompletten Prozesszustand des erzeugenden
Prozesses.
[1] Die Kopie ist nicht exakt gleich: Beispielsweise erhält der
neue Prozess eine eigene Prozessnummer, die sich von der
unterscheidet, die der alte hat.
Im Falle des Shells gehören auch die Shell‐Variablen zum
Prozesszustand des Prozesses.
Wenn man mit einem Prozessdebugger beide Prozesse untersuchen
würde, würde man feststellen, dass man in beiden Hinweise darauf
findet, dass vor kurzem der Systemaufruf „fork“ aufgerufen und
beendet worden ist – und das, obwohl nur einer von beiden, nämlich
der Erzeugerprozess, „fork“ aufgerufen hat. Der neu erzeugte
Prozess kommt gewissermaßen mit einer fiktiven Vergangenheit zur
Welt, die er zwar nicht selbst «erlebt» hat, an die er sich aber
«erinnert», als hätte er sie selbst erlebt. Der neueste Teil
dieser Erinnerung an eine fiktive Vergangenheit ist die Rückkehr
aus dem Systemaufruf «fork», denn ab da endet die fiktive
Vergangenheit und der neue Prozess lebt wirklich selber.
Ein kurzes Beispielprogramm, an dem man sieht, wie beide Prozesse
trotzdem anschließend unterschiedliche Wege gehen, obwohl sie
denselben Programmcode und die gleichen Daten im
Usermode‐Virtuellspeicher haben, gibt es auf der Handbuchseite
«wait(2)» zum Systemaufruf „wait“. Es sei im Voraus verraten: Es
hat mit dem Funktionsergebnis des Systemaufrufs «fork» zu tun.
Der Shell‐Prozess, der „fork“ aufgerufen hat, arbeitet weiterhin
seinen Shell‐Programmcode ab: Er tut, wenn das zu startende
Subshell‐Environment im Vordergrund laufen soll, im Wesentlichen
nichts anderes, als auf das Ende des neu erzeugten Prozesses mit
einem der Systemaufrufe „wait“, „waitpid“ oder „waitid“ zu warten.
Der neu erzeugte Shell‐Prozess (das Subshell‐Environment) arbeitet
die Shellkommandos innerhalb der runden Klammern ab.
Erst dann, wenn der neu erzeugte Prozess den Systemaufruf «execve»
aufruft, um ein anderes Programm (in deinem Beispiel: «echo»)
laufen zu lassen, verliert er seinen kompletten
Usermode‐Virtuellspeicherinhalt, einschließlich der
Umgebungsvariablen (denn die sind auch nur Teil des
Usermode‐Virtuellspeicherinhalts). Umgebungsvariable kommen über
die «execve»‐Schranke zum neuen Prozess nur dadurch, dass sie beim
«execve»‐Systemaufruf ähnlich wie die Aufrufparameter in einer
Liste der Umgebungsvariablen übergeben werden. Das ist in der
Handbuchseite «execve(2)» der dritte Parameter beim
«execve»‐Aufruf:
int execve(const char *filename, char *const argv[],
char *const envp[]);
Im «fork/execve»‐Beispiel in der Handbuchseite «execve(2)» sieht
das so aus (Ausschnitt):
char *newargv[] = { NULL, "hello", "world", NULL };
char *newenviron[] = { NULL };
newargv[0] = argv[1];
execve(argv[1], newargv, newenviron);
Der Beispielersteller hat das Beispiel auf ein absolutes Minimum
gebracht: Als dritten Parameter des Systemaufrufs «execve», der
Liste der Umgebungsvariablen, gibt er eine leere Liste an. Dadurch
hat der Prozess nach dem Aufruf von «execve» keine
Umgebungsvariablen mehr. In der Praxis gibt man als Liste der
Umgebungsvariablen die Variable «environ» (siehe die Handbuchseite
«environ(7)») an. Sie enthält die Umgebungsvariablen des «execve»
aufrufenden Prozesses.
Der Shell gibt dort seine Liste der Shell‐Variablen, die «für den
Export in die Umgebung markiert» sind, an. In der Beschreibung des
Effekts des Shell‐Kommandos «export» in der Handbuchseite «bash(1)»
heißt es:
export -p
The supplied names are marked for automatic export to
the environment of subsequently executed commands.
Ich bin mir sicher, unzählige Leser – mich eingeschlossen – haben
sich beim Lesen über diese seltsam umständliche Formulierung
gewundert – ich auch, bis ich den Bach (siehe unten) gelesen hatte.
Schau dir dringend die Beispiele an, oder, viel besser noch, lies
ein Buch über die Unix‐Interna. Und dir als Systemadministrator
sage ich: Tue es auf der Stelle! Lege jedes Buch «So funktioniert
die Shell» oder «Shell‐Programmierung für Systemadministratoren»
zur Seite. Die werden dir alle nicht helfen, ebensowenig wie die
Handbuchseite «bash(1)»: Die Manual-Pages der Shells erwarten vom
Leser, dass er bereits in groben Zügen weiß, wie Unix oder Linux
funktioniert.
Mir hat das Buch
Maurice J. Bach: The Design Of The Unix Operating System
Prentice-Hall International, London, 1986
sehr zum Verständnis geholfen. Es soll auch eine deutsche
Übersetzung (»So funktioniert das UNIX Betriebssystem« oder so
ähnlich) geben. Obwohl inzwischen Jahrzehnte alt, liefert es
u. a. genau das Wissen, das dir fehlt. »Still a good read«, meinte
mal eine Rezension.
Es enthält viele Codeschnipsel in C-ähnlichem Pseudeocode, die die
Vorgehensweise des Betriebssystemkerns verdeutlichen, und
Übungsaufgaben, die dem Leser helfen, die richtigen Fragen zu
stellen.
Sicher gibt es inzwischen neuere Bücher. (Ich kenne aber keins.)
Ich prophezeie dir: Wenn du ein entsprechendes Buch gelesen hast,
kannst du dich beim Lesen des Shell-Handbuchs in Abwandlung eines
Spruchs von Beate Goebel vor Aha-Erlebnissen nicht mehr retten:
Hat man erst mal verstanden, wie Unix funktioniert, versteht man
auch das Shell-Handbuch.
--
Hat man erst verstanden, wie Unix funktioniert, ist auch
das Shell-Handbuch kein Buch mit sieben Siegeln mehr.