Exercice Corrigé Gestion Des Processus Linux – Partie 4

Les exercices pratiques sur la gestion des processus linux comportent des exercices théoriques et pratiques sur les concepts fondamentaux de la gestion des processus, y compris la création, la planification, et la terminaison des processus. Vous aurez l’occasion d’explorer des commandes essentiels tels que ps et top pour surveiller les processus en temps réel, ainsi que d’utiliser des commandes comme kill pour gérer les processus.

De plus, des exercices aborderont la gestion de la mémoire, l’utilisation des identifiants de processus (PID), ainsi que l’interaction entre les processus via des signaux. Les travaux pratiques incluront des scénarios de simulation où vous pouvez créerer des scripts pour automatiser la gestion des processus et résoudre des problèmes courants liés aux processus orphelins et aux zombies.

L’objectif est de fournir une compréhension approfondie de la manière dont le système d’exploitation Linux gère les processus, ainsi que des compétences pratiques pour gérer efficacement les processus dans un environnement réel.

 
 

Exercice 1: Question générale sur la gestion des processus Linux

1.1) Considérez les événements suivants qui se produisent lors d’un changement de contexte du processus P (mode utilisateur) au processus Q (mode utilisateur), déclenché par une interruption de temporisation qui s’est produite lorsque P était en cours d’exécution, dans un système d’exploitation de type Unix. Classez les événements par ordre chronologique, du plus ancien au plus récent.

A Le compteur de programme de l’unité centrale passe de l’espace d’adressage du noyau de P à l’espace d’adressage du noyau de Q.

B L’unité centrale exécutant le processus P passe du mode utilisateur au mode noyau.

C Le pointeur de pile de l’unité centrale passe de la pile du noyau de P à la pile du noyau de Q.

D Le compteur de programme de l’unité centrale passe de l’espace d’adressage du noyau de Q à l’espace d’adressage de l’utilisateur de Q.

D Le code du planificateur du système d’exploitation est invoqué.

1.2) Considérons un système avec deux processus P et Q, exécutant un système d’exploitation de type Unix. Considérons les événements suivants qui peuvent se produire lorsque le système d’exploitation exécute simultanément P et Q, tout en gérant les interruptions.

(A) Le compteur de programme de l’unité centrale passe du code du noyau en mode noyau du processus P au code du noyau en mode noyau du processus Q.
(B) Le pointeur de pile de l’unité centrale passe de la pile du noyau de P à la pile du noyau de Q.
(C) L’unité centrale exécutant le processus P passe du mode utilisateur de P au mode noyau de P.
(D) L’unité centrale exécutant le processus P passe du mode noyau de P au mode utilisateur de P.
(E) L’unité centrale exécutant le processus Q passe du mode noyau de Q au mode utilisateur de Q.
(F) Le code de gestion des interruptions du système d’exploitation est invoqué.
(G) Le code du planificateur du système d’exploitation est invoqué.

Pour chacun des deux scénarios ci-dessous, indiquez l’ordre chronologique dans lequel les événements ci-dessus se produisent. Notez que tous les événements ne doivent pas nécessairement se produire dans chaque question.

(a) Une interruption de temporisation se produit pendant l’exécution de P. Après avoir traité l’interruption, le code de l’ordonnanceur du système d’exploitation est appelé. Après avoir traité l’interruption, le planificateur du système d’exploitation décide de revenir au processus P.
(b) Une interruption de temporisation se produit lorsque P est en cours d’exécution. Après avoir traité l’interruption, le planificateur du système d’exploitation décide de passer au processus Q, et le système se retrouve dans le mode utilisateur de Q.

1.3) Considérons les trois processus suivants, qui arrivent dans un système aux moments spécifiés, ainsi que la durée de leurs rafales de CPU. Le processus P1 arrive à l’instant t=0 et dispose d’une charge d’unité centrale de 10 unités de temps. Le processus P2 arrive à l’instant t=2 et dispose d’une durée d’utilisation de l’unité centrale de 2 unités. Le processus P3 arrive à l’instant t=3 et dispose d’un temps d’utilisation du processeur de 3 unités. Supposons que les processus ne s’exécutent qu’une seule fois pendant la durée de leur rafale de CPU et qu’ils se terminent immédiatement. Calculez le temps d’achèvement des trois processus pour chacune des politiques d’ordonnancement suivantes. Pour chaque politique, vous devez indiquer le temps d’achèvement des trois processus P1, P2 et P3. Supposez qu’il n’y a pas d’autres processus dans la file d’attente de l’ordonnanceur. Pour les politiques préemptives, supposez qu’un processus en cours peut être immédiatement préempté dès l’arrivée du nouveau processus (si la politique décide de préempter).

(a) First Come First Serve (« Premier arrivé, premier servi »)
(b) Shortest Job First (SJF) (« Plus Court Job d’abord ») (non préemptif)
(c) Shortest Remaining Time First (« plus court temps restant en premier ») (préemptif)
(d) Round robin (préemptif) avec une tranche de temps de (au moins) 5 unités par processus.

1.4) Considérons un système avec un seul cœur de CPU et trois processus A, B, C. Le processus A arrive à t = 0, et s’exécute sur le CPU pendant 10 unités de temps avant de se terminer. Le processus B arrive à t = 6 et nécessite un temps initial de 3 unités sur le CPU, après quoi il se bloque pour effectuer des E/S pendant 3 unités de temps. Après avoir quitté l’attente d’E/S, il s’exécute pendant 5 unités supplémentaires avant de se terminer. Le processus C arrive à t = 8 et s’exécute pendant 2 unités de temps sur l’unité centrale avant de se terminer. Pour chacune des politiques d’ordonnancement ci-dessous, calculez le temps d’achèvement de chacun des trois processus. Rappelez-vous que seule la taille du cycle d’utilisation de l’unité centrale (à l’exclusion du temps d’attente des entrées-sorties) est considérée comme la « taille du job » dans ces ordonnanceurs.

(a) First Come First Serve (« Premier arrivé, premier servi ») (non préemptif).
(b) Shortest Job First (SJF) (« Plus Court Job d’abord ») (non préemptif)
(c) Shortest Remaining Time First (« plus court temps restant en premier ») (préemptif)

1.5) Considérez les différents mécanismes et techniques d’ordonnancement de l’unité centrale utilisés dans les ordonnanceurs modernes.

(a) Décrivez une technique par laquelle un ordonnanceur peut donner une priorité plus élevée aux processus liés aux E/S avec de courtes durées d’utilisation du CPU qu’aux processus liés au CPU avec de plus longues durées d’utilisation du CPU, sans que l’utilisateur n’ait à spécifier explicitement les priorités ou les durées d’utilisation du CPU à l’ordonnanceur.
(b) Décrivez une technique permettant à l’ordonnanceur de s’assurer que les processus de haute priorité n’affament pas indéfiniment les processus de faible priorité.

1.6) Considérons le programme C suivant. Supposons qu’il n’y ait pas d’erreurs de syntaxe et que le programme s’exécute correctement. Supposons que les appels système à fork réussissent. Quelle est la sortie affichée à l’écran lorsque nous exécutons le programme ci-dessous ?

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
void main(argc, argv) {
for(int i = 0; i < 4; i++) {
int ret = fork();
if(ret == 0)
printf("child %d\n", i);
}
}
void main(argc, argv) { for(int i = 0; i < 4; i++) { int ret = fork(); if(ret == 0) printf("child %d\n", i); } }
void main(argc, argv) {
	for(int i = 0; i < 4; i++) {
		int ret = fork();
		if(ret == 0)
		printf("child %d\n", i);
	}
}

1.7) Considérons un processus parent P qui a forké un processus enfant C dans le programme ci-dessous.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
int a = 5;
int fd = open(...) //ouvrir un fichier
int ret = fork();
if(ret > 0) {
close(fd);
a = 6;
...
}
else if(ret==0) {
printf("a=%d\n", a);
read(fd, str);
}
int a = 5; int fd = open(...) //ouvrir un fichier int ret = fork(); if(ret > 0) { close(fd); a = 6; ... } else if(ret==0) { printf("a=%d\n", a); read(fd, str); }
int a = 5;
int fd = open(...) //ouvrir un fichier
int ret = fork();
if(ret > 0) {
	close(fd);
	a = 6;
	...
}
else if(ret==0) {
	printf("a=%d\n", a);
	read(fd, str);
}

Une fois que le nouveau processus est forké, supposons que le processus parent est planifié en premier, avant le processus enfant. Lorsque le processus parent reprend après le fork, il ferme le descripteur de fichier et modifie la valeur d'une variable comme indiqué ci-dessus. Supposez que le processus enfant est ordonnancé pour la première fois seulement après que le parent a effectué ces deux changements.

(a) Quelle est la valeur de la variable 'a' telle qu'elle est affichée dans le processus enfant, lorsqu'il est programmé pour la prochaine fois? Expliquez pourquoi.
(b) La tentative de lecture du descripteur de fichier réussira-t-elle dans le processus enfant? Expliquez.

1.8) Considérons le pseudocode suivant. Supposons que tous les appels système aboutissent et qu'il n'y ait pas d'autres erreurs dans le code.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
int ret1 = fork(); //fork1
int ret2 = fork(); //fork2
int ret3 = fork(); //fork3
wait();
wait();
wait();
int ret1 = fork(); //fork1 int ret2 = fork(); //fork2 int ret3 = fork(); //fork3 wait(); wait(); wait();
int ret1 = fork(); //fork1
int ret2 = fork(); //fork2
int ret3 = fork(); //fork3
wait();
wait();
wait();

Appelons P le processus parent d'origine de ce programme. Dessinez/décrivez un arbre généalogique de P et de tous ses descendants (enfants, petits-enfants, etc.) créés au cours de l'exécution de ce programme. Votre arbre doit avoir pour racine P. Faites apparaître les descendants engendrés comme des nœuds dans l'arbre, et reliez les processus liés par la relation parent-enfant par une flèche allant du parent à l'enfant. Donnez des noms contenant un numéro pour les descendants, où les processus enfants créés par la fork ci-dessus devraient avoir des numéros. Par exemple, les processus enfants créés par la fork 3 ci-dessus doivent porter les noms C31, C32, etc.

1.9) Prenons l'exemple d'un processus parent qui a forké un processus enfant dans l'extrait de code ci-dessous.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
int count = 0;
ret = fork();
if(ret == 0) {
printf("count in child=%d\n", count);
}
else {
count = 1;
}
int count = 0; ret = fork(); if(ret == 0) { printf("count in child=%d\n", count); } else { count = 1; }
int count = 0;
ret = fork();
if(ret == 0) {
	printf("count in child=%d\n", count);
}
else {
	count = 1;
}

Le parent exécute l'instruction « count = 1 » avant que l'enfant ne l'exécute pour la première fois. Quelle est la valeur de count affichée par le code ci-dessus ?

 

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *