![]() |
Projet PAF : Développement d'un petit système d'exploitation |
Nous allons maintenant rentrer dans le vif du sujet et s’intéresser aux processus et plus particulièrement : aux structures de données nécessaire pour les représenter, à leur création et démarrage, et au basculement de l’exécution d’un processus à un autre (changement de contexte).
Dans cette page, nous allons parcourir les différents concepts mis en œuvre. Ne commencez pas à coder tout de suite, lisez la page en entier et essayer de comprendre les différentes parties.
Lors de la création d’un processus, on indiquera quelle fonction on souhaite faire exécuter par celui-ci. Cette fonction s’exécutera dans le contexte de ce processus et quand elle sera terminée, le processus sera considéré comme terminé.
Ce mode de fonctionnement (regrouper le système d’exploitation ainsi que le code des différents processus dans une seule image) permettra d’éviter de devoir se préoccuper pour le moment du chargement de plusieurs exécutables en mémoire, du partitionnement de celle-ci, etc., le tout étant fait par l’éditeur des liens ici.
Pour la création d’un processus, on cherchera donc à coder une fonction ayant la signature suivante :
typedef void *(*task_func_c)(void *);
int new_task(task_func_t f, void *arg);
La fonction exécutée dans le nouveau processus aura donc la signature suivante :
void * foo(void *arg);
L’argument (de type void *
) permettra de passer à cette fonction des données en entrée, et la valeur de retour (également de type void *
) lui permettra de transmettre des données en retour au système à la fin du processus.
Une fois les processus créés, il va falloir les programmer pour l’exécution.
Les différents processus vont se partager le temps processeur les uns à la suite des autres (time sharing). Un timer permettra régulièrement au système d’exploitation de reprendre la main (multitâche préemptif) pour allouer le temps processeur à un autre processus.
Lors d’un changement de contexte, il faudra sauvegarder l’état du processus actuel puis restaurer l’état du processus suivant.
Il va donc falloir trouver ce qui fait partie du contexte d’exécution d’un processus, c’est-à-dire ce qui doit être sauvegardé (ainsi que par qui et où) et restauré lors du passage de l’exécution d’un processus à un autre.
Cela peut paraître anecdotique et sans lien avec le sujet mais on commencera par s’intéresser à ce qui se passe lors d’un appel de fonction. Cherchez les réponses aux questions suivantes, notamment dans le document Procedure Call Standard for the Arm Architecture (le lien est dans la première page du projet) :
Nous aurons besoin du mécanisme d’exception pour gérer les quantums de temps processeur et les changements de contexte.
Le timer que nous allons utiliser est le SysTick qui permet le déclenchement périodique d’une exception.
Regardez comment il fonctionne et comment le programmer.
Nous utiliserons également une deuxième exception, qui sera chargée uniquement de faire les changements de contexte : PendSV. L’exception SysTick programmera le déclenchement de cette dernière si un changement de contexte est nécessaire.
Regardez comment déclencher cette exception.
Regardez ce qui se passe lorsqu’une exception se déclenche et se qui se passe à la fin du traitement d’une interruption.
Vous devriez maintenant avoir un aperçu de ce que constitue le contexte d’exécution d’un processus, comment et où se passe sa sauvegarde lors du déclenchement d’une exception.
Regardez les modes d’exécution Handler et Thread, ainsi que les deux pointeurs de pile proposés (Main stack et Process stack). Comment passer de l’une à l’autre ? Comment pourrions-nous les utiliser ?
Regardez les deux niveaux de privilèges et comment passer de l’un à l’autre.
Une fois que vous avez vu les différentes parties ci-dessus et cherché les informations demandées, vous devriez avoir tout en main pour écrire :
Il s’agit probablement de l’étape la plus compliquée de tout le projet. Prenez votre temps, n’hésitez pas à interagit avec votre encadrant, notamment à chaque étape pour vérifier que vous partez dans la bonne direction.
N’hésitez pas non plus à le solliciter pour qu’il vous explique, s’il ne l’a pas déjà fait, comment utiliser gdb
pour déboguer votre code avec QEMU
.
Testez avec au minimum deux processus. Vérifiez qu’ils fonctionnent bien.
Voici quelques étapes que je vous conseille de suivre :
Configurez le mécanisme SysTick pour qu’il génère une exception toutes les secondes (approximativement). Affichez un caractère à chaque déclenchement pour vérifier que tout fonctionne.
Faites en sorte que le gestionnaire d’exception SysTick déclenche l’exception PendSV. De même, testez en faisant afficher un caractère au gestionnaire de PendSV.
Créez les structures de données qui vont être nécessaires pour gérer un processus et les changements de contexte :
Créez deux fonctions (qui seront exécutées dans deux processus différents) qui ne font qu’en boucle afficher un caractère et faire une attente active
Codez l’ordonnanceur et appelez le depuis le gestionnaire d’exception SysTick
Codez (en assembleur) le gestionnaire d’exception PendSV (qui fait le véritable changement de contexte), la partie sauvegarde du contexte du processus courant (si celui-ci n’est pas inexistant) :
Dans main, créez deux processus
Configurez les interruptions pour que l’exception PendSV ait la priorité minimale et SysTick, la priorité maximale
Une fois tous les processus créés et les interruptions configurées, relâchez les privilèges
Testez et débuggez le tout…
Discutez avec votre encadrant avant d’aller plus loin, c’est-à-dire à la page suivante.
© Copyright 2020 Guillaume Duc. Le contenu de cette page est mis à disposition selon les termes de la Licence Creative Commons Attribution - Partage dans les Mêmes Conditions 4.0 International (à l'exception des exemples de code tirés du noyau Linux et qui sont distribués sous leurs licences d'origine).