Linenoise, part. 2

|=[ 0x02 ]=---------=[ Méthode de construction d'OS ]=-------------------=|
|=-----------------------------------------------------------------------=|
|=--------------=[ Bill Blunden <wablunden@hotmail.com> ]=---------------=|


--[ Table des matières

  0 - Introduction

  1 - Le moment critique
	1.1 Choisir une plate-forme hôte
	1.2 Construire un émulateur
	1.3 - Coder un compilateur transversal
	1.4 - Coder et porter l'OS
	1.5 - Téléporter le compilateur transversal.
  2 - Composants de l'OS
	2.1 - Gestion des tâches
	2.2 - Gestion de la mémoire
	2.3 - Interface d'E/S
	2.4 - Système de fichiers
	2.5 - Notes sur la sécurité
  3 - Etude d'un cas simple
	3.1 - Plate-forme hôte
	3.2 - Points de compilations
	3.3 - Démarrage
	3.4 - Initialiser l'OS
	3.5 - Construire et déployer l'OS
  4 - Références et Crédits

--[ 0 - Introduction

Sur les innombrables livres au sujet des systèmes d'exploitation, il n'y en a
que deux ou trois (que je connaisse) qui traitent vraiment de la manière de
créer un système d'exploitation complet. Ces livres se focalisent sur un
matériel spécifique et les principales étapes (de construction) s'enterrent
sous une montagne de détails étouffants. Ce n'est pas toujours une mauvaise
chose, mais seulement une conséquence involontaire. Les systèmes d'exploitation
sont des logiciels de plus en plus compliqués; en disséquer un mettra à jour
d'innombrables détails.

Mon but en soumettant cet article est de fournir une série d'étapes génériques
qui peuvent être utilisées pour construire un OS, à partir du début et sans
penchant pour un fabricant ( d'hardware ).
[NDT: OS: Operating System, systeme d'exploitation] 

"dit oncle Tom, comment on construit un OS..."

Ma propre connaissance de la construction d'OS était plutôt sommaire jusqu'à ce
que j'aie le privilège de rencontrer quelques vieux embrumés de chez "Control
Data". Ce sont eux qui ont travaillé sur le CDC 6600 avec Seymour Cray. La
méthode que je vous expose a été utilisée pour faire le système d'exploitation
SCOPE76 de Control Data. Bien que quelques-uns uns des ingénieurs avec lesquels
j'ai discuté soient maintenant dans leurs 70 ans, je peux vous assurer que
l'approche qu'ils m'ont décrite est toujours très utile et efficace.

Pendant ces longues heures où j'ai harcelé ces vétérans du CDC pour des
détails, j'ai appris plus d'une histoire intéressante. Par exemple, quand
Control Data arriva avec le 6600, c'était plus rapide que tout ce qu'IBM
vendait. Les dirigeants à Big Blue étaient tellement furieux qu'ils ont fait un
tigre de papier: ils ont demandé à tout le monde d'attendre quelques mois.
Malheureusement ça a marché; ils ont tous attendu qu'IBM livre (ce qu'ils n'ont
pas fait, ces connards) et ça a forcé CDC à baisser le prix du 6600 de moitié
pour attirer les clients.

Si vous êtes familier des pratiques commerciales d'IBM, ce type de comportement
ne vous surprendra pas. Saviez-vous qu'IBM a vendu les calculateurs Hollerith
aux nazis pendant la deuxième guerre mondiale?

Cet article est découpé en trois parties.

La partie 1 présente une approche générale pour créer un système
d'exploitation. Je serrai volontairement ambigu. Je veux que mon approche soit
utile tout en ne tenant pas compte de la plate-forme matérielle que vous
utiliseriez.

Pour le détail des procédés eux-même, je les ai différés jusqu'à la partie 2.
Celle-ci présente un plan grossier; pour déterminer l'ordre dans lequel les
composants de l'OS devraient être implémentés.

Pour éclairer un peu les problèmes qu'un ingénieur système rencontrera durant
l'implémentation d'un OS, j'ai inclus une brève discussion sur un exemple plus
détaillé dans la partie 3. Mon but dans cette partie est d'illustrer quelques
remarques que j'aie faites dans la partie 1. Je n'ai pas l'intention d'offrir
un produit commercial de qualité, il y en a déjà beaucoup d'excellents
disponibles. Les lecteurs intéressés pourront aller chercher les références
fournies en fin d'article.

--[ 1 - Le moment critique

Typiquement, en bourse, vous avez besoin d'argent pour faire de l'argent. Pour
les OS, c'est la même chose: vous avez besoin d'un OS pour en faire un autre.

Appelons l'OS initial et le matériel sur lequel il tourne la "plate-forme
hôte". Je ferai référence à l'OS construit et le matériel sur lequel il
fonctionnera sous le nom de "plate-forme cible".

--[ 1.1 - Choisir une plate-forme hôte

Je me souviens avoir demandé à un éclaireur des marines quelle arme-blanche
était la plus efficace selon lui. Sa réponse: "Celle avec laquelle tu es le
plus familier."

Ca reste vrai pour le choix d'une plate-forme hôte. La meilleur est celle avec
laquelle vous êtes le plus familier. Vous allez devoir faire quelques
acrobaties et quelques fantaisies dans vos programmes et être intimement lié à
votre OS et à vos utilitaires de développement. Dans certains cas pathologiques
ça vous aidera aussi d'être familier avec le langage machine de votre matériel.
Ca vous permettra de vérifier deux fois ce que votre utilitaire est en train de
cracher.

Vous allez peut être découvrir qu'il y a des bugs dans votre environnement
initial, et être forcé de changer de plate-forme C'est une bonne raison pour
choisir une plate-forme assez populaire pour qu'il y ai un assez bon choix. Par
exemple, pendant un de mes travaux, sur Windows, j'ai découvert un bug dans
l'assembleur (MASM). Quand ça s'est passé, MASM refusait d'assembler un fichier
source excédant un certain nombre de lignes. Heureusement que j'ai pu acheter
Turbo Assembleur de Borland (TASM) et poursuivre.

--[ 1.2 - Construire un émulateur

Une fois que vous avez trouvé votre plate-forme hôte et choisi vos logiciels de
développement, vous avez besoin de construire un émulateur pour simuler le
comportement de la plate-forme cible.

Ca peut être plus long que prévu. Non seulement vous devez reproduire la base
du matériel mais aussi mimer le BIOS qui est encré dans la ROM. Vous devez
aussi simuler les périphériques et les micros contrôleurs.

NB: La meilleure façon de vérifier que votre émulateur fonctionne correctement
c'est de créer un fichier image d'une de vos partitions et de voir si
l'émulateur est capable de faire fonctionner le système qui y est écrit. Par
exemple, si vous faites un émulateur pour x86, vous pouvez y tester une image
de la partition principale de Linux.

Le principal avantage d'un émulateur c'est qu'il vous évitera de travailler
dans le noir. Il n'y a rien de pire que d'avoir sa machine crashée et de ne pas
savoir pourquoi. Regarder son PC se planter peut s'avérer extrêmement frustrant
parce qu'il est presque impossible de diagnostiquer un problème une fois qu'il
s'est produit. C'est particulièrement vrai pendant la phase d'amorçage, quand
vous n'avez pas encore codé assez d'infrastructures pour déverser des messages
sur la console.

Un émulateur vous permet de voir ce qu'il se passe dans un environnement stable
et contrôlé. Si votre code crash l'émulateur, vous pouvez insérer des
procédures de diagnostique pour faciliter le travail de débugage. Vous pouvez
aussi lancer l'émulateur dans un mode similaire au débuggeur pour pouvoir
exécuter pas à pas pendant la phase problématique.

L'alternative, c'est de lancer directement votre OS sur la machine, ce qui vous
empêchera d'enregistrer l'état de la machine quand elle crashe. Les techniques
de diagnostiques et de debugage que vous utilisiez avec l'émulateur seront
remplacée par des tactiques purement spéculatives. Ce n'est pas gai,
croyez-moi.

Pour un excellent exemple d'émulateur, vous devriez jeter un coup d'oeil à
l'émulateur boch x86. Il est disponible à:

        http://sourceforge.net/projects/bochs/

Encore une chose, c'est mieux d'utiliser bochs avec Linux. Parce que boch
travaille avec des images de disque et la commande 'dd' est facilement
utilisable et produit facilement des images de disque. Par exemple, la commande
suivante prend une disquette et en fait une image nommée floppy.img.

        dd if=/dev/fd0 of=floppy.img bs=1k

Windows ne fournit pas d'équivalent. Quelle surprise !

"plus tôt dans la journée..."

Jadis, créer un émulateur était souvent une question de nécessité parce que le
matériel cible n'avait pas encore été produit. En ce temps, un crash test était
vraiment un crash test... ils allumaient la machine et surveillaient si ça ne
fumait pas!

--[ 1.3 - Coder un compilateur transversal

[NDT: le terme anglais est "Cross-Compiler", souvent utilisé en français mais
le terme "transversal" traduit mieux l'idée. ]

Une fois l'émulateur codé, vous devriez coder un compilateur transversal.
Spécifiquement, vous aurez besoin d'un compilateur qui fonctionne sur la
plate-forme hôte mais génère du code pour la plate-forme cible. Initialement,
vous utiliserez l'émulateur pour faire tourner tout ce qu'il produit. Quand
vous serez à l'aise avec votre environnement, vous pourrez lancer le code
directement sur la plate-forme cible.

"Speaking words of wisdom, write in C..."
[NDT: référence aux Beatles ]

Etant donne que le C est le langage pour la programmation système, je
recommande fortement l'acquisition du code source d'un compilateur comme gcc et
de modifier le fond. Le compilateur gcc est même fourni avec la documentation
dédiée à ce travail, c'est pourquoi je recommande gcc. Il y a d'autres
compilateurs C publiques, comme small-C, qui obéit à une partie de la norme
ANSI et sera plus facile à porter.

        gcc:            http://gcc.gnu.org
        small-C:        http://www.ddjembedded.com/languages/smallc

Si vous voulez faire autrement, je suppose que vous pourrez trouver un
compilateur Pascal ou Fortran et vous dépatouiller avec. Ce ne serait pas la
première fois que quelqu'un prenne le chemin le mois fréquenté. A ses débuts,
les ingénieurs de Control Data ont inventé leur propre variation du Pascal pour
coder le NOSVE (aka NOSEBLEED) OS. NOSVE fut l'une de ces tours de Babel qui
n'a jamais été produite. Chez Control Data, vous n'étiez jamais considéré comme
un vrai manager tant que vous n'aviez pas au moins une bonne erreur à votre
palmarès. Je suppose que NOS/VE a élevé le manager au statut de VP!

--[ 1.4 - Coder et porter l'OS

Bien, vous avez fait les préliminaires. Il est temps de coder l'OS proprement
dit. Les détails de cette méthode sont expliqués dans la deuxième partie. Une
fois que vous avez un prototype de votre OS qui fonctionne bien sur
l'émulateur, vous êtes confronté au -gros- problème... faire fonctionner votre
code réellement sur la machine cible.

Je trouve que c'est un obstacle que vous devriez franchir très tôt. Faites un
test sur la machine cible dès que vous avez le minimum de composants. Découvrir
que votre code ne démarre pas après 50.000 lignes d'efforts est démoralisant.

Si vous avez été méthodique dans l'élaboration et les tests de votre émulateur,
la plupart de vos problèmes concerneront le code de votre OS et peut être des
parties non documentées de vos périphériques. C'est là qu'investir dans la
construction d'un émulateur blindé est vraiment rentable. Savoir que
l'émulateur fait correctement son travail vous facilitera le diagnostique des
problèmes... et vous économisera des heures de sommeil.

Enfin, je vous recommande d'utiliser une disquette de démarrage, comme ça, vous
ne prenez pas de risques pour vos disques durs. On peut même arriver à mettre
le noyau Linux sur une disquette alors pour l'instant, ne vous inquiétez pas
pour les contraintes de tailles du code.

--[ 1.5 - Téléporter le compilateur transversal.

Félicitations. Vous êtes arrivé où seulement quelques-uns uns sont déjà
arrivés. Vous avez construit un système d'exploitation. Toutefois, ne serait-ce
pas agréable d'avoir quelques outils de développement qui fonctionneraient sur
votre OS? Ca peut se faire en téléportant le compilateur transversal.

Voici comment la téléportation fonctionne: vous prennez le code source de votre
compilateur et vous le donnez à manger à ce même compilateur sur la plateforme
hôte. Le compilateur transversal digère ce code source et produit du code
binaire qui s'exécute sur votre système cible. Vous obtenez un compilateur qui
tourne sur l'Os cible et qui crée des exécutables qui tournent aussi sur l'OS
cible.

Naturellement, je fais quelques hypothèses. Spécifiquement, je suppose que les
librairies que le compilateur transversal utilise sont aussi disponibles sur
l'OS cible. Les compilateurs prennent beaucoup de temps pour accomplir les
manipulations de chaînes et les entrées/sorties. Si ce travail d'arrière plan
n'est pas présent ni supporté sur la plate-forme cible, le nouveau compilateur
n'a pas beaucoup d'utilité.

--[ 2 - Composants de l'OS

Un OS est un étrange programme en ce qu'il doit se charger et fonctionner
lui-même en plus de charger et faire fonctionner les autres programmes. Donc,
la première chose dont il a besoin, c'est de se charger et de démarrer ses
composants pour pouvoir faire son travail.

Je vous recommande de trouver la documentation de votre matériel. Si vous
ciblez Intel, vous avez de la chance parce que j'explique le processus
d'amorçages du x86 dans la troisième partie.

Quel que soit l'architecture, je recommande un profil modulaire et orienté
objet. Ca ne veut pas dire que vous devez utiliser le C++. Je vous encourage
plutôt à séparer différentes portions de l'OS en parties de données et de code.
Que vous utilisiez un compilateur ou non pour renforcer cette séparation est à
votre jugement. Cette approche est avantageuse puisqu'elle permet de souligner
nettement les séparations entre composants. C'est mieux puisque ça permet de
modifier/cacher chaque sous partie.

Tenenbaum à pris cette idée à l'extrême en permettant à des composants du
noyau, comme le système de fichier et la gestion de la mémoire, d'être
chargeable à l'exécution. Avec d'autres systèmes d'exploitations, on aurait dû
recompiler le noyau pour échanger deux composants centraux comme la gestion de
la mémoire. Avec Minix, ces composants peuvent être échanger à l'exécution.
Linux a essayé d'implémenter quelque chose de similaire via des modules du
noyau chargeables.

Parallèlement, vous voudrez apprendre le langage assembleur de votre matériel
cible. Il y a des composants de l'OS directement liés au matériel qui ne
peuvent être fournis qu'en exécuter quelques douzaines de ligne de cet
assembleur spécifique. L'ensemble d'instruction d'Intel est probablement l'un
des plus compliqués. C'est dû principalement à des contraintes historiques qui
ont conduit Intel à s'efforcer d'une compatibilité ascendante [NDT: le code qui
tournait sur une ancienne machine tournera sur une nouvelle]. L'encodage
binaire d'Intel est particulièrement embarrassant.

Quel composant de l'OS devez vous implémenter en premier?

Dans quel ordre doivent-ils être implémenter ?

Je vous recommande d'implémenter les différentes zones de fonctionnalités de la
manière décrite dans la section suivante.

--[ 2.1 - Gestion des tâches

Dans son livre sur l'élaboration d'OS, Richard Burgess affirme qu'on doit
commencer par la gestion des tâches, et je tendrais à être d'accord avec lui.
Le modèle de gestion que vous choisirez influencera tout ce que vous ferez
ensuite.

D'abord, et surtout, un système d'exploitation gère des tâches. Qu'est-ce
qu'une tâche? La documentation du Pentium d'Intel les définis comme une "unité
de travail" (V3 p.6-1).

Qu'est-ce qu'ils fumaient? C'est comme dire qu'un chapeau est un vêtement. Ca
ne donne aucune perspicacité à la vraie nature d'une tâche. Je préfère penser
aux tâches comme étant un ensemble d'instructions exécutées par le CPU en
fonction de l'état de la machine que cette exécution produit.

Inévitablement, la définition exacte d'une tâche est déterminée par le code
source du système d'exploitation.

Le noyau Linux (2.4.18) représente chaque tâche par une structure task_struct
définie dans /usr/src/linux/include/linux/sched.h. L'ensemble des processus
sont regroupés de deux façons. D'abord, ils sont indexés dans une table de
hachage de pointeurs:

        extern struct task_struct *pidhash[PIDHASH_SZ];

La structure des tâches est aussi liée par des pointeurs next_task et prev_task
pour former une liste doublement chaînée.

    struct task_struct
    {
        :
        struct task_struct *next_task, *prev_task;
        :
    };

Vous devrez décider si votre OS sera multi-tâches ou non, et alors quelle
politique sera appliquée pour choisir quand échanger les tâches ( le changement
de tâche est aussi appelé changement de contexte). Isoler cette politique de la
procédure est important parce que vous pourriez décider de changer de politique
plus tard et vous ne voudriez pas avoir à réécrire tout le code.

Mécanisme de changement de contexte:
------------------------------------

Sur les plates-formes Intel, le changement de tâche est facilité par un
ensemble de structures de données systèmes et une série d'instructions
spéciales. Spécifiquement, les processeurs de la classe des pentium d'Intel ont
un registre de Tâche (TR) qui est supposé être chargé (via l'instruction LTR)
avec un pointeur 16-bits de segment. Ce pointeur de segment indexe un
descripteur dans la table globale des descripteurs (GDT). Les informations d'un
descripteur incluent l'adresse de base et la taille du segment d'état de la
tâche (TSS). Le TSS est un descriptif d'état de la tâche. Il inclut l'état des
registres de donnée (EAX, EBX, etc. ) et garde une trace du segment de mémoire
utilisé par cette tâche. En d'autres mots, il conserve le 'contexte' d'une
tâche.

Le registre TR contient toujours le pointeur du segment pour la tâche en cours.
Changer de tâche se fait en sauvant l'état du processus courant dans son TSS et
puis en chargeant un nouveau pointeur dans TR. Comment ça se décide, en terme
d'implémentation, est souvent relégué à l'horloge interne.

La plupart des systèmes multi-tâches assignent un quantum de temps à chaque
processus. Le temps qu'une tâche reçoit est une question de politique. Un
minuteur intégré, comme le 82C54, peut être configuré pour générer des
interruptions à des intervalles de temps réguliers? Chaque fois que ces
interruptions son générées, le noyau a une opportunité de vérifier et de voir
s'il doit faire un changement de tâche. Si oui, un OS base sur Intel peut
initialiser un changement de tâche en exécutant un JMP ou un CALL vers le
descripteur, dans la GDT, de la tâche cible. Ca implique le changement de
valeur de TR.

Utiliser l'horloge est ce qu'on appelle le multi-tâches préemptif. Dans ce cas,
l'OS décide quelle tâche va s'exécuter en accord avec sa politique. De l'autre
cote, on trouve le multi-tâches coopératif, où chaque tâche décide quand elle
laisse le CPU à une autre.

Pour un traitement exhaustif de la gestion des tâches, voir le manuel du
Pentium d'Intel (Volume 3, Chapitre 6).

Politique de changement de contexte:
------------------------------------

Décider quel processus prend le contrôle du CPU, et pour combien de temps, est
une question de politique. Cette politique est implémentée par le
planificateur. Le noyau Linux à un planificateur implémenté par la fonction
schedule() dans /usr/src/linux/kernel/sched.c.

Il y a un paquet de petits détails dans la fonction schedule() relatif à la
prise en compte de différents scénarios où il y a plusieurs processeurs, et
aussi une paire de cas spéciaux. Toutefois, les actions de base prises par le
planificateur sont relativement directes. Le planificateur regarde dans
l'ensemble des tâches qui peuvent être exécutées. Ces tâches exécutables sont
liées "par le chaînage de leur structure"
[NDT: by the runqueue data structure ].

Le planificateur cherche la tâche dans la file avec la plus grande "bonté" et
la planifie pour s'exécuter. La bonté est une valeur calculée par la fonction
goodness(). Elle retourne une valeur qui reflète le besoins qu'a la tâche
d'être exécutée.


            -----------------
            -1000: ne jamais exécuter
            0: réexaminer toutes les tâches, pas seulement celle de la file
               d'exécution
            +ve: Au plus, au mieux
            +1000: processus en temps réel, sélectionner celui-ci.

Si la plus grande valeur de bonté dans la file des tâches est de zéro, le
planificateur fait un pas en arrière et recherche dans toutes les tâches, pas
seulement celles dans la file d'exécution.

Pour vous donner une idée d'implémentation, j'ai inclus un petit morceau de la
fonction schedule() et quelques-unes de ses lignes les plus importantes.

asmlinkage void schedule(void)
{
    struct schedule_data * sched_data;
    struct task_struct *prev, *next, *p;
    struct list_head *tmp;
    int this_cpu, c;
        :
        :
    /*
     * Voici le code proprement dit:
     */

    repeat_schedule:
    /*
     * Processus par défaut à selectionner...
     */
    next = idle_task(this_cpu);
    c = -1000;
    list_for_each(tmp, &runqueue_head)
    {
        p = list_entry(tmp, struct task_struct, run_list);

        if (can_schedule(p, this_cpu))
        {
            int weight = goodness(p, this_cpu, prev->active_mm);
            if (weight > c){ c = weight, next = p; }
        }
    }

    /* A-t-on besoin de recalculer les compteurs? */
    if (unlikely(!c))
    {
        struct task_struct *p;

        spin_unlock_irq(&runqueue_lock);
        read_lock(&tasklist_lock);
        for_each_task(p)
        {
            p->counter = (p->counter >> 1) + NICE_TO_TICKS(p->nice);
        }
        read_unlock(&tasklist_lock);
        spin_lock_irq(&runqueue_lock);
        goto repeat_schedule;
    }
    :
    :

--[ 2.2 - Gestion de la mémoire

Un processus occupe ET alloue de la mémoire. Une fois que vous avez ébauché
votre gestionnaire de tâches, vous allez avoir besoin de leurs donner accès à
un sous système de gestion de mémoire. Débrouillez-vous pour garder un
interface vers le sous-système de mémoire clair, comme ça, vous pourrez le
jeter et le remplacer plus tard, si vous le voulez.

Au niveau de l'OS, la protection de la mémoire est mise en oeuvre de deux
façons:

    i-    segmentation
    ii-   pagination

Vous allez devoir décider si oui ou non vous voulez supporter ces deux
caractéristiques. La pagination, en particulier, est une tâche très liée au
matériel. Ca veut dire que si vous décider de prendre en compte la pagination,
porter l'OS sera difficile. D'après Tanenbaum, c'est la raison principale pour
laquelle Minix ne supporte pas la pagination.

La segmentation peut être reléguée au matériel ou être implémentée manuellement
via une technique de sablier [NDT: sand boxing technique] au niveau du noyau.
Presque tout le monde préfère une implémentation plus matérielle parce que
c'est plus facile. Comme la pagination, la segmentation basée sur le matériel
demandera un paquet de code spécifique et une dose vitale de langage
assembleur.

Le système d'exploitation MMURTL divise sa mémoire virtuelle en trois parties.
Il y en a un segment pour l'OS, un pour l'application et le dernier pour le
segment de données. Ca ne protège pas exactement une application des autres,
mais ça protège l'OS.

        MMURTL Segment        Valeur de l'adresse
        --------------        -------------------
        OS code              0x08
        Apps code            0x18
        Apps data            0x10

La gestion de la mémoire de MMURTL est en fait installée dans le secteur de
boot! C'est correct, j'ai dit le secteur de boot. Si vous regardez dans le code
source de bootlok.asm, que Burgess compile avec TASM, vous constaterez que le
code de ce registre le rend nécessaire pour passer en mode protégé. Voici
quelques morceaux intéressants du fichier:

        IDTptr    DW 7FFh        ;LIMIT 256 IDT Slots
                  DD 0000h       ;BASE (Linear)
        GDTptr    DW 17FFh       ;LIMIT 768 slots
                  DD 0800h       ;BASE (Linear)
                  :
                  :
        LIDT FWORD PTR IDTptr ;Charge le pointeur ITD du processeur
        LGDT FWORD PTR GDTptr ;Charge le pointeur GDT du processeur
                :
                :
        MOV EAX,CR0 ;Registre de controle
        OR AL,1 ;Etablis le bits du mode protégé
        MOV CR0,EAX
        JMP $+2 ;Libere deux instruction avec un JMP
        NOP
        NOP
        MOV BX, 10h ;Initialise le registre de segment
        MOV DS,BX
        MOV ES,BX
        MOV FS,BX
        MOV GS,BX
        MOV SS,BX

        ;Définition d'un long saut
        DB 66h
        DB 67h
        DB 0EAh
        DD 10000h
        DW 8h
        ; Maintenant en mode protégé

Avant de charger le GDTR et l'IDTR, Burgess charge l'OS en mémoire de manière à
ce que les adresses de bases dans le sélecteur pointent vraiment sur des
entrées valides du descripteur. Ca lui évite aussi de devoir mettre ces
structures de données dans le code de démarrage, ce qui est utile vu la limite
de taille de 512 octets.

La plupart des systèmes d'exploitation utilisent la pagination comme un moyen
d'augmenter l'espace adressable qu'ils gèrent. La pagination est compliquée et
implique un paquet de code spécifique, et ce code s'exécute fréquemment... ce
qui implique une énorme perte de performances. Les E/S sur disques sont
sûrement les opérations les plus coûteuses qu'un PC isolé puisse exécuter. Même
si cette gestion était reléguée au matériel, la pagination boufferait encore
trop de temps. 

Barry Brey, un expert en puces d'Intel, m'a dit que la pagination sous Windows
mange jusqu'à 10% du temps d'exécution. En fait, la pagination est si coûteuse,
en terme de temps d'exécution, et la RAM est si bon marché que c'est souvent
une meilleure idée d'acheter plus de mémoire et de désactiver la pagination. Au
vu de ceci, vous ne trouverez pas la pagination si nécessaire. Si vous êtes en
train de développer un OS embarqué, vous n'en aurez pas besoins de toutes
façons.

Quand les premières mémoires de bases étaient de 16KB, et que ces petites
barrettes étaient si chères, la pagination avait sûrement plus de sens.
Aujourd'hui, acheter une paire de SDRAM d'un GB n'est pas si inhabituel et je
peux dire que, peut-être, la pagination est une relique du passé.

--[ 2.3 - Interface d'E/S

C'est la partie cauchemardesque.

Vous avez maintenant des processus, et ils vivent dans la mémoire. Mais ils ne
peuvent pas interagir avec le monde extérieur sans connections avec les
périphériques d'E/S. La connexion vers les périphériques d'E/S est
traditionnellement implémentée dans les tripes de l'OS. Comme pour les autres
composants de l'OS, vous allez devoir user de votre talent en langages
assembleur.

En mode protégé, utiliser le BIOS pour afficher des données n'est pas
qu'optionnel parce que la vielle méthode de lancer des interruptions et
d'adresser la mémoire en mode réel n'existe plus. Une manière d'envoyer des
messages à l'écran est d'écrire directement dans la mémoire vidéo. La plupart
des écrans, même les plats, se lancent en mode texte VGA 80x25 monochrome et/ou
couleur.

    Zone mémoire    adresses en mode réel    adresses linéaires du buffer
    -------------    -----------------        ----------------------
    monochrome        B000[0]:0000                 B0000H
    couleur           B800[0]:0000                 B8000H

Dans les autres cas, l'écran peut afficher 25 lignes et 80 colonnes de
caractères[NDT: dans le texte original, il parle de 80 lignes, erreur que j'ai
corrigée.]. Chaque caractère prend deux octets dans la mémoire vidéo (ce qui
n'est pas si mal... 80x25=2000 x2=4000 octets ). Vous pouvez placer un
caractère à l'écran en altérant simplement le contexte de la mémoire vidéo.
L'octet de poids faible contient le code ASCII du caractère, et celui de poids
fort contient l'attribut.

Les bits d'attributs sont organisés comme suit:

                   bit 7   clignotant
                   ---------------
                   bit 6
                   bit 5   couleur de fond ( 0H=noir )
                   bit 4
                   ---------------
                   bit 3
                   bit 2   couleur d'avant plan ( 0EH=blanc )
                   bit 1
                   bit 0

Pour tenir compte de plusieurs écrans, créez simplement des buffers et placez
l'écran virtuel dans la mémoire vidéo quand vous voulez le voir. Par exemple,
en mode protégé, le code suivant ( écrit par DJGPP ) placera un 'J' à l'écran.

        #include <sys/farptr.h>
        #include <go32.h>
        _farpokeb(_dos_ds, 0xB8000, 'J');
        _farpokeb(_dos_ds, 0xB8000+1, 0x0F);

Quand j'ai vu le morceau de code suivant dans le fichier console.c de Minix,
j'ai su que Minix utilise cette technique pour écrire à l'écran.

#define MONO_BASE    0xB0000L	/* base de la memoire video monochrome */
#define COLOR_BASE   0xB8000L    /* base de la memoire video couleur */
        :
        :
PUBLIC void scr_init(tp)
tty_t *tp;
{
        :
        :
  if (color)
  {
    vid_base = COLOR_BASE;
    vid_size = COLOR_SIZE;
  }
  else
  {
    vid_base = MONO_BASE;
    vid_size = MONO_SIZE;
  }
        :
        :

Gérer les E/S vers les autres périphériques basé sur Intel n'est jamais aussi
facile. C'est là qu'arrive notre vieil ami, le gestionnaire d'interruption
programmable 8259(PIC) vient en action. [NDT : Programmable Interrupt
Controller] Récemment, j'ai lu beaucoup de documentations Intel sur un PIC
avancé (comme APIC), mais tout le monde semble s'accrocher vieux controleur
d'interruptions.

Le 8259 PIC est la liaison matérielle entre le matériel et le processeur.
L'installation la plus commune implique deux 8259 PICs configuré dans un
arrangement maître-esclave. Chaque PIC possède huit lignes de requêtes
d'interruptions ( lignes d'IRQ ) qui reçoivent les données des périphériques
externes ( comme le clavier, disque dur, etc. ). Le 8259 maître utilise sa
troisième patte pour se lier au 8259 esclave, comme ça, ils fournissent 15
lignes d'IRQ pour les périphériques externes. Le 8259 maître communique avec le
CPU via la patte INTR d'interruption du CPU. L'esclave utilise son slot INTR
pour parler au maître via sa troisième ligne d'IRQ.

Normalement, le BIOS programme le 8259 quand le PC démarre, mais pour parler
aux périphériques matériels en mode protégé, le 8259 doit être re-programmé.
C'est parce que le 8259 couple les lignes d'IRQ aux signaux d'interruptions.
Programmer le 8259 utilise les instructions IN et OUT. Dans le fond, vous
enverrez des valeurs 8-bits vers le registre de commandes d'interruptions (ICR)
et vers le registre de masquage d'interruptions (IMR) dans un certain ordre. Un
mauvais geste et vous plantez tout.

Mon exemple favori de programmation du 8259 vient de MMURTL. Le code suivant se
trouve dans INITCODE.INC et est invoqué pendant la séquence d'initialisation
dans MOS.ASM.

;=========================================================================
; Ceci initialise les vecteurs IRQ00-0F du 8259
; pour être l'interruption 20, fonction 2F.
;
; Quand les PICU's sont initialisés, toutes les interruptions matérielles sont masquées.
; Chaque driver qui utilise une interruption matérielle est responsable
; du démasquage de sa ligne d'IRQ.
;
PICU1      EQU 0020h
PICU2      EQU 00A0h

Set8259 PROC NEAR
    MOV AL,00010001b
    OUT PICU1+0,AL        ;ICW1 - MAITRE
    jmp $+2
    jmp $+2
    OUT PICU2+0,AL        ;ICW1 - EXCLAVE
    jmp $+2
    jmp $+2
    MOV AL,20h
    OUT PICU1+1,AL        ;ICW2 - MAITRE
    jmp $+2
    jmp $+2
    MOV AL,28h
    OUT PICU2+1,AL        ;ICW2 - EXCLAVE
    jmp $+2
    jmp $+2
    MOV AL,00000100b
    OUT PICU1+1,AL        ;ICW3 - MAITRE
    jmp $+2
    jmp $+2
    MOV AL,00000010b
    OUT PICU2+1,AL        ;ICW3 - EXCLAVE
    jmp $+2
    jmp $+2
    MOV AL,00000001b
    OUT PICU1+1,AL        ;ICW4 - MAITRE
    jmp $+2
    jmp $+2
    OUT PICU2+1,AL        ;ICW4 - EXCLAVE
    jmp $+2
    jmp $+2
    MOV AL,11111010b      ;Masquage de toutes sauf l'orloge et le chainage
;   MOV AL,01000000b      ;Masquage de la disquette
    OUT PICU1+1,AL        ;Masquage - MAITRE (0= Ints ON)
    jmp $+2
    jmp $+2
    MOV AL,11111111b
;   MOV AL,00000000b
    OUT PICU2+1,AL        ;Masquage - SLAVE
    jmp $+2
    jmp $+2
    RETN
SET8259  ENDP
;=========================================================================

Notez comment Burgess fait deux sauts COURTS après chaque instruction OUT.
C'est pour laisser aux PIS le temps d'exécuter la commande.

Ecrire un driver peut devenir une expérience déchirante. C'est parce que les
drivers ne sont rien de moins que des membres officiels de l'image mémoire du
noyau. Quand vous construisez un driver, vous construisez une partie de l'OS.
Ca veut dire que si vous n'implantez pas correctement un driver, vous pouvez
tuer votre système en le crashant de la pire manière... tombé sous le feu ami.

Construire un driver est aussi effrayant avec toutes sortes d'encodage binaire
spécifiques aux fabricants et de codage acrobatique. Le meilleur conseil que je
puisse vous donner est de rester fidèle aux matériels les plus utilisés et les
plus communs. Une fois que vous avez une console qui fonctionne, vous pouvez
essayer de communiquer avec un disque dur et peut-être une carte réseau.

Vous pourriez aussi considérer de concevoir votre OS pour que les drivers
puissent être chargés et déchargés à l'exécution. Devoir recompiler le noyau
pour accommoder un simple driver est une paine. Cela vous obligera à créer un
mécanisme d'appels indirects de telle manière que l'OS puisse invoquer le
driver, même sans savoir à l'avance où il se trouve.

Le noyau Linux autorise du code à être chargé à l'exécution via des modules
chargeables (LKM's). Ces modules chargeables dynamiquement ne sont rien de
moins que des fichiers objets ELF (ils ont été compile mais l'édition de lien
n'a pas été faite officiellement). Il y a plein de services qui peuvent être
utilisés pour gérer les LKM's. Deux des plus communs sont insmod et rmmod, qui
sont utilisés pour insérer et retirer des LKM's à l'exécution.

L'utilitaire insmod agit comme un éditeur de lien/chargeur et assimile le LKM
dans l'image mémoire du noyau. Insmod le fait en invoquant l'appel système
init_module. Il se trouve dans /usr/src/linux/kernel/module.c.

asmlinkage long
sys_init_module(const char *name_user, struct module *mod_user){ ...

Cette fonction, à son tour, invoque une autre fonction appartenant au LKM qui
aussi, ça tombe bien, s'appelle init_module(). Voici un morceau intéressant de
sys_init_module():

    /* Initialise le module.  */
    atomic_set(&mod->uc.usecount,1);
    mod->flags |= MOD_INITIALIZING;
    if (mod->init && (error = mod->init()) != 0)     {
        atomic_set(&mod->uc.usecount,0);
        mod->flags &= ~MOD_INITIALIZING;
        if (error > 0)    /* "module buggant"*/
            error = -EBUSY;
        goto err0;
    }
    atomic_dec(&mod->uc.usecount);

[NDT: "buggy module" à ete traduit par "module buggant"]

La fonction init_module() du LKM, qui est appelée par le code du noyau
ci-dessus, invoque alors une routine du noyau pour enregistrer les
sous-routines du LKM. Voici un exemple:

    /* Initialise le module - Enregistre le périphérique des caractères
       ["character device"] */
    int init_module()
    {
        /* Enregistre le peripherique des caracteres (du moins, essaie) */
        Major = module_register_chrdev(    0,
                                        DEVICE_NAME,
                                        &Fops);

        /* Valeur négative signifie une erreur */
        if (Major < 0)
        {
            printk ("%s device failed with %d\n",
                    "Sorry, registering the character",
                    Major);
            return Major;
        }

        printk ("%s The major device number is %d.\n",
                "Registeration is a success.",
                Major);
        printk ("If you want to talk to the device driver,\n");
        printk ("you'll have to create a device file. \n");
        printk ("We suggest you use:\n");
        printk ("mknod <name> c %d <minor>\n", Major);
        printk ("You can try different minor numbers %s",
                "and see what happens.\n");

        return 0;
    }

Les OS Unix, dans le but de simplifier les choses, traitent chaque périphérique
comme un fichier. C'est pour minimiser le nombre d'appel système et pour offrir
une interface uniforme d'un matériel à l'autre. C'est une approche qui vaut la
peine d'être considérée. Bien que, d'autre part, l'approche Unix n'a pas
toujours été bien vue en terme de facilité d'utilisation. J'ai entendu beaucoup
de plaintes à propos du montage et démontage des utilisateurs de Windows qui se
mettaient à Unix.

Notez, si vous prenez la route des LKM's, vous devrez faire attention de ne pas
laissez de faille de sécurité dans vos drivers chargeables.

Au vu de ces détails cinglés et audacieux [nuts-and-bolds], pour la plate-forme
Intel, je vous recommanderai le livre de Frank Gilluwe. Si vous ne travaillez
pas sur Intel, alors, vous devrez faire quelques fouilles. Prenez le combiné et
Internet et contactez les fabricants.

--[ 2.4 - Système de fichiers

Vous avez maintenant des processus, en mémoire, qui peuvent parler au monde
extérieur. L'étape finale est de leur donner une façon de persister et
d'organiser des données.

En général, vous allez construire le gestionnaire du système de fichier au
début du driver de disques que vous avez implémenté à la dernière étape. Si
votre OS gère un système embarqué, vous ne pourrez pas toujours implémenter de
système de fichiers parce que parfois, aucun disque matériel n'existe.
Cependant, j'ai déjà vu un système de fichiers implémenté dans la RAM. Même les
systèmes embarqués ont besoin de produire et stocker des fichiers log...

Il y a plusieurs spécification de systèmes de fichiers documentées disponibles
au publique, comme le système de fichier ext2 rendu célèbre par Linux. Voici le
lien principal pour l'implémentation du ext2:

    http://e2fsprogs.sourceforge.net/ext2.html

La documentation sur ce site devrait être suffisante pour que vous puissiez
commencer. En particulier, il y a une page nommée "Design ans Implementation of
Second Extended File System" [NDT: site en anglais...] que j'ai trouvée être
une introduction bien menée.

Si vous avez le code source du noyau Linux et que vous voulez regarder les
structures de base du ext2fs, alors, regardez dans:

    /usr/src/linux/include/linux/ext2_fs.h
    /usr/src/linux/include/linux/ext2_fs_i.h

Pour voir les fonctions qui manipulent ces structures de données, regardez dans
le répertoire suivant:

    /usr/src/linux/fs/ext2

Dans ce répertoire, vous trouverez quelque chose comme:

#include <linux/module.h>

MODULE_AUTHOR("Remy Card and others");
MODULE_DESCRIPTION("Second Extended Filesystem");
MODULE_LICENSE("GPL");

in inode.c, and in super.c you will see:

EXPORT_NO_SYMBOLS;

module_init(init_ext2_fs)
module_exit(exit_ext2_fs)

Evidemment, après la dernière discussion, vous devriez voir que le support du
ext2fs peut être assuré par un LKM!

Quelques créateurs d'OS, comme Burgess, prennent la route du système de fichier
MS-DOS FAT, pour des raisons de facilités, et comme ça, ils ne doivent pas
reformater leurs disques durs. Je ne recommande pas le système FAT. En général,
vous voudrez garder en mémoire que c'est une bonne idée d'implémenter un
système de fichier qui permet l'appartenance des fichiers et les contrôles
d'accès. Plus sur le sujet dans la section suivante.

--[ 2.5 - Notes sur la sécurité

La complication est l'ennemi de la sécurité. Les procédures simples sont
faciles à vérifier et sûre, les compliquées ne le sont pas. N'importe quel
comptable vous dira que nos taxes byzantines [Byzantine tax laws] laissent de
la place pour les abus.

Les logiciels, c'est pareil. Un code source compliqué renferme toutes sortes de
bugs insidieux potentiels. Comme les OS ont évolués, ils sont devenus plus
compliqués. D'après un témoignage d'un cadre de chez Microsoft du 2 février
1999, Windows 98 contient 18 millions de lignes de code. Pensez-vous qu'il y
ait un bug quelque part? Oh... non... Microsoft ne vendrait pas du code
erroné...

<d'après Dr. Evil, a la Austin Powers, qui dît la phrase ci-dessus>

La sécurité n'est pas quelque chose que vous pourrez ajouter à votre OS quand
vous en aurez presque finis avec lui. La sécurité devrait être une part
inhérente aux opérations de bases du système. Gardez ça en tête pendant toutes
les phases de constructions, de la gestion de tâches au système de fichier.

De plus, vous voudrez peut être qu'un organisme répute une vérification sur les
mécanismes de sécurité avant de proclamer votre OS comme 'sécurise'. Par
exemple, la NSA évalue le degré de confiance de votre système d'exploitation
sur une échelle de C2 à A1.

Un OS de confiance est juste un OS qui des mécanismes de sécurité en place. La
principale caractéristique de sécurité d'un système est le rang que la NSA lui
a attribue. Un système de classe C2 n'a que des contrôles limites d'accès et
d'authentifications. Un système de classe A1, de l'autre cote du spectre, a des
mécanismes rigoureux et obligatoires de sécurité.

Les gens qui imaginent des ennemis sont appelles des paranoïas, ceux qui ont
des ennemis imaginatifs sont appelles victimes. C'est souvent dur de faire la
différence avant qu'il ne soit trop tard. Si j'avais à confier mon entreprise à
un OS, j'en prendrais un qui serait plus du cote de la paranoïa.

--[ 3 - Etude d'un cas simple

Dans cette section, je vous présente quelques codes fait-maison dans le but
d'éclairer les points que j'ai exposés en partie 1.

--[ 3.1 - Plate-forme hôte

Pour nombre de raisons, j'ai décide de prendre un raccourci et de créer mon OS
sur une architecture Intel 8x86. Le coût est un point frappant et c'est pareil
pour le choix des systèmes hôtes potentiels (Linux, openBSD, MMURTL, Windows,
etc. ).

Cependant, le bénéfice principal est que je peux éviter (dans une certaine
mesure) de devoir coder un compilateur transversal et un émulateur. En ayant la
plate-forme hôte et le système cible tournant sur le même matériel, j'ai pu
prendre avantage des outils existant qui génèrent du code binaire x86 et
émulent le matériel x86.

Pour avoir recours au plus petit dénominateur commun, j'ai décidé d'utiliser
Windows comme plate-forme hôte. Windows, mis à part ses bugs, apparaît avoir le
plus d'utilisateurs. Presque tout le monde est donc capable de suivre les
points et idées que je présente dans la partie 3.

Un bénéfice du choix de Windows c'est qu'il est fourni avec sont propre
émulateur. La machine virtuelle DOS un émulateur brut. J'ai dit 'brute' parce
qu'il ne fournit pas toutes les fonctionnalités que boch fournis. En fait, j'ai
testé un paquet de code dans la VM DOS [Virtual Machine].

--[ 3.2 - Points de compilations

Il existe des douzaines de compilateurs C qui fonctionnent sous Windows. J'ai
fini par avoir trois critères pour en choisir un:

        i-    génère du code binaire brute ( par exemple les fichiers .COM MS )
        ii-   permet des instructions en lignes spéciales (comme INT, LGDT )
        iii-  est libre

Les PC Intel démarrent en mode réel, ce qui veut dire que je dois commencer la
partie avec un compilateur 16-bits. En plus, Le code système doit être du code
binaire brute de façon à ce que les adresses à l'exécution ne doivent pas être
implémentées manuellement. Ce n'est pas obligatoire mais ça rend la vie plus
facile.

Le seul compilateur commercial 16-bits qui génère du code binaire brute est
passé de mode l'année dernière... j'ai donc du faire des recherches.

Après avoir fouillé le net pour trouver un compilateur, j'ai fini par élaborer
ce tableau:

    compilateur     décision    raison
    --------        --------    ------
    TurboC          NO          Assembleur en ligne nécessite NASM (€€€)
                                [NDT: $ se traduit par €]
    Micro-C         YES         Génère une sortie MASM libre
    PacificC        NO          ne supporte pas les petits MM (comme .COM)
    Borland 4.5C++  NO          le coût €€€
    VisualC++ 1.52  NO          le coût €€€
    Watcom          NO          ne supporte pas les petits MM (comme .COM)
    DJGPP           NO          assembleur avec syntaxe AT&T ( zute, flutte
                                et crotte de bique ) [NDT: yuck]

J'ai fini par travailler avec Micro-C, bien qu'il ne supporte pas la totalité
du standard ANSI. La sortie de Micro-C est en assembleur et peut être routée
vers MASM sans trop de problèmes. Micro-C à été créé par Dave Dunfield et peut
être trouvé à:

    ftp://ftp.dunfield.com/mc321pc.zip

Ne vous en faites pas pour les dépendances du MAMS. Vous pouvez trouver MASM
6.1 gratuitement comme composant de Windows. Allez voir le lien suivant pour
plus de détails:

http://www.microsoft.com/ddk/download/98/BINS_DDK.EXE
http://download.microsoft.com/download/vc15/Update/1/WIN98/EN-US/Lnk563.exe

Le seul mauvais côté d'obtenir cette version libre de MASM (comme les fichiers
ML.EXE, ML.err et LINK.EXE ) est qu'il n'y a pas de documentation.

Ha Ha, l'Internet vient nous sauver...

    http://webster.cs.ucr.edu/Page_TechDocs/MASMDoc

En utilisant Micro-C, je suis le conseil que j'ai donné en partie 1 et reste
fidèle aux outils avec lesquels je suis à l'aise. Je me suis amélioré en
utilisant MASM et TASM. J'ai facile à les utiliser en ligne de commande et à
lire leurs fichiers de sortie. J'ai préféré MASM à TASM parce qu'il est
gratuit, même s'il y a encore quelques bugs.

Le problème avec la plupart des compilateurs C pour générer le code Os est
qu'ils ajoutent des informations formatées aux fichiers exécutables qu'ils
produisent. Par exemple, la version actuelle du Visual C++ crée des exécutables
en mode texte qui suivent le format PE (Portable Exécutable). Ce formatage
ajouté est utilisé par le programme de lancement par l'OS à l'exécution.

Les compilateurs ajoutent aussi du code des librairies à leurs exécutables,
même s'ils n'en ont pas besoin.

Considérons le fichier texte nommé file.c contenant le code suivant:

    void main(){}

Je vais le compiler en tant que fichier .COM en utilisant TurboC. Regardez la
taille du fichier objet et celui exécutable.

C:\DOCS\OS\lab\testTCC>tcc -mt -lt -ln file.c
C:\DOCS\OS\lab\testTCC>dir

.              <DIR>        03-29-02  9:26p .
..             <DIR>        03-29-02  9:26p ..
FILE     C              19  03-30-02 12:07a file.c
FILE     OBJ           184  03-30-02 12:09a FILE.OBJ
FILE     COM         1,742  03-30-02 12:09a file.com


Mon Dieu,... il y a un paquet de lest que le compilateur a ajouté. C'est
exactement le travail des compilateurs et des linkers. Ces connards!

Pour voir à quel point c'est excessif, regardons un fichier .COM codé en
assembleur. Par exemple, créons un fichier file.asm qui ressemble à:

CSEG SEGMENT
start:
ADD ax,ax
ADD ax,cx
CSEG ENDS
end start

On peut l'assembler avec MASM

C:\DOCS\OS\lab\testTCC>ml /AT file.asm
C:\DOCS\OS\lab\testTCC>dir

.              <DIR>        03-29-02  9:26p .
..             <DIR>        03-29-02  9:26p ..
FILE     OBJ            53  03-30-02 12:27a file.obj
FILE     ASM            67  03-30-02 12:27a file.asm
FILE     COM             4  03-30-02 12:27a file.com
         5 file(s)            187 bytes
         2 dir(s)        7,463.23 MB free

Comme vous pouvez le voir, l'exécutable ne fait que 4 octets! L'assembleur n'a
rien ajouté, à l'inverse du compilateur C, qui ajoute tout et n'importe quoi
dans le code. L'espace supplémentaire est sûrement pris par des librairies que
le linker a ajouté.

Le point douloureux, à moins que vous ayez codé votre propre implémentation du
compilateur C, est que vous allez vous trouver face à du code et des données
supplémentaires dans les binaires de l'OS. Une solution est d'ignorer
simplement ces octets supplémentaires. C'est à dire que le chargeur de
programme de l'OS passera ces données formatées et ira dans le code que vous
avez écrit. Si vous décidez de prendre ce chemin, vous voudrez regarder dans le
code hexa. de votre binaire pour déterminer l'offset du fichier auquel votre
code commence.

J'ai échappé à ce problème parce que les compilateurs C Micro-C (MCC)
fournissent un fichier assembleur à la place d'un fichier objet. Ca m'a permis
d'ajuster et de supprimer le code supplémentaire avant qu'il ne soit écrit dans
l'exécutable.

Cependant, j'avais encore des problèmes...

Par exemple, les compilateurs MCC ajouteront toujours des segments
supplémentaires et placerons les données dans ceux-ci. Les variables traduites
en assembleur sont toujours préfixées dans ce segment non désiré (i.e. OFFSET
DGRP:_var ).

Prenons le programme:

char arr[]={'d','e','v','m','a','n','\0'};
void main(){}

MCC traitera ce fichier et sortira:

DGRP GROUP DSEG,BSEG
DSEG SEGMENT BYTE PUBLIC 'IDATA'
DSEG ENDS
BSEG SEGMENT BYTE PUBLIC 'UDATA'
BSEG ENDS
CSEG SEGMENT BYTE PUBLIC 'CODE'
ASSUME CS:CSEG, DS:DGRP, SS:DGRP
EXTRN ?eq:NEAR,?ne:NEAR,?lt:NEAR,?le:NEAR,?gt:NEAR
EXTRN ?ge:NEAR,?ult:NEAR,?ule:NEAR,?ugt:NEAR,?uge:NEAR
EXTRN ?not:NEAR,?switch:NEAR,?temp:WORD
CSEG ENDS
DSEG SEGMENT
PUBLIC _arr
_arr DB 100,101,118,109,97,110,0
DSEG ENDS
CSEG SEGMENT
PUBLIC _main
_main: PUSH BP
MOV BP,SP
POP BP
RET
CSEG ENDS
END

Plutôt que de retravailler le coeur du compilateur, j'ai implémenté une
solution plus immédiate en créant un pré-processeur à la va-vitte.
L'alternative aurait été d'ajuster manuellement chaque fichier assembleur que
MCC à produit, et c'était trop de travail.

Le programme suivant ( convert.c ) crée un squelette de fichier .COM de la forme:

    .486
    CSEG SEGMENT BYTE USE16 PUBLIC 'CODE'

    ORG 100H ; for DOS PSP only, strip and start OS on 0x0000 offset

    here:
    JMP _main

    ; --> Ajouter le code ici <----

    EXTRN ?eq:NEAR,?ne:NEAR,?lt:NEAR,?le:NEAR,?gt:NEAR
    EXTRN ?ge:NEAR,?ult:NEAR,?ule:NEAR,?ugt:NEAR,?uge:NEAR
    EXTRN ?not:NEAR,?switch:NEAR,?temp:WORD

    CSEG ENDS
    END here

Il prend ensuite les procédures et données du fichier assembleur original et
les place dans le corps du squelette. Voici quelque chose d'ennuyeux, mais un
programme efficace qui fait ce boulot:

/* convert.c------------------------------------------------------------*/

#include<stdio.h>
#include<string.h>

/* Lit une ligne de fptr, la place dans buff */

int getNextLine(FILE *fptr,char *buff)
{
    int i=0;
    int ch;

    ch = fgetc(fptr);
    if(ch==EOF){ buff[0]='\0'; return(0); }

    while((ch=='\n')||(ch=='\r')||(ch=='\t')||(ch==' '))
    {
        ch = fgetc(fptr);
        if(ch==EOF){ buff[0]='\0'; return(0); }
    }

    while((ch!='\n')&&(ch!='\r'))
    {
        if(ch!=EOF){ buff[i]=(char)ch; i++; }
        else
        {
            buff[i]='\0';
            return(0);
        }

        ch = fgetc(fptr);
    }

    buff[i]='\r';i++;
    buff[i]='\n';i++;
    buff[i]='\0';

    return(1);

}/*end getNextLine*/

/* changes DGRP:_variable  en CSEG:_variable */

void swipeDGRP(char *buff)
{
    int i;
    i=0;
    while(buff[i]!='\0')
    {
        if((buff[i]=='D')&&
            (buff[i+1]=='G')&&
            (buff[i+2]=='R')&&
            (buff[i+3]=='P'))
        {
            buff[i]='C';buff[i+1]='S';buff[i+2]='E';buff[i+3]='G';
        }
        if((buff[i]=='B')&&
            (buff[i+1]=='G')&&
            (buff[i+2]=='R')&&
            (buff[i+3]=='P'))
        {
            buff[i]='C';buff[i+1]='S';buff[i+2]='E';buff[i+3]='G';
        }
        i++;
    }
    return;
}/*end swipeDGRP*/

void main(int argc, char *argv[])
{
    FILE *fin;
    FILE *fout;

    /*MASM permet des lignes longues de 512 caractères, donc voici une borne supp.*/

    char buffer[512];
    char write=0;

    fin = fopen(argv[1],"rb");
    printf("Opening %s\n",argv[1]);
    fout = fopen("os.asm","wb");

    fprintf(fout,".486P ; enable 80486 instructions\r\n");
    fprintf(fout,"CSEG SEGMENT BYTE USE16 PUBLIC \'CODE\'\r\n");
    fprintf(fout,";\'USE16\' forces 16-bit offset addresses\r\n");
    fprintf(fout,"ASSUME CS:CSEG, DS:CSEG, SS:CSEG\r\n");
    fprintf(fout,"ORG 100H\r\n");
    fprintf(fout,"here:\r\n");
    fprintf(fout,"JMP _main\r\n\r\n");

    fprintf(fout,"EXTRN ?eq:NEAR,?ne:NEAR,?lt:NEAR,?le:NEAR,?gt:NEAR\r\n");
    fprintf(fout,"EXTRN ?ge:NEAR,?ult:NEAR,?ule:NEAR,?ugt:NEAR,?uge:NEAR\r\n");
    fprintf(fout,"EXTRN ?not:NEAR,?switch:NEAR,?temp:WORD\r\n\r\n");

    while(getNextLine(fin,buffer))
    {
        if((buffer[0]=='P')&&
            (buffer[1]=='U')&&
            (buffer[2]=='B')&&
            (buffer[3]=='L')&&
            (buffer[4]=='I')&&
            (buffer[5]=='C')){ fprintf(fout,"\r\n"); write=1;}

        if((buffer[0]=='D')&&
            (buffer[1]=='S')&&
            (buffer[2]=='E')&&
            (buffer[3]=='G')){ write=0;}

        if((buffer[0]=='B')&&
            (buffer[1]=='S')&&
            (buffer[2]=='E')&&
            (buffer[3]=='G')){ write=0;}

        if((buffer[0]=='R')&&
            (buffer[1]=='E')&&

            (buffer[2]=='T')){ fprintf(fout,"%s",buffer); write=0;}

        if(write)
        {
            swipeDGRP(buffer);
            fprintf(fout,"%s",buffer);
        }
        buffer[0]='\0';
    }

    fprintf(fout,"CSEG ENDS\r\n");
    fprintf(fout,"END here\r\n");

    fclose(fin);
    fclose(fout);
    return;

}/*end main-------------------------------------------------------------*/

--[ 3.3 - Démarrage

Dans la discussion qui suit, je parlerai du démarrage à partir d'une diskette.
Démarrer à partir d'un disque dur, d'un CD-ROM ou d'un autre système de
stockage est typiquement plus compliqué à cause partitionnement et du
formatage.

OK, la première chose que je vais faire c'est de coder un programme de
démarrage. Il doit être petit. En fait, il doit être plus petit que 512 octets
parce qu'il doit tenir sur le premier secteur logique de la diskette. La
plupart des diskettes 1.44 ont 80 pistes par face et 18 secteurs par piste. Le
BIOS nomme les deux faces ( 0,1 ), les pistes de 0-79 et les secteurs 0-18.

Quand une machine Intel démarre, le BIOS (qui se trouve dans la ROM sur la
carte mère) regarde après un disque de démarrage. L'ordre dans lequel il
procède peut être configuré via un système de menu du BIOS au démarrage. S'il
trouve une diskette de démarrage, il chargera le secteur de démarrage (piste 0,
face 0, secteur 1) dans la mémoire et l"'exécutera. Parfois, ce code ne fera
rien de plus qu'imprimer un message à l'écran:

    Not a boot disk, you are hosed.

Toutes les machines 8x86 démarrent en mode réel, et le secteur de démarrage est
situé à l'adresse mémoire 0000[0]:7C00 ( ou 0x07C00 ) en hexadécimal. Une fois
que c'est fait, le BIOS se lave les mains en vous laissant tout seul avec votre
propre périphérique.

Beaucoup de systèmes d'exploitation demandent au secteur de démarrage de
charger un programme de démarrage plus grand qui, à son tour, charge l'OS
proprement dit. C'est ce qu'on appelle le multi-démarrage. Les grands systèmes
d'exploitation, qui utilisent un système de fichier compliqué, et une
configuration flexible, utilisent un programme de démarrage multi-étapes. Un
exemple classique est le GRand Unified Bootloader ( GRUB ) de GNU.

    http://www.gnu.org/software/grub

Comme d'habitude, je prendrai le chemin le plus court. Le secteur de démarrage
charge directement le code système. Ce code de démarrage suppose que le code
système se situe directement après le secteur de démarrage (piste 0, face 0,
secteur 2). Ca m'évite de devoir inclure des données et des instructions
spéciales pour lire le système de fichier. Enfin, vu les contraintes de
tailles, tout le code de cette section est écrit en assembleur.

Le code de démarrage suit:

;-boot.asm----------------------------------------------------------------

.8086
CSEG SEGMENT
start:

; Etape 1) Charge l'OS de la diskette
;          vers la zone après la
;          tables d'interruption existante (0-3FF)
;          et la zone des données du BIOS (400-7FF)

MOV AH,02H ; Commande de lecture
MOV AL,10H ; 16 secteurs = 8KB de données à charger
MOV CH,0H  ; les 8 bits de poids faible du numéro de piste
MOV CL,2H  ; début du secteur ( contigu au secteur de démarrage )
MOV DH,0H  ; face
MOV DL,0H  ; lecteur
MOV BX,CS
MOV ES,BX  ; le segment où charger les données
MOV BX,0H
MOV BX,800H  ; Offset pour charger le code ( après IVT )
INT 13H

; signa que le code à été chargé et on va faire un jump

MOV AH,0EH
MOV AL,'-' INT 10H
MOV AH,0EH
MOV AL,'J'
INT 10H
MOV AH,0EH
MOV AL,'M'
INT 10H
MOV AH,0EH
MOV AL,'P'
INT 10H
MOV AH,0EH
MOV AL,'-'
INT 10H

; Etape 2) Saut vers l'OS
;          bonzai!!!

JMP BX

CSEG ENDS
END start

;-end file----------------------------------------------------------------

Ce charger suppose aussi que le code système se trouve sur les secteurs 2-17 de
la première piste. Quand l'OS devient plus grand ( passé 8K ), des instructions
supplémentaires sont nécessaires pour charger du code additionnel. Mais pour
l'instant, supposons que le code sera plus petit que 8K en taille.

OK, vous devrez construire le code précédant dans un fichier .COM et le caser
dans le secteur de démarrage. Le fichier boot.asm est assemblé via:

    C:\> ML  /AT  boot.asm

Comment on le case dans le secteur de démarrage de la diskette?

Ah ha! Debug à la rescousse. Notez, pour des plus grands travaux, je recommande
plus de rigueur. C'est un petit travail et Debug suffit. Sans parler que j'ai
un peut de nostalgie pour Debug. J'ai assemblé mon premier programme avec lui;
dans les années 1980, quand c'était comme du parachute.

En supposant que le code a été assemblé dans un fichier nommé boot.com, voici
comment on l'écrit dans le secteur de démarrage de la diskette.

C:\DOCS\OS\lab\bsector>debug showmsg.com
-l
-w cs:0100 0 0 1
-q
C:\DOCS\OS\lab\bsector>

La commande 'l' charge le fichier en mémoire à l'adresse CS:0100 en hexa. La
commande 'w' écrit cette mémoire vers de disque A ( 0 ) commençant au secteur 0
et écrit un seul secteur. La commande 'w' à la forme générale suivante:

    w  adresse  lecteur  debut-secteur  #-secteurs

Notez que le DOS voit les secteurs logiques (commençant par 0) alors que les
secteurs physiques (que le BIOS manipule) commencent toujours par 1.

Si vous voulez tester toute cette procédure, assemblez le programme suivant en
fichier .COM et placez-le dans le secteur de démarrage d'une disquette avec
debug.

.486
CSEG SEGMENT
start:
MOV AH,0EH
MOV AL,'-'
INT 10H
MOV AH,0EH
MOV AL,'h'
INT 10H
MOV AH,0EH
MOV AL,'i'
INT 10H
MOV AH,0EH
MOV AL,'-'
INT 10H
lp LABEL NEAR
JMP lp
CSEG ENDS
END start

Ceci imprimera '-hi-' à la console et bouclera ensuite. C'est une manière
amusante de casser la glace et de s'habituer. Surtout si vous n'avez jamais
construit de disque de démarrage.

--[ 3.4 - Initialiser l'OS

Le secteur de démarrage charge le code exécutable système en mémoire et affecte
CS et IP du premier (plus bas) octet des instructions du code. Mon code système
ne fait rien de plus que d'imprimer quelques messages à l'écran en mode
protégé. L'exécution prend fin dans une boucle infinie.

J'ai écrit le programme en utilisant des instructions en mode réel. Les
machines Intel démarrent toutes en mode réel. C'est à la responsabilité du code
initial de placer le PC en mode mémoire protégée. Une fois en mode protégé,
l'OS ajustera son registre de segment, installera une pile et établira un
environnement d'exécution pour les applications (table des processus, drivers,
etc.).

Ca rend la vie plus difficile car je ne peux aller plus loin en utilisant les
registres et instructions en mode réel. Finalement, je devrais utiliser les
registres étendus (i.e. EAX ) pour accéder à plus de mémoire.

Quelques compilateurs n'accepteront pas des instructions 16-bits ni 32-bits ou
ils [ get persnickety ] et encoderont incorrectement ces instructions. Si vous
regardez au long JMP que j'ai fait à la fin de setUpMemory(), vous verrez que
j'ai du l'encoder manuellement.

Ma situation était mince parce que j'ai du tout mettre dans un seul segment.
Une fois en mode protége, ce n'était plus le cas et j'ai pu enfin faire quelque
chose d'intéressant.

Une solution serait de mettre mon code système 16-bits dans la deuxième phase
de démarrage. En d'autres mots, demander au code système, chargé par le secteur
de démarrage, de charger le code 32-bits en mémoire avant de faire la
transition en mode protégé. Quand le long JMP est fait, je peux exécuter le
code 32-bits... qui peut alors s'exécuter de là-bas. Si vous regardez dans
MMURTL, vous verrez que c'est exactement ce que Burgess a fait. Doh, Si je
l'avais su plus tôt.

J'étais intéressé dans la pensée de manier [leverage] le compilateur Micro-C.
Bien que, comme vous le verrez, le plus part du travail d'installation s'est
fait en assembleur en ligne. Seulement quelques petites portions ancrées
directement dans le matériel, et le mieux que vous pourriez espérer c'est
d'enfuir le code assembleur dans les entrailles de l'OS et d'envelopper le
reste en C.

Voici le code système (os.c), dans toute sa gloire:

/* os.c ----------------------------------------------------------------*/

void printBiosCh(ch)
char ch;
{
    /*
    ch = BP + savedBP + retaddress = BP + 4 bytes
    */
    asm "MOV AH,0EH";
    asm "MOV AL,+4[BP]";
    asm "INT 10H";
    return;
}/*end printBiosCh---------------------------------------*/

void printBiosStr(cptr,n)
char* cptr;
int n;
{
    int i;
    for(i=0;i<n;i++){ printBiosCh(cptr[i]); }
    return;
}/*end printBiosStr--------------------------------------*/

void setUpMemory()
{     /*passer en mode protégé est une danse à six pas*/

    /* premier pas) construire GDT ( voire la tabre des GDT plus ci-dessous )*/
    printBiosCh('1');

    /*
    deuxième pas) désactiver les interruptions pour travailler tranquillement
    ( Notez, une fois que nous avons fait le CLI, on ne peut plus utilisez les
      interruptions du BIOS
      pour écrire à l'écran )
    */

    printBiosCh('2');
    asm "CLI";

    /*
    troisième pas) autoriser les lignes d'adresses A20 via le controleur du
    clavier
            60H = status port, 64H = control port sur 8042
    */

    asm "MOV AL,0D1H";
    asm "OUT 64H,AL";
    asm "MOV AL,0DFH";
    asm "OUT 60H,AL";
     /*
    quatrième pas) executer les instructions LGDT pour charger GDTR avec les
    infos GDT
            recall GDTR = 48-bits
                        = [32-bit base address][16-bit limit]
                     HI-bit                       LO-bit
    */

    asm "JMP overRdata";
    asm "gdtr_stuff:";
    asm "gdt_limit  DW  0C0H";
    asm "gdt_base   DD  0H";
    asm "overRdata:";

    /*
    copier GDT vers 0000[0]:0000 ( l'adresse linéaire est 00000000H )
    rend la vie plus facile, alors, ne modifiez pas gdt_base
    REP MOVSB déplace DS:[SI] vers ES:[DI] jusqu'à ce que CX=0
    */

    asm "MOV AX,OFFSET CS:nullDescriptor";
    asm "MOV SI,AX";
    asm "MOV AX,0";
    asm "MOV ES,AX";
    asm "MOV DI,0H";
    asm "MOV CX,0C0H";
    asm "REP MOVSB";

    asm "LGDT FWORD PTR gdtr_stuff";

    /* cinquième pas) affecter le premier bits de CR0, bits du mode protégé*/

    asm "smsw    ax";
    asm "or      al,1";
    asm "lmsw    ax";

    /*
    sixième pas) exécuter un long JMP manuellement
            ( MASM l'encodera incorrectement en utilisant le mode 'USE16' )
    */

    asm "DB 66H";
    asm "DB 67H";
    asm "DB 0EAH";
    asm "DW OFFSET _loadshell";
    asm "DW  8H";

    /* à la ligne, boucle infinie */

    asm "_loadshell:";
    asm "NOP";
    asm "JMP _loadShell";

    return;
}/*end setUpMemory---------------------------------------*/

/* notre GDT a 3 descripteur (null,code,data)*/

void GDT()
{
    /*
    arrète de traiter le corp de la fonction comme donnée
    ( on arrète de traiter du code comme donnée quand on l'éxécute ;-))
    */

    asm "nullDescriptor:";
    asm "NDlimit0_15            dw    0    ; seg. limit";
    asm "NDbaseAddr0_15         dw    0    ; base address";
    asm "NDbaseAddr16_23        db    0    ; base address";
    asm "NDflags                db    0    ; segment type et flags";
    asm "NDlimit_flags          db    0    ; segment limit et flags";
    asm "NDbaseAddr24_31        db    0    ; final 8 bits of base address";

    asm "codeDescriptor:";
    asm "CDlimit0_15            dw    0FFFFH";
    asm "CDbaseAddr0_15         dw    0";
    asm "CDbaseAddr16_23        db    0";
    asm "CDflags                db    9AH";
    asm "CDlimit_flags          db    0CFH";
    asm "CDbaseAddr24_31        db    0";

    asm "dataDescriptor:";
    asm "DDlimit0_15            dw    0FFFFH";
    asm "DDbaseAddr0_15         dw    0";
     asm "DDbaseAddr16_23       db    0";
    asm "DDflags                db    92H";
    asm "DDlimit_flags          db    0CFH";
    asm "DDbaseAddr24_31        db    0";


    return;

}/*end GDT-----------------------------------------------*/

char startStr[7] = {'S','t','a','r','t','\n','\r'};
char startMemStr[10] = {'I','n','i','t',' ','m','e','m','\n','\r'};
char tstack[128];

void main()
{
    /*instaélle une pile temporaire du mode réel*/
    asm "MOV AX,CS";
    asm "MOV SS,AX";
    asm "MOV AX, OFFSET CSEG:_tstack";
    asm "ADD AX,80H";
    asm "MOV SP,AX";

    /*fait un JMP avec succès vers le'OS à partir du chargeur du démarrage*/
    printBiosStr(startStr,7);

    /*installe le mode protégé de base*/
    printBiosStr(startMemStr,10);
    setUpMemory();

    return;
}/*end main-------------------------------------------------------------*/

--[ 3.5 - Construire et déployer l'OS

Parce que l'OS a été écrit en C et en assembleur en-ligne, le processus de
construction implique trois pas. D'abord, compiler le code système en
assembleur via

    mcp os.c | mcc > osPre.asm

Notez, mcp est le pré-processeur de Micro-C.

Transforme tout en segment 16 bits:

    convert osPre.asm

Une fois que j'ai un fichier .ASM, je l'assemble:

    ML /Fllist.txt /AT /Zm -c osPre.asm

Notez comment je dois utiliser l'option /Zm pour pouvoir assembler le code en
obéissant aux conventions plus vieilles de MASM. C'est typiquement l'étape où
les problèmes arrivent. Pas besoin d'en parler, je suis fatigué de devoir
retirer le début des segments plutôt vite et c'est pour ça que j'ai écrit
convert.c.

Finalement, après quelques larmes, j'ai édité les liens des fichiers objets de
l'OS en un fichier objet de Micro-c.

    LINK os.obj PC86RL_T.OBJ /TINY

Si vous regardez dans convert.c, vous verrez un paquet de chargement de
directives EXTRN. Tous ces symboles importés sont des librairies de math,
situées dans le fichier PC86RL_T.OBJ.

Si vous avez une copie de NASM sur votre machine, vous pouvez vérifier votre
travail avec la commande suivante:

    ndisasmw -b 16 os.com

Ça donnera une version désassemblée du code à l'écran. Si vous voulez une
version plus permanente, utilisez les options de listing dans des fichiers
quand vous invoquez ML.EXE:

    ML /AT /Zm /Fl -c os.asm

Une fois que le code de l'OS et du secteur de d"démarrage est fini, vous
devriez les mettre sur disquette. Vous pouvez le faire avec l'utilitaire debug
du DOS.

C:\DOCS\OS\lab\final>debug boot.com
-l
-w cs:0100 0 0 1
-q

C:\DOCS\OS\lab\final>debug os.com
-l
-w cs:0100 0 1 2
-q

Après ça, vous n'avez qu'à démarrer avec la disquette et accrochez-vous!

J'espère que cet article vous a donné quelques idées à expérimenter. Bonne
chance et amusez-vous bien.

"Contrasting this modest effort [of Seymour Cray in his laboratory to
build the CDC 6600] with 34 people including the janitor with our vast
development activities, I fail to understand why we have lost our
industry leadership position by letting someone else offer the world's
most powerful computer."
-Thomas J. Watson, IBM President, 1965

"It seems Mr. Watson has answered his own question."
-Seymour Cray

--[ 4 - References and Credits

[1] Operating Systems: Design And Implementation,
Andrew S. Tanenbaum, Prentice Hall, ISBN: 0136386776
    Ce livre explique comment le système d'exploitation Minix fonctionne.
    Linux était initialement un essais de Linus de créer un version
    commerciale de qualité de Minix. Minix est un Os d'Intel.

[2] MMURTL V1.0, Richard A. Burgess, Sensory Publishing, ISBN: 1588530000
    MMURTL est un autre OS d'Intel. Contrairement à Tenenbaum, Burgess plonge
    plus profondément dans les sujets sophistiqués, comme la pagination. Une
    autre chose que j'admire chez Burgess c'est qu'il répondra à vos e-mails
    sans être pédant [snooty] comme Tenenbaum. Si Minix a donné naissance à
    Linux, alors MMURTL sera réincarné comme prochain grand projet.

[3] Dissecting DOS, Michael Podanoffsky, Addison-Wesley Pub,
ISBN: 020162687X
    Dans ce livre, Podanoffsky décrit un clone du DOS nommé RxDOS. RxDos est
    présenté comme un OS en mode réel et entièrement écrit en assembleur.

[4] FreeDOS Kernel, Pat Villani, CMP Books, ISBN: 0879304367
    Un autre clone du DOS ... mais, cette fois, écrit en C, whew!

[5] Virtual Machine Design and Implementation In C/C++, Bill Blunden,
Wordware Publishing, ISBN: 1556229038
    Oui, c'est le moment de l'auto pub. Ecrire une VM [Virtual Machine, machine
    virtuelle] est juste un petit saut après l'écriture d'un émulateur. Mon
    livre présente toutes les informations de cet article et un paquet
    d'autres. Ca inclut une machine virtuelle complète, assembleur et
    débogueur.

[6] Linux Core Kernel Commentary, 2nd Edition, Scott Andrew Maxwell,
The Coriolis Group; ISBN: 1588801497
    C'est une autre petite promenade à travers le code source de la gestion des
    tâches et de la mémoire de Linux.

[7] The Design and Implementation of the 4.4BSD Operating System,
Marshall Kirk McKusick (Editor), Keith Bostic, Michael J. Karels (Editor)
Addison-Wesley Pub Co; ISBN: 0201549794
    Ces gars-là sont geeks profonds. Si vous ne me croyez pas, regardez la
    photo de groupe à l'intérieur. Ce livre est une description compréhensive
    de l'OS FreeBSD.

[8] The Undocumented PC : A Programmer's Guide, Frank Van Gilluwe,
Addison-Wesley Pub, ISBN: 0201479508
    Si vous faites de l'E/S sur Intel, ça vous aidera beaucoup d'avoir ce
    livre.

[9] Control Data Corporation
    Il y a un nombre de vieux embrumés de Control Data que je voudrais
    remercier pour m'avoir donné leur aide et leurs conseils. Control Data a
    été tué par ses gestionnaires, mais il y avait une poignée d'ingénieurs
    doués, comme Cray, qui étaient sûr que quelques-unes de leurs bonnes idées
    trouveraient une maison.

[10] IBM and the Holocaust: The Strategic Alliance Between Nazi Germany
and America's Most Powerful Corporation, Edwin Black,
Three Rivers Press; ISBN: 0609808990
    Initialement, j'ai entendu parlé de ça à la radio Dave Emory. Mae Brussell
    serait d'accord avec moi... le profit à tout prix n'est pas une bonne
    chose.

Je voudrais remercier George Matkovitz, qui a écrit le premier noyau basé sur
des messages au monde et Mike Adler, un concepteur de compilateurs qui étaient
là quand Cray cassait IBM en partageant leurs expériences avec moi.

[Traduit de l'anglais par Tbowan pour arsouyes.org]