You are on page 1of 15

Archi te ctu re de s Sy st em es

GESTION DES PROCESSUS

Applications comportant des processus concurrents


• Motivations pour les processus concurrents :
– améliorer l'utilisation de la machine (cas monoprocesseur)
– multiprogrammation sur des serveurs
– parallélisme entre le calcul et les E/S sur PC
– lancer des sous-calculs dans une machine parallèle ou répartie
– créer des processus réactifs pour répondre à des requêtes ou à des
événements externes (systèmes temps réel, systèmes embarqués et/mobiles)
• Construire des applications concurrentes, parallèles ou
réparties

Définitions
• Processus:
– Instance d’un programme en exécution
– et un ensemble de données
– et un ensemble d’informations (BCP, bloc de contrôle de processus)
• Deux types de processus
– Système: propriété du super-utilisateur (démons)
– Utilisateur: lancés par les utilisateurs
• Code de retour d’un processus
– =0: fin normale
– ≠0: comportement anormal (en cours d’exécution ou à la terminaison)
• Interruption d’un processus à la réception d’un signal
– A partir d’un programme (SIGINT, SIGQUIT)
– A partir d’un shell avec la commande: kill num_signal pid_processus
• La table des descripteurs de fichiers:
– Chaque processus peut posséder au maximum OPEN_MAX descripteurs de
fichier
– En plus des descripteurs qu’un processus hérite de son père, il peut en créer
d’autres avec open, creat, dup, …
• Enchaînement d’exécution des processus
– Si les commandes associées sont séparées par « ; »
– Exemple: commande1 ; commande2 ; … ; commanden
– Communication par l’intermédiaire de tube. Les résultats d’un
processus servent de données d’entrée à l’autre
− Exemple: commande | commande | … | commande

Descripteur des processus Linux


• Bloc de contrôle (utilisé par Linux) comporte :
- État du processus
- Priorité du processus
- Signaux en attente et masqués
- Pointeur sur le processus suivant dans la liste des processus prêts
- Numéro du processus
- Numéro du groupe contenant le processus
- Pointeur sur le processus père
- Numéro de la session contenant le processus
- Identificateur de l’utilisateur réel et effectif
- Politique d’ordonnancement utilisé
- Temps processeur consommé en mode noyau et en mode utilisateur
− Date de création du processus, etc.
Visualisation de processus
• La commande « ps » permet de visualiser les processus existant à son
lancement sur la machine
• Sans option, elle ne concerne que les processus associés au terminal depuis
lequel elle est lancée

Ordonnancement de processus
L’ordonnanceur (scheduller) d’Unix:
– Associe une priorité dynamique à chaque processus, recalculée
périodiquement
– Utilise la stratégie du tourniquet (round robin) pour les processus de même
priorité
La fonction de calcul de la priorité courante utilise les paramètres suivants:
– Une priorité de base: correspondant au niveau d’interruption du processus
– Le temps CPU consommé récemment
– Une priorité ajustable par le processus lui-même grâce à:
#include <unistd.h>
int nice(int incr);
qui permet d’augmenter la valeur de incr [0-39] et donc de diminuer sa priorité
Le super-utilisateur peut augmenter la priorité en donnant une valeur <0 à incr.

État d’un processus


Création de processus
• La primitive fork() permet de créer un nouveau processus (fils) par
duplication
• Afin de distinguer le père du fils, fork() retourne:
-1 : en cas d’erreur de création
0 : indique le fils
> 0 : le pid du processus fils au processus père
• Les primitives getpid() et getppid() permettent d’identifier un processus et
son père.
#include <unistd.h>
pid_t fork(void)// crée un nouveau processus
pid_t getpid(void) // donne le pid du processus
pid_t getppid(void) // donne le pid du père du processus

L’héritage du fils
• Le processus fils hérite de beaucoup d’attributs du père mais n’hérite pas:
De l’identification de son père
Des temps d’exécution qui sont initialisés à 0
Des signaux pendants (arrivées, en attente d’être traités) du père
De la priorité du père; la sienne est initialisée à une valeur standard
Des verrous sur les fichiers détenus par le père
• Le fils travaille sur les données du père s’il accède seulement en lecture. S’il
accède en écriture à une donnée, celle-ci est alors recopiée dans son espace
local.
Primitives wait et waitpid
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *etat);
pid_t waitpid(pid_t pid, int *etat, int options);
• Ces deux primitives permettent l’élimination des processus zombis et la
synchronisation d’un processus sur la terminaison de ses descendants avec
récupération des informations relatives à cette terminaison.
• Elles provoquent la suspension du processus appelant jusqu’à ce que l’un de
ses processus fils se termine
• La primitive waitpid permet de sélectionner un processus particulier parmi les
processus fils (pid)
Communication avec l’environnement
• int main(int argc, char *argv[], char *argp[])
• Le lancement d’un programme C provoque l’appel de sa fonction principale
main() qui a 3 paramètres optionnels:
– argc: nombre de paramètres sur la lignes de commande (y compris le nom de
l’exécutable lui-même)
– argv: tableau de chaînes contenant les paramètres de la ligne de commande
– envp: tableau de chaînes contenant les variables d’environnement au
moment de l’appel, sous la forme variable=valeur

Primitives de recouvrement
• Objectif: charger en mémoire à la place du processus fils un autre programme
provenant d’un fichier binaire
• Il existe 6 primitives exec. Les arguments sont transmis:
– sous forme d’un tableau (ou vecteur, d’où le v) contenant les paramètres:
• execv, execvp, execve
– Sous forme d’une liste (d’où le l): arg0, arg1, …, argN, NULL:
• execl, execlp, execle
– La lettre finale p ou e est mise pour path ou environnement
• La fonction system("commande…") permet aussi de lancer une commande à
partir d’un programme mais son inconvénient est qu’elle lance un nouveau
Shell pour interpréter la commande.

Tubes & Signaux


Introduction aux tubes
Tube : mécanisme de communication unidirectionnel.
− Possède deux extrémités, une pour y lire et l’autre pour écrire
− La lecture dans un tube est destructrice: l’info lue est supprimée du tube
− Les tubes permettent la communication d’un flot continu de caractères
(mode stream)
− Un tube a une capacité finie
− La gestion des tubes se fait en mode FIFO

Les tubes ordinaires


• A un tube est associé un nœud du système de gestion de fichiers (son
compteur de liens est = 0 car aucun répertoire ne le référence).
• Le tube sera supprimé et le noeud correspondant libéré lorsque plus aucun
processus ne l'utilise.
• Un tube ordinaire n’a pas de nom.
• L'existence d'un tube correspond à la possession d'un descripteur acquis de
deux manières:
-un appel à la primitive de création de tube pipe;
-par héritage: un processus fils hérite de son père des descripteurs de tubes,
entre autres.

#include<unistd.h>
int pipe(int p[2]);
crée un tube, c-à-d:
- un noeud sur le disque des tubes,
- deux entrées dans la table des fichiers ouverts (1 en lecture, 1 en écriture), c-
à-d deux descripteurs dans la table des processus appelant. pipe retourne dans
p respectivement les descripteurs de lecture et d'écriture:
p[0] est le descripteur en lecture / p[1] celui en écriture.
La lecture se fait grâce à la primitive read EXEMPLE :
nb_lu= read(p[0], buf, nbr_car); correspond à la lecture d'au plus nbr_car
caractères qui seront rangés dans une zone pointée par buf.

Fonctionnement d’un read


Si tube non vide et contient nbr caractères alors la primitive extrait du tube
nb_lu=min (nbr, nbr_car) caractères et les place à l'adresse buf;
Sinon
Si nombre d'écrivains = 0, la fin de fichier est atteinte alors, aucun
caractère n'est lu et la primitive renvoie la valeur nb_lu=0;
Fsi
Si nombre d'écrivains != 0 alors
Si lecture bloquante alors le processus est bloqué jusqu'à tube non
vide;
Fsi;
Si lecture non bloquante alors le retour est immédiat et la valeur de
retour est -1 et errno=EAGAIN
Fsi;
Fsi
Fsi

L’écriture dans un tube ordinaire


Elle se fait à l'aide de la primitive write.
Exemple:
nb_ecrit= write(p[1], buf, n); demande l'écriture dans le tube de descripteur
p[1] de n caractères accessibles à l'adresse buf.

Fonctionnement d’un write


Si nombre de lecteurs dans le tube = 0 alors le signal SIGPIPE est envoyé au
processus, ayant pour handler de terminer ce processus;
Sinon
Si écriture bloquante alors le retour de la primitive (avec la valeur n) n'a
lieu que lorsque les n caractères ont été écrits (le processus passe à l'état
bloqué dans l'attente que le tube se vide);
sinon
si n > PIPE_BUF alors le retour est un nombre < n éventuellement
-1
fsi
si n ≤ PIPE_BUF et nbre emplacements libres dans le tube >= n
alors une écriture atomique est réalisé;
fsi
si n ≤ PIPE_BUF et nbre emplacements libres dans le tube < n alors
le retour est immédiat sans écriture avec la valeur de retour 0
ou -1
fsi
fsi
fsi

Les signaux
Introduction
• Signal
– Interruption: événement extérieur au processus
• Frappe au clavier
• Signal déclenché par programme: primitive kill, …
– Déroutement: événement intérieur au processus généré par le hard
• FPE (floating point error)
• Violation mémoire
• Il existe NSIG (<signal.h>) signaux différents, identifiés par un numéro de 1 à
NSIG
Envoi d’un signal
• Les processus peuvent communiquer par le biais des signaux.
La commande kill envoie un signal à un processus (ou groupe de processus) :
int kill (pid_t pid, int sig);
• sig est le nom d'un signal ou un entier entre 1 et NSIG.
• On peut utiliser sig = 0 pour tester l'existence d'un processus.

Traitements par défaut


• A chaque type de signal est associé un handler par défaut appelé SIG_DFL.
• Les traitements par défaut sont:
1- terminaison du processus (avec/sans image mémoire : fichier core)
3- signal ignoré (sans effet)
4- suspension du processus
5- continuation : reprise d'un processus stoppé.
• Un processus peut ignorer un signal en lui associant le handler SIG_IGN.
• Les signaux SIGKILL, SIGCONT et SIGSTOP ne peuvent avoir que le handler
SIG_DFL.
Traitements spécifiques
• Les signaux (autres que SIGKILL, SIGCONT et SIGSTOP) peuvent avoir un
handler spécifique installé par un processus:
• La primitive signal() fait partie du standard de C et non de la norme de POSIX:
#include<signal.h>
void (*signal (int sig, void (*p_handler)(int))) (int);
• Elle installe le handler spécifié par p_handler pour le signal sig. La valeur
retournée est un pointeur sur la valeur ancienne du handler.

Attente d’un signal


• La primitive pause() bloque en attente le processus appelant jusqu’à l’arrivée
d’un signal.
#include<unistd.h>
int pause (void);
• A la prise en compte d'un signal, le processus peut :
- se terminer car le handler associé est SIG_DFL;
- passer à l'état stoppé; à son réveil, il exécutera de nouveau pause() et s’il a
été réveillé par SIGCONT ou SIGTERM avec le handler SIG_IGN, il se mettra de
nouveau en attente d'arrivée d'un signal;
- exécuter le handler correspondant au signal intercepté.
• pause() ne permet pas d'attendre un signal de type donné ni de connaître le
nom du signal qui l'a réveillé.

Gestion des threads


Descripteur des processus Unix
Bloc de contrôle (utilisé par Linux) comporte :
-État du processus
-Priorité du processus
-Signaux en attente et signaux masqués
-Pointeur sur le processus suivant dans la liste des processus prêts
-Numéro du processus
-Numéro du groupe contenant le processus
-Pointeur sur le processus père
-Numéro de la session contenant le processus
-Identificateur de l’utilisateur réel
-Identificateur de l’utilisateur effectif
-Politique d’ordonnancement utilisé
-Temps processeur consommé en mode noyau et en mode utilisateur
-Date de création du processus
Création d’un processus Unix
Un processus Unix = { un espace d’adressage et une unité d’exécution
unique }
Contexte d’un processus Unix :
- pid,
- répertoire de travail,
- descripteurs,
-propriétaires,
-implantation en mémoire,
-registres,
-priorité d’ordonnancement,
-masque des signaux,
-pile d’exécution
Cela rend coûteuse la création d’un processus

Création de threads Unix


Thread Unix: - processus léger
- permet de multiplier l’implémentation d’activités caractérisées par des
contextes plus légers que ceux des processus Unix
Contexte d’exécution d’une thread Unix:
- registres,
- priorité d’ordonnancement,
-masque des signaux,
-pile d’exécution.
L’espace virtuel d’une thread :
- code exécuté par la thread,
- données définies dans la thread et
− une pile d’exécution propre à la thread.

Primitives de manipulation de threads Unix


Création et activation d’une thread :
int pthread_create(
pthread_t *idptr, /* ptr sur la zone où sera retournée l’identité de la
thread */
pthread_attr_t attr,/* attributs de la thread: pthread_attr_default ou
0 */
void *(*fonc) (void *), /* pointeur sur la fonction exécutée par la
thread */
/* pointeur sur le(s) argument(s) passé(s) à la thread */
void *arg
);
Terminaison d’une thread :
void pthread_exit (
void *status /* ptr sur le résultat retourné par la thread */
);
Libération des ressources d’une thread :
int pthread_detach(
pthread_t *idptr /* pointeur sur l’identité de la thread */
);
Attente de la terminaison d’une thread :
int pthread_join(
pthread_t id, /* identité de la thread attendue */
void *status /* pointeur sur le code retour de la thread attendue */
);

Création de
segments de
mémoire
partagés
• Pour créer un segment de mémoire, on attache à ce segment un certain
nombre d’informations.
Partage de données
• Les segments de mémoire partagés permettent à deux processus distincts de
partager physiquement des données.

Obtention d’un segment de mémoire


int shmget( key_t cle, int taille, int options);
- cle est la clé obtenu par la fonction ftok()
- taille est le nombre d'octets du segment
- options indique les droits d'accès au segment, PC_CREAT | 0666

Attachement d’un segment de mémoire


*shmat(int shmid, const void *adr, int options);
- shmid est l'identificateur du segment
-adr est l'adresse virtuelle à laquelle doit être implanté le segment dans
l'espace adressable du processus; une valeur 0 permet de laisser le choix de
cette adresse au système.
-options peut valoir SHM_RDONLY pour s'attacher un segment en lecture seule.

Détachement d’un segment de mémoire


La primitive shmdt() détache un segment de l'espace adressable d'un
processus.
int shmdt( const void *adr ); //adr est l’adresse retournée par shmat
Un segment non détaché sera détaché automatiquement lorsque tous les
processus qui se le sont attachés se terminent.

Suppression d’un segment de mémoire


int shmctl(int shmid, int option, struct shm_ds *p ) //permet d'agir sur un
segment partagé.
Si l’option est égale à :
- IPC_RMID : suppression du segment identifié par shmid (la suppression sera
effective lorsque aucun processus n'est plus attaché au segment.

You might also like