Académique Documents
Professionnel Documents
Culture Documents
Maroun CHAMOUN
maroun.chamoun@usj.edu.lb
Génie Logiciel
USJ FI
Semestre 4
Partie I
• Problème:
– Comment gérer l’exécution de plusieurs processus sur beaucoup moins d’UCT
• Un processus sera:
– parfois en exécution sur une UCT,
– parfois en attente d’une E/S,
– parfois en attente d’une UCT
• Système monoprocesseur
– Pour fournir l’illusion que plusieurs processus tournent en parallèle, le système
interrompt et reprend les processus.
– A un instant donné, 1 seul processus actif (ÉLU)
– Autre processus:
• en attente d’exécution (PRET)
• bloqué en attente de ressources (BLOQUÉ)
Partie II
Commutation de processus et
Process Control Block
Informations utilisées
Informations dont le SE a
besoin à tout moment: uniquement lorsque le
processus est en exécution:
– Numéro d’identification
– Compteur de programme (PC)
unique (PID)
– Valeurs des registres
– Informations sur son
– Liste des fichiers ouverts
processus père et ses
– Liste des périphériques ouverts
processus fils
– …
– Etat du processus
– Espace d’adressage
– Priorité
– Information de comptabilité
–…
Systèmes d'exploitation 02- Les processus Maroun CHAMOUN E S I B 8/70
Table des processus Génie Logiciel
USJ FI
Semestre 4
Partie III
Les processus sous UNIX
s = execve (nom, params, env) Provoque l’exécution d’un autre programme par le
processus courant.
exit (status) Termine le processus courant.
▪ Tout processus Unix peut créer un ou plusieurs processus qui, à leur tour,
peuvent en créer d’autres
– Arborescence de processus avec un rapport père -fils entre processus créateur et
processus créés
▪ pid_t fork( )
▪ La seule manière de dupliquer un processus est d’utiliser fork
– Tout processus Unix/Linux hormis le processus 0 est créé à l’aide de cette primitive.
▪ Cette primitive permet la création dynamique d’un nouveau processus qui
est une copie exacte du processus appelant:
– il s’appelle processus fils. ret=fork()
– Il hérite de son père :
• le même code ret==0 ret>0
• une copie de la zone de données
• la priorité On est dans le On est dans le
• les propriétaires processus fils processus père
• les descripteurs de fichier
• …
– Le processus fils s’exécute de manière concurrente avec le processus qui l’a créé, on
ne peut pas faire d’hypothèse sur celui qui exécutera la prochaine instruction.
Après le fork():
• Le père et le fils vont poursuivre leurs
exécutions à partir de l’instruction qui suit
l’appel au fork mais chacun a sa propre zone
de données.
• Le père récupèrera dans sa variable ret, le
PID du fils créé.
• Le fils récupèrera dans sa variable ret la
valeur 0.
• Version correcte:
for (i=0 ; i < nbfils ; i++)
{
val=fork();
if (val==0)
{
/* un fils */
int n,num;
char message[15];
cout<<"je suis le fils " <<i<<" de ID " << getpid() << " et de pere " << getppid()<<endl;
……
exit(i); /* ou break; */
}
}
#include <iostream>
#include <unistd.h>
using namespace std;
int main ( )
{
pid_t p;
int i, n=5 ;
for (i=1; i<n; i++) {
p = fork();
if (p > 0 ) break ;
cout<<" Processus " << getpid() << " de père " << getppid() << ". \n" ;
}
sleep(1); // dormir pendant 1 seconde
return 0 ;
}
$ make fork2
g++ fork2.cpp -o fork2
$ ./fork2
Processus 23707 de père 23706.
Processus 23708 de père 23707.
Processus 23709 de père 23708.
Processus 23710 de père 23709.
Systèmes d'exploitation 02- Les processus Maroun CHAMOUN E S I B 22/70
Synchronisation père / fils Génie Logiciel
USJ FI
Semestre 4
#include <iostream>
#include <unistd.h>
#include <wait.h>
int main( ) {
int i , pidfils , etat ;
std::cout<<"Début d'exécution du programme père\n";
if ( pidfils = fork( ) ){
std::cout<<"dans le processus père pidfils=pid de fils="<<pidfils<<std::endl ;
i = wait ( &etat ) ; /* père attend ici */
}
else { /* sinon exécuter fils */
int pid ;
std::cout<<"\n Début d'exécution du processus fils\n";
pid=getpid(); /*primitive qui renvoie le numéro du processus courant */
std::cout<<"les 8 bits du poids faible de pid valent "<< pid % 256 <<std::endl ;
std::cout<<"le processus fils se termine\n";
exit( pid ) ;
}
Systèmes d'exploitation 02- Les processus Maroun CHAMOUN E S I B 26/70
Exemple (Suite) Génie Logiciel
USJ FI
Semestre 4
#include <iostream>
#include <unistd.h>
#include <wait.h>
int main() {
pid_t pere,fils;
int vingt, x=1;
std::cout<<"C'est partis!\n";
switch(fils = fork()) {
case -1:
std::cerr<<"erreur, je sors\n";
exit(1);
case 0: /* c'est le fils */
std::cout<<getpid()<<": Je suis le fils de "<<getppid()<<std::endl;
sleep(1);
x=2; $ make wait2
std::cout<<getpid()<<": Je sors \n"; g++ wait2.cpp -o wait2
exit(20); $ ./wait2
default: C'est partis!
std::cout<<getpid()<<": Je suis le pére de "<<fils<<std::endl; 35925: Je suis le pére de 35926
waitpid(fils, &vingt, 0); 35926: Je suis le fils de 35925
std::cout<<getpid()<<": Le processus fils "<<fils<<" est sorti \n";
35926: Je sors
}
} /* FIN du main */ 35925: Le processus fils 35926 est sorti
▪ Effet:
– Charge le nouveau code du processus et « repart à 0 » avec le nouveau code,
– réinitialise la pile,
– réinitialise la zone data.
– La zone U-area n'est (presque) pas touchée:
• Il garde son pid, ppid, son propriétaire et groupe réel, son répertoire courant, son umask, ses
signaux pendants, ...
• Il change de propriétaire/groupe effectif si le setuid/setgid bit du binaire qu’il exécute est
positionné.
6 variantes
▪ L'exécution d'une commande telle que "ls" sous Unix agit de cette manière :
– le shell est dupliqué,
– la copie du shell est transformée en la commande ls,
– ls termine et le shell principal reprend la main.
Partie IV
Fin d’exécution d’un processus
et gestion des signaux
• Attention: il est important d’utiliser les noms symboliques des signaux, les
valeurs entières associées étant dépendantes de l’implémentation (ex:
SIGCHLD = 17 sous LINUX, 18 sous Solaris et 20 sous FreeBSD)
• Dans l’exemple suivant, un processus père envoie un signal à son processus fils après
avoir testé son existence en lui adressant le signal 0.
#include <iostream>
#include <unistd.h>
#include <wait.h>
#include <signal.h>
int main( ) {
pid_t pid ;
int etat ;
if ( ( pid = fork( ) ) == 0 )
pause(); /* processus fils bouclant*/
sleep ( 10 ) ;
if( kill ( pid , 0 ) == -1 )
std::cout<< "fils "<< pid<<" inexistant\n";
else {
std::cout<< "envoi du signal SIGUSR1 au processus "<<pid<<std::endl ;
kill ( pid , SIGUSR1 ) ;
pid = wait (& etat ) ;
std::cout<< "Etat du fils "<<pid<<" : "<<etat<<std::endl ;
}
}
• Tout processus peut installer, pour chaque type de signal (hormis SIGKILL
et SIGSTOP), un nouveau handler.
• Associer un traitement explicite à un signal
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t fct);
– définit la fonction fct (le handler) comme étant à appeler lors de la réception du
signal signum. Au retour de la fonction, le contrôle retourne à l'endroit où le
processus a été interrompu
– le traitement par défaut de SIGKILL et SIGSTOP ne peut être modifié;
– Un handler ne doit utiliser que des fonctions sûres, dites réentrantes.
• Il faut toujours se rappeler que même une affectation peut correspondre à plusieurs instructions
machines non atomique.
• signal(sig, SIG_IGN)
permet d'ignorer le signal sig
• signal(sig, SIG_DFL)
rétablit le traitement par défaut du signal sig.
• A tout signal est associé un traitement (Handler) par défaut qui correspond
à la terminaison du processus
• Plusieurs possibilités:
– exit - Le processus se termine (avec si possible un fichier core)
– ignore - Le processus ignore le signal
– pause - Suspension du processus
– continue - Reprise du processus si il était suspendu
Remarque: signal() n'émet pas de signal (la fonction qui émet un signal est la
fonction kill …)
Systèmes d'exploitation 02- Les processus Maroun CHAMOUN E S I B 49/70
Traitement des signaux: Exemple Génie Logiciel
USJ FI
Semestre 4
/* temporisation */
#include <iostream>
#include <unistd.h>
#include <signal.h>
constexpr auto TEMPS = 15;
void hand(int sig) {
std::cout<<"la réponse n’est pas arrivée à temps \n";
exit(0);
}
int main( ) {
int reponse;
signal(SIGALRM,hand);
std::cout<<"Question ?";
alarm(TEMPS);
std::cin>>reponse;
alarm(0);
std::cout<<"Réponse lue \n";
}
#include <signal.h>
int sigaction( int sig , struct sigaction *act , struct sigaction * old_act);
La structure sigaction a la forme suivante.
struct sigaction
{
void (* sa_handler)(int);
int sa_flags;
sigset_t sa_mask;
}
• sa_handler: un pointeur de fonction à un argument entier
– SIG_DFL: traitant par défaut.
– SIG_IGN: ignorer le signal.
– Si act.sa_handler == f, l’appel sigaction(sig,&act,&old_act) installe le traitant f() pour
le signal sig et récupère l’ancien dans old_act.
Partie V
Les fils d’exécution (threads)
• Contexte volumineux
• Espace protégé
– Espace d’adressage non accessible depuis un autre processus
– Partage / échange de données
• Au moment de la création d’un processus fils: fork()
• Via des segments de mémoire partagée
• Via des messages
• Commutations coûteuses
En commun
Séparés
Mono-flot Multi-flots
Systèmes d'exploitation 02- Les processus Maroun CHAMOUN E S I B 60/70
Thread vs processus Génie Logiciel
USJ FI
Semestre 4
Système d'exécution:
logiciel procurant des
services à un programme
• Gestion de la mémoire
• Débogueur
• Threads utilisateur
• Bibliothèques partagées
• Etc.
• Parallélisme (-)
– Pas de parallélisme réel entre les threads d’un espace virtuel
• Efficacité (+)
– La commutation de contexte est rapide
• Appels systèmes bloquants (-)
– Le processus est bloqué au niveau du noyau
– Tous les threads sont bloqués tant que l’appel système (I/O) n’est pas terminé
– Il n’y a donc pas de multiprogrammation dans un espace virtuel
#include <iostream>
#include <thread>
#include <vector>
#include <algorithm>
void threadFunction (std::vector<int> &speeds, int start, int end, int& res) {
std::cout << "starting thread ... " << std::endl;
for (int i = start; i <= end; ++i)
res += speeds[i];
std::cout << "end thread ... " << std::endl;
}
int main() {
std::vector<int> speeds (100000);
std::generate(begin(speeds), end(speeds), [] (){ return rand() % 10 ; });
int th1Result = 0, th2Result = 0;
std::thread t1 (threadFunction, std::ref(speeds), 0, 49999, std::ref(th1Result));
std::thread t2 (threadFunction, std::ref(speeds), 50000, 99999, std::ref(th2Result));
t1.join();
t2.join();
std::cout << "Result = " << th1Result + th2Result << std::endl;
return 0;
}
Systèmes d'exploitation 02- Les processus Maroun CHAMOUN E S I B 70/70