Previous Up Next

3  Isolation mémoire de processus

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.


Exercice 3
 (Mapping statique)   Nous souhaitons donc partager N pages de mémoire physique (N est considéré pair) entre deux processus utilisateurs. Chacun des deux processus adresse la mémoire virtuelle pour accéder à une matrice sur laquelle il effectue quelques calculs.
Question 1   Proposez un mapping de N/2 pages pris dans l’espace d’adressage virtuel, placé dans l’espace physique lorsque le premier processus s’exécute, puis un autre mapping pour les mêmes N/2 pages de mémoire virtuelle vers N/2 pages de mémoire physique différentes.
On rappelle que les pages virtuelles comme physiques sont de taille 4 ko (soit 212). Le numéro de page correspondant à une adresse peut donc être obtenu en ignorant les 12 bits de poids faible de l’adresse.
Question 2   Implémentez une fonction :
static int ppage_of_vpage(int process, unsigned vpage);
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.

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.


Tableau 2: Format des entrées de la TLB (par ordre décroissant de poids de bits)
taillecontenu
8 bitsR.F.U.
12 bitspage virtuelle
8 bitspage physique
1 bitaccès en exécution
1 bitaccès en écriture
1 bitaccès en lecture
1 bitentrée utilisée ou non


Exercice 4
 (Entrées de la TLB)   En concordance avec le tableau 2, définissez un type struct tlb_entry_s qui sera utilisé pour représenter une entrée de la TLB.

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
portR/Wcommande ou donnéesobjet
MMU_CMDWMMU_PROCESSdésactive / active la MMU
  MMU_RESETréinitialise la MMU
MMU_FAULT_ADDRRadresse mémoireadresse fautive courante
TLB_ADD_ENTRYWentrée TLBentrée à ajouter dans la TLB
TLB_DEL_ENTRYWentrée TLBentrée à supprimer de la TLB
TLB_ENTRIESR/WTLB_ENTRIES × entrée TLB ensemble des entrées de la TLB


Exercice 5
   Écrivez un handler pour l’interruption MMU_IRQ capable de mettre en œuvre le mapping décrit précédemment. Une variable globale current_process indique le numéro du processus qui s’exécute. Lorsqu’une faute se produit, il est nécessaire d’ajouter dans la TLB une nouvelle entrée correspondant à la page accédée, ou de lever une erreur de segmentation.

Interruptions logicielles

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().


Exercice 6
 (Programme utilisateur et code système)  
Question 1   Quel est le comportement du programme suivant ?
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);
}
Question 2   On séparera en deux fichiers sources mi_kernel.c et mi_user.c les fonctions et séquences de code exécutées en mode maître et utilisateur.

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/src

On 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.


Previous Up Next