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.