Contrôle Module Noyau

Transcription

Contrôle Module Noyau
Module Noyau
M1 – MASTER SAR
Novembre 2012
2 heures – Tout document papier autorisé
Barème donné à titre indicatif
L. Arantes, E. Encrenaz, S. Monnet, P. Sens, J. Sopena, G. Thomas
I. SAUT - (7 POINTS)
Nous souhaitons appeler une fonction int dangerous_function()qui peut échouer et retourner un
code d’erreur (1 ou 2 selon la cause de l’erreur) ou réussir et retourner 0.
Nous considérons que nous avons 2 fonctions de réparation qui peuvent potentiellement
remédier à la cause de l’erreur : void repair1() (pour l’erreur qui fait retourner 1) et void
repair2() (pour celle qui fait retourner 2). Par exemple, une des erreurs peut être un manque de
place dans un tableau, la réparation consiste alors à essayer de réallouer un tableau plus grand.
Le but de cet exercice est d’écrire une fonction main() dans laquelle on appelle int
dangerous_function(), si une erreur est déclenchée on revient en arrière et on appelle la fonction
de réparation adéquate, avant de retenter un appel à int dangerous_function()et ce, jusqu’à ce
que la fonction retourne 0 (les fonctions de réparation appellent exit() en cas d’échec).
On souhaite utiliser setjmp/longjmp vues en TD pour réaliser cela : en cas d’échec (valeur de
retour différente de 0) votre code doit effectuer un saut afin d’exécuter le code de la fonction de
réparation correspondant au code d’erreur avant de ré-appeler int dangerous_function().
I.1.
(2,5 points)
La fonction main() contient les lignes :
int main(int argc, char** argv) {
int res=0 ;
repair1() ;
repair2() ;
res=dangerous_function()
}
Sans changer l’ordre d’appel aux fonctions, insérez votre code, sans créer de nouvelle(s)
fonction(s), afin d’obtenir le comportement désiré.
Dites si oui ou non il vous semble nécessaire de sauvegarder/restaurer la pile, dans les deux cas,
justifiez votre réponse.
Cf. setjmp-longjmp.c :
#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
jmp_buf jmp_buf_tab[2];
void repair1() {
int i = 1;
1/6
printf("Dans repair 1 - i = %d\n",i);
}
void repair2() {
int i = 2;
printf("Dans repair 2 - i = %d\n",i);
}
int dangerous_function() {
int i, j, k, l;
i = random()%3;
printf("in dangerous_function(), going to return %d\n",i);
return i;
}
int main(int argc, char** argv) {
int res=0;
srandom(time(0));
if(setjmp(jmp_buf_tab[0])) {
repair1();
}
if(setjmp(jmp_buf_tab[1])) {
repair2();
}
res=dangerous_function();
if(res!=0) {
printf("dangerous_function() returned %d, jumping to repair%d\n", res, res);
longjmp(jmp_buf_tab[res-1],1);
}
return 42;
}
Pas besoin de sauvegarder/restaurer la pile : on saute toujours de main (ou plus haut, c’est pas
bien important) dans main.
- > on fini toujours par un « going to return 0 »
I.2. (4,5 point)
Pour masquer un peu le mécanisme de gestion des fautes, nous souhaitons placer tout le code
situé avant res=dangerous_function()dans une fonction set_repair(). Nous souhaitons bien
entendu conserver le comportement décrit ci-dessus.
Dites si oui ou non il vous semble nécessaire de sauvegarder/restaurer la pile, dans les deux cas,
justifiez votre réponse.
Donnez le code de la fonction set_repair() et les éventuelles modifications au reste du code.
Cf. setjmp-longjmp-2.c + recopie de pile :
#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
2/6
#include <time.h>
jmp_buf jmp_buf_tab[2];
void repair1() {
int i = 1;
printf("Dans repair 1 - i = %d\n",i);
}
void repair2() {
int i = 2;
printf("Dans repair 2 - i = %d\n",i);
}
int dangerous_function() {
int i, j, k, l;
i = random()%3;
printf("in dangerous_function(), going to return %d\n",i);
return i;
}
void set_repair(){
if(setjmp(jmp_buf_tab[0])) {
repair1();
}
if(setjmp(jmp_buf_tab[1])) {
repair2();
}
}
int main(int argc, char** argv) {
int res=0;
srandom(time(0));
set_repair();
res=dangerous_function();
if(res!=0) {
printf("dangerous_function() returned %d, jumping to repair%d\n", res, res);
longjmp(jmp_buf_tab[res-1],1);
}
return 42;
}
en ne sauvegardant pas la pile, en cas d’erreur, le return de set_repair nous fait finir sans relancer
la dangerous function… : exemple de sortie :
galadriel:noyau2012-partiel smonnet$ ./setjmp-longjmp-2
in dangerous_function(), going to return 1
dangerous_function() returned 1, jumping to repair1
Dans repair 1 - i = 1
Il faut donc suvegarder la pile.
3/6
II. SYNCHRONISATION (9 POINTS)
Le but de cet exercice est de fournir un nouveau mécanisme dans le noyau. Ce mécanisme doit
permettre de déléguer l’exécution de certaines fonctions du noyau à un processus unique. Ce
processus devient donc un serveur pour les autres processus.
Le processus serveur exécute des requêtes définies par la structure suivante :
typedef struct {
void (*func)(void* data) ; /* fonction à exécuter */
void* data ; /* donnée à passer comme argument à la fonction */
} request_t;
On suppose qu’on ajoute un champ de type request_t appelé request à la structure proc.
Lorsqu’un processus p souhaite faire exécuter une requête au serveur, il l’enregistre dans sa
structure proc. Il réveille ensuite le serveur et s’endort en attendant que le serveur ait exécuté sa
fonction.
De son côté, le serveur boucle indéfiniment dans le noyau. A chaque pas de boucle, il doit
parcourir la table des processus, exécuter les requêtes en attente en réveillant les processus client,
puis s’endormir en attendant les requêtes suivantes. De façon à endormir et réveiller le serveur,
vous pouvez supposer que vous disposez d’une variable int synchroServer.
II.1. (2,5 points)
Ecrivez le code de la fonction void executeByServer(void (*f)(void*), void* data).
void executeByServer(void (*f)(void*), void* data) {
u.u_procp->func = f ;
u.u_procp->data = data ;
wakeup(&synchroServer) ;
sleep(&u.u_procp.request, PRIO_CLIENT) ;
}
II.2. (4 points)
Ecrivez le code du processus serveur.
void server() {
while(1) {
for(int i=0 ; i<NPROC ; i++) {
if(proc[i].request.func) {
proc[i].request.func(proc[i].request.data) ;
wakeup(&proc[i].request) ;
}
}
sleep(&synchroServer, PRIO_SERVER) ;
}
}
4/6
II.3. (1,5 point)
Utilisez la fonction executeByServer pour envoyer un signal au processus d’identifiant pid.
Réponse complète :
struct the_struct {
int sig ;
int pid ;
};
void doit(struct the_struct* s) {
for(i=0 ; i<NPROC ; i++) {
if(proc[i].p_pid == s->pid) {
psignal(p, s->sig) ;
}
}
}
struct the_struc s = { pid, sig } ;
executeByServer(doit, s) ;
La réponse étant peut-être un peu longue, n’hésitez pas à noter gentillement. A partir du moment où il y a
la boucle pour trouver pid et un appel relativement correct à executeByServer (même si le paramètre est
psignal), c’est déjà pas mal du tout. Si un étudiant utilise kill directement, c’est aussi pas mal.
II.4. (1 point, bonus)
Que se passe-t-il si un processus envoie un signal au serveur ?
Rien, le serveur boucle indéfiniment dans le noyau et n’est pas en mesure de recevoir des signaux.
III. COMMUTATION (2 POINTS)
III.1. (2 points)
Rappeler le rôle de la variable runrun dans le noyau étudié en TD. Pourquoi, ne pas simplement
remplacer les « runrun++ » par des appels à switch()?
Demande une commutation lors du retour en mode u (appel à swtch si runrun !=0).
On aurait alors des problèmes de cohérences : notre noyau deviendrait préemptif.
IV. QUESTION DE COURS (3 POINTS)
IV.1. (2 points)
Soit un noyau préemptif. On considère 3 processus T1, T2, T3. T1 est plus prioritaire que T2 et T2
est plus prioritaire que T3.
Soit le scénario suivant :
A t=0, seul T3 est à l’état prêt, il accède directement à la ressource R du noyau. T3 a besoin de R
pendant 2 ms.
5/6
A t=1 ms, T2 devient prête, il demande un accès à la ressource R’ du noyau. T2 a besoin de R’
pendant 3 ms
A t=2 ms, T1 devient prêt, il demande un accès à R puis à R’
(a)
Sachant que le noyau implémente un héritage de priorité. A quels instants T3 et T2
terminent l’accès à leur ressource.
(b)
Reprendre la question (a) sans héritage de priorité.
(a) T3 à t = 3ms, T2 à t = 5ms
(b) T3 à t = 5ms, T2 à t = 4ms
6/6

Documents pareils