Vue d'ensemble de l'architecture de l'espace noyau

L'architecture du noyau de Manux est assez diff?rente de celles des noyaux habituels. Voyons cela plus en d?tail.

Un noyau modulaire monospace

Le noyau est d?crit comme ayant une architecture "modulaire monospace". Dans la mesure o? cette d?signation a ?t? cr??e sp?cialement pour lui, il semble utile de pr?ciser sa signification.

Le noyau Linux est monolithique modulaire, c'est-?-dire qu'il est compos? d'un monolithe central auquel s'ajoutent des modules ?ventuels charg?s dynamiquement (du moins, dans la configuration la plus courante; il peut aussi ?tre compil? de fa?on purement monolithique), l'ensemble s'ex?cutant dans un espace d'adressage unifi?.

Le noyau de Manux s'ex?cute lui aussi dans un espace d'adressage unifi?. Toutefois, il est int?gralement d?coup? en modules, et d?pourvu de tout monolithe central. De plus, actuellement, aucun module n'est amovible.

Pour ?tre exact, les ?l?ments du noyau se r?partissent en trois cat?gories :

L'amorce est le permier ?l?ment charg?. Il s'agit techniquement d'un ex?cutable ELF au format multiboot, auquel le chargeur de d?marrage transf?re le contr?le du processeur juste apr?s avoir fini son travail. Du point de vue du noyau, il s'agit de la portion de code charg?e d'effectuer l'initialisation du mat?riel et des modules.

Les divers modules permanents sont les composants r?els du noyau. Ils se r?partissent les diff?rentes t?ches, et communiquent par appel de fonction direct.

Enfin, les ?ph?modules sont des ?l?ments charg?s d'effectuer une t?che ponctuelle bien pr?cise avant d'?tre d?charg?s. Il peut s'agir de v?rifier l'absence de corruption de la m?moire d'un module, de tester un module au d?but de son d?velopement, de r?organiser la m?moire pour l'adapter ? l'injection d'une nouvelle version d'un module, etc... Le noyau comporte toujours un et un seul ?ph?module en m?moire. Initialement, l'?ph?module charg? est "blanc", qui ne fait rien.

Le d?coupage en module apporte les avantages suivants :

L'architecture des modules

Les modules sont compos?s de deux parties : le code et les donn?es. Le code est situ? dans un binaire au format ELF, li? statiquement, et cod? par la m?thode des refs sans defs - c'est-?-dire que ses donn?es sont r?f?renc?es sans jamais ?tre d?finies. Th?oriquement, une telle approche devrait entra?ner un code incompilable. En fait, l'adresse des donn?es est inject?e lors de l'?dition de liens (fichier scripts_ld/.lds), ce qui fonctionne tr?s bien, mais implique que les modules ne peuvent s'ex?cuter avant qu'un code ext?rieur (l'amorce) n'ait allou? et initialis? leurs structures de donn?es.

Quant aux donn?es, elles sont compos?es de deux portions contig?es : le bordereau et le tas. Le tas est la zone de m?moire dans laquelle les allocations dynamiques ont lieu, par la fonction Alloue_mem(). Le bordereau est la structure m?moire contenant ou r?f?ren?ant l'ensemble des donn?es du module.

A titre d'exemple, voici la structure du bordereau du module des tubes :

struct bordereau_tubes {
        struct en_tete_bordereau etb;
        struct tube tubes[NB_MAX_TUBES];
} __attribute__((packed));

L'en-t?te du bordereau contient les informations requises pour r?aliser une allocation dynamique dans le tas, et les structures tube contiennent les donn?es requises pour g?rer les diff?rents tubes.

Les avantages de cette architecture sont les suivants :

Conventions de codage associ?es

Dans le code (et les donn?es) du noyau, les conventions de codages suivantes sont appliqu?es :

(Cette liste n'est pas exhaustive; elle ne recense que les ?l?ments int?ressant l'architecture du noyau.)

Int?ret de ces conventions

Les fonctions d?clar?es "exported" sont les interfaces des modules. C'est ? travers elles que les modules communiquent. Evidemment, il y a un probl?me : lorsque gcc compile leur code, il leur attribue des adresses arbitraires au sein du module. Bien s?r, on pourrait ? l'?dition de liens informer chaque module des adresses des interfaces des autres modules, mais une telle op?ration rendrait la compilation d'un module d?pendante de celle des autres, et poserait des difficult?s tr?s ?lev?es lors des patchages dynamiques.

Pour r?soudre ce probl?me, une petite planche de code est plac?e au d?but du module, charg?e d'appeler les diverses interfaces depuis une position connue (il s'agit du fichier inclus/liens/.s , voyez le code source pour plus de d?tails). La position de chacun des appels de cette planche est une constante facilement calculable, ce qui permet de rendre chaque module ind?pendant de la version des autres.

A titre d'exemple, voici un d?sassemblage du d?but du module gmm :

Disassembly of section code:

f14000d4 :
        ...
f1400100:       e9 fb 04 00 00          jmp    0xf1400600
f1400105:       90                      nop
f1400106:       0f 0b                   ud2    
f1400108:       e9 83 05 00 00          jmp    0xf1400690
f140010d:       90                      nop
f140010e:       0f 0b                   ud2    
f1400110:       e9 db 05 00 00          jmp    0xf14006f0
f1400115:       90                      nop
f1400116:       0f 0b                   ud2    
        ...
f1400120:       55                      push   %ebp
f1400121:       57                      push   %edi
f1400122:       bf 01 00 00 00          mov    $0x1,%edi
f1400127:       56                      push   %esi
f1400128:       53                      push   %ebx

La planche de code initial s'?tend du d?but du module jusqu'en 0xf1400117 inclus. Elle est suivie d'un trou de 9 octets introduit par l'?diteur de liens, puis par le code du module tel que compil? par gcc. Au point 0xf1400100 se trouve l'interface Alloue(), qui est l'interface 0 de ce module; elle transf?re le contr?le ? la fonction alloue() du code C, situ?e ici en 0xf1400600. Deux autres interfaces suivent (Libere() et Realloue()), apr?s quoi s'?tend le binaire correspondant au fichier noyau/gmm/main.c .

Lors d'une recompilation, les adresses des fonctions du code C changent, mais pas celui des planches initiales. De la sorte, la compilation de chaque module ne d?pend pas de celle des autres, et il est possible de modifier dynamiquement chaque module s?par?ment.

Accessoirement, la convention de codage facilite l'identification de la nature des divers ?l?ments par le programmeur. Exemple tir? du module p3p :

        Inodes[j] = Alloue_mem(sizeof(struct inode_p3p), 0);
        verifie((Inodes[j] != NULL), RETOURNE, NULL, 0);

        Num_inode_courante = ((j+1) % P3P_NB_MAX_INODES);

Ici, nous voyons que Inodes est une structure du bordereau de ce module, j une variable locale, Alloue_mem() une fonction fournie par un autre module (en fait, c'est une macro cachant la fonction Alloue(), mais qu'importe); verifie() est une fonction locale (? nouveau, c'est une macro, mais elle n'appelle pas en soi de fonction externe); NULL, RETOURNE et P3P_NB_MAX_INODES sont des d?finitions du pr?processeur, et Num_inode_courante est une variable stock?e dans le bordereau.

Index de la documentation
Page principale