Multitasking in Shellskripten?

35 views
Skip to first unread message

Albrecht Mehl

unread,
Jan 5, 2007, 5:44:11 AM1/5/07
to
Ich bin mir nicht im klaren darüber, wie verschiedene Befehle in einem
Skript abgearbeitet werden, und bitte daher um Aufklärung.

In einem Skript steht z.B.

cp .......
echo .....

Bei dem Kopierbefehl handele es sich um große Dateien, wodurch
bekanntlich die CPU nicht sonderlich belastet wird, weil das über DMA
abgewickelt wird. Also _könnte_ während des Kopierens bereits der
echo-Befehl ausgeführt werden.

- Werden in einem Skript die Befehle streng sequentiell
ausgeführt, d.h. der nächste erst bearbeitet, wenn der
Vorgänger vollständig ausgeführt ist, oder wird
_automatisch_ bei unvollständiger Auslastung des Systems
bereits der nächste Befehl angefangen?

- Läßt sich das Standardverhalten - wie auch immer es sein
mag - bei Bedarf ändern?


A. Mehl
--
Albrecht Mehl |Absendekonto wird nicht gelesen; eBriefe an:
Schorlemmerstr. 33 |mehl bei hrz1PunkthrzPunkttu-darmstadtPunktde
D-64291 Darmstadt, Germany|sehenswert - Relativist. Effekte
Tel. (+49 06151) 37 39 92 |http://www.tempolimit-lichtgeschwindigkeit.de

Dirk Sohler

unread,
Jan 5, 2007, 5:58:03 AM1/5/07
to
Albrecht Mehl schrieb:

> - Werden in einem Skript die Befehle streng sequentiell
> ausgeführt, d.h. der nächste erst bearbeitet, wenn der
> Vorgänger vollständig ausgeführt ist

Ja.


> - Läßt sich das Standardverhalten - wie auch immer es sein
> mag - bei Bedarf ändern?

Ja, und zwar, wenn du den Befehl im Hintergrund ausführst.

Beispiel:

----
#!/bin/bash
gedit
echo foo
----

startet gedit, und wartet, bis geedit beendet ist, um dann "foo"
auszugeben. Wenn du jetzt

----
#!/bin/bash
gedit &
echo foo
----

benutzt, also mit einem Kaufmanns-Und nach dem Befehl, dann wird gedit
gestartet, und direkt danach "foo" ausgegeben, während gedit noch startet
oder läuft.

Soweit ich weiß, kannst du alle Befehle auf diese Art und Weise im
Hintergrund starten.


Grüße,
Dirk

--
Status von 11:45:01 Uhr (2007-01-05)
Uptime: 13 d, 17:14 h

Jörg Singendonk

unread,
Jan 5, 2007, 6:00:40 AM1/5/07
to
Albrecht Mehl schrieb:

> cp .......
> echo .....

Grundsätzlich sequentiell. Wäre ja lustig wenn Scripte bestimmen
wann was läuft.

> Bei dem Kopierbefehl handele es sich um große Dateien, wodurch
> bekanntlich die CPU nicht sonderlich belastet wird, weil das über DMA
> abgewickelt wird. Also _könnte_ während des Kopierens bereits der
> echo-Befehl ausgeführt werden.

Schreib es doch im Script:

cp ... &
echo Kopieren gestartet

> - Läßt sich das Standardverhalten - wie auch immer es sein
> mag - bei Bedarf ändern?

Nein. Du kannst nur weitere Prozesse starten.

Jörg

--
Einen so unglaublichen Appetit habe ich in dieser Herberge noch nie
gesehen! Er hat den zehnten gefilten Fisch verschlungen und verlangt
noch mehr!
XXVI: Die Odyssee S.31 B.9

Albrecht Mehl

unread,
Jan 5, 2007, 6:05:46 AM1/5/07
to
Jörg Singendonk schrieb:

>> - Läßt sich das Standardverhalten - wie auch immer es sein
>> mag - bei Bedarf ändern?
>
> Nein. Du kannst nur weitere Prozesse starten.

In meiner 'Nochlinuxunbedarftheit' verstehe ich nicht, was Sie damit
sagen wollen. Könnten Sie, bitte, das mit einem Beispiel erläutern?

Jörg Singendonk

unread,
Jan 5, 2007, 6:17:40 AM1/5/07
to
Albrecht Mehl schrieb:

> In meiner 'Nochlinuxunbedarftheit' verstehe ich nicht, was Sie damit
> sagen wollen. Könnten Sie, bitte, das mit einem Beispiel erläutern?

Da hilfen die Gläser:
http://www.google.de/search?hl=de&q=linux+multitasking

oder wikipedia:
http://de.wikipedia.org/wiki/Multitasking

oder der Buchhändler deiner Wahl.

Holger Marzen

unread,
Jan 5, 2007, 6:51:48 AM1/5/07
to
* On Fri, 05 Jan 2007 11:44:11 +0100, Albrecht Mehl wrote:

> Ich bin mir nicht im klaren darüber, wie verschiedene Befehle in einem
> Skript abgearbeitet werden, und bitte daher um Aufklärung.
>
> In einem Skript steht z.B.
>
> cp .......
> echo .....
>
> Bei dem Kopierbefehl handele es sich um große Dateien, wodurch
> bekanntlich die CPU nicht sonderlich belastet wird, weil das über DMA
> abgewickelt wird. Also _könnte_ während des Kopierens bereits der
> echo-Befehl ausgeführt werden.
>
> - Werden in einem Skript die Befehle streng sequentiell
> ausgeführt, d.h. der nächste erst bearbeitet, wenn der
> Vorgänger vollständig ausgeführt ist, oder wird
> _automatisch_ bei unvollständiger Auslastung des Systems
> bereits der nächste Befehl angefangen?

Nacheinander, wenn du nichts anderes festlegst.

> - Läßt sich das Standardverhalten - wie auch immer es sein
> mag - bei Bedarf ändern?

cp a b
echo "fertig"
echo "fertig"

Alternative:

cp a b &
echo "kopiere"
wait
echo fertig

wait wartet auf alle Prozesse, die du mit abschließendem & in den
Hintergrund kopiert hast. Du kannst auch Prozesse gruppieren:

(
echo "kopiere a"
cp a b
echo "kopiere b"
cp b c
echo "kopiere c"
cp c d
) &
echo "bin am kopieren"
wait
echo "bin fertig"

Mit () oder auch {} (lies den Unterschied nach mit man bash) kannst du
Befehle gruppieren und dann mit dem & zusammen in den Hintergrund
schicken.

Das sollte nun genug zum Anfassen sein, dass du weiter in den
Beschreibungen lesen und rumprobieren kannst. Viel Spaß.

Hauke J. Zuehl

unread,
Jan 5, 2007, 7:05:13 AM1/5/07
to
Dirk Sohler wrote:

> Soweit ich weiß, kannst du alle Befehle auf diese Art und Weise im
> Hintergrund starten.
>

Ja, aber es wird schwierig werden, wenn das Programm auf Eingaben des Users
wartet.

>
> Grüße,
> Dirk

Gruss,
Hauke
--
Diktatur Deutschland:
http://de.wikipedia.org/wiki/Überwachungsstaat

Kristian Köhntopp

unread,
Jan 5, 2007, 7:50:42 AM1/5/07
to
Albrecht Mehl wrote:
> - Werden in einem Skript die Befehle streng sequentiell
> ausgeführt, d.h. der nächste erst bearbeitet, wenn der
> Vorgänger vollständig ausgeführt ist, oder wird
> _automatisch_ bei unvollständiger Auslastung des Systems
> bereits der nächste Befehl angefangen?

Die Shell erzeugt mit der Unix-Systemfunktion fork(2) eine Kopie von sich.
Die Elternshell bekommt als Returnwert von fork(2) die PID des Kindes, die
Kindshell bekommt den Wert 0 zurück. Dadurch werden die beiden Prozesse
unterschieden.

In C:

kris@linux:/tmp/kris> cat probe1.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

main(void) {
pid_t pid = 0;

pid = fork();
if (pid == 0) {
printf("Ich bin der Kindprozess.\n");
}
if (pid > 0) {
printf("Ich bin der Elternprozess, das Kind ist %d.\n",
pid);
}
if (pid < 0) {
perror("In fork():");
}

exit(0);
}
kris@linux:/tmp/kris> make probe1
cc probe1.c -o probe1
kris@linux:/tmp/kris> ./probe1
Ich bin der Kindprozess.
Ich bin der Elternprozess, das Kind ist 16959.

Die Shell wartet dann im Elternprozeß auf das Ende des Kindprozesses. Erst
danach setzt sie sich selbst fort und druckt Dir einen neuen Prompt. Dies
geschieht intern mit der Unix-Systemfunktion wait(2):

kris@linux:/tmp/kris> cat probe2.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

#include <sys/types.h>
#include <sys/wait.h>

main(void) {
pid_t pid = 0;
int status;

pid = fork();
if (pid == 0) {
printf("Ich bin der Kindprozess.\n");
sleep(10);
printf("Ich bin der Kindprozess 10 Sekunden spaeter.\n");
}
if (pid > 0) {
printf("Ich bin der Elternprozess, das Kind ist %d.\n",
pid);
pid = wait(&status);
printf("Ende des Prozesses %d: ", pid);
if (WIFEXITED(status)) {
printf("Der Prozess wurde mit exit(%d) beendet.\n",
WEXITSTATUS(status));
}
if (WIFSIGNALED(status)) {
printf("Der Prozess wurde mit kill -%d beendet.\n",
WTERMSIG(status));
}
}
if (pid < 0) {
perror("In fork():");
}

exit(0);
}
kris@linux:/tmp/kris> make probe2
cc probe2.c -o probe2
kris@linux:/tmp/kris> ./probe2
Ich bin der Kindprozess.
Ich bin der Elternprozess, das Kind ist 17399.
Ich bin der Kindprozess 10 Sekunden spaeter.
Ende des Prozesses 17399: Der Prozess wurde mit exit(0) beendet.

Im Kindprozeß wartet die Shell nun nicht einfach ab, sondern tut was: Sie
schmeißt sich selbst weg und ersetzt sich dort durch ein anderes Programm,
und zwar das Programm, das Du auf der Kommandozeile angegeben hast. Dazu
wird eine der Funktionen aus der exec() Familie verwendet.

kris@linux:/tmp/kris> make probe3
cc probe3.c -o probe3
kris@linux:/tmp/kris> cat probe3.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

#include <sys/types.h>
#include <sys/wait.h>

main(void) {
pid_t pid = 0;
int status;

pid = fork();
if (pid == 0) {
printf("Ich bin der Kindprozess.\n");
execl("/bin/ls", "ls", "-l", "/tmp/kris", (char *) 0);
perror("In exec(): ");
}
if (pid > 0) {
printf("Ich bin der Elternprozess, das Kind ist %d.\n",
pid);
pid = wait(&status);
printf("Ende des Prozesses %d: ", pid);
if (WIFEXITED(status)) {
printf("Der Prozess wurde mit exit(%d) beendet.\n",
WEXITSTATUS(status));
}
if (WIFSIGNALED(status)) {
printf("Der Prozess wurde mit kill -%d beendet.\n",
WTERMSIG(status));
}
}
if (pid < 0) {
perror("In fork():");
}

exit(0);
}
kris@linux:/tmp/kris> ./probe3
Ich bin der Kindprozess.
Ich bin der Elternprozess, das Kind ist 17690.
total 36
-rwxr-xr-x 1 kris users 6984 2007-01-05 13:29 probe1
-rw-r--r-- 1 kris users 303 2007-01-05 13:36 probe1.c
-rwxr-xr-x 1 kris users 7489 2007-01-05 13:37 probe2
-rw-r--r-- 1 kris users 719 2007-01-05 13:40 probe2.c
-rwxr-xr-x 1 kris users 7513 2007-01-05 13:42 probe3
-rw-r--r-- 1 kris users 728 2007-01-05 13:42 probe3.c
Ende des Prozesses 17690: Der Prozess wurde mit exit(0) beendet.


Wenn man das wait(2) wegläßt, kann man mehr als einen Prozeß zugleich
ausführen. Im C-Programm oben wäre das ein Kommentar an der passenden
Stelle (und das Problem der Zombie-Prozesse, aber das würde jetzt zu weit
führen).

In der Shell macht man das einfach mit einem & hinter dem Kommando. Die
Shell hat ebenfalls eine "wait" Einbaufunktion, die Du verwenden kannst, um
auf das Ende eines Kindes zu warten.

In der Shell schreibt man das so:

kris@linux:/tmp/kris> cat probe1.sh
#! /bin/bash --

echo "Starte Kindprozess"
sleep 10 &
echo "Der Kindprozess hat die ID $!"
echo "Der Elternprozess hat die ID $$"
echo "$(date): Elternprozess geht schlafen."
wait
echo "Der Kindprozess $! hat den Exit-Status $?"
echo "$(date): Elternprozess ist aufgewacht."

kris@linux:/tmp/kris> ./probe1.sh
Starte Kindprozess
Der Kindprozess hat die ID 18071
Der Elternprozess hat die ID 18070
Fri Jan 5 13:49:56 CET 2007: Elternprozess geht schlafen.
Der Kindprozess 18071 hat den Exit-Status 0
Fri Jan 5 13:50:06 CET 2007: Elternprozess ist aufgewacht.

Kris

Hauke Laging

unread,
Jan 5, 2007, 8:36:09 AM1/5/07
to
Kristian Köhntopp schrieb am Freitag 05 Januar 2007 13:50:

> Die Shell erzeugt mit der Unix-Systemfunktion fork(2) eine Kopie
> von sich.

Meine Güte, wird hier mit Kanonen auf Spatzen geschossen...

Komisch nur, dass strace kein fork findet, wenn ich in

bash --version
GNU bash, version 3.1.17(1)-release (i586-suse-linux-gnu)

ls aufrufe - sei es interaktiv oder im Script.


CU

Hauke
--
http://www.hauke-laging.de/ideen/
http://www.hauke-laging.de/software/
http://zeitstempel-signatur.hauke-laging.de/
Wie können 59.054.087 Leute nur so dumm sein?

Philipp Tölke

unread,
Jan 5, 2007, 9:25:14 AM1/5/07
to
Hauke Laging <1q2...@hauke-laging.de> schrieb:

> Kristian Köhntopp schrieb am Freitag 05 Januar 2007 13:50:
>> Die Shell erzeugt mit der Unix-Systemfunktion fork(2) eine Kopie
>> von sich.
>
> Meine Güte, wird hier mit Kanonen auf Spatzen geschossen...
>
> Komisch nur, dass strace kein fork findet, wenn ich in

Gute Idee!

Man sieht sehr schön, wie die bash erst mit Hilfe von stat im Pfad nach
der richtigen Datei sucht:

|stat64("/home/me/bin/ls", 0xbfb1c51c) = -1 ENOENT
|stat64("/home/me/bin/ls", 0xbfb1c51c) = -1 ENOENT
|stat64("/usr/local/bin/ls", 0xbfb1c51c) = -1 ENOENT
|stat64("/usr/bin/ls", {st_mode=S_IFREG|0755, st_size=98560, ...}) = 0
|stat64("/usr/bin/ls", {st_mode=S_IFREG|0755, st_size=98560, ...}) = 0

Dann ein bisschen an den Signal-Masken rumfuhrwerkt:

|rt_sigprocmask(SIG_BLOCK, [INT CHLD], [], 8) = 0
|rt_sigprocmask(SIG_BLOCK, [CHLD], [INT CHLD], 8) = 0
|rt_sigprocmask(SIG_SETMASK, [INT CHLD], NULL, 8) = 0

Und dann (*trommelwirbel*):
|clone(child_stack=0,flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD,
| child_tidptr=0xb7e02908) = 24071

Nach clone(2):

|__clone erzeugt einen neuen ProzeB, ahnlich wie fork(2) dies tut. Im
|Gegensatz zu fork(2) erlaubt es __clone jedoch, daB der KindprozeB
|Teile seines Kontextes mit dem VaterprozeB teilt. Dazu zahlen Spe-
|icherbereiche, die verwendeten Dateideskriptoren oder Signalhandler.
|__clone wird in erster Linie dazu benutzt, um Threads zu implemen-
|tieren. Das sind mehrere parallel zueinander ablaufende Programmstrange
|eines Prozesses in einem gemeinsamen Speicherbereich
(Zerschossene "ß" nicht von mir)

Spannend. Das hätte ich mir sonst nie angeschaut.

Der Rest des Traces enthält übrigens tatsächlich ein "SIGCHLD"...

> bash --version
> GNU bash, version 3.1.17(1)-release (i586-suse-linux-gnu)
>
> ls aufrufe - sei es interaktiv oder im Script.

|$ bash --version
|GNU bash, version 3.1.17(1)-release (i686-pc-linux-gnu)

Grüße,
--
Philipp Tölke
PGP: 0x96A1FE7A

Hauke Laging

unread,
Jan 5, 2007, 9:30:15 AM1/5/07
to
Philipp Tölke schrieb am Freitag 05 Januar 2007 15:25:

> |Gegensatz zu fork(2) erlaubt es __clone jedoch, daB der
> |KindprozeB
> |Teile seines Kontextes mit dem VaterprozeB teilt. Dazu zahlen
> |Spe-
> |icherbereiche, die verwendeten Dateideskriptoren oder
> |Signalhandler.

Wieder was gelernt bzw. zu faul gewesen, nachzusehen. Ich hatte
eigentlich erwartet, dass neue Prozesse mit vfork aufgebaut werden,
aber offenbar erfüllt dies einen besonderen Zweck.

Kristian Köhntopp

unread,
Jan 5, 2007, 1:01:58 PM1/5/07
to
Hauke Laging wrote:
> Komisch nur, dass strace kein fork findet, wenn ich in
>
> bash --version
> GNU bash, version 3.1.17(1)-release (i586-suse-linux-gnu)
>
> ls aufrufe - sei es interaktiv oder im Script.

In Linux ist fork() eine Bibliotheksfunktion, die durch einen "clone(0)"
Systemaufruf implementiert wird. bash macht es noch ein wenig
differenzierter:

kris@linux:~> strace -f -e execve,clone,fork,waitpid bash
kris@linux:~> ls
clone(Process 30048 attached
child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD,
child_tidptr=0xb7dab6f8) = 30048
[pid 30025] waitpid(-1, Process 30025 suspended
<unfinished ...>
[pid 30048] execve("/bin/ls", ["/bin/ls", "-N", "--color=tty", "-T", "0"],
[/* 107 vars */]) = 0
...
Process 30025 resumed
Process 30048 detached
<... waitpid resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WSTOPPED
WCONTINUED) = 30048
--- SIGCHLD (Child exited) @ 0 (0) ---
...


Aber das ist alles neumodischer Scheiß. Damals, 1979, hat man das noch so
gemacht, wie in
http://minnie.tuhs.org/UnixTree/V7/usr/src/cmd/sh/xec.c.html gezeigt. Du
willst den Code ab case TFORK: lesen. Oder vielleicht auch nicht -
S.R.Bourne Code liest man nicht ohne vorher zwei Gläser Whisky getrunken zu
haben.

Kris

Bernd Nawothnig

unread,
Jan 7, 2007, 7:15:42 AM1/7/07
to
On 2007-01-05, Kristian Köhntopp wrote:

> S.R.Bourne Code liest man nicht ohne vorher zwei Gläser Whisky
> getrunken zu haben.

Selbst dann tut sowas weh ... weia!

Bernd

--
Religion ist eine Beleidigung der Menschenwürde.
Mit oder ohne sie würden gute Menschen Gutes tun und böse Menschen Böses.
Aber damit gute Menschen Böses tun, dafür bedarf es der Religion.
[Steven Weinberg]

Reply all
Reply to author
Forward
0 new messages