Déclaration de la classe ProcInfo


  class ProcInfo { 
    // megastructure avec le tout ce qui faut
    // un seul objet en sera cree a l'utilisation

    // contient entre autres un vector de ProcData's
    // qui sont les objets par programme/processus

    // chaque ProcData contient divers renseignements 
    // (initialisees, constants ou evoluant, comme des 
    // accumulateurs de temps, l'etat du processus, etc.)
    // et aussi un vector de ProcInstruction, nomme proGram

    // tres important, chaque ProcData a bien entendu un 
    // programCounter, qui est l'indice dans le proGram
    // donnant l'instruction EN COURS 

    // on parlera de pid de programme/processus a simuler, 
    // et ce pid est un simple entier, indice dans le
    // vector de ProcData's

    // ce pid ne doit pas etre confondu avec le pid Unix reel,
    // qui intervient egalement lors des fork()'s Unix reels, 
    // effectues pour simuler les operations de type io

    // lorsqu'il n'est pas clair du contexte, il en est fait 
    // mention explicite pour les distinguer


  public:    
    enum ProcStatus {
      STAT_WAITING, STAT_RUNNING, 
      STAT_IO, STAT_SYS, STAT_TERMINATED
    };
    enum ProcInfoOperType { // c'est pour demander a
      // la fonction
      // updateProcData(), d'avancer pas-a-pas
      // chaque processus en cours de simulation
      ADVANCE_PROC, 
      IO_TERM, CPU_TERM
    };
    
  private:
    enum ProcInstructionType { // pour la traduction
      // de chaque programme a simuler
      DO_CPU, DO_IO, DO_FORK
      // dont la syntaxe tres simple est
      // {compute,io,fork} {exact,approx,random} <nombre>
      // dans la suite, on ecrira 'compute', 'io' et 'fork' 
      // si autrement il y aurait ambiguite
    };
    
    enum ProcInstructionDurationType { // pour varier un peu
      // les manieres d'estimation des durees
      EXACT_TIME, APPROX_TIME, RANDOM_TIME
      // approx == <nombre> + (plus ou moins 20% de <nombre>)
      // random == entre 1 et <nombre>
    };

    // on appellera ce <nombre> la "Requested Duration"
    // et le resultat du calcul ci-avant la "Decided Duration"

    // toutes les durees sont en secondes,

    // rappel: meme le time_t est en secondes, a savoir le nombre
    // de seconde depuis l'Epoque Unix (1er janvier 1970)
    
    struct ProcInstruction { // pour chaque instruction
      ProcInstructionType         instructionType;
      ProcInstructionDurationType instructionDurationType;
      int                         instructionRequestedDuration; 
      int                         instructionDecidedDuration;   
      ProcInstruction (ProcInstructionType t, 
		       ProcInstructionDurationType d, 
		       int r, int v); 
      
    }; // seront mises dans le tableau proGram

    struct ProcData { // pour chaque programme/processus a simuler
      string       progName; // nom du fichier
      vector<ProcInstruction> proGram; 
      ProcStatus   procStatus;
      unsigned int programCounter; // index dans proGram
      int          currentInstructionRemainingTime; // en secondes
      time_t       startTime;      // Unix time du debut, reinitialise lors d'un 'fork'
      // (ici on parle du 'DO_FORK' et non pas du fork() unix pour le IO)
      int          totalTime;      // in seconds, Unix time at the end - startTime
      // sommes des requested durations
      unsigned int totalReqMinDuration;     // toutes
      unsigned int totalReqCPUDuration;     // compute
      unsigned int totalReqIODuration;      // io
      unsigned int totalReqSYSDuration;     // fork
      // sommes des instruction decided durations
      unsigned int totalMinDuration;        // toutes
      unsigned int totalCPUDuration;        // compute
      unsigned int totalIODuration;         // io
      unsigned int totalSYSDuration;        // fork
      // ces quatre valeurs sont affichees sur les quatre dernieres colonnes 
      // par ProcInfo::dumpProcInfoStat()
      unsigned int CPUusage;                // accumulator, which can be reset
      // cet accumulateur sert pour le multilevel queue
      time_t        waitDuration;           // accumulator
      time_t       lastTimeEnQueued;        // dans la waiting queue
      int          priorityLevel;           // pour la multilevel queue
      ProcData                 (const string &name = "<Anonymous>") ;
      int parseProg            (const string &fileLine, 
			        unsigned int lineNumber,
			        const string &fileName); // ligne par ligne, pour
      // empiler chaque fois un nouvel objet ProcInstruction dans proGram
      void computeDuration     ();
      void computeWaitDuration ();
      void resetCPUusage       ();
      int  computePriorityLevel(); // for the multilevel queue
      // ce qui suit est pour le pretty printing
      int line2print; 
      static string       linePad;
      static unsigned int reqLength;
      void   setLinePad        (const string &lp);
      string procStatus2Str    (char         *pad = 0) const;
      string progLine2Str      (const unsigned int lineNumber) const;
      string progCrtLine2Str   () const;
      string procStats         () const;
      void   resetLine2StrGen  ();
      string nextLine2Str      ();
    };
    
    map<pid_t,int>   procSupp2procData;   // pour faire correspondre
    // le pid Unix avec le pid du programme/processus a simuler
    // (ce dernier pid est un simple entier -- indice dans le tableau
    // vector<ProcData> ProcInfo::procData qui contient les renseignements
    // pour chaque processus)

    // ces deux methodes sont appelees par updateProcData(), qui fait 
    // tout avancer; elles s'occupent de 'fork' et respectivement 'io', 
    // SI l'instruction courante est de ce type, sinon elles ne font rien 
    
    int doForkIfCrtInstruction(const int procPid);
    int doIOIfCrtInstruction  (const int procPid);
    
  public:
    
    vector<ProcData>    procData;             // indexe par les pids des 
    // programmes/processus a simuler
    int                 outstandingProcCount; // decremente au fur et a
    // mesure que les processus terminent
    static const int    invalidProcPid = -1;

    double              averageWaitDuration;
    
    Scheduler          *scheduler;

    unsigned int        longestProgramLength; // pretty printing
    int                 progPerLine;          // pretty printing
    string              lineSeparator;        // pretty printing
    ProcInfo();
    ProcInfo(const string &);
    void   displayProcInfo   (ostream *);
    void   dumpProcInfoStat  (ostream *) const;
    string getProcName       (const int procPid) const;
    void computePriorityLevel(); // pour la multilevel queue
    void computeAverageWait  ();
    int    updateProcData    (const int              procPid, 
			      const ProcInfoOperType p, 
			      int                    allotedTime = 0, 
			      const pid_t            supportPid = 0);
  };

Explications supplémentaires pour l'exo 01

La structure de données centrale pour cette tâche est la deque waitQueue. Lors de chaque changement d'état vers STAT_WAITING le pid du processus en train d'être simulé doit y être empilé avec push_back(), dans la méthode enQueueProc().
L'élection se fait dans la méthode electAProc(), qui prend en compte la stratégie stockée à l'initialisation dans la donnée-membre thePolicy.
Par construction du simulateur, un processus a son pid dans waitQueue si et seulement s'il est en état STAT_WAITING. Il faut donc veiller à bien respecter cette affirmation tout le long du simulateur
L'extraction d'un processus lors de son élection se fait avec pop_back().
La mesure du temps d'attente se fait incrémentalement. Lors de chaque période de séjour dans waitQueue, au début, dans enQueueProc(), on appelle une fois time() pour enregistrer le moment dans la donnée-membre lastTimeEnQueued. Lors de l'élection, on appelle une deuxième fois time(), on fait la différence (car ce sont des secondes depuis l'Epoque) et on la rajoute à la valeur précédente de waitDuration. Ce deuxième calcul est déjà fait dans ProcInfo::ProcData::computeWaitDuration(), il suffit donc d'appeler cette fonction au bon moment depuis Scheduler::electAProc().
Rappel: pour faire tourner le programme, il faut lui fournir la liste de ses "programmes" comme premier argument, par exemple mettre 'tst0.sched tst1.sched tst2.sched'.
Maintenant
revenez à l'énoncé.

Que fait la méthode ProcData::computeWaitDuration() ?

Cette méthode a pour but le calcul du temps total passé dans la file d'attente -- donc en attendant pour avoir la main -- le CPU. Ce calcul se fait étape par étape, lors de chaque insertion dans la file, et de chaque extraction correspondante. La somme finale, pour chaque processus, est ensuite utilisée pour calculer le temps moyen d'attente, affiché dans les statistiques finales. Maintenant
revenez à l'énoncé.

Pourquoi faut-il bloquer SIGCHLD ?

Le simulateur utilise les structures de données ProcInfo et ProcData qui sont partagées entre le traitant de SIGCHLD et des méthodes comme ProcInfo::updateProcData() et Scheduler::electAProc(). Il faut donc faire attention à bien séparer les accès, car le traitant écrit dans ces structures de données (marque les fils qui ont fini, lesquels servent de timer pour la simulation des io), tandis que ProcInfo::updateProcData() lit et écrit également, pour faire avancer le processus courant, ou marquer la fin de son instruction courante, etc., et Scheduler::electAProc() aussi, pour changer l'état du processus élu. Maintenant
revenez à l'énoncé.

Explications supplémentaires pour l'exo 02

Le fait d'une part d'extraire par devant avec pop_front() et, d'autre part, de continuer à empiler par derrière, asasure la dynamique circulaire des processus.
Par contre, l'opération d'empilement doit se faire "indépendamment" de la stratégie choisie, avec la méthode enQueueProc() de la classe Scheduler. Maintenant
revenez à l'énoncé.

Explications supplémentaires pour l'exo 03

La solution proposée ici demande de faire attention à ne pas considérer d'autres processus que ceux en attente effective. Puisqu'on suggère de ne pas utiliser la waitQueue ici, mais d'utiliser directement le vector de ProcData gardé par ProcInfo dans le membre nommé procData, il faut donc tester l'état du processus avec la donnée-membre procStatus. Un candidat potentiel doit avoir procStatus égal à STAT_WAITING. Maintenant
revenez à l'énoncé.

Explications supplémentaires pour l'exo 04

L'élection proprement dite consiste à parcourir les files de la première (i.e. la plus prioritaire) vers la dernière, et à faire pop_front() une seule fois, dès que possible, c'est-à-dire sur la première file non-vide ainsi trouvée.
Une explication : le recalcul consiste à appliquer la formule récurrente de l'énoncé (ayant gardé la valeur précédente dans une donnée-membre). La conséquence immédiate en est la redistribution des pids dans les files à niveaux. Pour simplifier et ne pas commencer à "extraire" les pids d'une file pour les remettre dans une autre, on remet tout à plat : on vide le vector de deques (avec resize(0)), et on réinsère chaque pid en attente à sa place.
Attention : Le recalcul doit être fait pour TOUS les processus qui ne sont pas en STAT_TERMINATED (et la valeur stockée dans la donnée-membre priorityLevel de ProcData). Par contre, on n'insère effectivement QUE CEUX QUI SONT bien en STAT_WAITING. Les autres seront insérés dès qu'ils finiront leurs instructions en cours au moment du recalcul (typiquement le 'io'). La méthode enQueueAllProc() de Scheduler fait normalement bien ce travail.
Une petite astuce pour enQueueProc(): puisque cette méthode peut être appelèe soit lors d'un vrai empilement, soit lors du recalcul (), elle ne doit pas compter sur la bonne taille du vector. Elle va tout simplement tester et faire un resize() si nécessaire.
De plus, si elle est appelée pour le recalcul, il ne faut pas réinitialiser les lastTimeEnQueued, donc un paramètre booléen (avec une valeur par défaut, pour ne le spécifier que dans un des cas) est à prévoir.
Le recalcul des priorités doit se faire toutes les fois que
callCounter * allotedBaseTime >= mLevelTimeSlice
callCounter est incrémenté à chaque appel, et les deux autres valeurs sont constantes donnéees à l'initialisation (ou "câblées" dans le constructeur de Scheduler).
Bien entendu, lorsque la condition est réaliséée, non seulement le calcul est effectué selon la formule de l'énoncé, mais aussi callCounter est bien remis à zéro. Maintenant
revenez à l'énoncé.