Ce TP donnera lieu à une évaluation. Vous pouvez travailler en binômes.
Le TP est à rendre (par email) pour le 15 décembre à 23h59.
changer_nom
") : 1h,
superman
" et "kryptonite
") : 1h30,
Votre TP devra être envoyé par email à votre enseignant (Pierre.Hyvernat@univ-savoie.fr ou Florian.Hatat@univ-savoie.fr). Le sujet de votre mail doit contenir la chaîne "info502
".
Vous devez envoyer une unique archive tar
contenant :
nom_prenom
" ou "nom_prenom&nom_prenom
",
reponses.txt
" pour les réponses aux questions 2, 3, 6, 7 et 9 ; ainsi que vos remarques diverses,
sched
" et un sous-répertoire "pm
",
/usr/src/servers/sched/schedproc.h
/usr/src/servers/sched/schedule.c
/usr/src/servers/sched/main.c
/usr/src/servers/sched/proto.h
/usr/src/servers/pm/utility.c
/usr/src/servers/pm/main.c
/usr/src/servers/pm/proto.h
test
" contenant vos fichiers de tests (pour la question 9).
points importants :
Pierre_Hyvernat-TP2
" ; pour créer l'archive, la ligne de commande est :
$ tar cvf Pierre_Hyvernat.tar Pierre_Hyvernat/
Si ces consignes ne sont pas respectées, l'enseignant se réserve le droit de vous enlever 5 points (ou plus) sur la note finale.
L'archive fournie pour ce TP contient une image QEMU avec un MINIX déjà installé et configuré.
Pour l'utiliser, il suffit de télécharger l'image dans le répertoire /tmp
et la décompresser :
$ cd /tmp/ $ mkdir TP2-OS $ cd TP2-OS $ wget http://lama.univ-savoie.fr/~hyvernat/Enseignement/1011/info502/minix-TP.img.bz2 $ bunzip2 minix-TP.img.bz2
Pour démarrer MINIX :
$ qemu -localtime -net user -net nic,model=rtl8139 -redir tcp:2232::22 -m 256 -hda minix-TP.img
Le mot de passe pour l'utilisateur root
est root
, et le mot de passe pour l'utilisateur etu
est etu
.
Si vous êtes sur votre portable avec une version suffisamment récente de QEMU, vous pouvez remplacer la dernière ligne par
qemu -localtime -net user,hostfwd=tcp::2232-:22 -net nic,model=rtl8139 -m 256 \ -drive file=minix-TP.img,if=ide,media=disk,cache=writeback
pour accélérer les entrées / sortie disques. (Ce n'est pas très important pour ce TP...)
L'image contient un script pour recompiler le noyau et l'installer sans supprimer le noyau initial :
# nouveau_noyau 3 blabla
permet de recompiler l'image du noyaux et de l'installer en ajoutant une entrée 3
dans le menu de boot.
Comme MINIX est un micro-noyau, la plupart des appels systèmes sont gérés par un serveur (par exemple, "mfs
" pour le système de fichiers de MINIX, ou "sched
" pour l'ordonnanceur). Les sources de chaque serveurs sont dans le répertoire /usr/src/servers/
.
Sous MINIX, un appel système doit envoyer un message au serveur correspondant. Chaque message est une structure constituée de :
m_source
" contenant le numéro interne du processus qui doit recevoir le message,
m_type
" contenant le numéro de l'appel système que l'on effectue,
m_u
".
Les numéros d'appels système sont des constantes définies dans le fichier "/usr/include/minix/call.h
".
Suivant les arguments de l'appel système, le champ "m_u
" a un type différent. Par exemple, "m_u
" peut être de type
mess_1
" et contenir trois entiers et trois pointeurs,
mess_2
" et contenir trois entiers, deux entiers longs, un pointeur et 1 petit entier,
mess_3
" et contenir deux entiers, une chaîne de 16 caractères et un pointeur,
Le type des messages ainsi que des synonymes pour accéder aux différents champs sont définis dans le fichier "/usr/include/minix/ipc.h
".
Lorsque le message est prêt, on l'envoie avec la commande "send()
" ou "sendrec()
" (qui permet en plus de recevoir un message en réponse) de prototypes
int send (endpoint_t dest, message *m_ptr); int sendrec (endpoint_t src_dest, message *m_ptr);
/usr/src/lib/libc/arch/i386/rts/_ipc.S
".
On peut également utiliser la commande "_syscall()
" de prototype
int _syscall (endpoint_t _who, int _syscallnr, message *_msgptr);
/usr/src/lib/libc/other/syscall.c
". Modulo un traitement des erreurs, on a :
int _syscall(endpoint_t who, int syscallnr, message *msgptr) { int status; msgptr->m_type = syscallnr; _sendrec(who, msgptr); return(msgptr->m_type); }
Suivez les étapes ci-dessous pour ajouter l'appel système "changer_nom()
" qui permettra à un processus de changer son nom tel qu'affiché par la commande "top
".
Vérifiez que vous comprenez à quoi sert chacune des étapes car vous aurez besoin de refaire le même travail dans la suite du TP. N'hésitez pas à demander de l'aide à votre encadrant de TP.
/usr/include/minix/com.h
" et "/usr/src/include/minix/com.h
"./usr/include/minix/com.h
" contient les numéros des messages utilisé par MINIX et les serveurs. L'appel système que l'on va ajouter fait intervenir le serveur "pm
" (Process Manager) et s'appelle PM_CHANGE_NAME
.
Allez regarder comment le numéro est défini et affichez le (en décimal) grâce à un mini programme en C.
Ne modifiez pas le fichier "/usr/src/include/minix/com.h
" car il vous faudrait alors recompiler tout le noyau, ce qui prend du temps...
changer_nom()
" est le process manager dont les sources se trouvent dans "/usr/src/servers/pm/
". Le message correspondant à cet appel système va contenir le nouveau nom du processus dans son champ "m3_ca1
" (chaîne de caractères de taille inférieure à 16).
do_changename()
" dans le fichier "/usr/src/servers/pm/utility.c
". Cette fonction prend en argument un pointeur vers un message (qui contiendra le nouveau nom du processus) et un pointeur vers une structure "mproc
" (définie dans "/usr/src/servers/pm/mproc.h
") :
/*===========================================================================* * do_change_name * *===========================================================================*/ PUBLIC int do_changename(message *m_ptr, struct mproc *mp) { printf("Fonction \"do_changename\" pour le processus %d\n", mp->mp_pid); strncpy(mp->mp_name, m_ptr->m3_ca1, 16); /* Attention, dans "m3_ca1", le dernier caractère est le chiffre "un" et pas la lettre "l" minuscule (comme dans "lune")... */ return OK; }
do_changename()
" dans le fichier "/usr/src/servers/pm/proto.h
" :
... /* utility.c */ ... _PROTOTYPE( int do_changename, (message *m_ptr, struct mproc *mp) );
main
". Cette fonction contient une instruction "switch(call_nr)
" qui permet de gérer les messages entrants. À l'intérieur de cette fonction, la variable "m_in
" contient le message reçu et la variable "mp
" est un pointeur vers le processus qui a envoyé le message.
Ajoutez un cas pour gérer le message "PM_CHANGE_NAME
" :
... switch(call_nr) { ... case PM_SETGROUPS_REPLY: ... break; case PM_CHANGE_NAME: printf("serveur PM : changement de nom...\n"); result = do_changename(&m_in, mp); break; ...
# cd /usr/src/servers/pm/ # make clean all install
# nouveau_noyau 3 do_changename
3
)
# halt ... c0d0p0s0> off
#include <lib.h> #include <string.h> int changer_nom(const char *s) { message m; int r; strncpy(m.m3_ca1, s, 16); r = _syscall(PM_PROC_NR,PM_CHANGE_NAME,&m); return(r); } int main (void) { changer_nom("Bob l'Eponge"); while (1); return 0; }
top
" vous pourrez voir un processus avec le nom "Bob l'Eponge
"...
changer_nom
" dans les bibliothèques du système pour que l'utilisateur n'ait pas à utiliser la fonction bas-niveau "_syscall()
"...
Suivez les étapes ci-dessus.
Le nom du processus affiché par la commande "ps
" est l'ancien nom. Proposez une explication, si possible avec une justification.
Vous inclurez un fichier "reponses.txt
" à votre archive qui contiendra :
PM_CHANGE_NAME
",
Le but de ce TP est maintenant d'ajouter deux appels systèmes gérés par l'ordonnanceur :
superman()
" qui permet à un processus de devenir prioritaire sur tous les autres,
kryptonite()
" qui permet à un processus superman de redevenir normal.
Avant de commencer ceci, il faut d'abord comprendre quel type d'ordonnanceur MINIX utilise...
Les sources de l'ordonnanceur de MINIX se trouvent dans le répertoire "/usr/src/servers/sched/
". L'ordonnanceur de MINIX est un simple tourniquet avec quantum de temps et priorités. Chaque file ("queue" en anglais) contient des processus de même priorité :
0
pour les processus de priorité maximale,
NR_SCHED_QUEUES - 1
pour les processus de priorité minimal.
L'ordonnanceur choisit toujours le premier processus dans la file de priorité la plus forte (càd la file de numéro le plus petit possible).
La constante NR_SCHED_QUEUES
ainsi que d'autres constantes similaires sont définies dans le fichier "/usr/include/minix/config.h
".
NR_SCHED_QUEUES
".
TASK_Q
") ?
USER_Q
") ?
Faites tourner (à la main) l'algorithme du tourniquet avec priorité et quantum de temps. Montrez, avec un exemple simple, qu'il est très facile de provoquer une famine.
Pour éviter les famines qui apparaissent avec l'algorithme naïf, chaque processus qui termine son quantum de temps (qui fait donc une utilisation intensive du processeur) va devenir un peu moins prioritaire (sa priorité va diminuer et son numéro de file augmenter de 1). C'est ce que fait la fonction "do_noquantum()
" dans le fichier "/usr/src/servers/sched/schedule.c
".
Par contre, lorsqu'un processus se retrouve bloqué (entrées / sorties) avant que son quantum de temps ne soit écoulé, sa priorité ne change pas : il est simplement remis en fin de file.
Pour éviter que tous les processus se retrouvent avec un priorité minimale (càd dans la file de numéro le plus grand) au bout d'un moment, les processus peuvent regagner un niveau de priorité de temps en temps, à condition que cela ne leur donne pas une priorité supérieure à leur priorité initiale. C'est la fonction "balance_queues()
" du fichier "/usr/src/servers/sched/schedule.c
".
Regarder ce qu'il se passe sur vos exemples de famine si vous utilisez cette amélioration.
Vérifiez dans les fonctions "do_noquantum()
" et "balance_queues()
" que c'est bien ce qu'il se passe.
Nous allons maintenant ajouter les appels systèmes "superman()
et "kryptonite()
" de prototypes :
int superman(void) ; int kryptonite(void) ;
Ces appels systèmes auront l'effet suivant :
superman
" obtiendra la priorité maximale (0) et lorsqu'il sera préempté en fin de quantum, sa priorité ne baissera pas. Un second appel à "superman()
" ne changera rien...
kryptonite()
" permettra de supprimer l'effet de "superman()
".
Pour accéder aux processus, le noyau n'utilise pas le PID (qui est défini par le process-manager), mais un numéro appelé endpoint qui permet d'obtenir plus facilement au processus dans la table des processus. Vos appels système du process-manager vers l'ordonnanceur devront donc utiliser ce endpoint plutôt que le PID du processus appelant.
Implémentez les deux appels systèmes "superman()
" et "kryptonite()
" en suivant les consignes ci-dessous.
Consignes
SCHEDULING_SUPERMAN
"
SCHEDULING_KRYPTONITE
"
CHANGE_NAME
", ces messages utiliseront un champ ("m1_i1
" par exemple) pour stocker le numéro du processus appelant ; il passeront ce processus à l'état correspondant.
do_superman(message *m_ptr)
" et "do_kryptonite(message *m_ptr)
" correspondantes seront définies dans "/usr/src/servers/sched/schedule.c
". Le message pointé par "m_ptr
" contiendra le endpoint du processus que l'on veut modifier.
schedproc
" définie dans "/usr/src/servers/sched/schedproc.h
".
do_nice()
" définie dans "/usr/src/servers/sched/schedule.c
". En particulier :
struct schedproc *
" à partir d'un "endpoint
", on utilise :
int proc_nb; struct schedproc *rmp; int endpoint = m_ptr->m1_i1; /* on récupère le "endpoint" */ sched_isokendpt(endpoint, &proc_nb); /* on le convertit en indice dans la table des processus */ rmp = &schedproc[proc_nb]; /* on récupère un pointeur vers le "schedproc" correspondant */ ...
rmp
", on utilise :
... schedule_process(rmp); ...
do_nice
" fait tout cela, avec de la gestion d'erreurs en plus. Vos fonctions "do_superman
" et "do_kryptonite
" pourront être plus simple et ne pas gérer les cas problématiques...)
do_noquantum
" pour qu'elle ne baisse pas la priorité des supermen.
fork
, son fils est également un superman. Ceci sera pris en compte par la fonction "do_start_scheduling()
" du fichier "schedule.c
".
Vous devrez probablement modifier les fichiers suivants :
/usr/src/servers/sched/schedproc.h
/usr/src/servers/sched/schedule.c
/usr/src/servers/sched/main.c
/usr/src/servers/sched/proto.h
Si vous voulez modifier un autre fichier, demandez confirmation auprès de votre encadrant de TP.
On ne peut malheureusement pas envoyer de message directement à l'ordonnanceur : il faut passer par le process manager...
Ajouter un nouveau message "PM_SUPERMAN
" au process manager, en suivant les consignes ci dessous.
Le message "PM_SUPERMAN
" sera traité (comme "PM_CHANGENAME
") par le process manager. Le message contiendra un unique champ "m1_i1
" :
SCHEDULING_SUPERMAN
" à l'ordonnanceur,
SCHEDULING_KRYPTONITE
" à l'ordonnanceur.
L'utilisateur pourra alors définir :
int kryptonite(void) { message m; int r; m.m1_i1 = -1; r = _syscall(PM_PROC_NR,PM_SUPERMAN,&m); return r; } int superman(void) { message m; int r; m.m1_i1 = 1; r = _syscall(PM_PROC_NR,PM_SUPERMAN,&m); return r; }
superman()
" pour pouvoir passer un programme C en mode superman.
+
Vous devrez probablement modifier les fichiers suivants :
/usr/src/servers/pm/utility.c
/usr/src/servers/pm/main.c
/usr/src/servers/pm/proto.h
Si vous voulez modifier un autre fichier, demandez confirmation auprès de votre encadrant de TP.
Écrivez des petits programmes de test pour regarder ce qu'il se passe dans les conditions suivantes :
Commentez.