KevinPersonnic.fr

Je ne suis pas un Kévin !

By

Projet Système d’exploitation – Partie 4 (ASM)

Le projet est fourni avec de exemple de code assembleur exécuté par la machine, parmi ces codes on trouve aussi les fichier qui sert d’init0 à la machine est qui permet d’initialiser certains composant du système.

Init5

Voici le code en entier que nous allons l’étudier en petit morceaux :

#-------- start of $int5 ----------------------------
# R2 contains the number of items to write
# R3 contains the start address in process memory where to read the items one by one
# R4 contains the start address in process memory where to read the item types one by one (0 for int, 1 for char)
SETRI R0 0         ;LNR=$int5: consoleOut request for current process  , the address where its pid is stored
LDMEM R0 R1        ;LNR R1 now has the pid of the process which is requesting the consoleOut operation
SETRI R6 20       ;LNR offset to get the process slot address from the process id
ADDRG R0 R1 R6       ;LNR R0 now contains the process slot address
SETRI R5 1       ;LNR the readyToRun state
STMEM R0 R5       ;LNR store the readyToRun state for the current process
SETRI R7 301       ;LNR The address in kernel memory where we need to write the # of items for the consoleOut
STMEM R7 R5        ;LNR just one item for now
LDPRM R1 R3 R6     ;LNR R6 now contains the first item to be obtained (read) and thus sent to consoleOut (recall R3 is given to us by the proc)
SETRI R8 304       ;LNR The address in kernel memory where we decided to write the item (copying it from the process memory)
STMEM R8 R6        ;LNR now writing the item (from R6) which we just read from the process memory a few lines above, at address 304 in kernel mem
SETRI R7 302       ;LNR The address in kernel memory where we need to write the start address (param) where to read the items for the consoleOut
STMEM R7 R8       ;LNR now effectively preparing the start address "parameter" for consoleOut (i.e. write the number '304' at address 302)
LDPRM R1 R4 R7     ;LNR R7 now contains the type of first item to be obtained (read) and thus sent to consoleOut
SETRI R8 504       ;LNR The address in kernel memory where we decided to write the item (copying it from the process memory)
STMEM R8 R7        ;LNR now writing the item type (from R7) which we just read from the process memory a few lines above, at address 504 in kernel mem
SETRI R7 303       ;LNR The address in kernel memory where we need to write the start address (param) where to read the item types for the consoleOut
STMEM R7 R8       ;LNR now effectively preparing the type vect start address "parameter" for consoleOut (i.e. write the number '404' at address 303)
SETRI R7 300       ;LNR The address in kernel memory where, by writing a value of 1, we trigger the consoleOut
STMEM R7 R5       ;LNR there we go -- we just requested a "hardware consoleOut" through "memory-mapping IO"
JMBSI $int1        ;LNR we are done, so we make an absolute jump to $int1: to keep going , @@end of interrupt #5@@

Pour l’instant le code fourni n’est pas complet, pas juste. En théorie l’interruption 5 à pour but d’afficher sur le sortie standard une série de nombre/caractère(=Item) mais pour l’instant elle n’affiche qu’un caractère ou qu’un nombre.

Pour pouvoir afficher un Item le système doit savoir de quel type il est, si c’est un caractère ou un int, on doit donc lui fournir cette information.

Pour fonctionner certains registre doivent contenir des valeurs ‘utile’. Pour appel lors d’une interruption init* qui sont dans le fichier Proc_0_Program.asm les registres généraux ne sont pas écrasées, ils contiennent donc les même valeurs que pendant l’exécution du processus qui a lancé l’interruption. L’utilisateur doit donc avant d’appeler init5 de mettre certaines valeur dans les registre R2, R3 et R4.

  • R2 : le nombre d’Item que l’ont souhaite afficher
  • R3 : L’adresse mémoire du processus à la quel on a commencé à stocker les Item. C’est à dire que pour afficher plusieurs item on doit les ranger les uns à la suite des autres dans la mémoire et on donne l’adresse de la première case mémoire occupé par les Item. Par exemple, je veut afficher 1, 2, 3. l’utilisateur va mettre dans la mémoire ces valeurs à l’aide de STMEM, il doit choisir arbitrairement où dans la mémoire il stocke les nombre, disons à l’adresse 100, les nombre seront donc stocké dans les cases 100, 101 et 102, ces 3 cases forme ce que l’on appel communément un ‘vecteur’. On met donc la valeur 100 dans R3 avant d’appeler l’interruption, R3 contient donc l’adresse du ‘vecteur’ d’Item à afficher.
  • R4 : L’adresse mémoire du processus à la quel on a commencé à stocker les type des Item. Comme on peut afficher des nombre ou des caractère on doit indiquer de quel type est chaque Item, pour cela on va créer un ‘vecteur’ de type à une adresse arbitraire. Disons que l’on choisi l’adresse 200, on va donc mettre à l’adresse 200 le type du premier item à afficher, à l’adresse 201 le type de 2em … Puis on met dans R4 la valeur 200, R4 contient donc l’adresse du ‘vecteur’ de type des Item à afficher.

Pour rappel on utilise la fonction ConsoleInOut::output qui va lire les infos dont elle à besoin à l’adresse 300 et suivante de la mémoire du noyau.

  • 300 : code d’opération, 0 pour lire, 1 pour écrire
  • 300+1 : nombre d’Item à afficher
  • 300+2 : l’adresse du vecteur d’item à afficher
  • 300+3 : l’adresse du vecteur de type des item à afficher

Interruption pas à pas :

SETRI R0 0         ;LNR=$int5: consoleOut request for current process  , the address where its pid is stored
LDMEM R0 R1        ;LNR R1 now has the pid of the process which is requesting the consoleOut operation

On récupère le PID du processus qui a appelé l’interruption et on stocke cette valeur dans R1.

SETRI R6 20       ;LNR offset to get the process slot address from the process id
ADDRG R0 R1 R6       ;LNR R0 now contains the process slot address
SETRI R5 1       ;LNR the readyToRun state
STMEM R0 R5       ;LNR store the readyToRun state for the current process

On change dans la mémoire du noyaux l’état du processus appelant, on le passe de running (2) à readyToRun (1) car il n’est plus en train de s’exécuter vu qu’il a fait appel à une interruption.

SETRI R7 301       ;LNR The address in kernel memory where we need to write the # of items for the consoleOut
STMEM R7 R5        ;LNR just one item for now

On met R7 à 301 pour écrire le nombre d’item à afficher. R5 contient la valeur 1 à ce moment là. On fait appel à STMEM pour stocker à l’adresse 301(R7) la valeur 1(R5).

LDPRM R1 R3 R6     ;LNR R6 now contains the first item to be obtained (read) and thus sent to consoleOut (recall R3 is given to us by the proc)

On va chercher dans la valeur stocké dans la mémoire du processus appelant(R1) à l’adresse R3 et on la copie dans le registre R6. On a donc copié le premier Item à afficher dans R6.

SETRI R8 304       ;LNR The address in kernel memory where we decided to write the item (copying it from the process memory)

Comme on va devoir recopier tout les item à afficher de la mémoire du processus ver la mémoire du noyau on dévide de faire ça à l’adresse 304 de la mémoire du noyau. On met donc 304 dans R8.

STMEM R8 R6        ;LNR now writing the item (from R6) which we just read from the process memory a few lines above, at address 304 in kernel mem

Je copie la valeur de R6 que l’on a récupérer juste avant à l’adresse R8 de la mémoire du noyau.

SETRI R7 302       ;LNR The address in kernel memory where we need to write the start address (param) where to read the items for the consoleOut

On met R7 à 302 pour écrire l’adresse du vecteur d’Item à afficher.

STMEM R7 R8       ;LNR now effectively preparing the start address "parameter" for consoleOut (i.e. write the number '304' at add

Je met dans la case mémoire 302(R7) l’adresse 304(R8) qui est l’adresse à la quelle j’ai mit mon vecteur d’Item dans la mémoire du noyau.

LDPRM R1 R4 R7     ;LNR R7 now contains the type of first item to be obtained (read) and thus sent to consoleOut
SETRI R8 504       ;LNR The address in kernel memory where we decided to write the item (copying it from the process memory)
STMEM R8 R7        ;LNR now writing the item type (from R7) which we just read from the process memory a few lines above, at address 504 in kernel mem
SETRI R7 303       ;LNR The address in kernel memory where we need to write the start address (param) where to read the item types for the consoleOut
STMEM R7 R8       ;LNR now effectively preparing the type vect start address "parameter" for consoleOut (i.e. write the number '404' at address 303)

Même concept que pour les Item sauf que là on s’occupe de copier le vecteur de type d’Item.

SETRI R7 300       ;LNR The address in kernel memory where, by writing a value of 1, we trigger the consoleOut

On met dans R7 la valeur 300, cette valeur va permettre de modifier le comportement de STMEM, au lieu de stocker à la case mémoire donnée la valeur donnée l’instruction va appeller la fonction ConsoleInOut:output.

STMEM R7 R5       ;LNR there we go -- we just requested a "hardware consoleOut" through "memory-mapping IO"

On appel STMEM avec le registre R7 qui contient la valeur spéciale d’activation avec et en 2em paramètre 1(R5) ce qui veut dire que l’on veut afficher (cela aurait été 0 si on voulait lire).

Si vous avez fait attention vous avez remarqué que l’on copie un seul item de la mémoire du processus à la mémoire du noyau et que l’on copie un seul type d’item de la mémoire du processus ver la mémoire du noyau. Pour afficher plusieur valeur il faudrait donc modifier ce code assembleur, c’est l’objet de la question a) du projet.

Pour répondre à la question a) il faudrait donc créer une boucle qui au lieu de lire seulement la première valeur du vecteur d’Item et du vecteur de type va boucler autant de fois qu’il y a d’item (le nombre d’item est dans R2) pour recopier tout les item et tout les types.

By

Projet Système d’exploitation 2012 Partie 3

Dans cette partie nous allons nous attaquer aux sources du projets, ce poste sera donc très long.

Class Register

Register()

Register::Register():value(0) {}

Constructeur de la classe Register, la donnée membre est initialisé à 0 à sa création.

setVal(const int val)

void Register::setVal(const int val) {
value = val;
}

Met la valeur du Register à la valeur donnée en paramètre.

getVal()

int Register::getVal() const {
return(value);
}

Renvoi la valeur contenu dans le Register.

Class StackPointer

StackPointer()

StackPointer::StackPointer() : Register() {}

Initailise la variable StackPointer à l’aide du constructeur de Resgiter.

Class RunMode

RunMode()

RunMode::RunMode() : Register() {}

Initialise la variable StackPointer à l’aide du constructeur de Resgiter.

switchToMaster()

void RunMode::switchToMaster() {
setVal(1);
}

Fait passer le contexte en mode maître.

switchToUser()

void RunMode::switchToUser() {
setVal(0);
}

Fait passer le contexte en mode utilisateur.

qIsMaster()

int RunMode::qIsMaster() const {
return(getVal() == 1);
}

Renvoi 1 si le contexte est en mode maître, 0 sinon.

Class ProgramCounter

ProgramCounter(InstructionParser & iPar)

ProgramCounter::ProgramCounter(InstructionParser &iPar) :
Register(),instrParse(iPar) {}

C’est le constructeur de la classe, il prend en paramètre une variable de type InstructionParser. Le constructeur appel le constructeur de Register car il dérive de la classe Register. Il initialise sa variable instrParse avec la variable donnée en paramètre.

La donnée value hérité de la classe Register est utilisé pour stocker la ligne de la prochaine l’instruction parser, et par conséquent la prochaine instruction qui sera exécuté.

relativeJump(int offset)

void ProgramCounter::relativeJump(int offset) {
value += offset;
}

Cette méthode permet de faire un saut dans l’exécution du programme en modifiant « value » de la valeur de l’offset donné en paramètre.

fetch(ProgramText &progText)

Instruction ProgramCounter::fetch(ProgramText &progText) {
string instrWord,regOne,regTwo,regThree;
cerr << "ProgramCounter::fetch() line " << value << "\n"; //pour les logs
//on récupère la ligne du programme qui nous intéresse actuellement :
istringstream buffStr (progText[value]);
//une ligne est constitué d'au plus 4 mots intéressant séparé d'un espace :
buffStr >> instrWord >> regOne >> regTwo >> regThree;
//on initialise qImmediate, donnée membre de Instruction
int qImmediate(0);
//on donne tout le bordel aux fonctions du parser
InstructionType iT(instrParse . getInstrTypeFromString(instrWord,&qImmediate));
Instruction instr(progText[value],
iT,
instrParse . getRegIdFromString(regOne),
instrParse . getRegIdFromString(regTwo),
instrParse . getRegIdFromString(regThree),
regOne,regTwo,qImmediate);
//qui nous sortent une instruction toute propre
value += 1; //on incrémente value car au prochain fetch on veut accéder à la ligne suivante
return(instr);
}

Cette fonction à pour but de renvoyer une Instruction à partir du ProgramText qui lui est donnée en paramètre et de la valeur actuel de la donnée membre « value ». Pour rappel un ProgramText est un vecteur de string qui contient toute les lignes d’un programme que dois exécuter la machine virtuel. Cette opération est faite dans cette méthode car sa classe possède la donnée value qui représente le pcReg de notre machine.

Class Process

Process()

Process::Process() {}

C’est le constructeur de Process, il ne fait rien.

loadProc(const string &fileName, const string &pName)

void Process::loadProc(const string &fileName, const string &pName) throw(CExc){
//on prend en paramètre le nom du programme que contient le fichier
programName = pName; //le nom est placé dans programName
//on ouvre le fichier contenant le programme :
ifstream progFile(fileName . c_str());
if(!progFile) {
throw CExc("Process::loadProc(" + fileName + "," + pName + ")"," could not open file for reading.");
}
//On récupère toute les lignes du fichier en ignorant les lignes de commentaires
for(string oneLine; getline(progFile,oneLine);) {
if(!(oneLine . size()) || oneLine[0] == '#')  {
continue; // ignore comment-only lines as well as empty lines
}
      //on les places au fur est à mesure dans le vecteur programText :
programText . push_back(oneLine);
}
}

Cette fonction sert à charger dans la donnée membre programText de cette classe l’intégralité des lignes du fichier programme donnée à la machine virtuel.

Class ProcessCounter

ProcessCounter()

ProcessCounter::ProcessCounter() : Register() {
value = 0;
}

C’est le constructeur de la classe, on remarque que la classe est initialisé avec « value » = 0. Sachant que cette classe a en donnée membre le registre prReg donc « value » correspond à prReg, le premier Process qui sera exécuté est donc celui ayant l’identifiant 0.

Class Memory

Memory()

Memory::Memory() : fileDescr(-1), memSize(0), offset(-1) {}

Le constructeur initialise la classe avec des donnée abérante, il convient donc d’utiliser la donction setUp qui initialise les variables.

setUp(int fD, int mS, int ofs)

void Memory::setUp(int fD, int mS, int ofs)  {
//initialisation des différentes variables
fileDescr = fD;
memSize   = mS;
offset    = ofs;
//on créer un tableau de char de la taille donnée en paramètre
char valZero[memSize];
//Toutes les valaurs du tableaux sont mise à 0
memset(valZero,0,memSize);
//on se déplace de offset caractères dans le fichier afin d'atteindre
//la bonne tranche de mémoire représenté dans le fichier
Lseek(fileDescr, offset, SEEK_SET);
//on écris le contenu du tableau dans le fichier
Write(fileDescr,&valZero,memSize);
const int wordSize(sizeof(int));
//utilise la fonction storeAt de Memory pour stocker mS/wordSize au début de la mémoire.
storeAt(0,mS / wordSize); // preparing the initial value for the spReg slot for the PSW
//je n'ai pas encore saisi exactement le but de la manœuvre
}

Cette méthode initialise la tranche mémoire à 0;

loadFrom(int addr)

int Memory::loadFrom(int addr) throw (CExc) {
//on vérifie que l'addresse données est bien dans un intervalle correcte
if(addr > memSize) {
throw CExc("Memory::loadFrom()"," address too large.");
}
if(addr < 0) {
throw CExc("Memory::loadFrom()"," negative address.");
}
int val;
//on se place à l'addresse indiqué en paramètre,
//l'offset permet de se placer dans l'espace mémoire du processus courant
Lseek(fileDescr, offset + addr, SEEK_SET);
//on lit la valeur et on la stock dans val
Read(fileDescr,&val,sizeof(val));
//on renvoi val
return(val);
}

Cette méthode permet de récupérer la valeur stocké à l’adresse mémoire donnée en paramètre du processus courant.

storeAt(int addr, int val)

void Memory::storeAt(int addr, int val) throw (CExc) {
//on vérifie que l'addresse données est bien dans un intervalle correcte
if(addr > memSize) {
throw CExc("Memory::storeAt()"," address too large.");
}
if(addr < 0) {
throw CExc("Memory::storeAt()"," negative address.");
}
//on se place à l'addresse indiqué en paramètre,
//l'offset permet de se placer dans l'espace mémoire du processus courant
Lseek(fileDescr, offset + addr, SEEK_SET);
//on écris la valeur
Write(fileDescr,&val,sizeof(val));
}

Cette méthode stocke à l’adresse indiqué la valeur donnée en paramètre.

dumpAll(ostream &os)

void Memory::dumpAll(ostream &os) {
//determine le nombre d'int stocké dans une mémoire de memSize
int memSizeWord(memSize/sizeof(int));
//crée un tableau de la taille corrspondante
int memVal[memSizeWord];
//se déplace au début de la tranche mémoire du processus
Lseek(fileDescr, offset, SEEK_SET);
//récupère toute les valeurs qu'elle contient
Read(fileDescr,memVal,memSize);
int kLine(-1);
os << "0\t";
//affiche dans le flux les valeurs récupérées
for(int kAddr(0); kAddr < memSizeWord; kAddr++) {
os << memVal[kAddr] << " ";
if(++kLine >= 9 && kAddr < memSizeWord - 1) {
os << "\n" << ((kAddr + 1) / 10) * 10 << "\t";
kLine = -1;
}
}
}

Cette méthode écris l’intégralité de la mémoire du du processus dans le stream donnée en paramètre.

Class Instruction

Instruction()

Instruction::Instruction(const string    &iStr,
InstructionType iT,
int o1, int o2, int o3,
const string   &str1,
const string   &str2,
int             qImm) :
instrStr(iStr),iType(iT), op1(o1), op2(o2), op3(o3), iArg1(str1), iArg2(str2), qImmediate(qImm) {
if(qImmediate) {
//si il y a une valeur immédiate alors la string est transformé en int
numVal = atoi((qImmediate == 1 ? iArg1 :  iArg2) . c_str());
}
}

C’est le constructeur de la classe, il prend en argument l’intégralité des arguments nécessaires à sa construction et initialise ses données membres.

operator <<

ostream &operator <<(ostream &os, const Instruction& instr) {
os << instr . instrStr;
return(os);
}

Surcharge de l’opérator << permettant d’injecté des Instruction directement dans un flux.

InstructionParser()

InstructionParser::InstructionParser() {
instructDict["NOPER"] = NOPER;
instructDict["DMPRG"] = DMPRG;
instructDict["MSTRM"] = MSTRM;
instructDict["USERM"] = USERM;
instructDict["NINTR"] = NINTR;
instructDict["INTRP"] = INTRP;
instructDict["SETRI"] = SETRI; qInstructHasImmediateArg[SETRI] = 2;
instructDict["SETRG"] = SETRG;
instructDict["SETPR"] = SETPR;
instructDict["CPPRG"] = CPPRG;
instructDict["ADDRG"] = ADDRG;
instructDict["SUBRG"] = SUBRG;
instructDict["PSHRG"] = PSHRG;
instructDict["POPRG"] = POPRG;
instructDict["JMABS"] = JMABS;
instructDict["JMBSI"] = JMBSI; qInstructHasImmediateArg[JMBSI] = 1;
instructDict["JMPTO"] = JMPTO;
instructDict["JMTOI"] = JMTOI; qInstructHasImmediateArg[JMTOI] = 1;
instructDict["JZERO"] = JZERO;
instructDict["JNZRO"] = JNZRO;
instructDict["JZROI"] = JZROI; qInstructHasImmediateArg[JZROI] = 2;
instructDict["JNZRI"] = JNZRI; qInstructHasImmediateArg[JNZRI] = 2;
instructDict["LDMEM"] = LDMEM;
instructDict["STMEM"] = STMEM;
instructDict["LDSHM"] = LDSHM;
instructDict["STSHM"] = STSHM;
instructDict["LDPSW"] = LDPSW;
instructDict["SPSWR"] = SPSWR;
instructDict["LDPRM"] = LDPRM;
instructDict["STPRM"] = STPRM;
instructDict["WKCPU"] = WKCPU;
instructDict["GETI0"] = GETI0;
instructDict["GETI1"] = GETI1;
instructDict["CLLSB"] = CLLSB;
instructDict["RETSB"] = RETSB;
instructDict["CLINT"] = CLINT;
instructDict["SPECP"] = SPECP;
instructDict["SDOWN"] = SDOWN;
}

C’est une « map » un type en C++ qui permet de créer des structures de données permettant une recherche efficace.

getInstrTypeFromString(const string &iStr, int *pqImm)

InstructionType InstructionParser::getInstrTypeFromString(const string &iStr, int *pqImm) throw(CExc) {
const map<string,InstructionType>::iterator pInstr(instructDict . find(iStr));
if(pInstr == instructDict . end()) {
throw CExc("InstructionParser::getInstrTypeFromString("+iStr+",...)","Invalid instruction");
}
if(pqImm == 0) {
throw CExc("InstructionParser::getInstrTypeFromString("+iStr+",...)"," null pointer, please debug.\n");
}
const map<InstructionType,int>::const_iterator pImm(qInstructHasImmediateArg . find(pInstr -> second));
*pqImm = (pImm != qInstructHasImmediateArg . end() ? pImm -> second : 0);
return(pInstr -> second);
}

Cette fonction renvoie une InstructionType quand on lui donne une string, elle renpli aussi le type d’instruction, c’est à dire si elle a un argument « immédiat » ou pas.

getRegIdFromString(const string &rStr)

int InstructionParser::getRegIdFromString(const string &rStr) throw(CExc) {
if(rStr . size() > 1 && rStr[0] == 'R') {
//renvoi la sous chaine de Str contenue après le caractère 1
//le tout transformé en int à l'aide de atoi
return(atoi(rStr . substr(1) . c_str()));
}
return(-1);
}

Renvoi le numéro du registre indiqué dans le ProgramText. Cela permet de savoir quels registre utiliser pour executer l’instruction.

Class ConsoleInOut

input()

void ConsoleInOut::input(istream &s,
Memory  &mem,
int      startAddr) {
const int wordSize(sizeof(int));
// @startAddr   : just the operation code
// @startAddr+1 : the # of items to read
// @startAddr+2 : the start address where to write the items one by one after console input
// @startAddr+3 : the start address where the item type vect is stored (the "format")
//on récupère le nombre d'Item à récupérer, un item c'est un int, ou un char
const int nItems         (mem . loadFrom((startAddr + 1) * wordSize));
//on récupère l'adresse à partir de la quelle on va stocker
const int destAddrStart  (mem . loadFrom((startAddr + 2) * wordSize));
//on récupère un int qui indique le type de donnée à stocker,
//0 pour un int et 1 pour un char
const int formatAddrStart(mem . loadFrom((startAddr + 3) * wordSize));
cerr << "ConsoleInput for " << nItems << " items:\n";
//on boucle autant de fois qu'il y a d'Item
for(int kItem(0); kItem < nItems; kItem++) {
const int itemFormat (mem . loadFrom((formatAddrStart + kItem) * wordSize));
//en fonction du type de donnée on le traite
if(itemFormat == 0) { // int value
int a; s >> a; mem . storeAt((destAddrStart + kItem) * wordSize, a);
}
else if(itemFormat == 1) { // char value
char a; s >> a; mem . storeAt((destAddrStart + kItem) * wordSize, static_cast<int>(a));
}
else {
cerr << "ConsoleInputOutput SYSTEM FAULT: Wrong format code " << itemFormat << ", only 0 (int) or 1 (char)\n";
exit(2);
}
}
cerr << "\nConsoleInput end\n";
}

On donne en paramètre le flux d’entrée, la tranche mémoire sur la quel on agit et la startAddr qui est l’adresse dans la mémoire où l’on aura pris soin de stocker préalablement :

  • À startAddr + 1 : le nombre d’Item à lire dans le flux.
  • À startAddr + 2 : l’addresse à la quelle stocker les Item lus.
  • À startAddr + 3 : le type de donnée à récupérer, 0 pour un int, 1 pour un char.

output()

void ConsoleInOut::output(ostream &s,
Memory  &mem,
int      startAddr) {
const int wordSize(sizeof(int));
// @startAddr   : just the operation code
// @startAddr+1 : the # of items to write
// @startAddr+2 : the start address where to read the items one by one for console output
// @startAddr+3 : the start address where the item type vect is stored (the "format")
const int nItems (mem . loadFrom((startAddr + 1) * wordSize));
const int fromAddrStart  (mem . loadFrom((startAddr + 2) * wordSize));
const int formatAddrStart(mem . loadFrom((startAddr + 3) * wordSize));
cerr << "ConsoleOutput for " << nItems << " items:\n";
for(int kItem(0); kItem < nItems; kItem++) {
const int itemFormat (mem . loadFrom((formatAddrStart + kItem) * wordSize));
if(itemFormat == 0) { // int value
const int a(mem . loadFrom((fromAddrStart + kItem) * wordSize));
s << a;
}
else if(itemFormat == 1) { // char value
const char a(mem . loadFrom((fromAddrStart + kItem) * wordSize));
s << a;
}
else {
cerr << "ConsoleInputOutput SYSTEM FAULT: Wrong format code " << itemFormat << ", only 0 (int) or 1 (char)\n";
exit(2);
}
}
cerr << "\nConsoleOutput end\n";
}

C’est le même concept que la fonction précédente à la différence près que l’ont lit les Item dans la mémoire et qu’on les affiche dans le flux. Il faut donc remplir là aussi les StartAddr correctement.

Class BaseCPU

BaseCPU()

BaseCPU::BaseCPU(const string &id, ostream  &log, istream &consoleIn, ostream &consoleOut,
const int nGenReg, const int nProc) : pcReg(iPrs), cpuId(id), logStream(log),
consoleOutputStream(consoleOut),
consoleInputStream(consoleIn) {
//Le vector de Register est redimensionné à la taille donné
//en paramètre lors de la création du CPU
genReg . resize(nGenReg);
//de même pour le vector de processus
proc   . resize(nProc);
//ainsi que pour le vector de Memory
mem    . resize(nProc);
//l'état du CPU est défini à "Lancé"
qRun           = true;
//le CPU ne peut pas être intérrompu pour l'instant
qInterruptible = false;
logStream << "BaseCPU::BaseCPU() complete for " << cpuId << "\n";
}

C’est le constructeur de la classe, il s’occupe d’initailiser certaines variables à l’aide des paramètre qui lui sont donnés.

dumpReg(ostream &os, bool qRegAsWell)

void BaseCPU::dumpReg(ostream &os, bool qRegAsWell) {
os << " PR = " << prReg . getVal()
<< " SP = " << spReg . getVal()
<< " PC = " << pcReg . getVal()
<< " MD = " << mdReg . getVal()
<< " qI = " << qInterruptible;
if(qRegAsWell) {
for(unsigned int kReg(0); kReg < genReg . size(); kReg++) {
os << " R" << kReg << " = " << genReg[kReg] . getVal();
}
}
os << "\n";
}

Cette méthode permet de copier dans le flux de sortie les valeurs des différents registres du CPU, que ce soit les registres spécifique tel que PR, SP, PC, MD, qI ou les registre genérique rangé dans genReg.

run()

void BaseCPU::run() throw(CExc) {
for(iTick = uTick = 0; qRun && iTick < 10000; uTick += (mdReg . getVal() != 1),iTick++) {
execute(pcReg . fetch(proc[prReg . getVal()] . programText));
if(qInterruptible) {
pendingIntIfAny();
}
}
logStream << "CPU " << cpuId << " is now fully stopped, total " << iTick << " instructions overall.\n";
}

C’est méthode est l’unes des plus importante du projet. Cette méthode passe d’une ligne à la suivante dans l’execution du programme. Elle utilise 4 variables :

  • iTick qui représente les tour d’horloge fait par la machine. Si il arrive à 10000 et que la valeur de qRun n’a pas changé alors la machine s’arrète.
  • uTick qui est utilisé par l’ordonanceur (le scheduler) pour gérer le passage d’un processus à un autre. Suivant la valeur de uTick la fonction CPU::Interrupt() va être lancé.
  • pcReg est de type ProgramCounter, cette variable contient le numero de la ligne dont il faut « créer » à la volé l’Instruction.Mais pcReg ne sait pas de quel processus il s’agit et n’a même pas accé directement au texte du programme. C’est grâce à la méthode fetch que l’on peut agir, cette méthode prend en paramètre un ProgramText, donc en donnant le « bon » ProgrammText on peut choisir quel processus executer.
  • prReg, c’est elle qui va nous permettre de donner le « bon » ProgramText. Les ProgramText sont stockés dans le vector (de type Process) proc, donnée membre de BaseCPU et prReg stock le numéro du processus à executer. Il n’y a donc qu’a récupérer sa valeur avec getVal().

Et voila, Il suffit d’agir sur 2 variables pour modifier le processus en cours d’éxécution, pcReg et prReg. Il faut par contre faire attention de bien sauvegarder l’intégralité du « contexte » du processus avant de passer à un autre sinon on ne pourra pas revenir à lui plus tard.

Class CPU

CPU()

CPU::CPU(const string &id, ostream  &log, istream &consoleIn, ostream &consoleOut,
const int nGenReg, const int nProc) :
BaseCPU(id,log, consoleIn, consoleOut, nGenReg, nProc) {
//création du fichier mémoire
const int memFd (Open("Memory",O_RDWR|O_CREAT, S_IRUSR | S_IWUSR));
//définition de la taille d'un mot en octet
const int wordSize(sizeof(int));
for(int kProc(0); kProc < nProc; kProc ++) {
//mise à 0 de l'ensemble des zone mémoire des processus
mem[kProc]  . setUp(memFd,MEMORYSIZEPERPROCESS * wordSize, kProc * MEMORYSIZEPERPROCESS * wordSize);
ostringstream buffstr,buff2str;
buffstr  << "Proc_" << kProc << "_Program.asm";
buff2str << "Prog_" << kProc << "_0";
//chargement des fichier dans les ProgramText des Process rangé dans le vector<Process> proc
proc[kProc] . loadProc(buffstr . str(),buff2str . str());
}
//préparation de la mémoir partagée
sharedMem . setUp(memFd,MEMORYSIZEPERPROCESS * wordSize, nProc * MEMORYSIZEPERPROCESS * wordSize);
//initialisation de prReg avec le premier Processus à executer
prReg  . setVal(PROCPSWFRAMESTART);
//initialisation de la ligne à executer dans le premier processus
pcReg  . setVal(0);
//initialisation de le StackPointer à la dernière case de la tranche mémoire du processus
spReg  . setVal(MEMORYSIZEPERPROCESS - 1);
//passage en mode maître
mdReg  . switchToMaster();
inReg0 . setVal(nProc - 1);
logStream << "CPU::CPU() complete for " << cpuId << "\n";
}

C’est le constructeur du CPU, il fait lui même appel à base CPU pour itnitaliser une partie de son environement. CPU sert à itnitialiser tous ce dont BaseCPU ne s’occupe pas.

~CPU()

CPU::~CPU() {
logStream << "\nMemory dump at end time\n";
logStream << "Shared memory\n";
sharedMem . dumpAll(logStream);
logStream << "\n-----------------------------------\n";
for(unsigned int kProc (0); kProc < mem . size(); kProc++) {
logStream << "Proc " << kProc << "\n";
mem[kProc] . dumpAll(logStream);
logStream << "\n-----------------------------------\n";
}
}

Destructeur de la classe CPU

interrupt(const int interruptNumber, bool qCLINTInstr)

void CPU::interrupt(const int interruptNumber, bool qCLINTInstr) throw(CExc) {
//rend le CPU interruptible
qInterruptible = false;
//ecrit dans les log le numéro de l'intérruption si qCLINTInstr est faux
if(!qCLINTInstr) {
logStream << cpuId << " Interrupt #" << interruptNumber << " ";
}
const int proc2stPSW   (prReg . getVal());
const int wordSize  (sizeof(int));
if(proc2stPSW) { // save PSW for regular processes only, not for kernel
const int frameStart(PROCPSWFRAMESTART);
mem[proc2stPSW] . storeAt(frameStart,                spReg . getVal());
mem[proc2stPSW] . storeAt(frameStart + wordSize,     pcReg . getVal());
mem[proc2stPSW] . storeAt(frameStart + 2 * wordSize, mdReg . getVal());
for(unsigned int kReg(0); kReg < genReg . size(); kReg++) {
mem[proc2stPSW] . storeAt(frameStart + (3 + kReg) * wordSize, genReg[kReg] . getVal());
}
mem[KERNELPID] . storeAt(KERNELSAVECURRENTPROCID, prReg . getVal());
prReg . setVal(KERNELPID); // execute kernel code
}
mdReg . switchToMaster();
pcReg . setVal(mem[KERNELPID] . loadFrom(interruptNumber * wordSize));
dumpReg(logStream,true);
}

hum

By

Projet Système d’exploitation 2012 Partie 2

Maintenant que l’avant projet est fini il est temps de passer à l’étape suivante du projet c’est à dire la compréhension du code fournis.

Pour comprendre correctement la suite des explication il faut bien penser que le code fournis est celui d’une machine virtuel simplifié. C’est machine virtuel reproduit les bases d’un cpu, de la mémoire et de d’autres composants.
Nous allons commencer par l’analyse des fichiers include ce qui devrait faciliter la compréhension de la suite des fichiers.

Petite liste des #define de CPU.h :

#define MEMORYSIZEPERPROCESS     1000
#define KERNELSAVECURRENTPROCID     0
#define KERNELPID                   0
#define PROCPSWFRAMESTART           0
#define SAMEPROCMAXINSTRTICKCHUNK   15
#define SCHEDULENEXTPROCINTERRUPT   3
#define MAXSPROCCOUNT               9
#define MAXSEMAPHORECOUNT           9
#define SEMAPHOREVECTORSTART      100
#define PROCSEMWAITLISTSTART      200
#define PROCSEMWAITLISTSTARTELEMCNT 2
#define CONSOLEINPUTOUTPUTTRIGGER 300

Class Register

class Register {
protected:
    int value;
public:
    Register();
    void setVal(const int val);
    int  getVal() const;
}; 

La classe Register simule un registre CPU, il possède une variable de type int qui sert à stocker sa valeur. Un constructeur afin que des fonctions setVal() et getVal() sont présente est permettent respectivement de donner une valeur au registre et de récupérer la valeur d’un registre.

RegisterSet

 typedef vector RegisterSet; 

Cette ligne permet de créer un type vector de registre nommé RegisterSet ceci simplifie l’accès aux registres dans le cas où le système en créer plusieurs. Ce qui est le cas pour cette machine. (nous le verrons un peut plus tard)

Class StackPointer

class StackPointer : public Register {
public:
    StackPointer();
};

Je n’ai pas encore trouvé l’utilité de cette classe, elle ne possède qu’un constructeur et dérive de la classe Register.

Class RunMode

class RunMode : public Register {
public:
    RunMode();
    void switchToMaster();
    void switchToUser();
    int  qIsMaster() const;
};

La classe RunMode dérive de Register, elle possède donc une variable de type int, Value (qu’elle hérite de Register). À ce qu’elle hérite de Register s’ajoute quelques fonction membre.

  • void switchToMaster() Sert à passer le contexte d’exécution en mode maître.
  • void switchToUser() Sert à passer le contexte d’exécution en mode utilisateur.
  • int qIsMaster() permet de savoir si l’ont est en mode maître ou en mode utilisateur.

Il peut être utile de rappeler que le changement de contexte permet de de modifier ce que peut faire un programme pendant son exécution et les ressources aux quel il a accès.

ProgramText

typedef vector       ProgramText;

Ce type est utiliser par la suite pour stocker les lignes des programme en pseudo assembleur qui sont donnée à exécuter à la machine virtuel. Il s’agit simplement d’un vector de string.

Class ProgramCounter

class ProgramCounter : public Register {
    InstructionParser &instrParse;
public:
    ProgramCounter(InstructionParser &iPar);
    void        relativeJump(int offset);
    Instruction fetch(ProgramText &progText);
};

Cette classe dérive de Register, elle possède donc un argument de type int nommé Value ainsi que les fonctions de manipulation qui lui sont associés. En plus de cela elle possède une variable de type InstructionParser, nous verrons de quoi il s’agit un peut plus loin. Sinon elle possède aussi un constructeur et deux fonctions membres :

  • void relativeJump (int offset) qui permet de sauter offset instruction plus loin dans le programme.
  • Instruction fetch(ProgramText &progText) qui sert à récupérer la prochaine instruction à traiter, c’est à dire la prochaine ligne de programme à traiter par la machine virtuel

Class Process

class Process {
public:
ProgramText programText;
string      programName;
Process();
void loadProc(const string &fileName, const string &pName) throw(nsSysteme::CExc);
};

Cette classe représente un processus, c’est c’est à dire une suite d’instruction indépendante (ou pas forcément) à traiter. La machine peut charger plusieurs Process à son démarrage. Elle possède en donnée membre un ProgramText qui contient toute les lignes du programme à traiter, une string qui contient le nom du Process. Elle possède aussi un constructeur et une fonction loadProc() qui permet de charger le fichier contenant les instructions.

ProcessTable

typedef vector<Process>  ProcessTable;

Ce type de variable sert juste à créer un vector de Process afin d’y accéder plus facilement.

Class ProcessCounter

class ProcessCounter : public Register {
public:
ProcessCounter();
};

Cette classe dérivant de register elle possède une donnée membre de type int, Value ainsi que des fonction membre qui permet d’interagir avec lui. Nous reviendrons plus en détails sur cette classe un peut plus tard.

Class Memory

class Memory {
int fileDescr;
int memSize;
int offset;
public:
Memory();
void setUp(int fD, int mS, int ofs);
int  loadFrom(int addr) throw (nsSysteme::CExc);
void storeAt (int addr, int val) throw (nsSysteme::CExc);
void dumpAll(ostream &os);
};

Cette classe représente la mémoire dans la machine virtuel. La mémoire étant simulé à l’aide d’un fichier la classe contient un file descriptor permettant d’accéder au fichier, un int memSize qui contient la taille de la mémoire et un int offset qui représente le décalage par rapport au début du fichier de l’emplacement mémoire. Pour rappel les espace mémoire sont alloué les uns à la suite des autres dans un fichier et font 1000 int chacun (donc en théorie pendant l’exécution le variable memSize sera égal à 1000 pour les processus utilisateur, sachant qu’il y en a aussi un pour le noyau et un pour la mémoire partagée. La classe possède aussi des fonctions membre :

  • void SetUp() qui initialise la mémoire à 0 et initialise le StackPointer, spReg.
  • int loadFrom() qui lit un int à l’adresse de la mémoire indiqué en paramètre.
  • void storeAt() qui stock un int à l’adresse mémoire donné en paramètre.
  • void dumpAll() qui écris toute la mémoire dans le flux donné en paramètre.

MemoryTable

typedef vector<Memory>  MemoryTable;

Ceci est tout simplement un vector de Memory. Il faut bien se dire qu’un Memory représente l’espace mémoire alloué à un processus et que la MemoryTable est l’ensemble des Memory du noyau et des processus.

InstructionType

enum InstructionType {NOPER=0,
DMPRG, // ex: DMPRG              ; dump content of all registers to logstream
MSTRM, // ex: MSTRM              ; MD <- MasterMode
USERM, // ex: USERM              ; MD <- UserMode
NINTR, // ex: NINTR              ; disable interrupts
INTRP, // ex: INTRP              ; enable interrupts
SETRI, // ex: SETRI R2 254       ; R2 <- 254
SETRG, // ex: SETRG R2 R3        ; R2 <- R3
SETPR, // ex: SETPR R10          ; PR <- R10 (only in mastermode)
CPPRG, // ex: CPPRG R2           ; R2 <- PR
ADDRG, // ex: ADDRG R2 R3 R4     ; R2 <- R3 + R4
SUBRG, // ex: SUBRG R2 R3 R4     ; R2 <- R3 - R4
PSHRG, // ex: PSHRG R5 R4        ; mem[(SP - R5) * sizeof(int)] <- R4
POPRG, // ex: POPRG R5 R7        ; R7 <- mem[(SP + R5) * sizeof(int)];
JMABS, // ex: JMABS R1           ; PC <- R1
JMPTO, // ex: JMPTO R1           ; PC <- PC + R1
JZERO, // ex: JZERO R12 R5       ; if(R12 == 0) { PC <- PC + R5 }
JNZRO, // ex: JNZRO R12 R5       ; if(R12 != 0) { PC <- PC + R5 }
JMBSI, // ex: JMBSI 23           ; PC <- 23
JMTOI, // ex: JMTOI 24           ; PC <- PC + 24
JZROI, // ex: JZROI R12 25       ; if(R12 == 0) { PC <- PC + 25 }
JNZRI, // ex: JNZRI R12 26       ; if(R12 != 0) { PC <- PC + 26 }
LDMEM, // ex: LDMEM R8 R21       ; R21     <- mem[R8] (for current proc memory space)
STMEM, // ex: STMEM R8 R21       ; mem[R8] <- R21  (for current proc memory space)
LDSHM, // ex: LDSHM R8 R21       ; R21     <- sharedMem[R8] (for all procs shared memory space)
STSHM, // ex: STSHM R8 R21       ; sharedMem[R8] <- R21  (for all procs shared memory space)
LDPSW, // ex: LDPSW R3           ; <SP,PR,PC,MD,R0,R1,...> <- memOfProc[R3][PROCPSWFRAMESTART]
SPSWR, // ex: SPSWR R3 R2 R4     ; memOfProc[R3][PROCPSWFRAMESTART+offset4.R2] <- R4 (master only)
LDPRM, // ex: LDPRM R3 R8 R21    ; R21     <- memOfProc[R3][R8] (master only)
STPRM, // ex: STPRM R3 R8 R21    ; memOfProc[R3][R8] <- R21 (master only)
WKCPU, // ex: WKCPU R3 R5 R6     ; CPU[R3].I0 <- R5 ; CPU[R3].I1 <- R6 ;  signal CPU[R3]
GETI0, // ex: GETI0 R5           ; R5 <- I0 (the only way to read I0 (after a WKCPU))
GETI1, // ex: GETI1 R5           ; R5 <- I1 (the only way to read I1 (after a WKCPU))
CLLSB, // ex: CLLSB R7 R5        ; SP <- SP - R7; mem[SP * sizeof(int)] <- PC; PC <- R5
RETSB, // ex: RETSB R6           ; PC <- mem[SP * sizeof(int)] ; SP <- SP + R6
CLINT, // ex: CLINT R7           ; PC <- IntVec[R7] (saves the PSW except for the kernel)
SPECP, // ex: SPECP R2 R3 R10 R4 ; special instruction given by R2 with (at most) three args(R3,..)
SCRASH,// ex: SCRASH             ; crash down the system (master only) -- major inconsistency issue (similar to kernel panic)
SDOWN};// ex: SDOWN              ; shut down system (master only)

Il s’agit d’un type variable énumératif qui contient la liste de toute les valeurs que peut prendre les instructions.

Class Instruction

class Instruction {
public:
string          instrStr;
InstructionType iType;
int             op1, op2, op3;
string          iArg1,iArg2;
int             numVal;
int             qImmediate;
Instruction(const string &iStr,
InstructionType iT,
int o1, int o2, int r,
const string &str1, const string &str2,
int qImm);
};

Cette classe contient toutes données nécessaire à l’exécution d’une instruction par le CPU virtuel.

  • string instrStr : l’instrucution sous forme de string.
  • InstructionType iType : l’instruction sous forme d’InstrcutionType, cette nuance permet au CPU d’utiliser un switch pour choisir quel code executer en fonction de l’instruction. C’est le fait que iType soit un type énumératif qui permet l’utilisation du switch par la suite ce qui explique pourquoi les deux forme sont présentes dans la classe Instruction.
  • int op1, op2, op2 : servent à stocker les numéros de registre utilisé par l’instruction. Il y a 3 int car les instructions « assembleur » utilisent au maximum 3 registre pour fonctionner.
  • string iArg1, iArg2 : stockent les argument « immédiat » (c’est à dire donnée en dur dans le texte du programme, en gros tout ce qui n’est pas un registre et un argument « immédiat »).
  • int numVal : valeur de l’argument « immédiat »
  • int qImmediate : indique si l’instruction possède des arguments « immédiat »
À côté de ces données membre elle possède simplement un constructeur.

Ostream InstrcutionType

ostream & operator<<(ostream &os, const Instruction& instr);

Une surcharge d’opérateur sur ostream. Il n’est pas nécessaire de comprendre le détail de cette ligne.

Class InstructionParser

class InstructionParser {
map<string,InstructionType> instructDict;
map<InstructionType,int>    qInstructHasImmediateArg;
public:
InstructionParser();
InstructionType getInstrTypeFromString(const string &iStr, int *pqImm) throw(nsSysteme::CExc);
int             getRegIdFromString    (const string &rStr) throw(nsSysteme::CExc);
};

Cette classe sert à décomposer les lignes des programmes données à la machine en Instruction, il n’est donc pas essentiel de la comprendre dans le détail.

Class ConsoleInOut

class ConsoleInOut {
public:
    void input (istream &s,
        Memory  &mem,
        int      startAddr);
    void output(ostream &s,
        Memory  &mem,
        int      startAddr);
};

Cette classe au travers de ses méthodes permet d’envoyer des données sur les flux input et output. Sont fonctionnement détallé sera vu un peut plus tard.

Class BaseCPU

class BaseCPU {
protected:
InstructionParser iPrs;
StackPointer     spReg;
ProgramCounter   pcReg;
ProcessCounter   prReg;
RunMode          mdReg;  // user or master
RegisterSet      genReg;
Register         inReg0,inReg1; // for interCPU communication (e.g. CPU<->DMA)
MemoryTable      mem;
Memory           sharedMem;
ProcessTable     proc;
string           cpuId;
ostream         &logStream;
ostream         &consoleOutputStream;
istream         &consoleInputStream;
ConsoleInOut     consoleInOut;
bool            qRun;
bool            qInterruptible;
int             iTick,uTick;
public:
BaseCPU(const string &id, ostream  &log, istream &consoleIn,  ostream  &consoleOut,
const int nGenReg, const int nProc);
virtual void dumpReg(ostream &os, bool qRegAsWell = false);
virtual void execute(const Instruction &) throw(nsSysteme::CExc);
virtual void run() throw(nsSysteme::CExc);
virtual void interrupt(const int, bool) throw(nsSysteme::CExc);
virtual void pendingIntIfAny() throw(nsSysteme::CExc);
};

Cette classe est l’unes des plus importante du programme, elle possède de nombreuses données membre et des méthode essentiel à la compréhension du programme.

  • InstructionParser iPrs : Cette variable permet d’utiliser la classe InstructionParser et les méthodes qui lui sont associé.
  • StackPointer spReg : C’est un pointeur ver la pile de la mémoire du processus actuel, dans le cadre du programme donné cette valeur est toujours la même, et est égal à MEMORYSIZEPERPROCESS – 1.
  • ProgramCounter pcReg : Cette variable contient le numéro de la ligne d’un processus à exécuter, elle permet aussi au traver de ses données membre et héritage d’accéder à certaines fonctions.
  • ProcessCounter prReg : Cette variable stock le numero du processus à executer.
  • RunMode mdReg : elle défini le mode d’execution de la machine.
  • Register inReg0, InReg1 : permet de commniquer entre CPU et avec le DMA, ce n’est pas encore implémenté.
  • MemoryTable mem : C’est le vecteur qui contient les Memory des différents processus, pour rappel le type Memory contient des informations sur les espaces mémoire des processus, la mémoire étant en elle même toujours sous la forme d’un fichier.
  • Memory sharedMem : C’est la Memory qui contient les information et méthodes sur la mémoire partagée entre les processus.
  • ProcessTable proc : C’est le vecteur de Process, pour rappel un Process contient le Text du processus à executer par la machine virtuel. Le vecteur contient donc les processus à executer.
  • string cpuId : C’est l’id du CPU dans le cas où la machine virtuel possède plusieurs CPU, pour l’instant l’implémentation de donne qu’un CPU.
  • ostream & logStream : c’est le stream de log de la machine.
  • ostream &consoleOutputStream : c’est le stream de sortie de la machine.
  • ostream &consoleInputStream : c’est le stream d’entrée de la machine.
  • ConsoleInOut consoleInOut : Cette variable permet d’accéder aux méthode fourni par la classe ConsoleInOut, pour rappel ces méthodes permettes d’écrire sur le stream de sortie depuis la mémoire d’un processus et d’écrire dans la mémoire depuis le stream d’entrée.
  • bool qRun : défini l’état du CPU. Lancé ou arreté, cette variable est utilisé comme condition dans la boucle qui « execute » les instruction des différents programme les uns à la suite des autres.
  • bool qInterruptible : indique si quelque chose peut interrompre ce que le CPU est en train de faire.
  • int iTick, uTick : Ces varibles représente en quelque sorte des Tick d’horloge, iTick est incrémenté à chaque nouvelle instruction executé alors que uTick est incrémenté que si l’instruction executé est en contexte utilisateur (mdReg != 1), de plus uTick est utilisé par le scheduler de la machine virtuel pour déterminer quand donner la main à un autre processus.

Passons maintenant aux méthodes :

  • BaseCPU() , c’est le constructeur de la classe, il va construire toute ses données membre et initialiser certaines.
  • void dumpReg() , ecris tous les registres dans le flux de sortie.
  • void execute() , n’est pas utilisé directement, c’est celle de la classe CPU qui est utilisé, elle sera vu un peut plus loin.
  • void run() , C’est cetet fonction qui lance le CPU est execute les instruction les unes à la suite des autres.
  • void interrupt() , n’est pas utilisé directement, c’est celle de la classe CPU qui est utilisé, elle sera vu un peut plus loin.
  • void pendingIntIfAny() , n’est pas utilisé directement, c’est celle de la classe CPU qui est utilisé, elle sera vu un peut plus loin.

Class CPU

class CPU : public BaseCPU {
public:
CPU(const    string &id, ostream  &log,
istream &consoleIn,  ostream  &consoleOut,
const int nGenReg, const int nProc);
~CPU();
void execute  (const Instruction &instr) throw(nsSysteme::CExc);
void interrupt(const int interruptNumber, bool qCLINTInstr) throw(nsSysteme::CExc);
void pendingIntIfAny() throw(nsSysteme::CExc);
};

Cette classe dérive de BaseCPU, elle possède donc toutes ses données membres et ses méthodes. Elle ajoute un constructeur qui va initialiser certains paramètres. Il sera vu en détail plus tard. Elle surcharge aussi certaines méthode :

  • void execute() qui exécute une instruction passé en paramètre.
  • void interrupt () qui sert à simuler une interruption dans la machine virtuel.
  • void pendingIntIfAny() si un processus à eu la main sur le CPU pendant 15 Tick alors une interruption de type SCHEDULENEXTPROCINTERRUPT est lancé. Cela permet d’appeler le scheduler afin qu’il donne la main à un autre processus.

Dans la prochaine partie nous passerons à l’analyse des .cxx.

By

Projet Système d’exploitation 2012 Partie 1

Étant en 2em année de DUT informatique à Aix j’ai un projet de système à faire dont l’énoncé est le suivant (écris par la génial A.B Dragut) :

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

Voici mon rendu (Avant-Projet système d’exploitation 2012 – Kevin Personnic) pour l’avant projet, je pense qu’il n’est pas exempte d’erreurs et je ne suis pas tout à fait satisfait de ce que j’ai écris. J’ai réussi à trouver un grand nombre de sources intéressante pour répondre à toute ces question en particulier sur les site internet de différent laboratoire d’informatique fondamental dont le champ d’enseignement recoupe bien le sujet du projet.

Différentes sources que j’ai trouvé sur le sujet :

By

Afficheur 7 segments

bannière affichage 7 segments

En même temps que mon starter kit j’ai acheté un afficheur 7 segments pour pouvoir afficher quelques chiffes (j’aurais du en prendre 2 pour rendre les choses plus intéressantes). L’afficheur que j’ai acquis est à anode commune c.a.d que l’alimentation est commune est que c’est la gestion de la masse qui permet de changer l’état des led de l’afficheur.

Voici un schéma du circuit que j’ai mis en place (toujours à l’aide du logiciel Fritzing) :

circuit afficheur 7 segment avec arduino

Vous avez sans doute remarqué qu’il y a aussi un capteur de température qui va me permettre de faire des choses un peut plus intéressante qui si je me contente de l’afficheur.

Pour faire fonctionner un afficheur à anode commune il faut utiliser les sortie digital, en niveau LOW le segment est relié à la masse et donc s’allume. Au contraire au niveau HIGH le segment est éteint. J’ai commencé par écrire un code tout simple qui se contente d’allumer les segments les uns après les autres :

void setup (void)
{
  randomSeed(analogRead(0));

  for (int i = 2; i < 10; ++i)
  {
    pinMode(i, OUTPUT);
    digitalWrite(i, HIGH);
  }
}

void loop (void)
{
  for (int i = 2; i < 10; ++i)
  {
    digitalWrite(i, LOW);
    delay(random(10, 200));
    digitalWrite(i, HIGH);
  }
}

J’ai ensuite écris un code un peut plus complet pour afficher les chiffres les uns après les autres :

boolean Numbers[10][10] = {{1,1,1,0,1,1,0,1},
                           {0,0,0,0,0,1,0,1},
                           {1,0,1,0,1,0,1,1},
                           {1,0,0,0,1,1,1,1},
                           {0,1,0,0,0,1,1,1},
                           {1,1,0,0,1,1,1,0},
                           {1,1,1,0,1,1,1,0},
                           {1,0,0,0,0,1,0,1},
                           {1,1,1,0,1,1,1,1},
                           {1,1,0,0,0,1,1,1}};
void setup (void)
{
  randomSeed(analogRead(0));

  for (int i = 2; i < 10; ++i)
  {
    pinMode(i, OUTPUT);
    digitalWrite(i, HIGH);
  }
}

void ClearAffichage (void)
{
  for (int i = 2; i < 10; ++i)
  {
    digitalWrite(i, HIGH);
  }
}

void AffichageNumber(int N)
{
  for (int i = 0; i < 10; ++i)
  {
    if (Numbers[N][i]) digitalWrite(i+2, LOW);
  }
}

void Chronometre (void)
{
  for( int j = 0; j < 10; ++j)
  {
    ClearAffichage();
    AffichageNumber(j);
    delay(200);
  }
}

void RandomNumbers (void)
{
  ClearAffichage();
  AffichageNumber(random(0,10));
  delay(200);
}

void loop (void)
{
  RandomNumbers();
}

Une fois cette base mise en place il est très simple d’ajouter la partie en charge de la température :

int Temperature (void)
{
  int R_value = analogRead(T_pin);
  return ((R_value*5)*100)/1024;
}

void loop (void)
{
  int temperature = Temperature();
  Serial.println(temperature);
  int dizaine = temperature / 10;
  int unitee = temperature % 10;
  AffichageNumber(dizaine);
  delay(300);
  ClearAffichage();
  AffichageNumber(unitee);
  delay(300);
  ClearAffichage();
  AffichageWait();
  delay(1000);
  ClearAffichage();
}

Vous pouvez voir que j’ai du sérialisé la température, n’ayant qu’un seul afficheur 7 segment j’affiche les dizaines puis les unité suivit un – pour marqué le redémarrage de la séquence.

By

Arduino Capteur/Afficheur de temperature

Ça y est, j’ai craqué pour un arduino. J’ai donc commandé un arduino UNO avec un starter kit histoire d’avoir sous la main de quoi faire quelques montage utile ou inutile.

J’ai commandé tout ça sur le site snootlab qui propose des prix intéressant d’après ce que j’ai pus voir ailleurs sur internet mais surtout propose un forum où on peut facilement trouver de l’aide pour débuter. Je l’ai utilisé pour mes premiers montages, le classique clignotement d’une led ou l’utilisation d’un capteur.

J’ai ensuite voulu créer un petit montage qui puisse m’être utile, sachant que je suis dans le sud avec des ordi dans une pièce sans clim je me suis souvent demandé si il faisait réellement plus chaud dehors que dans ma chambre. Je me suis donc attelé à la création d’un petit thermomètre à affichage led en binaire (car tout geek qui se respecte peut lire au moins sur 6 bits sans réfléchir).

J’ai fait un petit schéma à l’aide du logiciel Fritzing qui permet ça très agréablement :

Au niveau du code et de son upload dans l’arduino j’utilise l’IDE arduino que l’on peut trouver dans les dépôt et qui fait très bien son job. Donc voici le code que j’ai écris pour ce petit montage.

    // Declarations de la variable relative à la résistance
    int R_value = 0;
    double Temperature = 0;
    int calc1 = 0;
    int calc2 = 0;

    // Declaration de la pin analogique reliée aux bornes de la résistance
    char R_pin = 0;

    // MAIN

    void setup(void)
    {
      pinMode(2,OUTPUT);
      pinMode(3,OUTPUT);
      pinMode(4,OUTPUT);
      pinMode(5,OUTPUT);
      pinMode(6,OUTPUT);
      pinMode(7,OUTPUT);
      Serial.begin(9600);               //Mise en place de la communication série, via l'USB, à 9600bps
      analogReference(DEFAULT);  //Positionner la référence pleine échelle du convertisseur pour : 5v=1023
    }

    void loop(void)
    {
     R_value = analogRead(R_pin);
     Temperature = ((R_value*5)*100)/1024;

     calc1 = int(Temperature)%32;
     if(int(Temperature/32)) {digitalWrite(2, HIGH);}
     calc2 = calc1%16;
     if(int(calc1/16)) {digitalWrite(3, HIGH);}
     calc1 = calc2%8;
     if(int(calc2/8)) {digitalWrite(4, HIGH);}
     calc2 = calc1%4;
     if(int(calc1/4)) {digitalWrite(5, HIGH);}
     calc1 = calc2%2;
     if(int(calc2/2)) {digitalWrite(6, HIGH);}
     calc2 = calc1%1;
     if(int(calc1/1)) {digitalWrite(7, HIGH);}

     delay(1000);                         //Attendre 1s

     digitalWrite(2, LOW);
     digitalWrite(3, LOW);
     digitalWrite(4, LOW);
     digitalWrite(5, LOW);
     digitalWrite(6, LOW);
     digitalWrite(7, LOW);
    }

Ce qui au final nous donne une fois monté ceci :

La prochaine étape est d’utiliser 2 afficheurs 7 segments pour afficher la température, en attendant j’ai mis dans une galerie des documents technique suplémentaire sur ce montage.

By

Simuler un réseau avec GNS3

Une fois que l’on possède une installation de gns3 qui fonctionne correctement on peut passer à l’étape simulation, ce qui reste quand même le but final de ce logiciel.

L’interface de gns3 est très simple, il fonctionne en grand parti sur le principe du drag and drop. Il suffit de prendre un élément à placer sur le schéma dans la liste de gauche et de le déposer dans l’espace central.

Comme une vidéo vaut mieux qu’un long texte dans ce cas, voici une petite démo.

By

GNS3

GNS3 est une simulateur de réseau qui permet tout particulièrement de faire fonctionner des routeur cisco.

Installation

GNS3 utilise plusieurs paquets qui se trouvent dans les dépôts d’Ubuntu, gns3, dynamips et dynagen pour l’émulation des routeur cisco ainsi que qemu pour gérer les vm qui seront utilisées dans la simulation. Il peut aussi être intéressant d’installer wireshark pour faire des captures.

 apt-get install gns3 dynamips dynagen

Vous avez sans doute remarqué que le paquet de qemu n’est pas présent dans la commande car pour pouvoir utiliser qemu dans gns3 il faut le patcher. Il faut récupérer les sources du dernier patch de qemu disponible sur le souceforge de gns3, ainsi que les sources de la version correspondante de qemu que vous pouvez trouver ici.

Il faut aussi quelques biliothèque pour la compilation

sudo apt-get install libncurses5-dev zlib1g-dev libsdl-dev libpcap-dev

Une fois les fichier décompressés, copiez le  patch de qemu dans le dossier où sont les sources de qemu, appliquez le patch et finalisez l’installation.

patch -p1 -i qemu-0.13.0-mcast-udp.patch
./configure
make
sudo make install

Configuration

GNS3

Lancer gns3 à l’aide du lanceur qui se trouve dans application -> éducation, ou dans un terminal.

La fenêtre principal du logiciel va s’ouvrir avec en premier plan ceci :

Commençons donc avec l’étape une.

Sur la nouvelle fenêtre qui s’affiche vous pouvez commencer par régler votre langue et désactiver « Lauch the projet dialog at startup » pour éviter de se faire harceler à chaque démarrage de gns3. En allant dans l’onglet « Terminal Settings » vous pourrez sélectionner votre terminal favori pour vous connecter en telnet aux différentes vm.

Il faut ensuite vérifier que Dynamips est bien fonctionnel. Pour cela, allez dans Dynamips et appuyer sur le bouton « Tester », un message en ver devrait s’afficher si c’est bon.

Vous pouvez ensuite si vous le désirez modifier le répertoire de capture par défaut de wireshark dans la section « Capture ».

Il faut maintenant configurer quelques détails concernant Qemu pour pouvoir l’utiliser directement dans gns3. Nous auront besoin de 2 fichiers,  qemuwrapper.py et pemubin.py que vous pouvez trouver ici : gns3 files. Penser à cliquer sur « raw » pour télécharger directement une version propre des fichiers.

Une fois les fichiers téléchargé, copier les dans le .gns3 de votre dossier personnel et indiquer le chemin valide pour qemuwrapper.py dans la fenêtre de configuration.

C’est aussi à cette étape qu’il faut changer le chemin ver qemu. Comme on a procédé à une installation manuel à partir des sources il faut indiquer le bon chemin pour qemu et qemu-img.

Il faut maintenant ajouter une image binaire qui sera utilisé par Qemu pour les vm. Vous pouvez en créer une par vous même avec l’aide d’une débian ou utiliser une image toute faite et légère qui conviendra très bien pour ce que l’on veut faire, celle-ci par exemple.

N’oubliez pas de faire « Sauver » avant de fermer la fenêtre, sinon votre image ne sera pas prise en compte. Vous pouvez mainteant créer des vm directement dans gns3, pour l’image de qemu que j’ai indiqué, ‘root’ a pour pwd ‘root’ et l’utilisateur ‘tc’ n’en a pas.

Vous pouvez remarqué que j’ai ajouter quelques options à qemu. « -k fr » pour avoir un agencement clavier de type azerty, « -no-acpi » pour avoir moins de problème et « -nographic » pour ne pas avoir la fenêtre d’affichage qemu de chaque machine. On accède aux vm qemu dans gns3 par l’intermédiaire de telnet avec localhost et le port indiqué par gns3 dans « port console ».

Images ISO et Hyperviseur

GNS3 ayant pour objectif premier d’émuler des routeur cisco il convient de configurer gns3 pour pouvoir les utiliser. Pour cela il faut ajouter des iso cisco dans le gestionnaire d’image iso et hyperviseur. Vous pouvez trouver pas mal de ces images à cette adresse ou celle-ci ou encore en utilisant une recherche Google approprié.

Quand vous ajouter une première fois une image, n’indiquez rien dans « IDLE PC », cette valeur est calculé par gns3. Pour la calculer vous devez faire un clique droit sur le routeur (une fois celui ci ajouter dans une simulation) et cliquer sur « idle PC »

Une fois que vous avez ajouter les différentes iso dont vous voulez disposer vous pouvez commencer à faire vos simulation.

NB : Il faut lancer gns3 en tant que root ou à l’aide de sudo pour qu’il fonctionne correctement.

By

Le pourquoi du comment !

Ce blog aura principalement pour but de faire partager ce qui me semble intéressant. Cela me permettra aussi de garder une trace de ce que j’ai fait, et surtout de comment je l’ais fait et si tout se passe bien un wiki ne va pas tarder à apparaître pour servir de pense bête.