Ce TP donnera lieu à une évaluation. Vous pouvez travailler en binômes.
changer_nom
") : 1h,
superman
" et "kryptonite
") : 1h30,
Le rendu de TP se fera uniquement à travers l'interface web TPLab. Vous devrez fournir une archive .tar
(ou .tar.gz
) contenant
nom_prenom-TP3
" ou "nom1_prenom1-nom2_prenom2-TP3
",
reponses.txt
" pour les réponses aux questions 2, 3, 4, 5 et 8 ; 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/misc.c
/usr/src/servers/pm/main.c
/usr/src/servers/pm/proto.h
test
" contenant vos fichiers de tests (pour la question 8).
points importants :
Pierre_Hyvernat-TP3
" ; pour créer l'archive, la ligne de commande est :
MINIX$ tar cvf Pierre_Hyvernat-TP3.tar Pierre_Hyvernat-TP3/
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 :
LINUX$ cd /tmp/ LINUX$ mkdir TP3-OS LINUX$ cd TP3-OS LINUX$ wget http://lama.univ-savoie.fr/~hyvernat/Enseignement/1314/info524/minix-TP.img.bz2 LINUX$ bunzip2 minix-TP.img.bz2
Pour démarrer MINIX :
LINUX$ qemu-system-i386 -m 256 -net user -net nic,model=rtl8139 -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
. Pour ce TP, vous devrez utiliser le compte administrateur.
Si vous travaillez sur votre portable, il est possible que les entrées/sorties soient très lentes. Si vous avez l'impression que c'est le cas, remplacez l'option "-hda minix-TP.img
" par "-drive file=minix-TP.img,if=ide,media=disk,cache=writeback
".
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/com.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);
Ces fonctions sont définies en langage d'assemblage dans le fichier "/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);
définie dans le fichier "/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
.
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/misc.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" comme dans "m3_ca2" ou "m3_ca3" */ return OK; }
do_changename()
" dans le fichier "/usr/src/servers/pm/proto.h
" :
... /* misc.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
# cd /usr/src/tools # make install
# 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()
"...
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é minimale.
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
") ?
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
".
Vérifiez dans le code des fonctions "do_noquantum()
" et "balance_queues()
" que c'est bien ce qu'il se passe et donnez les lignes pertinentes dans votre fichier reponses.txt
.
Décrivez une situation théorique dans laquelle une famine peut apparaitre avec cet algorithme d'ordonnancement. (Pas besoin de programmer quoi que ce soit...)
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.
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.
mp
(variable définie dans la fonction main
) de type struct mproc
. Attention, cette structure n'est pas la même que la structure de processus définie dans sched/schedproc.h
.
L'utilisateur pourra alors définir :
#include <minix/ipc.h> 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; }
et utiliser "superman()
" pour pouvoir passer un programme C en mode superman.
Vous devrez probablement modifier les fichiers suivants :
/usr/src/servers/pm/misc.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.