• Aucun résultat trouvé

2.3 Contributions

3.1.2 L’évolution dynamique du système

Durant le cycle de vie d’un système, plusieurs événements peuvent se déclencher, tels que la créa- tion des processus ainsi que leurs exécutions, entraînant plusieurs changements de l’état des différents composants matériels et logiciels maintenus par le noyau. De ce fait, le micro-noyau doit fournir des mécanismes assurant des transformations correctes dans le but de remettre le système dans un état co- hérent. Dans cette section, nous détaillons la formalisation de l’évolution dynamique de l’état dans un langage fonctionnel en s’appuyant principalement sur une monade d’état.

CHAPITRE 3. ÉTUDE PRÉLIMINAIRE : MIMIC

3.1.2.1 La monade H

Gallina, le langage de spécification de Coq, est un langage purement fonctionnel qui ne permet pas des interactions avec l’environnement externe. Par exemple, effectuer un changement d’état ou gérer des exceptions. Cependant, afin de bénéficier à la fois des avantages de ce langage fonctionnel et de l’ef- ficacité de la programmation impérative, nous modélisons notre micro-noyau en Gallina et nous utili- sons une monade d’état, que nous avons appelée la monade H (Hardware monad). Une monade est une notion mathématique issue de la théorie des catégories [Mog91] et utilisée dans les langages fonction- nels pour enrober les effets de bords dans un type abstrait approprié. Elle permet ainsi de représenter la séquentialité des instructions (des langages impératifs) dans le langage fonctionnel de l’assistant de preuve Coq et offre un moyen pour définir un état modifiable par les fonctions d’un système à état tel qu’un système d’exploitation. La monade H consiste concrètement en un constructeur de type H(A) et deux opérations ret et bind.

• H(A) : est le type d’une opération monadique. Celle-ci retourne un résultat de type monadique : Definition H (A :Type) : Type := state → result (A * state).

où state est le type de l’état du système et result(X) est le resultat retourné par une opération monadique. result est un type inductif défini avec trois constructeurs : val pour retourner une valeur de type A et un nouvel état de type state , ainsi que hlt et undef pour définir, respective- ment, l’arrêt du système et les comportements non définis (dans ces cas-là il n’y a pas de nouvel état) ;

Inductive result (A : Type) : Type := | val : A → result A

| hlt : result A | undef : result A.

• ret(A) : est une opération monadique permettant de retourner une valeur de type A ; Definition ret {A : Type}(a : A) : H A := fun s ⇒ val (a, s).

• bind permet la mise en séquence d’un ensemble d’opérations monadiques. Il est défini de la ma- nière suivante :

Definition bind {A B : Type} (m : H A)(f : A → H B) : H B := fun s ⇒ match m s with

| val (a, s’) ⇒ f a s’ | hlt ⇒ hlt

| undef⇒ undef end.

la première opération monadique à exécuter est m qui peut retourner une valeur a de type A et un nouvel état s’ puis f sera calculée en fonction de s’ et a. Cette dernière est, également, une fonction monadique qui retourne un nouvel état et une valeur de type B.

Pour simplifier l’utilisation de la fonction bind nous avons utilisé le système de notations fourni par Coq. La notation perform x := m in econsiste en la mise en séquence par bind de m et e qui peut dépendre de la valeur x retournée par m. La notation m1 ;; m2 consiste en la mise en séquence par bind de m1 et m2 quand m2 ne dépend pas de la valeur retournée par m1. Le point intéressant ici est qu’en utilisant l’opération bind pour la mise en séquence, le passage de l’état est implicite, caché sous un type abstrait.

CHAPITRE 3. ÉTUDE PRÉLIMINAIRE : MIMIC

Dans la suite nous utilisons s pour nommer un état de type state. Ce dernier consiste en un enre- gistrement composé de l’ensemble de tous les composants logiciels et matériels détaillés plus haut. Il est à noter que dans ce modèle nous distinguons trois catégories d’opérations monadiques :

un composant matériel : modélisant le comportement d’un composant matériel. Cette catégorie d’opé- rations est nécessaire pour définir, par exemple, la fonction de traduction établie par le MMU qui est indispensable pour la modélisation du gestionnaire de mémoire ;

une instruction : correspond exactement à une seule instruction CPU ;

une routine : est une séquence non-interruptible d’un ensemble d’instructions exécutées par le noyau.

3.1.2.2 Les appels système et interruptions

Notre modèle de CPU, particulièrement la partie qui gère les appels système et les interruptions, est conçue dans un contexte assez générique. La figure3.2 adaptée de [Gho11] résume le modèle de l’évolution dynamique du système avec et sans interruptions. La spécification de cette transformation est illustrée par la figure3.3.

Start fetch next instruction execute instruction Check for interrupt; process interrupt Halt

Fetch cycle Execute cycle Interrupt cycle

Interrupts disabled

Interrupts enabled

FIGURE3.2 – L’évolution dynamique du système.

Composant matériel step : H(unit) Action:

Vérifier la présence d’une interruption;

if une interruption n a été levée (fech_interrupt) then interrupt(n) ; else i ← fetch_instruction ; incrémenter currentpc ; execute(i ); end

FIGURE3.3 – La spécification de l’évolution dynamique de l’état du système.

Nous identifions trois étapes principales dans le cycle de vie d’un système :

1. Fetch instruction : permet de lire ou extraire l’instruction à partir de la mémoire physique. Le composant matériel fetch_instruction correspond à cette étape ;

Il est à noter que dans le cas où la condition n’est pas vraie et il n’y a pas de bloc sinon, nous retour- nons soit une valeur de type unit soit une valeur par défaut ou bien nous exécutons l’instruction halt pour arrêter le système.

CHAPITRE 3. ÉTUDE PRÉLIMINAIRE : MIMIC

Composant matériel fetch_instruction : H(instr) Action:

if currentpc(s) est une position valide dans code(s) then retourner l’instruction ;

end

FIGURE3.4 – Extraction d’instruction.

2. Execute instruction : permet d’exécuter une instruction CPU, par exemple, cette instruction pourra demander au CPU de déterminer une certaine adresse physique afin de la charger ou sauvegar- der des données dans la mémoire (par exemple un processus qui sauvegarde une valeur dans la mémoire physique en utilisant la routine write (voir figure3.5). En effet avant de sauvegarder une donnée dans la mémoire le MMU doit déterminer l’adresse physique qui correspond à l’adresse virtuelle fournie par le processus, s’il existe une configuration pour cette adresse virtuelle ;

Instruction write : integer → integer → H(unit) Input : val : la valeur à sauvegarder

vaddr : l’adresse virtuelle vers laquelle la valeur sera sauvegardée Action:

virtual address vaddr; paddr ← translate(vaddr) ;

if paddr n’est pas une exception then

write_phy(val, paddr) ; (*écrire val à l’adresse physique paddr*) end

FIGURE3.5 – Écrire une valeur dans la mémoire.

Composant matériel translate : integer → H(integer + exception) Input : vaddr : une adresse virtuelle

Output: une adresse physique ou une exception Action :

if l’adresse virtuelle vaddr est valide then

calculer l’adresse de l’entrée dans la table de pages pte et la position dans cette table qui correspond à vaddr;

if il existe une page physique mappée dans pte et (kernel_mode(s) = true ou bien kernel_only(p t e) = 1) then

calculer l’adresse physique ; retourner l’adresse physique ; end

end

FIGURE3.6 – Traduire une adresse virtuelle vers une adresse physique.

3. Check for interrupt : permet de vérifier s’il y a une interruption à gérer avant de passer à l’exécu- tion de l’instruction suivante. On modélise cette action par le composant matériel fech_interrupt (voir figure 3.7). En effet, ce composant doit d’abord vérifier l’existence d’une interruption en consultant le premier élément de la liste infinie qui modélise le flux des interruptions. Si une inter- ruption est levée, le composant matériel interrupt, illustré par la figure3.8, sauvegarde le contexte

CHAPITRE 3. ÉTUDE PRÉLIMINAIRE : MIMIC

courant sur la pile du noyau (i.e. stack(s)), puis le CPU passe en mode noyau et branche vers le code du gestionnaire de cette interruption. Dans ce cas, le matériel identifie le gestionnaire ap- proprié en utilisant le numéro d’interruption comme position dans intr_table. Afin de reprendre correctement l’exécution du processus courant après une interruption, son contexte sera restauré à partir de la pile du noyau en exécutant l’instruction return_from_interrupt (voir figure3.9).

Composant matériel fetch_interrupt : H(option integer) Action:

retirer le premier élément de la liste interrupts(s); retourner le premier élément de la liste interrupts(s);

FIGURE3.7 – Vérifier l’existence d’une interruption.

Composant matériel interrupt : integer → H(unit) Input : n :le numéro d’interruption à exécuter Action:

Empiler currentpc et le mode d’exécution sur la pile du noyau; Passer en mode privilégié;

Récupérer l’adresse du gestionnaire d’interruption en fonction de n; Affecter à currentpc l’adresse du gestionnaire d’interruption;

FIGURE3.8 – Gérer une interruption.

Composant matériel return_from_interrupt : H(unit) Action:

Retirer le premier élément dans la pile;

restaurer le mode d’exécution du programme courant; restaurer currentpc;

FIGURE3.9 – Reprendre l’exécution après une interruption.

Les processus invoquent les appels système afin d’interagir avec le micro-noyau en levant une inter- ruption logicielle. Dans notre modèle on appelle cette interruption trap (voir figure3.10). Le traitement de cette interruption est similaire à celui des interruptions matérielles.

Instruction trap : integer → H(unit)

Input : n : le numéro d’interruption à exécuter Action:

incrémenter currentpc; interrupt(n);

CHAPITRE 3. ÉTUDE PRÉLIMINAIRE : MIMIC