setjmp
, longjmp
En C, il n'y a pas de
procédures emboitées. Donc pour faire un saut global, on sauve
les registres et le pointeur de pile par setjmp
, et on
peut restituer cet environnement par longjmp
. Attention,
les variables globales ne sont pas restituées. Par convention,
setjmp
retourne 0, et longjmp
la valeur de
son 2nd argument (différent 0). Exemple:
#include <setjmp.h> jmp_buf env; int i = 0; main () { void exit(); if(setjmp(env) != 0) { (void) printf("2eme retour de setjmp: i=%d\n", i); exit(0); } (void) printf("1er retour de setjmp: i=%d\n", i); i = 1; g(); /*NOTREACHED*/ } g() { longjmp(env, 1); /*NOTREACHED*/ }} donne
i=0
et i=1
.
Dans la programmation système, on a la concurrence pour:
Comment rendre Pop et Push concurrents?
Pile *head = NULL; Pile *Pop() { Pile *topElement; while (head == NULL) Wait_nonEmpty (); topElement = head; head = head -> next; } void Push (Pile *newElement) { newElement -> next = head; head = newElement; Signal_nonEmpty (); }} Il faut d'abord que l'accès à Pop et à Push soit exclusif, et écrire
Wait_nonEmpty
et Signal_nonEmpty
.
Section critique (ou exclusion mutuelle) est un classique de la
programmation concurrente.
TIT .Principles of concurrent programming SLA .M. Ben-Ari. - Englewood Cliffs, NJ ; London ; New .Delhi : Prentice-Hall, 1982. - XV-172 p. ; 22 cm.}
turn = 1; p1() p2() { { for (;;) { for (;;) { while (turn == 2) while (turn == 1) ; ; Crit1(); Crit2(); turn = 2; turn = 1; Reste1(); Reste2(); } } } }} Ok, mais manque de souplesse.
c1 = c2 = 1; p1() p2() { { for (;;) { for (;;) { while (c2 == 0) while (c1 == 0) ; ; c1 = 0; c2 = 0; Crit1(); Crit2(); c1 = 1; c2 = 1; Reste1(); Reste2(); } } } }} Erreur car:
c1 c2 1 1 p1 teste c2 1 1 p2 teste c1 1 1 p1 positionne c1 0 1 p2 positionne c2 0 0 p1 fait Crit1() 0 0 p2 fait Crit2() 0 0}
c1 = c2 = 1; p1() p2() { { for (;;) { for (;;) { c1 = 0; c2 = 0; while (c2 == 0) while (c1 == 0) ; ; Crit1(); Crit2(); c1 = 1; c2 = 1; Reste1(); Reste2(); } } } }} Deadlock car:
c1 c2 1 1 p1 positionne c1 0 1 p2 positionne c2 0 0 p1 teste c1 0 0 p2 teste c2 0 0 ...}
c1 = c2 = 1; turn = 1; p1() p2() { { for (;;) { for (;;) { c1 = 0; c2 = 0; while (c2 == 0) while (c1 == 0) if (turn == 2) { if (turn == 1) { c1 = 1; c1 = 1; while (turn == 2) while (turn == 1) ; ; c1 = 0; c2 = 0; } } Crit1(); Crit2(); turn = 2; turn = 1; c1 = 1; c2 = 1; Reste1(); Reste2(); } } } }}
enum {OUT, WANTING, IN} c[n]; s = 1; pi() { for (;;) { do { c[i] = WANTING; while (turn != i) if (c[turn] == OUT) turn = i; c[i] = IN; ok = TRUE; for (j = 0; j < n; j++) if (j != i) ok = ok & (c[j] != IN); } while (!ok); Crit1(); c[i] = OUT; Reste1(); } }} L'algorithme de Peterson est plus simple que Dekker, quoique moins intuitif.
c1 = c2 = 1; turn = 1; p1() p2() { { for (;;) { for (;;) { c1 = 0; c2 = 0; turn = 1; turn = 2; while (c2 == 0 && turn == 1) while (c1 == 0 && turn == 2) ; ; Crit1(); Crit2(); c1 = 1; c2 = 1; Reste1(); Reste2(); } } } }}
TEST_AND_SET
.
Dijkstra 65
.
s = 1; p1() p2() { { for (;;) { for (;;) { P(s); P(s); Crit1(); Crit2(); V(s); V(s); Reste1(); Reste2(); } } } }}
TIT .The Design of the UNIX operating system SLA .Maurice J. Bach. - Englewood Cliffs, NJ : .Prentice-Hall, 1986. - XIV-471 p. ; 23 cm.}
fork
établit une filiation entre processus. Tous
les processus sont créés à partir de init
(processus 1), cf cours 1.
exit
termine un processus. Unix regarde si le
processus à un père actif. Sinon init
devient son
père (processus 1). Si oui, le fils passe à l'état zombie (avec
uniquement un descripteur temporaire dans la table des processus, pour
qu'on puisse récupérer sa mémoire, ses fichiers ouverts, etc) et
disparait complètement quand le père fait wait
.
(init
appelle toujours wait
dès qu'un de
ses fils termine.)
exec
, et tout
processus peut y accéder par getenv()
char *getenv (char *name);}
/dev/tty
est le terminal de la session.
kill
et signal
.
kill (pid, signum); signal (signum, function);
kill
envoie le signal signum
au
processus pid
. Les nombres signum
sont
décrits dans signal.h
et valent moins que 32. Si
pid
< 0, le signal est envoyé au ``process
group''. Si pid
= -1 et l'envoyeur est le
super-utilisateur, envoyé à tout le monde.
signal
permet de dire l'action à entreprendre
(handler du signal) sur la réception d'un tel signal.
Valeurs conventionnelles: SIG_IGN
pour ignorer,
SIG_DFL
pour action par défaut, ...
malloc
). Il
faut donc faire attention. On peut toujours positionner un
flag, et le tester plus tard. De manière générale, les signaux ne
feront pas de mal pour les procédures réentrantes. Très peu
le cas en Unix, par exemple tout stdio n'est pas réentrant.
#include <signal.h> #include <setjmp.h> jmp_buf sjbuf; main() { int onintr(void); if (signal (SIGINT, SIG_IGN) != SIG_IGN) signal (SIGINT, onintr); setjmp (sjbuf); for (;;) { /* boucle d'interpretation */ ... } onintr() { signal (SIGINT, onintr); printf ("\nInterrupt\n"); longjmp (sjbuf, 0); }}
alarm(n)
génère un signal SIGALRM
dans les n
secondes.
signal (SIGALRM, onalarm); alarm (sec); ... onalarm () { kill (pid, SIGKILL); }}
if (fork() == 0) exec (...); signal (SIGINT, SIG_IGN); wait (&status); signal (SIGINT, onintr);}
read
s'arrête et fait
l'erreur EINTR
.
grep
parallèle, ie faire une commande pgrep [-N] string
[file]
où N est le nombre de processus forkés et file un
fichier contenant des noms de fichiers (un par ligne) qu'on obtient en
faisant par exemple find . -type f -print
La sémantique de pgrep [-N] string [file]
est:
grep string
sur chacun des seaux. Remarque:
on peut prendre le grep
du système ou écrire un
grep
perso trivial sans KMP ou Boyer-Moore avec
bêtement strcmp
et la méthode quadratique en
string et input.
& ; < >
et l'appel des
commandes standards (exec). On s'efforcera de traiter les signaux
SIGINT et SIGSTOP. Cet exercice reservira plus tard pour le
remote-shell.
setjmp
et
longjmp
pour commuter. L'ordonnancement peut être sans
préemption ou avec des signaux alarme. Lancer un Dekker ou Peterson.