[Kos-dev] Gestion de la memoire virtuelle : pour y voir plus clair

Thomas Petazzoni kos-dev@enix.org
Mon, 09 Jul 2001 23:27:13 +0200


Bonjour,

Par ce mail, j'aimerais essayer de mettre au clair les differents types
de segments de memoire virtuelle existants, les reactions du
gestionnaire de page fault face aux differents types de segments, et le
fonctionnement du swapper. Je parlerais aussi de liaison entre physique
et virtuel.

1. Differents types de segments
-------------------------------

	a. Segment code/donnees noyau
	-----------------------------

	Le noyau est compose de modules, etant eux memes du code, et des
donnees (initialises ou non). D'autre part, l'allocation de memoire
supplementaire peut se faire via les fonctions kmalloc() ou kvalloc().
Il parait clair que le noyau se doit de rester integralement en memoire
physique, pour eviter toute situation bloquee. Nous avons donc ici un
premier type de segment, appelle SEG_KERNEL.

	b. Piles CPL0
	-------------

Les piles CPL0, qu'elles soient pour les threads utilisateur ou noyau,
qu'elles soient dynamiques ou statiques ont un fonctionnement un peu
special, ce qui me force a les distinguer du code et des donnees du
noyau. C'est le deuxieme type de segment SEG_CPL0_STACK.

	c. Code/donnees initialises/donnees non initialises/piles CPL3
	--------------------------------------------------------------

Ce type de segment n'est utilise UNIQUEMENT pour les threads utilisateur
(CPL3). Le code et les donnees initialises d'un tel thread sont
directement la projection en memoire du fichier executable. Les donnees
non initialises ou la pile sont la projection en memoire d'un fichier
special /dev/zero. Le type de segment englobant ces informations est
appelle SEG_VNODE. 

	d. Mapping de peripheriques caracteres
	--------------------------------------

Meme si cette possibilite ne sera pas utilisee tout de suite, nous
pouvons deja prevoir de creer le type de segment SEG_DEV. Il pourra par
exemple etre utilise pour mapper des framebuffers en memoire, ou
d'autres peripheriques.

	e. Mapping rapide de fichiers pour read/write
	---------------------------------------------

Le noyau aura besoin de repondre a des requetes de lecture ou d'ecriture
sur des fichiers (donc des vnode), et ce assez rapidement. Le type de
segment SEG_VNODE pourrait etre utilise, mais sera surement une trop
grosse artillerie pour effectuer juste une petite lecture/ecriture. A
voir.

2. Reactions du gestionnaire de page faults
-------------------------------------------

	a. SEG_KERNEL
	-------------

Les pages de code du noyau ne peuvent pas etre proteges en ecriture
contre le noyau lui meme, a cause des limitations des processeurs x86.
Aucune faute ne sera donc levee dans ce cas la. Toutefois, on peut
suggerer que si un page fault intervient sur une page non mappee dans un
segment de type SEG_KERNEL, on arrete le systeme.
En effet, tout kmalloc ou kvalloc mappe automatiquement la ou les pages
requises, et n'attend pas le page fault pour allouer la page. Toute page
virtuelle allouee pour le noyau est donc TOUJOURS mappee en physique. Si
ce n'est pas le cas, on arrete le systeme.

	b. SEG_CPL0_STACK
	-----------------

Ici c'est plus complexe, et la gestion des defauts de page depend de
l'utilisation ou non de l'agrandissement dynamique des pages via le
double fault (expertise de la faisabilite de cette solution en cours).
Cas de piles dynamiques : tout page fault genere un double fault, qui se
charge de verifier si l'acces a la pile est correct (dans l'intervalle
de pile du thread courant), alloue une nouvelle page, la mappe en
memoire, et retourne dans le thread. Si la consommation de pile est trop
grande, on arrete le thread. Si un acces dans une zone de pile est faite
de maniere illegale (par un autre thread), on arrete le systeme.
Cas de piles statiques : les piles sont de taille n pages, TOUJOURS
mappes physiquement. Chaque pile est separe par une page de garde,
permettant d'eviter les stacks overflow qui irait ecrabouiller la pile
du dessous. Tout acces sur une page de garde provoquerait soit l'arret
du systeme, soit la destruction du thread incrime (a voir).

	c. SEG_VNODE
	------------

Les segments de type VNODE peuvent etre de deux types : MAP_SHARED ou
MAP_PRIVATE.
Les portions de code du thread utilisateur seront le mapping direct du
fichier en memoire, dans une zone en lecture seule. Toute tentative
d'ecriture dans cette zone provoquera la destruction du thread
(segmentation fault).
Les portions de donnees du thread utilisateur seront le mapping direct
du fichier en memoire, dans une zone en lecture ecriture. Toute
tentative de lecture provoquera l'allocation du page de type anonymous
(a zero) et la copie de la portion du fichier incrimine dans cette page.
Si le mapping est de type MAP_SHARED (partage entre les fork()), la
premiere tentative d'ecriture provoquera un COW (Copy on Write), les
autres tentatives d'ecriture se feront sur la meme page quelque soit le
thread realisant l'ecriture. Probleme : comment gerer la consistence
dans ce cas la ?
Si le mapping est de type MAP_PRIVATE : chaque tentative d'ecriture par
un thread different se solde par un COW.
Les portions de donnees non initialises, le tas et la pile sont des
pages de type anonymous. 
|---------|---- - - - - ----|
|         |                 |
|---------|---- - - - - ----|
\ BSS      \ TAS            \ STACK

Pour la pile, on maintient une adresse de bas de pile pour verifier que
l'accroissement se fait bien continuement. 2 solutions pour la
liberation de l'espace quand on remonte dans la pile : 1. swapping (bof
car on consomme de l'espace pour des donnees qui vont etre ecrabouilles)
ou 2. KGC (mieux).
Tout acces non continu a la pile entraine un Segmentation Fault.
Pour le tas, l'appel systeme sbrk() se charge d'ajouter une page de type
anonymous. Tout acces sur une page non valide est sanctionne par un
Segmentation Fault pour le thread courant.
Le BSS est une zone de taille statique, connue a l'execution du
programme, qui est compose de pages de type anonymous.

Pour gerer les pages on aura deux bits : le bit P (Present) et le bit S
(Swappe). Pour le tas, si la page a P=0&S=0 ==> SegFault, si la page a
P=0&S=1, on ramene la page en memoire.

	d. Mapping de peripheriques characteres
	---------------------------------------

Le mapping d'un peripherique caracteres est en fait une projection de
son vnode en memoire, mais d'une maniere speciale, differente de
SEG_VNODE : toute tentative d'ecriture ou de lecture est directement
repercutee par un read() ou write() sur ce peripherique.

	e. Mapping rapide de fichiers pour read/write
	---------------------------------------------

A etudier.

3. Fonctionnement du swapper
----------------------------

Dans KOS, il ne me semble pas utile de maintenir la notion de swapping
de processus. On ne va pas s'amuser a swapper un processus entier. Pour
moi le swapping est un phenomene qui ne concerne que la memoire physique
: notre swapper swappera des pages physiques.

Voici son boulot : pour chaque page physique, il parcoure la liste de
ses mappings en memoire virtuelle, si la page physique a ete accedee,
elle est ramenee au debut d'une liste des pages physiques. Sinon elle
reste a sa place. Ce processus est continu (implemente dans un thread
noyau), et quand la taille de memoire physique restante devient trop
critique, le swapper swappe les pages de la fin de la liste (les moins
accedes). Bien sur, le swapper prend en compte la nature des pages
physiques : certaines pages sont bloques en memoire phyisque (les pages
du noyau, et des piles CPL0 par exemple).

Le swapper pourra prendre en compte le nombre d'acces a des pages
(nombre de fois que le bit Accessed a ete mis a 1) pour permettre
d'optimiser la repartition des pages entre le swap et la memoire
physique. 

Bref, le swapper joue pour moi le role de repartiteur entre la memoire
physique et le swap.

Il va falloir jouer finement car le swapper ne pourra pas acceder aux
PTEs des pages virtuelles de toutes les taches : cela necessiterait de
changer de contexte. Il va falloier donc trouver une solution....

4. Relation entre pages physiques et virtuel
--------------------------------------------

Pour que le swapper puisse fonctionner, et qu'on puisse demapper et
remapper des pages physiques en virtuel, on va maintenir une liste
chainee des mappings d'une page physique.
typedef struct {
	int flags; // bloquee en memoire physique ou non ?
	gpfme_t *next; // pour le swapper
	gpfme_t *prev; //pour le swapper
	virtual_map_t *head;
	...
} gpfme_t;

typedef struct {
	addr_t virt_addr;
	team_t *mother_team; // team qui contient cette page
	virtual_map_t *next;
	virtual_map_t *prev;
} virtual_map_t;

ainsi on peut connaitre pour chaque page physique son adresse de mapping
en memoire.

Suggestions bienvenues :)

Voila j'en ai termine pour ce soir,

Thomas	
-- 
PETAZZONI Thomas
thomas.petazzoni@meridon.com     UIN : 34937744
Projet KOS : http://kos.enix.org
Page Perso : http://www.enix.org/~thomas/