Système de Gestion des Processus Unix/Linux
I. Concepts Fondamentaux
(A) Multitâche
- Le multitâche constitue la base de tous les systèmes d'exploitation et représente également le fondement de la programmation parallèle.
(B) Définition d'un Processus
- Un processus est l'exécution d'une image mémoire.
Dans le noyau du système d'exploitation, chaque processus est représenté par une structure de données unique appelée bloc de contrôle processus (PCB) ou bloc de contrôle tâche (TCB). Dans ce document, nous désignerons cette structure simplement sous le nom de structure TACHE.
(C) Système Multitâche
- Un système multitâche (MT) se compose des éléments suivants :
1. Fichier type.h
Le fichier type.h définit les constantes système et une structure TACHE simple pour représenter les processus.
/*********** fichier type.h ************/
#define NTACHE 9
#define TAILLE_PILE 1024
// État de la TACHE
#define LIBRE 0
#define PRET 1
#define VEILLE 2
#define ZOMBIE 3
typedef struct tache{
struct tache *suivante;
int *pointeur_pile_k;
int tid;
int etat;
int priorite;
int pile_k [TAILLE_PILE];
}TACHE;
2. Fichier ts.s
Le fichier ts.s implémente le commutage de contexte de processus en code assembleur 32 bits pour GCC.
3. Fichier liste.c
Le fichier liste.c contient les fonctions de manipulation de files et de listes chaînées. La fonction enfiler() place une TACHE dans la file selon sa priorité. Dans la file de priorité, les processus ayant la même priorité sont ordonnés selon le principe premier arrivé, premier servi (FIFO). La fonction defiler() renvoie le premier élément supprimé de la file ou de la liste. La fonction afficherListe() affiche les éléments de la liste.
4. Fichier tc.c
Le fichier tc.c définit les structures de données du système MT, le code d'initialisation système et les fonctions de gestion des processus.
5. Introduction au code du système multitâche
(1) CPU virtuelle : Le système MT est compilé et lié sous Linux avec
gcc -m32 systeme.c ts.s
Puis exécuter le fichier binaire resultant.
(2) init() : Au démarrage du système MT, la fonction main() appelle init() pour initialiser le système. init() initialise les structures TACHE, les place dans la liste libre et initialise la file prête à vide. Ensuite, il crée T0[0] comme processus initial en cours d'exécution. T0[0] a la priorité la plus basse, soit 0. Toutes les autres tâches ont une priorité de 1, elles s'exécutent donc tour à tour depuis la file prête.
(D) Synchronisation des Processus
1. Mode veille : kveille(int evenement)
Lorsqu'un processus a besoin de quelque chose qui n'est actuellement pas disponible, il entre dans un état de veille en attente d'un événement spécifique. Pour implémenter cette opération de veille, nous pouvons ajouter un champ evenement à la structure TACHE et implémenter la fonction kveille(int evenement) pour que le processus entre en veille.
2. Opération de réveil : kreveil(int evenement)
Lorsqu'un événement attendu se produit, une entité d'exécution (un autre processus ou un gestionnaire d'interruption) appelle kreveil(evenement). Cette fonction réveille tous les processus en veille en attente de cet événement. Si aucun processus n'est en veille pour cet événement, kreveil() ne fait rien.
(E) Terminaison des Processus
-
Terminaison normale : Un processus appelle exit(valeur), émettant un appel système _exit(valeur) pour exécuter kexit(valeur) dans le noyau du système d'exploitation.
-
Terminaison anormale : Un processus se termine anormalement à la suite d'un signal.
Dans les deux cas, lorsque le processus se termine, kexit() est finalement appelé dans le noyau du système d'exploitation.
(F) Gestion des Processus dans un Système MT
-
Amélioration du système MT de base pour implémenter les fonctions de gestion de processus :
(1) Implémenter l'arbre des processus sous forme d'arbre binaire. (2) Implémenter les fonctions de synchronisation kveille() et kreveil(). (3) Implémenter les fonctions de gestion de processus kexit() et kattendre(). (4) Ajouter une commande "w" pour tester et démontrer les opérations d'attente.
(G) Processus dans Unix/Linux
1. Origine des processus
Lors du démarrage du système d'exploitation, le code de démarrage du noyau crée de force un processus initial avec PID=0. Le système exécute ensuite ce processus. Après l'initialisation du système, P0 clone un processus fils P1 et bascule l'exécution sur P1 en mode utilisateur.
2. INIT et démons
La plupart des processus fils de P1 sont utilisés pour fournir des services système. Ils s'exécutent en arrière-plan et n'interagissent avec aucun utilisateur. Ils sont appelés démons.
3. Processus de connexion
En plus des démons, P1 clone de nombreux processus de connexion, un par terminal, pour permettre aux utilisateurs de se connecter. Chaque processus de connexion ouvre trois flux de fichiers associés à son terminal (stdin, stdout, stderr).
4. Processus sh
Lorsqu'un utilisateur se connecte avec succès, le processus LOGIN obtient le gid et l'uid de l'utilisateur, devennat ainsi le processus de l'utilisateur. Il change le répertoire vers le répertoire personnel de l'utilisateur et exécute le programme listé, qui est généralement l'interpréteur de commandes sh. Maintenant, le processus utilisateur exécute sh, il est donc appelé processus sh. Il invite l'utilisateur à exécuter des commandes. Certaines commandes spéciales comme cd (changer de répertoire), exit, logout, etc., sont exécutées directement par sh. La plupart des autres commandes sont des fichiers exécutables dans divers répertoires bin (comme /bin, /sbin, /usr/bin, /usr/local/bin, etc.). Pour chaque commande (fichier exécutable), sh clone un processus fils et attend la terminaison du processus fils. Le processus fils modifie son image d'exécution pour devenir le fichier de commande et exécute le programme de commande. Lorsque le processus fils se termine, il réveille le processus père sh, qui collecte l'état de terminaison du processus fils, libère la structure TACHE du processus fils et invite à exécuter une autre commande. Outre les commandes simples, sh prend également en charge la redirection E/S et plusieurs commandes connectées par des pipes.
5. Modes d'exécution des processus
(1) Interruption : Une interruption est un signal envoyé par un périphérique externe au CPU, demandant un service. Lors de l'exécution en mode utilisateur (Umode), les interruptions du CPU sont activées, il répond donc à toute interruption. Lorsqu'une interruption se produit, le CPU entre en mode noyau (Kmode) pour traiter l'interruption, ce qui fait que le processus passe en Kmode.
(2) Piège : Un piège est une condition d'erreur telle qu'une adresse invalide, une instruction illégale, division par zéro, etc. Ces conditions d'erreur sont identifiées par le CPU comme des exceptions, le forçant à entrer en Kmode pour traiter l'erreur. Dans Unix/Linux, le gestionnaire de pièges du noyau convertit la cause du piège en numéro de signal et transmet le signal au processus. Pour la plupart des signaux, l'action par défaut du processus est la terminaison.
(3) Appel système : Un appel système (syscall) est un mécanisme permettant à un processus en mode utilisateur d'entrer en mode noyau pour exécuter une fonction du noyau. Lorsqu'un processus termine l'exécution de la fonction du noyau, il s'attend à recevoir un résultat et une valeur de retour en mode utilisateur, généralement 0 (succès) ou -1 (erreur). En cas d'erreur, la varible globale errno (dans errno.h) contient un code d'erreur identifiant le problème.
(H) Redirection E/S
1. Flux de fichiers et descripteurs de fichier
Chaque flux de fichier correspond à un fichier ouvert dans le noyau Linux. Ils sont représentés par un descripteur de fichier. Les descripteurs de fichier pour stdin, stdout et stderr sont respectivement 0, 1 et 2.
2. Pipes et commandes en pipe
Un pipe est un canal de communication unidirectionnel entre processus pour l'échange de données. Il a une extrémité de lecture et une extrémité d'écriture. Dans Unix/Linux, la ligne de commande cmd1|cmd2 contient un symbole pipe "|". Sh exécutera cmd1 dans un processus et cmd2 dans un autre.
(I) Pipes
Un pipe est un canal de communication unidirectionnel entre processus pour l'échange de données. Un pipe a une entrée et une sortie. Lorsque nous utilisonsons man -k | grep xx, nous faisons appel à la fonctionnalité des pipes. L'utilisation des pipes peut être réalisée par programmation ou directement sur la ligne de commande.