Projet système : roadmap : Différence entre versions

De Ensiwiki
Aller à : navigation, rechercher
(Introduction)
Ligne 36 : Ligne 36 :
 
; [[#Phase 3|Phase 3]] : gérer l'ordonnancement, la création dynamique, la terminaison et la filiation des processus (toujours au niveau noyau) ;
 
; [[#Phase 3|Phase 3]] : gérer l'ordonnancement, la création dynamique, la terminaison et la filiation des processus (toujours au niveau noyau) ;
 
; [[#Phase 4|Phase 4]] : gérer la communication (via l'emploi de sémaphores) et l'endormissement (appel [[Projet_système_:_spécification#wait_clock - Attente temporisée|wait_clock]]) des processus ;
 
; [[#Phase 4|Phase 4]] : gérer la communication (via l'emploi de sémaphores) et l'endormissement (appel [[Projet_système_:_spécification#wait_clock - Attente temporisée|wait_clock]]) des processus ;
; [[#Phase 5|Phase 5]] : ajouter le mode utilisateur ;
+
; [[#Phase 5|Phase 5]] : {{PC_FPGA|texte_pc=ajouter le mode utilisateur ;|texte_fpga=développer le chargeur de noyau, et mettre en place une séparation des codes noyau et utilisateur, en s'inspirant du mode utilisateur (sur PC) ;}}
 
; [[#Phase 6|Phase 6]] : développer un pilote de console ;
 
; [[#Phase 6|Phase 6]] : développer un pilote de console ;
 
; [[#Phase 7|Phase 7]] : développer un interprète de commandes, ''shell'' ;
 
; [[#Phase 7|Phase 7]] : développer un interprète de commandes, ''shell'' ;
Ligne 61 : Ligne 61 :
 
le support de la <tt>libc</tt>, les facilités de débogage.
 
le support de la <tt>libc</tt>, les facilités de débogage.
  
* Ce n'est plus un ''shell'' qui lance votre programme mais un chargeur développé spécifiquement pour cette tâche. Il faut donc respecter sa convention de chargement basée sur le standard ''multiboot'' (ceci est déjà implanté dans le code de base qui vous est fourni).
+
* {{PC_FPGA|texte_pc=Ce n'est plus un ''shell'' qui lance votre programme mais un chargeur développé spécifiquement pour cette tâche. Il faut donc respecter sa convention de chargement basée sur le standard ''multiboot'' (ceci est déjà implanté dans le code de base qui vous est fourni).|texte_fpga=Ce n'est plus un ''shell'' qui lance votre programme mais le debugger qui charge la mémoire (dans un premier temps), puis un chargeur que vous développerez en [[#Phase 5|Phase 5]].}}
  
* L'absence du support usuellement fourni par le noyau Unix nous prive des E/S. L'absence de gestion des entrées n'est pas un handicap majeur pour les cinq premières phases du projet mais les sorties sont bien évidemment nécessaires dès le début pour pouvoir déboguer. La mémoire vidéo étant couplée en mémoire principale, les écritures à l'écran se font sans faire intervenir les interruptions. Ceci peut donc se faire sans problème dans cette phase. Il est important de vérifier le bon fonctionnement de vos primitives d'affichage car c'est sur elles que reposeront vos tests tout au long du projet.
+
* L'absence du support usuellement fourni par le noyau Unix nous prive des E/S. L'absence de gestion des entrées n'est pas un handicap majeur pour les cinq premières phases du projet mais les sorties sont bien évidemment nécessaires dès le début pour pouvoir déboguer. {{PC_FPGA|texte_pc=La mémoire vidéo étant couplée en mémoire principale, les écritures à l'écran se font sans faire intervenir les interruptions. Ceci peut donc se faire sans problème dans cette phase.|texte_fpga=La sortie se faisant par le port série, la visualisation des messages envoyés par le système sera visible sur un terminal série connecté au système (par exemple avec l'utilitaire <tt>cu</tt>, si on utilise la carte avec un câble série connecté au PC de développement, ou avec <tt>vpio</tt> lancé depuis <tt>xmd</tt> si on utilise la plateforme virtuelle). Au niveau de cette phase, la gestion de l'interface série, se fera sans interruption (attente active).}} Il est important de vérifier le bon fonctionnement de vos primitives d'affichage car c'est sur elles que reposeront vos tests tout au long du projet.
  
* L'absence de support de la <tt>libc</tt>, nous prive notamment d'E/S formatées, de gestion de la mémoire, d'<tt>exit</tt>. Pour la gestion de la mémoire, nous vous fournissons un allocateur pour la mémoire du noyau, nécessaire dès la [[#Phase 3|Phase 3]], ainsi qu'un allocateur pour les piles utilisateur des processus, nécessaire à partir de la [[#Phase 5|Phase 5]]. Pour pallier le manque de <tt>libc</tt> en ce qui concerne les sorties, nous vous fournissons une mini bibliothèque, incluant notamment <tt>printf</tt>, à interfacer avec vos primitives de gestion de l'écran pour faciliter votre débogage. Pour la terminaison d'un programme, vous pouvez faire un appel à <tt>void reboot()</tt> qui a pour effet de redémarrer la machine.
+
* L'absence de support de la <tt>libc</tt>, nous prive notamment d'E/S formatées, de gestion de la mémoire, d'<tt>exit</tt>. Pour la gestion de la mémoire, nous vous fournissons un allocateur pour la mémoire du noyau, nécessaire dès la [[#Phase 3|Phase 3]], ainsi qu'un allocateur pour les piles utilisateur des processus, nécessaire à partir de la [[#Phase 5|Phase 5]]. Pour pallier le manque de <tt>libc</tt> en ce qui concerne les sorties, nous vous fournissons une mini bibliothèque, incluant notamment <tt>printf</tt>, à interfacer avec vos primitives de gestion de l'écran pour faciliter votre débogage. Pour la terminaison d'un programme, vous pouvez faire un appel à <tt>void reboot()</tt> qui a pour effet de redémarrer le système.
  
* Au niveau du débogage, vous avez toujours la possibilité d'utiliser <tt>gdb</tt>/<tt>ddd</tt>. Il y a quelques petites différences d'utilisation liées au fait que le code à déboguer s'exécute sur une machine distante mais les fonctionnalités offertes par le débogueur sont les mêmes. Par ailleurs, vous êtes privés de la mémorisation de l'interaction faite par un <tt>xterm</tt> ainsi que de la possibilité de rediriger les sorties de votre programme dans un fichier. Vous êtes donc limités à ne pouvoir inspecter à un instant donné que les 25 lignes affichables sur l'écran.
+
* Pour le débogage, vous avez toujours la possibilité d'utiliser <tt>gdb</tt>/<tt>ddd</tt> sur PC ou <tt>mb-gdb</tt> pour le FPGA. Il y a quelques petites différences d'utilisation liées au fait que le code à déboguer s'exécute sur une machine distante mais les fonctionnalités offertes par le débogueur sont les mêmes. Par ailleurs, vous êtes privés de la mémorisation de l'interaction faite par un <tt>xterm</tt> ainsi que de la possibilité de rediriger les sorties de votre programme dans un fichier. {{PC_Only|texte_pc=Vous êtes donc limités à ne pouvoir inspecter à un instant donné que les 25 lignes affichables sur l'écran.}}
  
 
== Phase 2 ==
 
== Phase 2 ==
Ligne 173 : Ligne 173 :
 
Dans les phases précédentes, les programmes de tests étaient inclus  
 
Dans les phases précédentes, les programmes de tests étaient inclus  
 
directement dans le noyau, sous forme de fonctions. L'application en mode  
 
directement dans le noyau, sous forme de fonctions. L'application en mode  
utilisateur, elle, n'est pas liée avec le noyau. Elle est liée séparément et
+
utilisateur, elle, n'est pas liée avec le noyau. Elle est liée séparément  
chargée à l'adresse 16&nbsp;M. Il faut donc bien avoir conscience que le noyau et  
+
{{PC_FPGA|texte_pc=et chargée à l'adresse 16&nbsp;M.|texte_fpga=et chargée à l'adresse 0x2018 0000}}
 +
Il faut donc bien avoir conscience que le noyau et  
 
l'application sont deux programmes indépendants qui ne partagent pas de  
 
l'application sont deux programmes indépendants qui ne partagent pas de  
mémoire. Le <tt>crt0</tt> du noyau a mis en place une protection qui empêche  
+
mémoire.
l'application de lire ou écrire des données du noyau. Pour la même raison, il  
+
{{PC_FPGA|texte_pc=Le <tt>crt0</tt> du noyau a mis en place une protection qui empêche l'application de lire ou écrire des données du noyau. Pour la même raison, il est impossible pour l'application d'appeler des fonctions du noyau.|texte_fpga=Si le processeur le permettait il serait possible de mettre en place une protection qui empêche l'application de lire ou écrire des données du noyau dans le <tt>crt0</tt> du noyau. Pour cette raison (et se placer dans ce contexte), on s'interdira d'appeler directement des fonctions du noyau depuis l'application.}}
est impossible pour l'application d'appeler des fonctions du noyau.
+
  
 
==== Réalisation d'une bibliothèque des appels noyau ====
 
==== Réalisation d'une bibliothèque des appels noyau ====
Ligne 184 : Ligne 184 :
 
Dans les étapes précédentes, l'application appelait les services du
 
Dans les étapes précédentes, l'application appelait les services du
 
noyau par pur appel procédural. Dès que le noyau et l'application sont séparés,
 
noyau par pur appel procédural. Dès que le noyau et l'application sont séparés,
ceci n'est plus possible. En outre, il faut réaliser une transition du mode esclave vers
+
ceci n'est plus possible.
le mode maître. L'application ne peut le faire qu'en exécutant une instruction
+
{{PC_FPGA|texte_pc=En outre, il faut réaliser une transition du mode esclave vers le mode maître. L'application ne peut le faire qu'en exécutant une instruction <tt>int</tt>.|texte_fpga=En outre, on réalisera un appel simulant la transition d'un mode esclave (interruptible) vers un mode maître (non interruptible). L'application le fera en exécutant un appel à une routine placée dans <tt>USER VECTOR</tt> (cf. documentation du Microblaze p.31).}}
<tt>int</tt>. Comme on veut laisser au programmeur la facilité d'écrire dans son
+
Comme on veut laisser au programmeur la facilité d'écrire dans son
 
source des appels au noyau par appel de fonction, il faut écrire tout un
 
source des appels au noyau par appel de fonction, il faut écrire tout un
 
ensemble de fonctions (une par appel noyau) qui auront pour but d'exécuter une
 
ensemble de fonctions (une par appel noyau) qui auront pour but d'exécuter une
Ligne 195 : Ligne 195 :
 
==== Écriture du module de récupération de l'<tt>int</tt> ====
 
==== Écriture du module de récupération de l'<tt>int</tt> ====
  
Puisque l'application déclenche une interruption par <tt>int</tt>,
+
{{PC_FPGA|texte_pc=Puisque l'application déclenche une interruption par <tt>int</tt>, il faut bien la récupérer dans le noyau.|texte_fpga=Puisque l'application déclenche un appel noyau, il faut bien le récupérer dans le noyau.}}
il faut bien la récupérer dans le noyau.
+
 
Il est donc nécessaire d'écrire un traitant pour gérer ces interruptions
 
Il est donc nécessaire d'écrire un traitant pour gérer ces interruptions
 
logicielles. Les remarques évoquées au sujet du traitant des interruptions
 
logicielles. Les remarques évoquées au sujet du traitant des interruptions
 
''timer'' en [[#Phase 2|Phase 2]], quant au découpage assembleur / C, restent valables ici.
 
''timer'' en [[#Phase 2|Phase 2]], quant au découpage assembleur / C, restent valables ici.
 +
{{FPGA_Only|texte_fpga=Il faudra mettre en place le masquage des interruptions non prévu dans le cas de l'appel à <tt>USER VECTOR</tt>.}}
 
Le point délicat ajouté dans leur gestion
 
Le point délicat ajouté dans leur gestion
 
est de retrouver les paramètres de l'appel noyau pour les passer
 
est de retrouver les paramètres de l'appel noyau pour les passer
à la routine du noyau qui en a besoin. Il y a deux stratégies possibles pour
+
à la routine du noyau qui en a besoin.
transmettre les paramètres&nbsp;:
+
{{PC_Only|texte_pc=Il y a deux stratégies possibles pour transmettre les paramètres&nbsp;:
 
* les stocker sur la pile utilisateur&nbsp;;
 
* les stocker sur la pile utilisateur&nbsp;;
 
* utiliser les registres du processeur.
 
* utiliser les registres du processeur.
 
+
La première solution est assez compliquée, en particulier parce qu'elle oblige à gérer d'éventuels défauts de page. Il est beaucoup plus simple et élégant de recourir à la seconde solution. Cela limite le nombre de paramètres à six, mais ce n'est pas un problème. Nous vous demandons d'utiliser la solution basée sur les registres.}}
La première solution est assez compliquée, en particulier parce qu'elle oblige à
+
{{FPGA_Only|texte_fpga=La stratégie à adopter pour transmettre les paramètres est la suivante&nbsp;:
gérer d'éventuels défauts de page. Il est beaucoup plus simple et élégant de
+
* utiliser les registres du processeur.}}
recourir à la seconde solution. Cela limite le nombre de paramètres à six, mais
+
ce n'est pas un problème. Nous vous demandons d'utiliser la solution basée sur
+
les registres.
+
  
 
==== Lancement des processus de l'application en mode esclave ====
 
==== Lancement des processus de l'application en mode esclave ====
Ligne 218 : Ligne 215 :
 
l'application par un appel procédural. Ceci n'est plus possible lorsque le noyau
 
l'application par un appel procédural. Ceci n'est plus possible lorsque le noyau
 
s'exécute en mode maître et l'application en mode esclave puisque le lancement
 
s'exécute en mode maître et l'application en mode esclave puisque le lancement
d'un processus par le noyau correspond à une transition de mode. Il faut donc
+
d'un processus par le noyau correspond à une transition de mode.
trouver un moyen de lancer un processus en faisant une transition de mode.
+
{{PC_FPGA|texte_pc=Il faut donc trouver un moyen de lancer un processus en faisant une transition de mode. Il faut pour cela utiliser une instruction <tt>iret</tt>.|texte_fpga=Pour faire comme si le processeur était capable de gérer les transitions de mode, il faut donc trouver un moyen de lancer un processus en faisant une transition de mode. Il faut pour cela utiliser une instruction <tt>retid</tt>.}}
Il faut pour cela utiliser une instruction <tt>iret</tt>.
+
 
Une application doit s'exécuter avec les interruptions démasquées.
 
Une application doit s'exécuter avec les interruptions démasquées.
  
 
==== Gestion de deux piles par processus ====
 
==== Gestion de deux piles par processus ====
  
Lorsque le processeur <tt>x86</tt> réalise une transition du mode esclave vers le
+
{{PC_FPGA|texte_pc=Lorsque le processeur <tt>x86</tt> réalise une transition du mode esclave vers le mode maître, il procède également à un changement de pile. Lors de la transition esclave vers maître, le pointeur de pile est chargé avec une valeur prise dans la structure <tt>TSS</tt>, et lors de la transition maître vers esclave, le registre <tt>esp</tt> est chargé avec la valeur sauvegardée dans la pile maître. Ce mécanisme est imposé par le processeur, il est impossible d'y échapper. À partir de cette phase, il faut donc gérer deux piles par processus, une pour le mode maître et une pour le mode esclave.|texte_fpga=Le principe des 2 piles est à mettre en place pour l'implémentation sur le microblaze.}}
mode maître, il procède également à un changement de pile. Lors de la transition
+
esclave vers maître, le pointeur de pile est chargé avec une valeur prise dans
+
la structure <tt>TSS</tt>, et lors de la transition maître vers esclave, le
+
registre <tt>esp</tt> est chargé avec la valeur sauvegardée dans la pile maître. Ce
+
mécanisme est imposé par le processeur, il est impossible d'y échapper. À partir
+
de cette phase, il faut donc gérer deux piles par processus, une pour le mode
+
maître et une pour le mode esclave.
+
  
 
==== Protection de l'espace des entrées/sorties ====
 
==== Protection de l'espace des entrées/sorties ====
  
L'espace des registres d'E/S ne doit être accessible qu'en mode maître. Cette
+
{{PC_Only|texte_pc=L'espace des registres d'E/S ne doit être accessible qu'en mode maître. Cette protection peut être mise en oeuvre en affectant un <tt>iopl</tt> de 0 dans les ''flags'' de l'application. Lorsque cette protection est mise en place, l'application ne peut donc pas écrire directement. Pour les besoins des sorties des programmes de test, vous devrez donc créer un appel système [[Projet_système_:_spécification#cons_write - Affichage sur le terminal|cons_write]] pour gérer l'affichage à l'écran. Le formatage des sorties effectué par <tt>printf</tt> doit quant à lui rester en mode utilisateur.}}
protection peut être mise en oeuvre en affectant un <tt>iopl</tt> de 0 dans les
+
 
''flags'' de l'application. Lorsque cette protection est mise en place,
+
=== Chargeur du noyau ===
l'application ne peut donc pas écrire directement. Pour les besoins des sorties
+
 
des programmes de test, vous devrez donc créer un appel système
+
{{FPGA_Only|texte_fpga=À partir de cette phase, on demande de mettre en place un chargeur du noyau. Ce programme sera mis  dans la bram (0x0000 à 0x1FFF). Au ''reset'' du système, la carte envoie sur le port série un message du type : "Attente du noyau". À partir de ce moment, les octets du noyau pourront être envoyés à la carte (via le port série) afin d'être placés aux adresses 0x2010 0000 et suivantes. Un fois le noyau chargé en mémoire, le système démarre.}}
[[Projet_système_:_spécification#cons_write - Affichage sur le terminal|cons_write]]
+
pour gérer l'affichage à l'écran. Le formatage des sorties
+
effectué par <tt>printf</tt> doit quant à lui rester en mode utilisateur.
+
  
 
=== Tests ===
 
=== Tests ===
  
En fin de [[#Phase 5|Phase 5]], vous devez au minimum valider les tests suivants&nbsp;:
+
{{PC_Only|texte_pc=En fin de [[#Phase 5|Phase 5]], vous devez au minimum valider les tests suivants&nbsp;:}}
  
 
==== Test du mode utilisateur ====
 
==== Test du mode utilisateur ====
  
Écrire et faire fonctionner le programme suivant&nbsp;:
+
{{PC_Only|texte_pc=Écrire et faire fonctionner le programme suivant&nbsp;:
 
* imprimer le message <tt>Je démarre</tt>
 
* imprimer le message <tt>Je démarre</tt>
 
* exécuter une instruction privilégiée
 
* exécuter une instruction privilégiée
Ligne 257 : Ligne 243 :
 
* terminer
 
* terminer
  
À l'exécution, on doit voir le premier message mais pas le second car lors de
+
À l'exécution, on doit voir le premier message mais pas le second car lors de l'exécution de l'instruction privilégiée, il doit se produire une exception, que le noyau pourra récupérer pour tuer le programme fautif. Cette gestion des exceptions n'est pas obligatoire dans le cahier de charges de base du projet, et si elle n'est pas implémentée, votre noyau plantera en cas d'exception.}}
l'exécution de l'instruction privilégiée, il doit se produire une exception, que
+
le noyau pourra récupérer pour tuer le programme fautif. Cette
+
gestion des exceptions n'est pas obligatoire dans le cahier de charges de base
+
du projet, et si elle n'est pas implémentée, votre noyau plantera en cas
+
d'exception.
+
  
 
==== Test de la protection de la mémoire et de l'espace d'E/S ====
 
==== Test de la protection de la mémoire et de l'espace d'E/S ====
  
C'est une variation du programme précédent&nbsp;: au lieu d'exécuter une instruction
+
{{PC_Only|texte_pc=C'est une variation du programme précédent&nbsp;: au lieu d'exécuter une instruction privilégiée, le programme essaye d'accéder à l'espace d'E/S, par exemple en tentant de modifier la position du curseur, ou à une zone mémoire protégée, par exemple la mémoire vidéo. Là aussi, le noyau pourra récupérer l'exception et tuer le programme fautif.}}
privilégiée, le programme essaye d'accéder à l'espace d'E/S, par exemple en
+
tentant de modifier la position du curseur, ou à une zone mémoire protégée, par
+
exemple la mémoire vidéo. Là aussi, le noyau pourra récupérer l'exception et
+
tuer le programme fautif.
+
  
 
==== Test du passage de paramètres ====
 
==== Test du passage de paramètres ====
  
Écrire un programme qui crée plusieurs processus en leur passant des paramètres
+
{{PC_Only|texte_pc=Écrire un programme qui crée plusieurs processus en leur passant des paramètres différents. Les processus se contentent d'imprimer les paramètres reçus et se terminent.}}
différents. Les processus se contentent d'imprimer les paramètres reçus et se
+
terminent.
+
  
 
== Phase 6 ==
 
== Phase 6 ==
  
Cette partie consiste à implémenter une [[Projet_système_:_spécification#Le pilote de console|console]]. Les sorties sur écran étant
+
Cette partie consiste à implémenter une [[Projet_système_:_spécification#Le pilote de console|console]].
normalement opérationnelles depuis la [[#Phase 1|Phase 1]], il reste à coder un [[Projet_système_:_spécification#Le clavier|pilote de clavier]]. Il faut réaliser un vrai pilote, c'est-à-dire fonctionnant par
+
 
interruptions.
+
{{PC_FPGA|texte_pc=Les sorties sur écran étant normalement opérationnelles depuis la [[#Phase 1|Phase 1]], il reste à coder un [[Projet_système_:_spécification#Le clavier|pilote de clavier]]. Il faut réaliser un vrai pilote, c'est-à-dire fonctionnant par interruptions. Ce qui vous est principalement demandé ici, c'est de gérer les événements liés au clavier et d'implémenter les appels système [[Projet_système_:_spécification#cons_read - Lecture sur le terminal|cons_read]] et [[Projet_système_:_spécification#cons_write - Affichage sur le terminal|cons_write]]. Tout ce qui concerne la reconnaissance des ''scan codes'' vous est fourni.|texte_fpga=Il faut coder un pilote d'entrée (entrée Rx du port série). Il faut réaliser un vrai pilote, c'est-à-dire fonctionnant par interruptions. Ce qui vous est principalement demandé ici, c'est de gérer les événements liés aux caractères arrivant sur le port série et d'implémenter les appels système [[Projet_système_:_spécification#cons_read - Lecture sur le terminal|cons_read]] et [[Projet_système_:_spécification#cons_write - Affichage sur le terminal|cons_write]].}}
  
Ce qui vous est principalement
+
Le travail qui vous est demandé concerne les mécanismes de base de gestion des E/S.
demandé ici, c'est de gérer les événements liés au clavier et d'implémenter les
+
appels système [[Projet_système_:_spécification#cons_read - Lecture sur le terminal|cons_read]] et [[Projet_système_:_spécification#cons_write - Affichage sur le terminal|cons_write]]. Tout ce qui concerne la
+
reconnaissance des ''scan codes'' vous est fourni. Le travail qui vous est demandé
+
concerne les mécanismes de base de gestion des E/S.
+
  
 
== Phase 7 ==
 
== Phase 7 ==

Version du 4 juin 2010 à 10:44

AttentionCette page est maintenue par les enseignants et utilisée par les élèves de la matière concernée. Vos contributions sont les bienvenues, mais merci d'en discuter avant de faire des modifications non triviales de la page, pour être sûr de ne pas perturber le déroulement du cours.


Résumé

Ce document est complémentaire aux spécifications du mini système d'exploitation que vous devrez programmer. Il vise à vous aider en vous fournissant une méthode de développement bien précise.

Introduction

Habituellement, la conduite d'un projet consiste à passer par les phases d'analyse du problème, de conception de l'architecture, de codage et de débogage. Cette méthode ne peut être strictement appliquée dans le cadre du projet système à cause du trop court laps de temps imparti. En effet, si on vous demandait de la respecter, il est probable que vous ne seriez en mesure de produire les premières lignes de code que très tard dans le calendrier, et qu'à la fin du projet, très peu de choses fonctionneraient. Or, nous considérons que le principal apport du projet système réside dans l'expérience que vous apportera le fait de faire fonctionner votre noyau.

Nous vous demandons donc de suivre un plan de développement en sept phases, de bien respecter dans chaque étape le cycle analyse-architecture-codage-débogage, mais d'accepter de faire chaque phase sans avoir une vision très détaillée du fonctionnement final de votre noyau. Par exemple, les phases 2 à 5 visent à vous guider pas à pas dans l'implémentation d'un noyau de processus, en raffinant progressivement le cahier des charges. Cette méthodologie ne doit pas être perçue comme une contrainte, son unique but est de vous aider à organiser le développement de votre projet.

Plan de développement

Le développement du projet système s'effectue en sept phases obligatoires et une partie facultative. En quelques mots, les différentes étapes consistent à :

Phase 1 
prendre en main l'environnement de développement (chargement et débogage de noyau) et gérer l'affichage à l'écran ;
Phase 2 
gérer la notion de processus, et le changement de contexte entre deux processus (au niveau noyau) ;
Phase 3 
gérer l'ordonnancement, la création dynamique, la terminaison et la filiation des processus (toujours au niveau noyau) ;
Phase 4 
gérer la communication (via l'emploi de sémaphores) et l'endormissement (appel wait_clock) des processus ;
Phase 5 
PC
ajouter le mode utilisateur ;
FPGA
développer le chargeur de noyau, et mettre en place une séparation des codes noyau et utilisateur, en s'inspirant du mode utilisateur (sur PC) ;
Phase 6 
développer un pilote de console ;
Phase 7 
développer un interprète de commandes, shell ;
Phase d'extension 
ajout de nouvelles fonctionnalités (par exemple, espaces virtuels d'adressage).

Toutes les phases ne sont pas équivalentes en termes de difficulté et de temps de développement/mise au point. En particulier, les phases 2 et 5 concernent des concepts clés du projet et sont délicates à déboguer.

Pour bien maîtriser l'évolution de votre travail au fil du projet, nous vous invitons à conserver une version de votre code pour au moins chacune des phases validées, en utilisant l'outil de gestion de versions svn, et nous vous demandons de faire un point régulier chaque semaine avec un enseignant.

Phase 1

Cette partie consiste à prendre en main l'environnement de travail qui vous sera nécessaire tout au long du projet. La principale différence avec les projets précédents concerne la façon de tester et déboguer le code que vous produisez.

Passer de l'environnement d'exécution Unix, que vous avez l'habitude d'utiliser pour vos projets, à un PC quasiment nu change beaucoup de choses : le démarrage du programme, le support des entrées/sorties, le support de la libc, les facilités de débogage.

  • PC
    Ce n'est plus un shell qui lance votre programme mais un chargeur développé spécifiquement pour cette tâche. Il faut donc respecter sa convention de chargement basée sur le standard multiboot (ceci est déjà implanté dans le code de base qui vous est fourni).
    FPGA
    Ce n'est plus un shell qui lance votre programme mais le debugger qui charge la mémoire (dans un premier temps), puis un chargeur que vous développerez en Phase 5.
  • L'absence du support usuellement fourni par le noyau Unix nous prive des E/S. L'absence de gestion des entrées n'est pas un handicap majeur pour les cinq premières phases du projet mais les sorties sont bien évidemment nécessaires dès le début pour pouvoir déboguer.
    PC
    La mémoire vidéo étant couplée en mémoire principale, les écritures à l'écran se font sans faire intervenir les interruptions. Ceci peut donc se faire sans problème dans cette phase.
    FPGA
    La sortie se faisant par le port série, la visualisation des messages envoyés par le système sera visible sur un terminal série connecté au système (par exemple avec l'utilitaire cu, si on utilise la carte avec un câble série connecté au PC de développement, ou avec vpio lancé depuis xmd si on utilise la plateforme virtuelle). Au niveau de cette phase, la gestion de l'interface série, se fera sans interruption (attente active).
    Il est important de vérifier le bon fonctionnement de vos primitives d'affichage car c'est sur elles que reposeront vos tests tout au long du projet.
  • L'absence de support de la libc, nous prive notamment d'E/S formatées, de gestion de la mémoire, d'exit. Pour la gestion de la mémoire, nous vous fournissons un allocateur pour la mémoire du noyau, nécessaire dès la Phase 3, ainsi qu'un allocateur pour les piles utilisateur des processus, nécessaire à partir de la Phase 5. Pour pallier le manque de libc en ce qui concerne les sorties, nous vous fournissons une mini bibliothèque, incluant notamment printf, à interfacer avec vos primitives de gestion de l'écran pour faciliter votre débogage. Pour la terminaison d'un programme, vous pouvez faire un appel à void reboot() qui a pour effet de redémarrer le système.
  • Pour le débogage, vous avez toujours la possibilité d'utiliser gdb/ddd sur PC ou mb-gdb pour le FPGA. Il y a quelques petites différences d'utilisation liées au fait que le code à déboguer s'exécute sur une machine distante mais les fonctionnalités offertes par le débogueur sont les mêmes. Par ailleurs, vous êtes privés de la mémorisation de l'interaction faite par un xterm ainsi que de la possibilité de rediriger les sorties de votre programme dans un fichier.
    PC
    Vous êtes donc limités à ne pouvoir inspecter à un instant donné que les 25 lignes affichables sur l'écran.

Phase 2

C'est une phase importante car elle introduit des concepts clés pour le projet. Il y a peu de code à produire mais il faut prendre le temps de bien cerner et comprendre tout ce qu'elle implique. Elle consiste à gérer :

  • dans un premier temps, la notion de processus et de changement de contexte entre deux processus ;
  • dans un second temps, le timer et les interruptions, pour obtenir un système à temps partagé.

Jusqu'à la Phase 4 incluse, les processus à gérer s'exécutent uniquement en mode superviseur. Ils ont une pile chacun pour leur exécution. Nous vous conseillons de commencer avec deux processus infinis, créés statiquement, qui se passent la main à tour de rôle, par un appel direct à context_switch. Les piles peuvent être allouées statiquement ou par appel à l'allocateur de mémoire. Le code suivant propose un exemple minimaliste de processus de test :

int tstA(void *arg)
{
	unsigned long i;
	while (1) {
		printf("A"); /* l'autre processus doit afficher des 'B' */
		/* boucle d'attente pour ne pas afficher trop de caractères */
		for (i = 0; i < 5000000; i++); 
		context_switch();		
	}
}

Une fois le mécanisme de base opérationnel, vous pouvez ajouter la gestion des interruptions et partager le temps d'exécution entre deux processus. Pour gérer le temps partagé, vous devez programmer le timer, ce qui ne présente aucune difficulté majeure, et gérer les interruptions matérielles qu'il génère (cf. Gestion du temps). Pour répondre aux interruptions du timer, il est nécessaire d'écrire un traitant en assembleur qui se termine par une instruction iret. Ce programme en assembleur ne doit faire que le strict nécessaire pour déléguer le gros du travail à une fonction écrite en C appelée par une instruction call. Dans un premier temps cette fonction fera toujours un changement de contexte entre les deux processus. Dans un deuxième temps, il faudra suivre le compte du temps écoulé afin de changer de contexte à une fréquence SCHEDFREQ (=50) différente de CLOCKFREQ (=100). Le code suivant propose un exemple de processus test pour le temps partagé. Le démasquage des interruptions après l'appel à printf est nécessaire pour que le processus courant puisse être interrompu. En principe, à cette exception près, tout votre code noyau doit toujours tourner en mode masqué.

int tstA(void *arg)
{
	unsigned long i;
	while (1){
		printf("A"); /* l'autre processus doit afficher des 'B' */
		asm volatile ("sti"); /* demasquage des interruptions */
		/* une ou plusieurs it du timer peuvent survenir pendant cette boucle d'attente */
		for (i = 0; i < 5000000; i++); 
		asm volatile ("cli"); /* masquage des interruptions */	
	}
}

Phase 3

Cette étape est la suite directe de la phase précédente. Elle consiste à gérer tout ce qui concerne le cycle de vie des processus, toujours en mode superviseur. Nous vous conseillons de procéder par étapes, dans l'ordre qui suit :

  • gestion de l'ordonnancement par le scheduler, et de la création dynamique des processus ;
  • gestion de la terminaison des processus ;
  • gestion de la filiation.

Vérifiez bien que vos fonctions ont un comportement conforme à la sémantique de l'énoncé.

Phase 4

Cette phase consiste à implémenter les files de messages et le cas échéant la gestion de l'endormissement de processus. Les tests minimaux à valider à la fin de cette phase sont :

files de messages 
un système producteur/consommateur(s).
wait_clock
le programme principal lance 4 processus P1 à P4 et exécute un wait_clock d'une minute ;
  • le processus P1 imprime le caractère . à raison de un par seconde ;
  • le processus P2 imprime le caractère - toutes les 2 secondes ;
  • le processus P3 imprime le caractère + toutes les 5 secondes ;
  • le processus P4 imprime le caractère * toutes les 10 secondes ;
  • la périodicité est gérée par un appel à wait_clock dans tous les cas ;
  • à son réveil, le processus principal tue ses fils et se termine.
On doit voir une alternance correcte des différents caractères.

Il est important de bien déboguer cette partie, en envisageant le maximum de cas de figure. Bien penser que ces fonctionnalités de synchronisation seront utilisées dans de nombreux tests jusqu'à la fin du projet.

Phase 5

C'est une phase délicate. L'ensemble des problèmes qu'elle pose doit être pensé comme un tout.

Tour des problèmes

Séparation physique du noyau et de l'application

Dans les phases précédentes, les programmes de tests étaient inclus directement dans le noyau, sous forme de fonctions. L'application en mode utilisateur, elle, n'est pas liée avec le noyau. Elle est liée séparément

PC
et chargée à l'adresse 16 M.
FPGA
et chargée à l'adresse 0x2018 0000

Il faut donc bien avoir conscience que le noyau et l'application sont deux programmes indépendants qui ne partagent pas de mémoire.

PC
Le crt0 du noyau a mis en place une protection qui empêche l'application de lire ou écrire des données du noyau. Pour la même raison, il est impossible pour l'application d'appeler des fonctions du noyau.
FPGA
Si le processeur le permettait il serait possible de mettre en place une protection qui empêche l'application de lire ou écrire des données du noyau dans le crt0 du noyau. Pour cette raison (et se placer dans ce contexte), on s'interdira d'appeler directement des fonctions du noyau depuis l'application.

Réalisation d'une bibliothèque des appels noyau

Dans les étapes précédentes, l'application appelait les services du noyau par pur appel procédural. Dès que le noyau et l'application sont séparés, ceci n'est plus possible.

PC
En outre, il faut réaliser une transition du mode esclave vers le mode maître. L'application ne peut le faire qu'en exécutant une instruction int.
FPGA
En outre, on réalisera un appel simulant la transition d'un mode esclave (interruptible) vers un mode maître (non interruptible). L'application le fera en exécutant un appel à une routine placée dans USER VECTOR (cf. documentation du Microblaze p.31).

Comme on veut laisser au programmeur la facilité d'écrire dans son source des appels au noyau par appel de fonction, il faut écrire tout un ensemble de fonctions (une par appel noyau) qui auront pour but d'exécuter une instruction int. Ces fonctions seront écrites en assembleur et mises dans une bibliothèque qui jouera donc pour vos applications le rôle que joue la libc dans le système Unix.

Écriture du module de récupération de l'int

PC
Puisque l'application déclenche une interruption par int, il faut bien la récupérer dans le noyau.
FPGA
Puisque l'application déclenche un appel noyau, il faut bien le récupérer dans le noyau.

Il est donc nécessaire d'écrire un traitant pour gérer ces interruptions logicielles. Les remarques évoquées au sujet du traitant des interruptions timer en Phase 2, quant au découpage assembleur / C, restent valables ici.

FPGA
Il faudra mettre en place le masquage des interruptions non prévu dans le cas de l'appel à USER VECTOR.

Le point délicat ajouté dans leur gestion est de retrouver les paramètres de l'appel noyau pour les passer à la routine du noyau qui en a besoin.

PC
Il y a deux stratégies possibles pour transmettre les paramètres :
  • les stocker sur la pile utilisateur ;
  • utiliser les registres du processeur.
La première solution est assez compliquée, en particulier parce qu'elle oblige à gérer d'éventuels défauts de page. Il est beaucoup plus simple et élégant de recourir à la seconde solution. Cela limite le nombre de paramètres à six, mais ce n'est pas un problème. Nous vous demandons d'utiliser la solution basée sur les registres.
FPGA
La stratégie à adopter pour transmettre les paramètres est la suivante :
  • utiliser les registres du processeur.

Lancement des processus de l'application en mode esclave

Dans les phases précédentes, le noyau pouvait lancer un processus de l'application par un appel procédural. Ceci n'est plus possible lorsque le noyau s'exécute en mode maître et l'application en mode esclave puisque le lancement d'un processus par le noyau correspond à une transition de mode.

PC
Il faut donc trouver un moyen de lancer un processus en faisant une transition de mode. Il faut pour cela utiliser une instruction iret.
FPGA
Pour faire comme si le processeur était capable de gérer les transitions de mode, il faut donc trouver un moyen de lancer un processus en faisant une transition de mode. Il faut pour cela utiliser une instruction retid.

Une application doit s'exécuter avec les interruptions démasquées.

Gestion de deux piles par processus

PC
Lorsque le processeur x86 réalise une transition du mode esclave vers le mode maître, il procède également à un changement de pile. Lors de la transition esclave vers maître, le pointeur de pile est chargé avec une valeur prise dans la structure TSS, et lors de la transition maître vers esclave, le registre esp est chargé avec la valeur sauvegardée dans la pile maître. Ce mécanisme est imposé par le processeur, il est impossible d'y échapper. À partir de cette phase, il faut donc gérer deux piles par processus, une pour le mode maître et une pour le mode esclave.
FPGA
Le principe des 2 piles est à mettre en place pour l'implémentation sur le microblaze.

Protection de l'espace des entrées/sorties

PC
L'espace des registres d'E/S ne doit être accessible qu'en mode maître. Cette protection peut être mise en oeuvre en affectant un iopl de 0 dans les flags de l'application. Lorsque cette protection est mise en place, l'application ne peut donc pas écrire directement. Pour les besoins des sorties des programmes de test, vous devrez donc créer un appel système cons_write pour gérer l'affichage à l'écran. Le formatage des sorties effectué par printf doit quant à lui rester en mode utilisateur.

Chargeur du noyau

FPGA
À partir de cette phase, on demande de mettre en place un chargeur du noyau. Ce programme sera mis dans la bram (0x0000 à 0x1FFF). Au reset du système, la carte envoie sur le port série un message du type : "Attente du noyau". À partir de ce moment, les octets du noyau pourront être envoyés à la carte (via le port série) afin d'être placés aux adresses 0x2010 0000 et suivantes. Un fois le noyau chargé en mémoire, le système démarre.

Tests

PC
En fin de Phase 5, vous devez au minimum valider les tests suivants :

Test du mode utilisateur

PC
Écrire et faire fonctionner le programme suivant :
  • imprimer le message Je démarre
  • exécuter une instruction privilégiée
  • imprimer le message Je stoppe
  • terminer
À l'exécution, on doit voir le premier message mais pas le second car lors de l'exécution de l'instruction privilégiée, il doit se produire une exception, que le noyau pourra récupérer pour tuer le programme fautif. Cette gestion des exceptions n'est pas obligatoire dans le cahier de charges de base du projet, et si elle n'est pas implémentée, votre noyau plantera en cas d'exception.

Test de la protection de la mémoire et de l'espace d'E/S

PC
C'est une variation du programme précédent : au lieu d'exécuter une instruction privilégiée, le programme essaye d'accéder à l'espace d'E/S, par exemple en tentant de modifier la position du curseur, ou à une zone mémoire protégée, par exemple la mémoire vidéo. Là aussi, le noyau pourra récupérer l'exception et tuer le programme fautif.

Test du passage de paramètres

PC
Écrire un programme qui crée plusieurs processus en leur passant des paramètres différents. Les processus se contentent d'imprimer les paramètres reçus et se terminent.

Phase 6

Cette partie consiste à implémenter une console.

PC
Les sorties sur écran étant normalement opérationnelles depuis la Phase 1, il reste à coder un pilote de clavier. Il faut réaliser un vrai pilote, c'est-à-dire fonctionnant par interruptions. Ce qui vous est principalement demandé ici, c'est de gérer les événements liés au clavier et d'implémenter les appels système cons_read et cons_write. Tout ce qui concerne la reconnaissance des scan codes vous est fourni.
FPGA
Il faut coder un pilote d'entrée (entrée Rx du port série). Il faut réaliser un vrai pilote, c'est-à-dire fonctionnant par interruptions. Ce qui vous est principalement demandé ici, c'est de gérer les événements liés aux caractères arrivant sur le port série et d'implémenter les appels système cons_read et cons_write.

Le travail qui vous est demandé concerne les mécanismes de base de gestion des E/S.

Phase 7

Cette partie consiste à implémenter un interprète de commandes, qui vous sera utile pour tester et démontrer le bon fonctionnement de votre système. Il est important de comprendre que ce shell doit être un programme utilisateur. Il ne doit pas accéder aux structures de données du noyau, la protection mémoire mise en place par le crt0 qui vous est fourni l'en empêchera. Si votre shell doit récupérer des informations auprès du noyau, par exemple la liste des processus existants pour la commande ps, cela doit se faire par le biais d'appels systèmes. Vous pouvez définir tous les appels systèmes qui vous semblent nécessaires.

Phase d'extension

Avant de vous lancer dans cette phase, nous vous demandons de faire le point avec un enseignant pour :

  • vérifier l'état courant de votre implémentation ;
  • valider vos objectifs, en fonction du temps restant et de vos intérêts.

Des idées de réalisations sont proposées à la fin de la spécification du noyau.

Remerciements

Certains passages de ce document sont inspirés d'un texte de Bernard Cassagne.