De l'Alpha à la portabilité

Le portage Alpha a débuté en 1993 et dura environ un an. Il n'était pas complètement achevé au bout d'un an, mais toutes les bases s'y trouvaient. Alors que ce premier port était difficile, il a établi quelques principes de conception gouvernant Linux depuis, qui ont rendu les autres portages plus simples.

Le noyau Linux n'est pas écrit pour être portable sur toute architecture. J'ai décidé que si une architecture était suffisamment saine et satisfaisait certaines règles simples, alors Linux devrait pouvoir y être adapté sans problème majeur. Par exemple, la gestion de la mémoire peut être très différente d'une machine à l'autre. J'ai lu des documents décrivant la gestion de la mémoire des 68000, Sparc, Alpha et PowerPC et trouvé, malgré des différences dans les détails, qu'il y avait beaucoup de points communs dans l'utilisation de la pagination, du cache etc. Il suffisait que la gestion de la mémoire du noyau Linux soit écrite sur la base du dénominateur commun de toutes ces architectures pour qu'il ne soit ensuite pas trop difficile de modifier ce code pour régler les détails propres à une architecture particulière.

Quelques suppositions simplifient beaucoup le problème des ports. Si, par exemple, vous déclarez qu'un microprocesseur doit pouvoir utiliser la pagination il doit disposer d'une sorte de table de traduction (en anglais Translation Lookup Buffer ou TLB), qui lui dit à quoi correspond la mémoire virtuelle. Bien entendu, vous ne pouvez savoir quelle sera la forme de la TLB. Mais en fait, les seules choses que vous devez connaître sont les méthodes pour la remplir et pour la purger lorsque vous décidez que vous n'en avez plus besoin. Ainsi, dans cette architecture bien pensée, vous savez que vous aurez besoin de placer dans le noyau quelques parties spécifiques à la machine, mais que le plus gros du code exploite des mécanismes généraux sur lesquels reposent des choses comme la TLB.

Une autre règle que j'applique est qu'il est toujours préférable d'utiliser une constante de compilation plutôt qu'une variable et, en utilisant cette règle, le compilateur fait souvent une bien meilleure optimisation du code. Il est évident que c'est plus sage, car vous pouvez écrire votre code afin qu'il soit défini de façon facile à adapter et à optimiser.

Ce qu'il y a d'intéressant dans cette approche — essayer de définir une architecture commune bien pensée —  est que, ce faisant, vous pouvez présenter au système d'exploitation une architecture meilleure que celle qu'offre le matériel. Cela semble aller à l'encontre de toute intuition, mais c'est important. Les généralisations que vous cherchez en résumant les systèmes sont souvent les mêmes que les optimisations que vous voudriez faire pour améliorer les performances du noyau.

En fait, quand vous faites un résumé suffisamment important de problèmes comme la mise en œuvre des tables de pagination et quand vous prenez une décision fondée sur vos observations — par exemple, le fait que l'arbre de pagination doit être seulement de profondeur trois —, vous vous apercevez plus tard que c'était la seule approche raisonnable pour obtenir de bonnes performances. En d'autres termes, si vous ne vous étiez pas intéressé à la portabilité en tant que critère de conception, mais aviez simplement essayé d'optimiser le noyau sur une architecture donnée, vous auriez souvent atteint la même conclusion —  c'est-à-dire que la profondeur optimale pour représenter l'arbre de pagination au niveau du noyau est trois.

Cela ne procède pas seulement du hasard. Quand une architecture diffère par certains détails d'une conception générale fonctionnelle, c'est souvent parce qu'elle est mal conçue. Ainsi, les mêmes raisons qui vous font passer outre les particularités de conception à des fins de portabilité vous obligent aussi à contourner les défauts de conception tout en préservant une conception générale plus optimisée. J'ai essayé d'atteindre le juste milieu en combinant le meilleur de la théorie avec la réalité des architectures actuelles.