Croisière au coeur d'un OS Etape 3 : Gestion de la mémoire physique

Gestionnaire de mémoire physique physmem

L'ensemble du systeme de gestion de mémoire physique est implantée dans le fichier sos/physmem.c. Le fichier sos/physmem.h contient les prototypes des fonctions définies et également quelques macros tout à fait essentielles pour le reste du noyau SOS.

  1. Macros fondamentales
  2. Descripteurs de pages physiques
  3. Initialisation
  4. Allocation
  5. Déréférencement d'une page
  6. Référencement d'une page
  7. Précautions d'usage

1. Macros fondamentales

Le fichier sos/physmem.h définit tout d'abord les trois constantes fondamentales à toute la gestion de la mémoire :

/** The size of a physical page (arch-dependent) */
#define SOS_PAGE_SIZE (4*1024)
/** The corresponding shift */
#define SOS_PAGE_SHIFT 12 /* 4 kB = 2ˆ12 B */
/** The corresponding mask */
#define SOS_PAGE_MASK ((1<<12) - 1)

La première macro définit la taille d'une page physique. Les macros suivantes permettent d'accélérer certaines opérations arithmétiques, en les transformant en opérations booléennes élémentaires puisque la taille d'une page physique est une puissance de 2 :

x << SOS_PAGE_SIZE_SHIFT est équivalent à x * SOS_PAGE_SIZE
x >> SOS_PAGE_SIZE_SHIFT est équivalent à x / SOS_PAGE_SIZE (arrondi entier inférieur)
x & SOS_PAGE_SIZE_MASK est équivalent à x modulo SOS_PAGE_SIZE

Dans certains OS, dont Linux, ces valeurs tendent à ne plus être des constantes. C'est-à-dire que ces noyaux sont appelés ç gérer des pages de mémoire de tailles variables. Ce ne sera pas le cas dans SOS.

Deux autres macros très pratiques pour toute la suite sont également définies dans ce même fichier d'en-tête :

#define SOS_PAGE_ALIGN_INF(val) [...]
#define SOS_PAGE_ALIGN_SUP(val) [...]

Elles reposent elles-mêmes sur des macros définies dans sos/macros.h. Pour une adresse donnée val, leur objectif est de calculer l'adresse de la page inférieure (resp. supérieure) ou égale la plus proche. Pour l'adresse 0x1234 par exemple, ces macros renverront respectivement 0x1000 et 0x2000.

2. Descripteurs de pages physiques

A chaque page physique est associée une structure de type physical page descr, qui définit un descripteur de page physique. Cette structure contient l'adresse physique de la page, un compteur de références à la page, et des pointeurs précédent et suivant pour chainer la structure dans une liste :

struct physical_page_descr
{
/** The physical base address for the page */
sos_paddr_t paddr;
/** The reference count for this physical page.
> 0 means that the page is in the used list. */
sos_count_t ref_cnt;
/** The other pages on the list (used, free) */
struct physical_page_descr *prev, *next;
};

Deux listes chainées sont en effet créées : une liste pour les pages physiques libres (free ppage) et une liste pour les pages physiques occupées (used ppage). Tous les descripteurs de page sont par ailleurs stockés dans un unique tableau : le descripteur i du tableau est associé à la page [i  4Ko; (i+1)  4Ko[. Ceci permet de connaître très simplement le descripteur associé à n'importe quelle adresse physique, comme le montre le code de la fonction interne suivante :

inline static struct physical_page_descr *
get_page_descr_at_paddr(sos_paddr_t ppage_paddr)
{
return physical_page_descr_array
+ (ppage_paddr >> SOS_PAGE_SHIFT);
}

La figure 4 résume le fonctionnement global de ce système : la partie de la mémoire physique en cyan en bas renferme le tableau des descripteurs, détaillée (sous la forme d'un zoom) par le tableau en haut de la figure.

Suppression d'un élément C

3. Initialisation

Avant de détailler les macros, illustrons leur utilisation au travers d'un exemple.

La fonction sos physmem setup() est chargée de l'initialisation du système de gestion de la mémoire physique. Elle prend en argument la taille de la mémoire physique disponible, ram size, fournie par Grub exclusivement (le secteur de boot d´ecrit dans l'article 1 ne permet pas de la connaitre). Elle retourne les adresses physiques de début et de fin de la zone noyau utilisée par le code et les données du noyau. Cette zone renferme à la fois l'exécutable du noyau chargé par Grub et le tableau des descripteurs des pages physiques. Les adresses retournées seront utilisées dans les articles suivants.

La fonction commence par calculer l'adresse de début de la zone noyau en utilisant le symbole __b_kernel. Celui-ci est défini dans support/sos.lds, le script de linkage de SOS.

La fonction poursuit en calculant l'adresse de fin de la zone noyau. Pour cela, il s'agit d'abord de calculer où va pouvoir se situer le tableau des descripteurs de pages physiques. Nous choisissons de le placer après l'exécutable du noyau chargé par Grub (voir la figure 5). L'adresse de ce tableau est contenue dans la macro PAGE DESCR ARRAY ADDR. Elle correspond en fait à la première page libre après la fin de l'exécutable du noyau, elle-même définie par le symbole __e_kernel dans support/sos.lds. A cette adresse, on ajoute la taille du tableau, c'est-à-dire le nombre de pages de mémoire physique multiplié par la taille d'un descripteur, afin d'obtenir l'adresse de fin de la zone noyau :

#define PAGE_DESCR_ARRAY_ADDR \
SOS_PAGE_ALIGN_SUP((sos_paddr_t) (& __e_kernel))
[...]
*kernel_core_base = SOS_PAGE_ALIGN_INF((sos_paddr_t)(& __b_kernel));
*kernel_core_top = PAGE_DESCR_ARRAY_ADDR + SOS_PAGE_ALIGN_SUP( (ram_size >> SOS_PAGE_SHIFT) * sizeof(struct physical_page_descr));

La fonction écrit ensuite dans physmem base et physmem top les adresses de début et de fin de la mémoire physique gérée par le gestionnaire de mémoire physique physmem. physmem base n'est pas initialisé à 0 car on ne souhaite pas gérer la première page physique. Ainsi, lorsqu'une fonction de physmem renverra l'adresse 0, on pourra considérer qu'il s'agit d'un statut d'erreur. Enfin, physmem top correspond à la taille de mémoire physique détectée (paramêtre ram size passé à la fonction).

Suppression d'un élément C

4. Allocation

L'allocation de mémoire physique s'effectue page par page en appelant la fonction sos physmem ref physpage new(). On peut la voir comme une sorte de malloc(4096); d'un programme utilisateur sous Unix. Cette fonction prend un booléen can block en paramètre, pour l'instant inutilisé. Il sera utile lorsque le système de swap sera mis en place.

sos physmem ref physpage new() s'occupe très simplement de récupérer la première page dans la liste des pages libres, d'incrémenter son compteur de références, puis d'ajouter le descripteur dans la liste des pages occupées, avant de retourner l'adresse physique de la page allouée :

sos_paddr_t
sos_physmem_ref_physpage_new(sos_bool_t can_block)
{
  struct physical_page_descr *ppage_descr;
  /* Retrieve a page in the free list */
  ppage_descr = list_pop_head(free_ppage);
  /* Mark the page as used (set ref count to 1) */
  ppage_descr->ref_cnt = 1;
  /* Put the page in the used list */
  list_add_tail(used_ppage, ppage_descr);
  return ppage_descr->paddr;
}

5. Déréférencement d'une page

La fonction sos physmem unref physpage() permet de diminuer le nombre de références à une page physique, c'est-à-dire de décrémenter son compteur de références. Et lorsque celui-ci atteint 0, la page est libérée.

Pour le moment, nous pouvons considérer que la fonction sos physmem unref physpage() est une sorte d'appel à free() par une application Unix car nous n'utilisons pour l'instant (dans la petite démo) que sos physmem ref physpage new() qui positionne les compteurs de références à 1. Dans ces conditions en effet, la page sera libérée systématiquement à l'appel de cette fonction puisque la condition du if ci-dessous sera toujours vérifiée :

sos_ret_t
sos_physmem_unref_physpage(sos_paddr_t ppage_paddr)
{
  /* By default the return value indicates
  that the page is still used */
  sos_ret_t retval = FALSE;
  struct physical_page_descr *ppage_descr = get_page_descr_at_paddr(ppage_paddr);
  /* Unreference the page, and, when no mapping is active anymore, put the page in the free list */
  ppage_descr->ref_cnt--;
  if (ppage_descr->ref_cnt <= 0)
  {
    /* Transfer the page, considered USED,
    to the free list */
    list_delete(used_ppage, ppage_descr);
    list_add_head(free_ppage, ppage_descr);
    /* Indicate that the page is now unreferenced */
    retval = TRUE;
  }
  return retval;
}

La fonction renvoie TRUE lorsque la page a été effectivement libérée, FALSE lorsqu'elle est toujours utilisée, et un code d'erreur (négatif) sinon.

6. Référencement d'une page

La fonction sos physmem ref physpage() permet d'augmenter le nombre de références à une page physique, c'est-à-dire d'incrémenter le compteur de références. Nous ne l'utilisons pas pour le moment, mais son code fait l'inverse de la précédente : si la page indiquée par le paramètre ppage_paddr n'est pas encore allouée (son compteur de références vaut 0), alors cette opération est réalisée : on la retire de la liste free_pages pour l'insérer dans la liste used pages.

7.Précautions d'usage

Les fonctions précédentes permettent d'allouer et de libérer des pages : les précautions à prendre avec ces fonctions sont donc analogues à celles qu'on doit prendre avec les célèbres malloc()/free().

Toutefois, elles ont une autre caractéristique. Elles oeuvrent en effet à une gestion logique des ressources (les pages physiques) ; les ressources physiques, elles, restent toujours disponibles (la mémoire ne va pas s'évaporer). Ceci veut dire que si l'on manipule des données à des adresses physiques en dehors des pages marquées "utilisées", le noyau ne plantera pas immédiatement. Il n'en demeure pas moins qu'il y a un bogue latent, puisqu'on devrait en principe accéder uniquement aux pages marquées "utilisées". Tôt ou tard, il y aura écrasement de données et donc plantage ou comportement incorrect.

Nous verrons dans l'article suivant que la pagination permet, entre autres, de détecter au plus tôt une partie de ces bogues.