[Kos-dev] Re: [Kos-announce] Nouveautes du Week End
d2
kos-dev@enix.org
28 Aug 2001 10:07:29 +0200
Bonjour,
L'annonce faite hier concernant les nouveautés du WE ne rentrait pas
dans les détails des modifications et de nos réflexions sur la VM.
Thomas a donc rédigé le compte-rendu plus détaillé et plus précis que
voici.
------
Seuls David et Thomas étaient présents à ce WE, Julien n'ayant pas pu
nous rejoindre.
Au programme des réalisations de ce WE :
1 - Compilation silencieuse tous warnings activés
Auparavant, nous compilions simplement KOS avec l'option -Wall
permettant d'obtenir un certain nombre de warnings, mais pas tous
ceux détectés par gcc.
La nouvelle séquence de CFLAGS présente dans le fichier MkVars est
la suivante :
CFLAGS += -ffreestanding -W -Wundef -Wshadow -Wpointer-arith
-Wcast-align -Wsign-compare -Waggregate-return
-Wmissing-prototypes -Wredundant-decls -Wnested-externs
-Winline -Wold-style-cast -Wall -O2
Par rapport à la version initialement prévue, nous avons supprimé
les warnings suivants :
-Wbad-function-cast car il générait un warning chaque fois
que l'on castait un void* en addr_t ou l'inverse. Ce
warning nous embêtait vraiment : les fonctions d'allocation
de mémoire retournent généralement des void*, mais nous
voulons parfois stocker le résultat dans une variable de
type addr_t (qui n'est pas un pointeur).
-Wcast-qual car il générait un warning dès que l'on castait
une variable ayant un qualifier (par exemple const ou
register) en un type n'ayant pas ce qualifier.
-Wconversion car il générait un warning dans le cas où nous
passions un int à une fonction demandant un short par
exemple (problème de taille). Cette vérification nous a
semblé trop contraignante.
Ces nouveaux warnings ont permis de mettre de l'ordre dans le
code. Ils permettent notamment de vérifier que :
- tous les membres d'une structure sont bien initialisés (pas
d'initialisation partielle de la structure). Cette
vérification a introduit une grosse correction de
modules/debug/disasm.c qui pratiquait énormément
l'initialisation partielle de structures.
- toutes les fonctions qui n'ont pas de prototypes dans un .h
(ou précédemment dans le .c), sont bien locales, et donc
sont bien déclarées comme étant statiques.
- toutes les valeurs négatives placées dans un unsigned sont
correctement castées, et donc que le programmeur est bien
conscient de ce qu'il fait. Ex unsigned int toto = -4;
- aucune variable dans un scope donne ne cache une variable
de meme nom d'un scope englobant. Par exemple : une
variable locale toto est définie alors qu'une variable
globale toto existe déjà.
2 - Passage en liste circulaire
Jusqu'à maintenant la liblist (modules/liblist) implémentait la
gestion de listes doublement chaînées classiques. La gestion de ces
listes nécessitait de maintenir à la fois une tête et une queue, ce
qui est un peu ennuyeux si l'on a une solution avec laquelle ce
n'est pas nécessaire. C'est la raison pour laquelle nous avons
modifié liblist pour implémenter des listes doublement chaînées
circulaires.
Cette gestion est implémentée par des macros, ce qui évite d'avoir
à caster comme c'est le cas pour les splaytrees.
Les fonctions disponibles sont :
- list_init(list,item) permet d'initialiser la liste 'list' avec
le premier élément 'item'.
- list_insert_before(before_this,item) insère l'élément 'item'
juste avant l'élément 'before_this'
- list_insert_after(after_this,item) insère l'élément 'item'
juste après l'élément 'after_this'
- list_add_head(list,item) insère l'élément 'item' en tête de la
liste 'list'.
- list_add_tail(list,item) insère l'élément 'item' en queue de la
liste 'list'.
- list_delete(list,item) supprime l'élément 'item' de la liste
'list'
- list_for_each(list,iterator,nb_elements) permet de créer une
boucle dans laquelle le 'iterator' pointe successivement vers
tous les éléments de la liste 'list'. Le paramètre
'nb_elements' est une variable contenant à la fin de la boucle
le nombre d'éléments parcourus. Cette dernière variable est
obligatoire.
Pour chacune de ces fonctions on dispose de l'alternative avec un
suffixe _named permettant de gérer des listes dans lesquelles les
pointeurs vers l'élément suivant et l'élément précédant ne
s'appellent pas next et prev. Se reporter à
modules/liblist/liblist.h pour plus de détails.
Le plus gros travail concernant ce point à été de modifier
l'ensemble des modules pour utiliser la nouvelle gestion de liste
chaînée.
3 - Compilation avec -fomit-frame-pointer, sans plantage à l'éxécution
Par défaut, au début de chaque fonction, gcc introduit les
instructions suivantes :
push %ebp
movl %esp, %ebp
Et introduit l'instruction leave à la fin de chaque fonction.
Ceci permet de maintenir dans %ebp un pointeur sur la pile dans
l'état ou elle était au début de la fonction. Ceci lui est utile
pour aller chercher les paramètres de la fonction empilée dans la
pile. Toutefois, il peut se débrouiller en utilisant directement
des calculs sur le esp courant, sans utiliser ebp. Ceci est
possible grâce à l'option -fomit-frame-pointer, et permet :
- d'économiser deux instructions à chaque début de fonction,
et une instruction à chaque fin de fonction. Ceci peut
paraître ridicule mais étant donné le nombre d'appels de
fonction, ceci peut être intéressant.
- d'utiliser ebp comme un registre de travail pour réaliser
des optimisations (utilisation des registres pour certaines
variables au lieu de la mémoire).
L'utilisation de l'option -fomit-frame-pointer a nécessité la
modification du fichier modules/task-x86/_cpl0_switch.c. Ce fichier
contenait deux fonctions en assembleur inline pour switcher entre
threads. Toutefois, gcc ne permettait pas de spécifier esp en tant
que cloberred register, et nous étions donc dans l'impossibilité de
réaliser ces fonctions en assembleur inline. Nous avons donc
remplacé le fichier .c par un fichier .s, ce qui a permis de gérer
manuellement l'utilisation des registres.
La compilation avec -fomit-frame-pointer économise un registre et
l'éxécution de quelques instructions, mais ne permet plus le
backtracking. Cependant nous pouvons toujours compiler sans cette
option, et ainsi utiliser éventuellement le backtracking (qui reste
à implémenter). Le backtracking permettrait de savoir quelle est la
suite d'appels de fonctions qui a conduit là on on se trouve (très
utile pour le debugging).
Ces trois premiers points ont constitué la phase de
proprification-amélioration du code source existant.
4 - Gestion de la mémoire virtuelle
Le brainstorming sur la VMM du début du mois d'aout (réunion
Méridon) n'avait pas donné lieu à du coding. Durant ce WE, nous
avons poursuivi notre réfléxion, concernant :
- le partage de virtual_region_t entre différentes team
- le partage de PTs
- l'implémentation orientée-objet
- les liens unissant team/address space/virtual region/shadow
resource/resource
et entamé l'implantation.
Address space : cette nouvelle entité est placée entre la team et
l'arbre des régions virtuelles. Elle permet de bien séparer la team
(du ressort du module task) de l'ensemble des régions virtuelles
(du ressort du module vmm).
Partage de virtual_region_t : Hlide nous avait proposé (via une
discussion Icq) de partager les virtual_region_t entre différents
address space, afin d'éviter les surcoûts induits par les recopies
lors d'un fork. Nous avons beaucoup réfléchi sur le sujet, envisagé
différentes solutions, mais finalement il s'est avéré que ce
partage n'est pas possible car chaque virtual_region_t contient un
pointeur vers l'address space qui la contient. Or ce pointeur est
unique (ce n'est pas un tableau de pointeur) et donc une
virtual_region_t ne peut se trouver à un instant donné que dans un
seul address space. Il est clair que nous avons besoin d'un
pointeur vers l'address space dans chaque virtual_region_t, pour
savoir dans quel memory context (PD/PT...) il faut mapper la page
qui a provoqué un page fault... La seule solution trouvée pour le
moment est de recopier l'arbre des virtual_region_t durant le fork.
Partage de PT : étant donné que nous maintenons pour chaque page
physique l'ensemble de ses mappings en mémoire virtuelle
(l'ensemble de PTE la désignant), nous avons besoin de limiter le
nombre de PTE pointant sur une même page physique. Le partage de PT
permet de répondre à ce souci.
La shadow resource maintiendra deux listes de virtual_region_t. La
première liste contiendra les virtual_region_t mappant la shadow
resource sur un espace où elle est seule (région virtuelle seule
sur une zone multiple de 4Mo). On peut alors utiliser directement
les PT de cette virtual_region pour mapper de nouveau le meme
objet. La deuxième liste contiendra les virtual_region ne
correspondant pas à cette contrainte.
Le mapping de ressource va se dérouler comme suit :
- trouver une zone libre dans notre address space
(vérification overlapping)
- négocier avec la shadow resource pour trouver éventuellement
une virtual_region_t existante, correspondant à nos besoins,
afin de partager les PTs
- créer la nouvelle région virtuelle (vr_new)
- ajouter la région dans l'address space (as_add_vr)
Droits d'accès :
Pour ne pas avoir à splitter une région en deux lorsque l'on change
les droits d'accès sur une portion d'une région, nous avons
introduit dans chaque virtual_region_t un arbre de
access_range_t. Chacun des éléments de cet arbre définit les droits
d'accès maximaux pour la portion de région [node.key (start), end].
L'implémentation orientée objet :
Le modèle objet de la mémoire virtuelle est présenté dans le
fichier doc/vm_model.eps. (Le source doc/vm_model.dia est lisible
via le logiciel dia).
Au niveau de l'address space, on dispose d'une série de fonctions
permettant :
- d'initialiser un address space relativement à une team donnée
- de supprimer le contenu d'un address space
- de trouver la région correspondant à une adresse virtuelle donnée
- de dupliquer un address space
- d'ajouter, de supprimer et de redimensionner une région de
l'address space
- de gérer une page fault reçu dans l'address space courant
Au niveau des régions, on dispose d'une série de fonctions
permettant :
- de créer/supprimer une région
- d'afficher les informations concernant une ou l'ensemble des
régions
- de gérer le page fault
Chaque region dispose d'un pointeur vers un driver, ce qui permet
de spécialiser le comportement d'une virtual region en fonction de
sa nature. Chaque driver doit implanter les méthodes
d'initialisation/désinitialisation, de notification de remappage en
mémoire, et de notification de swap.
De la même manière chaque shadow resource possède un pointeur vers
un driver permettant de spécialiser le comportement lorsque l'on
reçoit un page fault, lorsque l'on a besoin de lire ou écrire sur
la ressource mappée en mémoire. La gestion des shadow resource
reste à mettre en place.
Pages anonymous : afin de partager au maximum les pages dites
anonymes, SunOS utilise pour chaque virtual_region_t un tableau de
pointeurs vers les pages anonymous de la virtual_region. Or, avec
notre système de reverse mapping, nous sommes directement capable
lors du remapping (après swap par exemple) d'une page en mémoire de
la remapper à TOUS les endroits où elle était mappée. Nous pouvons
donc nous passer de gestion de page anonymous. Donc, contrairement
au fonctionnement de la VM SVR4, ceci permet de découpler la partie
shadow_resource/virtual_region de la gestion du swap.
Il reste donc à implémenter (pour que le système fonctionne de
nouveau) :
- la gestion des shadow resource
- la création d'un équivalent à /dev/kmem pour mapper la zone
noyau. Nous avons identifie qu'il faut 2 versions du page
fault de la shadow resource suivant que la page fault
provient du cpl3 (=> recopie de la zone noyau) ou du cpl0
(allocation de page physique).
- la création d'une sorte de shadow resource /dev/kstack ou
équivalent pour gérer la région de piles noyau de supportant
aucun page fault
Puis :
- reverse mapping
- partage de PT
- shadow resource /dev/zero
- fork : ce sont les fonctions de gestion des shadow resources
qui font l'essentiel du travail sur le plan algorithmique en
cas de création d'un mapping en MAP_PRIVATE (passage de tous
les mappings de la zone en read-only), puis en cas de page
fault sur une zone ou un mapping MAP_PRIVATE existe
(différenciation).
- régions de type MAP_PRIVATE / MAP_SHARED avec le COW, etc...
Bonne journee,
--
d2