Une MMU est couramment utilisée pour isoler la mémoire de différents processus à l’aide d’un mécanisme de TLB, Tranlation Lookaside Buffer. L’isolation mémoire permet à un processus d’accéder à sa mémoire et lui interdit d’accéder à la mémoire des autres processus.
La MMU émulée par notre bibliothèque hardware fonctionne par simple usage d’une TLB. La TLB contient TLB_ENTRIES entrées, 32 par défaut. Chaque entrée décrit la correspondance entre une page virtuelle et une page physique, ainsi que les droits d’accès à la page.
qui retourne le numéro de la page physique associée à la page virtuelle vpage du process process (0 pour le premier processus, 1 pour le second). La fonction retourne -1 si l’adresse de page virtuelle est en dehors de l’espace alloué au processus.static int ppage_of_vpage(int process, unsigned vpage);
Quand une faute d’accès se produit, la MMU déclenche une interruption. Lorsque le noyau traite l’interruption, il peut typiquement
À un instant donné, les données de la TLB ne sont valides que pour le processus qui est en train de s’exécuter. La TLB sert de cache de translation à la MMU. À chaque changement de contexte, le noyau doit donc vider la TLB, car les mappings qu’elle contient ne seront plus valables après le changement de contexte.
Le tableau 2 décrit la structure des entrées de la TLB. Chaque entrée a une taille de 32 bits.
En mode maître, la MMU peut être pilotée via des lectures ou écritures (_in() et _out()) sur les ports de contrôle de la MMU. Le tableau 3 décrit les ports et commandes permettant de gérer la MMU et sa TLB. Ces commandes prennent effet directement (donc aucune interruption n’est déclenchée pour en notifier la terminaison).
Tableau 3: Ports et commandes de gestion de la MMU
port R/W commande ou données objet MMU_CMD
W MMU_PROCESS
désactive / active la MMU MMU_RESET
réinitialise la MMU MMU_FAULT_ADDR
R adresse mémoire adresse fautive courante TLB_ADD_ENTRY
W entrée TLB entrée à ajouter dans la TLB TLB_DEL_ENTRY
W entrée TLB entrée à supprimer de la TLB TLB_ENTRIES
R/W TLB_ENTRIES
× entrée TLBensemble des entrées de la TLB
La bibliothèque hardware permet également de manipuler des interruptions logicielles. Comme vu précédemment, les 16 premières entrées du vecteur d’interruption IRQVECTOR[] sont associées à des interruptions matérielles. En fait, ce vecteur est de taille IRQ_VECTOR_SIZE, soit 256. Les interruptions suivantes sont dédiées aux interruptions logicielles.
Les interruptions logicielles peuvent être déclenchées par un processus utilisateur. Elles permettent aux processus de demander au noyau d’exécuter une tâche, comme par exemple accéder aux I/O du système. C’est ce mécanisme qui permet l’implémentation d’appels systèmes.
Le déclenchement d’interruptions logicielles se fait via la commande _int(). Par exemple, _int(23) va déclencher l’interruption 23. La fonction présente en position 23 dans le vecteur d’interruptions est alors exécutée en mode maître. Quand elle retourne, l’exécution reprend après l’instruction _int().
static int current_process; static int sum(void *ptr) { int i; int sum = 0; for(i = 0; i < PAGE_SIZE * N/2 ; i++) sum += ((char*)ptr)[i]; return sum; } static void switch_to_process0(void) { current_process = 0; _out(MMU_CMD, MMU_RESET); } static void switch_to_process1(void) { current_process = 1; _out(MMU_CMD, MMU_RESET); } int main(int argc, char **argv) { void *ptr; int res; ... /* init_hardware(); */ IRQVECTOR[16] = switch_to_process0; IRQVECTOR[17] = switch_to_process1; _mask(0x1001); ptr = virtual_memory; _int(16); memset(ptr, 1, PAGE_SIZE * N/2); _int(17); memset(ptr, 3, PAGE_SIZE * N/2); _int(16); res = sum(ptr); printf("Resultat du processus 0 : %d\n",res); _int(17); res = sum(ptr); printf("Resultat processus 1 : %d\n",res); }
Le point d’entrée sera le «boot» du système, en mode noyau. Ce code noyau consistera en une initialisation du matériel et du vecteur d’interruptions. Il passera ensuite la main au code utilisateur par l’appel d’une fonction init() qui passera alternativement à l’exécution du processus 0 et du processus 1.
Ces deux fichiers se partageront un fichier d’entête mi_syscall.h qui identifie les «appels systèmes» possibles :
#define SYSCALL_SWTCH_0 16 #define SYSCALL_SWTCH_1 17
et identifie le point d’entrée du fonctionnement en mode utilisateur :
void init(void);
Manipulation pratique
On retrouvera le code présenté question 1 dans les fichiers misc/mi.{c,h} du dépôt
https://gitlab-etu.fil.univ-lille1.fr/ms1-ase/srcOn y ajoutera le code du handler défini à l’exercice 5.
Comme décrit à la question 2, on séparera le code noyau et le code utilisateur.
![]()
![]()
![]()