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