Rustines
-
Dans la ligne 154 de Proc_0_Program.asm, au lieu de l'instruction
SETRI R10 200
il faut mettre
SETRI R10 201
autrement dit, le début de ce tableau doit être à 201.
- Dans Proc_1_Program.asm dans l'instruction de saut qui suit le
second CLINT R4 -- celui pour le V() -- , il faut mettre
-6 au lieu de -5, donc
JZROI R6 -6 ; jump back to waitLoop ...
Je vous remercie d'avoir signalé ces problèmes. Si vous le souhaitez, vous pouvez retélécharger l'archive, qui a été ainsi corrigée.
Préambule
Le projet, disponible pour téléchargement en entier ici, et avec l'énoncé disponible également ici,
comporte deux parties : une à effectuer individuellement et à rendre pour
le 18 novembre 2012, 23h59 heure de Paris, et une autre, à faire en équipe, et à
rendre pour le 23 décembre 2012, 23h59 heure de Paris.
La partie à faire en équipe est divisée en deux sous-parties -- la compréhension
détaillée du code fourni, suivie d'un travail d'extension de code effectif. Lorsque vous
commencez à travailler là-dessus, il est fortement conseillé de faire bien entendu
d'abord la partie compréhension, qui est indispensable pour mener à bien le travail
d'extension de code proprement dit.
Avant-projet
L'avant-projet est à faire individuellement, et un rapport est à
rendre par courrier électronique, en format PDF (à l'exclusion de tout autre format, sous peine
de non-considération du travail). Le texte de l'avant projet peut tenir sur environ quatre pages, par exemple avec une taille de police de caractères de 11pt (ou 12pt), et des marges raisonnables.
Projet
Pour le projet, vous allez donc travailler en équipes qui auront une personne désignée comme chef d'équipe -- elle sera censé coordonner le travail et s'assurer de la bonne marche.
Le rapport du projet doit également être rendu par courrier électronique, en format PDF (à l'exclusion de tout autre format, sous peine de non-considération du travail).
Je vous prie de bien vouloir nommer vos fichiers (que vous m'envoyez en pièce jointe par courrier électronique) seulement avec vos noms et prénoms tout en majuscule, avec le souligné (underscore)
(et sans accents, cédille, etc.).
Dans le rapport il faut mettre
. des explications pour le code initialement fourni, a savoir
l'ordonnanceur
le diagramme des etats (donc *initial*)
les descriptions des transitions dans ce diagramme
. des explications pour votre code (rajoute, modifie, etc.)
le nouveau diagramme des etats
les nouvelles descriptions des transitions dans ce diagramme
comment vous avez implemente vos idees
quels ont ete vos choix, pourquoi
bien commenter votre code
comment vous avez procede pour tester votre code
. le cahier des charges individuel (donc pour chaque membre du groupe)
le plan initial, avec les durees initialement prevues
ce que vous avez reellement reussi a faire, en combien de temps
ce que vous n'avez pas pu faire, et pourquoi
ce que vous avez rajoute par rapport au plan initial, pourquoi, combien de temps
Vous devez vous constituer des équipes de cinq ou de quatre membres, choisissant une personne comme chef d'équipe dans les deux listes qui seront affichées ci-après. Une fois constituée, chaque équipe m'envoie un seul mail avec la composition, et après approbation, je l'annonce sur cette page.
Foire aux questions
- Regardez en haut de cette page pour la "rustine" -- il faut mettre 201
et non pas 200 à la ligne 154 de Proc_0_Program.asm (il s'agit
bien de la numérotation explicite -- ";154") et
également -6 (et non pas -5) pour le JZROI R6 de Proc_1_Program.asm .
- Pour la question 2.d de l'énoncé, la ligne 168 est-elle dans le fichier dirprojet/Proc_0_Program.asm ou bien dans le fichier dirprojet/ini/Proc_0_Program.asm et le numéro 168 est-il celui de la numérotation explicite dans
dirprojet/Proc_0_Program.asm ou bien celui indiqué par un éditeur de texte?
Le fichier est bien dirprojet/Proc_0_Program.asm et le numéro 168 est bien
celui donné explicitement dedans (et non pas par un éditeur de texte) et donc la
ligne en question est
SETRI R20 0 ;168 the V() semop value (and V state value)
- Pour la question 3.c, est-ce ce schéma-ci :
boucle
P();
on lit / écrit en mémoire partagée UN nombre;
V();
++index sémaphore;
++adresses sémaphores;
fin boucle; // pour chaque boucle on crée des nouveaux id de sémaphore
ou plutôt celui-là
boucle
P();
on lit / écrit en mémoire partagée UN nombre;
V();
fin boucle; // on garde toujours le même index et la même adresse pour les sémaphores
?
Ni l'un ni l'autre de manière idéale. Le premier utilise "trop" de
sémaphores -- on n'en pas un nombre "grand" dans les systèmes (et on ne peut
pas garantir que le producteur passe en premier).
Le second est assez proche, mais bon, sans plus de "choses", il n'est pas complet (même
chose pour qui passe en premier) -- et le consommateur
pourrait lire plusieurs fois le même nombre avant que le producteur puisse en "produire"
un autre
(en général tout processus peut être interrompu n'importe où lors de son exécution, et il peut reprendre arbitrairement "plus tard", donc un autre processus peut exécuter "beaucoup" de ses propres instructions pendant ce temps-là : ici on peut avoir le cas
où le consommateur, après avoir lu et passé par son V(), garde
la main et revient dans sa boucle en haut, arrivant donc à poser son P() tout
de suite). On peut utiliser judicieusement deux sémaphores. Si cela ne vous semble pas suffisant, on peut également utiliser des valeurs en
mémoire partagée (en tant que drapeaux -- flags en anglais). Dans les vrais systèmes Unix on dispose de plus d'outils pour contraindre un tel ensemble producteur-consommateur à commencer dans le bon état, tandis qu'ici on n'a pas un contrôle aussi fin.
- Pouvez vous m'expliquer quels sont les liaisons entre une variable de
type Memory (comme mem[i], élément de la donnée-membre
BaseCPU::mem) et le descripteur de fichier associé ?
En regardant le code qui se trouve dans Board.h, Board.cxx, CPU.h et CPU.cxx (surtout la méthode Memory::setUp() et le constructeur
CPU::CPU()), on s'aperçoit qu'on ouvre un seul fichier, dont le descripteur
est passé à chaque appel de Memory::setUp() pour chaque "tranche" de
mémoire (donc élément de MemoryTable, qui n'est qu'un vector de Memory). Par contre, on passe également une valeur de décalage,
afin que chaque tranche soit logée dans le fichier à un endroit qui lui est propre. Donc le descripteur sert à faire effectivement les opérations avec la mémoire, comme on peut le voir en lisant le code des méthodes comme Memory::loadFrom()
ou Memory::storeAt() (toujours dans Board.cxx). Enfin, pour aller jusqu'au
bout, on peut trouver dans CPU.cxx un exemple d'utilisation de ces méthodes,
dans l'implémentation des instructions comme LDMEM et STMEM.
-
L'adresse 200 dans la mémoire du noyau est-elle correctement utilisé dans Proc_0_Program.asm pour un processus donné de pid N ?
En effet, comme dit plus haut au sujet de "rustines", il faut mettre 201 et non pas 200 à la ligne 154 Proc_0_Program.asm, autrement il y a un
décalage entre ce qui est initialisé et ce qui est utilisé.
-
Pourrions-nous avoir plus de précisions pour la question 3.d ?
- pour avoir des nombre alétoires style memory-map -- on
suggère de faire comme dans l'interruption 5 (avec d'autres valeurs que
300 et 301 pour les adresses). Regardons les lignes 124 et 125
(fin de l'interruption 5), et dans CPU.cxx dans le
case STMEM:
et enfin dans CPU.h où l'on voit que la valeur de CONSOLEINPUTOUTPUTTRIGGER est bien 300. Donc on pourrait rajouter une autre interruption
sur le même principe, avec sa classe C++ similaire à ConsoleInOut.
- "augmenter le scheduleur pour le randomiser ensuite" : ceci veut dire en effet étendre la manière dont le scheduleur élit
des processus, afin de le faire utiliser des nombres
aléatoires dans son choix pour l'élection du processus suivant
à faire exécuter
- "rajouter une gestion CPU multi-core" : faire en sorte que plusieurs
Proc_I_Program.asm puissent s'exécuter en même temps. Le
système tel qu'il vous est fourni n'exécute qu'un seul
.asm à la fois, et il commute de l'un à l'autre. Sur un
multi-core on peut en exécuter plusiuers (tout en commutant de l'un
à l'autre, selon un scheduleur étendu de manière appropriée).
- Est-ce bien vrai que pour les instructions de saut comme JMTOI,
JZROI, etc. il faut bien compter la ligne de l'instruction
elle-même lors du calcul de la valeur du saut?
Oui, tout à fait, et Proc_1_Program.asm doit être
corrigé à cet effet, comme dit plus au sujet des "rustines".
-
Pour la question 2.c, la structure de mémoire à détailler
est-elle pour tous les processus en général ou également
pour chaque processus (ses spécificités, stockées en
mémoire) ?
On ne demande que la structure en général, sans exemple particulier d'exécution. Bien entendu, le fait d'analyser le fil d'exécution des programmes fournis peut également vous être utile -- mais
ceci n'est pas demandé dans le rapport.
- Pour la question 2.d, la question est-elle "à quoi sert la ligne 168
tout simplement?" ou bien "à quoi sert la ligne 168 par rapport au
reste du code?" ?
La question porte bien sur son rôle ensemble avec le reste du code.
-
Toujours pour la question 2.d, je voudrais aussi savoir dans quel cas on
exécute cette ligne 168, car elle se trouve à un endroit qui
ne semble pas accessible, puisqu'elle se trouve après un "shutdown"
et juste avant le début de semoptest.
En général on peut arriver sur une ligne pour l'exécuter
aussi en suivant un saut, donc cette analyse, qui est très bien partie,
devrait simplement être achevée proprement. Il n'y a rien de
contradictoire jusqu'ici.
-
Pour la question 3a, a-t-on le droit de modifier les paramètres donnés
à int5 ou bien devons-nous les recopier pour pouvoir les
modifier?
Les interruptions prennent en général comme paramètres
deux types d'information : des valeurs de registres, et des valeurs en
mémoire (dont on donne l'adresse dans des registres).
Grâce au mécanisme de commutation de
contexte, les valeurs des registres vues au moment du CLINT par le
processus P qui fait appel à une interruption I seront
bien restaurés lors du retour de I (code noyau) vers P
(code utilisateur).
Par contre, les valeurs qui se trouvent dans la mémoire vive du
processus sont bien accessibles en lecture-écriture au noyau (qui
exécute donc le code de I), et il n'y en a pas de
sauvegarde/restauration "automatique". C'est donc au code de I de
bien "prendre soin" de ces valeurs-là. Si le processus "s'attend"
à ce que le contenu de certaines de ses zones mémoire soit
modifié (et le "sait pourquoi"), tout va bien. Sinon, il ne faut pas "y
toucher".
Donc ceci est une affaire de convention, ou de protocole. Dans le cas d'une
interruption censée obtenir des valeurs d'un processus (donc les lire)
et faire quelque chose avec, mais sans les modifier, il faut donc bien
respecter cela de cette manière-ci.
-
Est-il grave si on finit par exécuter plus de 10000
instructions ? (Dans le code C++ il y a une telle limite
dans BaseCPU::run()).
Cette limite est entièrement arbitraire ; elle est mise là
simplement pour aider surtout au déboguage (empêchant ainsi des
boucles infinies).
-
Qu'est-ce qu'un diagramme des états ? Que mettre dedans en
général ?
Un diagramme des états est une description des états (de quelque
chose -- et ici, dans notre cas, des processus de notre système virtuel
sur lequel on travaille dans ce projet), sous la forme de "bouboules" et de
flêches entre elles -- donc un dessin. Dans les bouboules on met le nom de chaque
état, et sur les flêches on écrit (succintement) dans
quelle situation (dans quelles conditions, etc.) on passe d'un état
à un autre. Un processus est en général dans un
état d'attente (de plusieurs types) ou bien d'exécution,
etc. C'est le noyau qui, en "étudiant" un processus donné,
décide de le faire changer d'état. Ici on demande un tel
diagramme qui décrit ce qui se passe donc dans notre système
virtuel tel qu'il vous est initialement fourni, et aussi un diagramme qui
décrit ce qui se passe dans le système tel qu'il a
été modifié par vous dans le cadre du travail lié
à ce projet.
-
Pour la question 3.b, pouvons nous avoir de plus amples informations sur la
notion "lire depuis le clavier" et sur la nature exacte du travail
demandé?
La notion "lire depuis le clavier" vous est bien connue : c'est par exemple
faire en C++
int a;
cin >> a;
(avec éventuellement du
cout << "Entrez un nombre entier "
avant le cin). Il s'agit donc ici de faire en sorte que la même
opération soit bien possible. Vous disposez bien entendu d'une base de
code déjà écrite, qui ne doit qu'être
complétée. Nous devons donc comprendre à quel niveau la
compléter.
L'énoncé du projet vous dit que l'int6 est
complémentaire à int5. Commençons par regarder
brièvement ce que fait donc l'int5. Une fois qu'on se met à
examiner le
code fourni, on observe tout d'abord que l'int5 en effet ne semble faire rien
d'autre qu'écrire dans la mémoire. Par contre, en regardant de
plus près, on s'aperçoit qu'elle n'écrit pas n'importe
où, mais dans un endroit "magique". L'écriture dans cet endroit
déclenche l'exécution d'un bout de code C++ appartenant à
la classe ConsoleInOut (dont le code se trouve
dans Board.cxx). Dans CPU.cxx on voit comment se
déroule la partie "magique" surtout dans l'implémentation de
l'instruction STMEM -- le commentaire dans le code parlant de
"memory-mapped I/O address and request". Ceci est l'élément
clé nécessaire à la compréhension du travail
à faire.
Donc, en regardant davantage dans Board.cxx, on s'aperçoit
qu'en fait une bonne partie du travail est déjà faite, puisque on
trouve bien le code de la méthode input()
de la classe ConsoleInOut,
qui fait ainsi la liaison entre "les doigts de
l'utilisateur sur le clavier" et le coeur du programme. Ce qui est à faire est donc,
comme le précise l'énoncé, la partie int6
analogue à int5, qui utilise donc la "magie" déjà
mise à votre disposition pour faire en sorte qu'un programme
écrit dans notre mini-language assembleur de ce projet puisse lire
depuis le clavier des valeurs (et faire quelque chose avec).