Windows 2000

Transcription

Windows 2000
11
Étude de cas n˚2 : Windows 2000
Windows 2000 est un système d’exploitation moderne fonctionnant sur des PC tant
desktop que serveurs. Dans ce chapitre, nous aborderons plusieurs de ses caractéristiques. Nous commencerons par un bref historique, avant d’aborder son architecture.
Nous poursuivrons par l’étude des processus, de la gestion de la mémoire, des
entrées/sorties et du système de fichiers, pour terminer sur les aspects liés à la sécurité
du système. Les fonctionnalités relatives au réseau ne seront pas abordées, car cela
nécessiterait un chapitre à part, voire un ouvrage entier.
11.1 L’historique de Windows 2000
Les systèmes d’exploitation Microsoft pour PC (desktop ou portables) se classent en
trois catégories : MS-DOS, la famille des Windows « utilisateurs » (Windows 95/98/Me) et Windows NT. Décrivons tout d’abord brièvement chacune de ces
catégories.
11.1.1 MS-DOS
En 1981, IBM, qui était à l’époque la première entreprise au monde dans le secteur
informatique, a présenté son PC, un ordinateur personnel animé par un INTEL 8088.
Le PC fonctionnait sous un système d’exploitation mono-utilisateur 16 bits avec
interface de type « ligne de commande » appelé MS-DOS 1.0. Il était fourni par
Microsoft, une petite startup connue à l’époque pour son interpréteur BASIC fonctionnant sur 8080 et Z-80. Ce système tenait dans 8 Ko de code résidant en mémoire
et était fortement inspiré de CP/M, un petit système d’exploitation dédié aux platesformes de type 8080 et Z-80. Deux ans plus tard apparaissait la version 2.0 de MSDOS, beaucoup plus puissante et fonctionnant avec 24 Ko de code, accompagnée
d’un interpréteur de commandes (shell) et d’un certain nombre de fonctionnalités
inspirées d’UNIX.
© 2003 Pearson Education France - Systèmes d'exploitation, 2e ed.
806 ◆ Systèmes d’exploitation
Alors que INTEL sortait le 80286, IBM a construit en 1986 une plate-forme spécifique : le PC/AT, pour Advanced Technology. Ce micro-ordinateur fonctionnait avec
une horloge à 8 MHz et pouvait adresser, non sans mal, jusqu’à 16 Mo de RAM. En
pratique, les systèmes se contentaient de 1 à 2 Mo de mémoire, en raison du prix de la
RAM à cette époque. Il était fourni avec la version 3.0 de MS-DOS, qui fonctionnait
alors sur 36 Ko. MS-DOS a continué d’évoluer dans les années qui ont suivi, mais
toujours sur la base d’un fonctionnement de type « ligne de commande ».
11.1.2 Windows 95/98/Me
Inspiré par l’interface utilisateur de Lisa, l’ancêtre chez Apple du Macintosh,
Microsoft décide de munir MS-DOS d’une interface utilisateur graphique, qu’il
nomme Windows. La première version, sortie en 1985, ne peut guère être prise
au sérieux. La version 2.0, dédiée au PC/AT et sortie en 1987, ne vaut guère
mieux. En revanche, Windows 3.0, pour les 80386, qui voit le jour en 1990, et
plus encore les versions suivantes Windows 3.1 et Windows 3.11, représentent un
progrès sensible et rencontrent un très vif succès. Aucune de ces versions n’est à
proprement parler un système d’exploitation : Windows n’est encore qu’un
habillage graphique de MS-DOS, qui continue à contrôler la machine comme le
système de fichiers. Tous les programmes partagent le même espace d’adressage
et un bogue dans l’un ou l’autre de ces programmes peut rendre le système instable,
voire le bloquer.
La sortie de Windows 95 en août 1995 n’élimine pas encore totalement MS-DOS,
mais on assiste tout de même à un transfert de nombreuses fonctionnalités de
MS-DOS vers Windows. À eux deux, Windows 95 et la dernière version 7.0 de MS-DOS
contiennent la plupart des caractéristiques nécessaires à l’émergence d’un nouveau
système d’exploitation : gestion de la mémoire virtuelle, des processus et de la multiprogrammation, etc. Cependant, Windows 95 n’est pas un programme entièrement
en mode 32 bits : il contient encore des parts importantes de code assembleur 16 bits
et utilise encore le système de fichiers de MS-DOS, avec ses nombreuses limitations.
La seule amélioration notable du système de fichiers est l’adoption des noms de
fichiers longs, à la place des traditionnels « 8 + 3 » (8 caractères pour le nom, 3 pour
l’extension) de MS-DOS.
Même Windows 98, sorti en juin 1998, n’élimine pas MS-DOS, présent dans sa version 7.1 et gérant toujours du code 16 bits. Bien que d’autres fonctionnalités aient
migré de MS-DOS vers Windows, et qu’une nouvelle gestion des disques, permettant
des partitions de grande taille, soit intégrée au système, peu de choses, dans le détail,
différencient cette nouvelle version de Windows 95. La différence principale se situe
au niveau de l’interface utilisateur, qui intègre l’internet dans le Bureau de façon plus
étroite. C’est d’ailleurs cette intégration qui attire l’attention du ministère de la Justice américain, qui décide de poursuivre pour abus de position dominante Microsoft,
qui rejette vigoureusement cette accusation. En avril 2000, une cour fédérale américaine donne raison au gouvernement.
Étude de cas n˚2 : Windows 2000 ◆ 807
Outre la présence d’ancien code 16 bits dans le noyau, Windows 98 souffre de deux
problèmes sérieux. Premièrement, bien que le système permette la multiprogrammation, le noyau lui-même n’est pas réentrant. Si un processus est en train de manipuler
des données « noyau » quand son quantum de temps expire, et qu’un autre processus
démarre, ce dernier peut trouver les données dans un état incohérent. Pour contourner ce problème, quand ils entrent en mode noyau, la plupart des processus commencent par positionner un énorme mutex couvrant l’ensemble du système. Même
si cette approche permet effectivement d’éliminer le problème, elle élimine aussi pour
une bonne part les avantages de la multiprogrammation, puisque les processus sont
fréquemment amenés à attendre que d’autres processus indépendants quittent le
noyau avant d’y entrer.
Deuxièmement, chaque processus Windows 98 a un espace d’adressage de 4 Go,
dont 2 Go sont totalement dédiés au processus. Un Go est cependant partagé (en
lecture-écriture) par tous les processus du système. Le dernier Go est également
partagé pour permettre à chaque processus d’accéder aux vecteurs d’interruptions
MS-DOS. Cette possibilité de partage de mémoire est abondamment utilisée par la
plupart des applications Windows 98. En conséquence, un bogue dans l’une d’elles
peut modifier des structures de données nécessaires à d’autres processus, et facilement conduire à un dysfonctionnement général. Pire, le dernier Go est également
accessible (en lecture-écriture) par le noyau, et contient des structures de données
fondamentales pour le fonctionnement du système. Tout programme qui corrompt
ces structures peut faire tomber le système. La solution consistant à ne pas placer de
structures de données noyau dans l’espace d’adressage utilisateur est inapplicable,
car ce placement est nécessaire au fonctionnement de programmes MS-DOS sous
Windows 98.
En l’an 2000, Microsoft commercialise une évolution mineure de Windows 98 : Windows Me (pour Millennium Edition). Même si cette version corrige quelques bogues
et présente quelques nouvelles fonctionnalités, sa base reste celle de Windows 98. Les
améliorations portent principalement sur la gestion et le partage d’images, de musique, de vidéo, sur un meilleur support des réseaux domestiques et des jeux multijoueurs, et sur l’ajout de fonctionnalités internet, comme le support de la messagerie
instantanée et des connexions à haut débit (modem-câble et ADSL). Un atout intéressant est la possibilité de restaurer le système dans un état précédemment sauvegardé, après une mauvaise configuration. Si l’utilisateur reconfigure son système (en
faisant, par exemple, passer la définition de l’écran de 640 × 480 à 1024 × 768) et que
ce dernier ne fonctionne plus, cette possibilité permet de revenir à la dernière configuration correcte.
11.1.3 Windows NT
À la fin des années 1980, Microsoft a pris conscience que le choix de construire un
système d’exploitation 32 bits moderne au-dessus d’un système 16 bits obsolète
comme MS-DOS n’était sans doute pas le plus judicieux. La firme a alors recruté
David Cutler, l’un des principaux concepteurs du système d’exploitation VMS
808 ◆ Systèmes d’exploitation
chez DEC, et lui a confié la responsabilité d’une équipe chargée de développer ab
initio un nouveau système d’exploitation 32 bits, compatible Windows. Ce nouveau système, appelé par la suite Windows NT (pour New Technology), visait des
applications professionnelles critiques comme les utilisateurs finals. À l’époque, le
monde professionnel était encore dominé par les mainframes, et l’idée que des
applications importantes pour une entreprise puissent être confiées à des ordinateurs de type PC était quelque peu visionnaire, même si elle s’est révélée juste. Des
caractéristiques comme la sécurité ou la haute disponibilité, absentes des versions
de Windows reposant sur MS-DOS, figuraient en bonne place dans le cahier des
charges de NT. L’héritage VMS de Cutler dans Windows NT est évident, sa conception
étant plus que comparable à celle de VMS.
Le projet a abouti, et la première version, Windows NT 3.1, a vu le jour en 1993. Le
choix du numéro de version initiale permet de faire le pont avec la populaire version 3.1
de Windows qui était disponible à l’époque. Microsoft espérait que NT remplacerait
rapidement Windows 3.1 dans la mesure où il lui était techniquement nettement
supérieur.
À sa grande surprise, la plupart des utilisateurs ont préféré conserver l’ancien système
16 bits, qui leur était familier, plutôt que de migrer vers un système 32 bits qu’ils ne
maîtrisaient pas, même s’il promettait d’être meilleur. En outre, NT réclamait beaucoup plus de mémoire que Windows 3.1, et aucun programme 32 bits n’était encore
disponible. Cet échec commercial de NT 3.1 explique la décision de Microsoft de
bâtir une version 32 bits de Windows 3.1, qui deviendra Windows 95. La réticence
persistante des utilisateurs à migrer vers NT a amené Microsoft à leur proposer
Windows 98, puis Windows Me. Chacune de ces versions passait en son temps pour
la dernière fondée sur MS-DOS.
En dépit de l’insuccès de NT 3.1 sur les postes de travail — tant dans l’informatique
domestique que professionnelle —, un mouvement s’est ébauché en sa faveur dans le
monde des serveurs. Quelques évolutions mineures ont fait l’objet de versions 3.x en
1994 et en 1995. Ces dernières ont permis à NT de commencer à pénétrer le marché
des postes de travail.
La première évolution majeure de NT a été la version 4.0, en 1996. Cette version
alliait la puissance, la sécurité et la fiabilité du nouveau système à une interface utilisateur analogue à celle du désormais populaire Windows 95. Cet atout a encouragé de
nombreux utilisateurs à migrer de Windows 95 vers NT. La figure 11.1 présente
quelques-unes des différences entre Windows 95/98 et Windows NT.
Dès l’origine, NT a été conçu pour être portable, c’est pourquoi il a presque entièrement été écrit en C, avec seulement quelques routines de code assembleur pour
des fonctions de bas niveau, comme la gestion des interruptions. La version initiale
comportait 3,1 millions de lignes de C pour le système d’exploitation, les bibliothèques et les sous-systèmes (décrits ci-après). Quand NT 4.0 est apparu, le code
comptait 16 millions de lignes, toujours en grande majorité en C, mais avec quelques modules de C++, notamment dans la gestion de l’interface utilisateur. À cette
date, le système était réellement portable, des versions tournant sur Pentium,
Étude de cas n˚2 : Windows 2000 ◆ 809
Alpha, MIPS et PowerPC, entre autres. Certaines de ces versions ont d’ailleurs
disparu depuis.
Figure 11.1 • Quelques différences entre Windows 98 et Windows NT.
Item
Windows 95/98
Windows NT
Système entièrement 32 bits ?
Non
Oui
Sécurité ?
Non
Oui
Mappage de fichiers protégé ?
Non
Oui
Espace d’adressage privé pour chaque
processus MS-DOS ?
Non
Oui
Unicode ?
Non
Oui
Fonctionne sur
Intel 80x86
80x86, Alpha, MIPS, etc.
Support du multiprocesseur ?
Non
Oui
Code du système d’exploitation réentrant ?
Non
Oui
Plug and play ?
Oui
Non
Gestion de l’alimentation ?
Oui
Non
Système de fichiers FAT32 ?
Oui
Optionnel
Système de fichiers NTFS ?
Non
Oui
API Win32 ?
Oui
Oui
Exécute n’importe quel binaire MS-DOS ?
Oui
Non
Données critiques du système modifiables
par l’utilisateur ?
Oui
Non
11.1.4 Windows 2000
La version de NT qui a suivi NT 4.0 devait logiquement se nommer NT 5.0. Cependant, en 1999, Microsoft a décidé de l’appeler Windows 2000, principalement pour
que les utilisateurs de Windows NT et de Windows 98 voient dans la neutralité de ce
label une volonté d’unification des systèmes. Si cette démarche aboutissait, Microsoft
pourrait enfin proposer un système d’exploitation unique, construit sur une technologie 32 bits, mais utilisant l’interface graphique bien connue de Windows 98.
Dans la mesure où Windows 2000 est en réalité NT 5.0, il hérite de la plupart des propriétés de NT 4.0. C’est un vrai système 32 bits (bientôt 64 bits), supportant la multiprogrammation et gérant la protection de chaque processus individuellement.
Chaque processus a en effet un espace d’adressage privé 32 bits (bientôt 64 bits)
810 ◆ Systèmes d’exploitation
paginé à la demande. Le système d’exploitation tourne en mode noyau, alors que les
applications fonctionnent en mode utilisateur, ce qui permet de protéger totalement
le système, ce qui n’était pas le cas avec Windows 98. Les processus peuvent être
constitués d’un ou de plusieurs threads, qui sont visibles du (et ordonnancés par le)
système d’exploitation. Il possède le niveau de sécurité C2 du ministère de la Défense
américain pour tous les objets partagés (fichiers, répertoires, processus, etc.), du
moins en l’absence de lecteur de disquettes et de réseau. Enfin, il est capable de gérer
des systèmes multiprocesseurs de 2 à 32 CPUs.
Que Windows 2000 soit en fait NT 5.0 est évident à plusieurs titres. Par exemple, le
répertoire système se nomme \winnt et le noyau (placé dans \winnt\system32),
ntoskrnl.exe. Si l’on examine les propriétés de ce fichier en cliquant du bouton droit
dans un explorateur, on constate que le numéro de version est 5.xxx.yyy.zzz, le « 5 »
signifiant NT 5, xxx étant le numéro de version, yyy le numéro de compilation, et zzz
le numéro de variante mineure. De même, de nombreux fichiers du répertoire \winnt
et de ses sous-répertoires contiennent « nt » dans leur nom : ntvdm est l’émulateur
MS-DOS de NT, par exemple.
Windows 2000 est plus qu’une simple amélioration de NT 4.0 avec une interface utilisateur de type Windows 98, ne serait-ce que parce qu’il présente des caractéristiques
qu’on ne trouvait jusque-là que dans Windows 98. Parmi ces dernières figurent
notamment le support complet des périphériques plug and play, du bus USB, des
normes IEEE 1394 (FireWire) et IrDA (liaison infrarouge entre portables et imprimantes) et la gestion de la charge des batteries. De plus, un ensemble de fonctionnalités inédites jusqu’alors dans le monde Microsoft a été ajouté : service Active
Directory, utilisation de Kerberos pour la sécurité, support des smart cards (cartes
intelligentes), présence d’outils de surveillance du système, meilleure intégration
entre portables et postes fixes, infrastructure de gestion du système ou encore présence de jobs. De même, le système de fichiers principal NTFS a été étendu pour la
prise en charge de fichiers cryptés, de quotas disque, de liens entre fichiers, de montage de partitions et d’indexation de contenu, entre autres. Une autre nouveauté de
NTFS, appelée single instance store, permet — lorsque deux utilisateurs utilisent le
même fichier — de générer automatiquement une copie du fichier lors de l’écriture
par l’un des processus utilisateur.
L’internationalisation du système est également une amélioration majeure. NT 4.0
était commercialisé dans des versions spécifiques à chaque langue, avec les chaînes de
caractères constantes incluses dans le code. L’installation d’un module anglais sur un
système hollandais provoquait souvent de la part du système d’exploitation l’abandon du hollandais au profit de l’anglais, à cause du remplacement de certains fichiers
contenant du code et des chaînes de caractères « système ». Ce problème ne se pose
plus. Windows 2000 est constitué d’un code unique pour l’ensemble des pays. Une
plate-forme — et même un utilisateur particulier — peut choisir la langue de son
choix, tous les messages du système ayant été retirés du code et placés dans des répertoires séparés, un par langue. Comme toutes les versions précédentes de NT,
Windows 2000 utilise l’Unicode pour le support des langues qui n’utilisent pas
l’alphabet latin, comme le russe, le grec, l’hébreu et le japonais.
Étude de cas n˚2 : Windows 2000 ◆ 811
Le grand absent de Windows 2000 est MS-DOS. Il n’y figure sous aucune forme
(c’était d’ailleurs déjà le cas pour les versions précédentes de NT). Une interface utilisateur de type « ligne de commande » est disponible, qui reprend les anciennes
fonctionnalités MS-DOS et en ajoute de nombreuses nouvelles.
Malgré de nombreuses caractéristiques favorisant la portabilité, que ce soit au niveau
du code, de la gestion du matériel, du langage, etc., Windows 2000 est moins portable
que NT 4.0 dans un domaine : il ne fonctionne que sur deux plates-formes, le Pentium et l’Intel IA-64. NT était au départ disponible sur d’autres plates-formes, parmi
lesquelles le PowerPC, les MIPS et l’Alpha, mais Microsoft abandonna progressivement le support de ces plates-formes pour des raisons commerciales.
Comme les versions précédentes de NT, Windows 2000 est disponible dans toute une
gamme de produits : Professional, Server, Advanced Server et Datacenter Server. Les
différences entre ces versions sont cependant minimes car le même code est utilisé
pour chacune d’elles. Lors de l’installation, le type de produit est enregistré dans une
base de données interne (Registre, ou base de registres). Au démarrage, le système
contrôle le Registre pour connaître la version installée. La figure 11.2 montre les
différences entre ces versions.
Figure 11.2 • Les différentes versions de Windows 2000.
Version
RAM max.
CPU
nb max. clients
taille cluster
optimisé pour
Professional
4 Go
2
10
0
temps
de réponse
Server
4 Go
4
illimité
0
débit
Advanced server
8 Go
8
illimité
2
débit
Datacenter Server
64 Go
32
illimité
4
débit
Comme l’indique le tableau, les différences portent sur la taille maximale de mémoire
supportée, le nombre maximal de CPU pris en compte pour les configurations multiprocesseurs, et le nombre maximal de clients gérables par le serveur. La mise en
cluster de deux ou quatre machines sous Windows 2000 permet de les faire voir de
l’extérieur comme une machine unique, ce qui est particulièrement intéressant, par
exemple dans le cas de serveurs Web. Enfin, les paramètres par défaut du système
varient d’une version à l’autre : les jobs interactifs sont favorisés sur la version Professional au détriment des traitements par lots (batch). Ces valeurs peuvent cependant
être facilement modifiées. Le dernier point concerne des modules logiciels supplémentaires sur les versions serveurs et des outils de gestion de gros jobs sur la version
Datacenter Server.
La déclinaison de Windows 2000 en versions multiples est une simple décision marketing : elle permet à Microsoft de vendre le même système plus cher aux entreprises
qu’aux particuliers. L’idée n’est cependant pas nouvelle, et n’est pas une invention de
812 ◆ Systèmes d’exploitation
Microsoft. Depuis des années, les compagnies aériennes font payer leurs billets beaucoup plus cher — tant en classe affaire qu’en classe touriste — lorsqu’ils sont pris la
veille du vol.
Techniquement, la maintenance des différentes versions dans le code se fait au moyen
de deux variables dont les valeurs sont stockées dans le Registre, ProductType et ProductSuite. En fonction de ces valeurs, un code légèrement différent est exécuté. La
licence d’utilisation interdit de changer ces valeurs. D’ailleurs, le système enregistre
de manière indélébile toute tentative de modification.
Autour du système de base, Microsoft a développé un ensemble de boîtes à outils
logicielles pour les utilisateurs avancés. Citons en particulier les Support Tools, le
Software Development Kit (SDK), le Driver Development Kit (DDK) et le Resource Kit
(RK). Elles comprennent de nombreux utilitaires pour adapter et auditer le système.
Les Support Tools sont sur le CD-ROM de Windows 2000, dans le répertoire \support\tools. La procédure d’installation standard ne les installe pas, mais un fichier
setup.exe dans ce répertoire permet de le faire. Les développeurs peuvent obtenir le
SDK et le DDK sur http://msdn.microsoft.com. Le Resource Kit est un produit
Microsoft à part entière. De nombreux utilitaires permettent également d’explorer
l’intérieur de Windows 2000, comme l’ensemble gratuit que l’on peut trouver sur le
site http://www.sysinternals.com. Certains d’entre eux permettent même d’obtenir
davantage d’informations que les outils Microsoft correspondants.
Windows 2000 est un système extrêmement complexe, maintenant constitué de
29 millions de lignes de code C. Si on les imprimait à raison de 50 lignes par page et
de 1 000 pages par tome, la totalité du code représenterait 580 volumes. Cela représenterait 23 mètres linéaires d’étagères !
Pour l’anecdote, la figure 11.3 présente une comparaison de la taille du code source
de quelques systèmes d’exploitation. Cependant, ces données doivent être prises
avec un minimum de recul, car les fonctionnalités incluses dans un système
d’exploitation varient d’un système à l’autre. Ainsi, le système de fenêtrage et
l’interface utilisateur graphique font partie du noyau de Windows, ce qui n’est le
cas dans aucune version d’UNIX, où il s’agit d’un simple processus utilisateur. Si
l’on inclut X Window à UNIX, on doit ajouter quelque 1,5 million de lignes de
code, sans même tenir compte de l’interface utilisateur (Motif, GNOME, etc.). En
outre, certains systèmes contiennent du code destiné à des architectures variées
(par exemple, 5 pour BSD 4.4 et 9 pour Linux), ce qui ajoute entre 10 000 et
50 000 lignes pour chaque type d’architecture pris en compte. La raison pour
laquelle Free BSD 1.0 n’a que 235 000 lignes de code, alors que BSD4 Lite, duquel il
est tiré, en contient 743 000, est justement dans ce support d’architectures obsolètes
(comme le VAX) qui a été retiré de Free BSD.
Le nombre de systèmes de fichiers et de périphériques gérés, ainsi que le nombre de
bibliothèques fournies, varie également de façon considérable d’un système à
l’autre. De plus, Windows contient de grandes portions de code de test absent
d’UNIX, ainsi que des utilitaires de gestion des langues étrangères. Enfin, les mesures proviennent de sources différentes, ce qui entraîne une grande variabilité dans
Étude de cas n˚2 : Windows 2000 ◆ 813
les résultats (selon, par exemple, que les makefiles, les fichiers d’en-tête, de configuration et de documentation ont été ou non pris en compte dans le calcul, et la proportion qu’ils représentent). Cela dit, les résultats relatifs à une famille donnée de
systèmes proviennent d’une source unique, ce qui les rend plus significatifs les uns
par rapport aux autres.
En dépit de ces restrictions, deux conclusions claires se dégagent :
1. La croissance de l’embonpoint des systèmes semble aussi inévitable que celle des
impôts.
2. Windows est beaucoup plus gros qu’UNIX.
Que la qualité d’un système réside dans sa concision ou au contraire dans sa grande
taille est un sujet âprement débattu. On peut mettre à l’actif du premier point de vue
le caractère plus maintenable, plus fiable et plus aisément compréhensible par les utilisateurs. Le second point de vue est justifié par le fait qu’un grand nombre d’utilisateurs réclament un grand nombre de fonctionnalités.
Figure 11.3 • Une comparaison de la taille de quelques systèmes d’exploitation.
Année
AT&T
1976
V6
9K
1979
V7
21K
1980
1982
BSD
MINIX
Linux
Solaris
Windows NT
4.1
38K
Sys III
58K
1984
4.2
98K
1986
4.3
179K
1987
SVR3
92K
1989
SVR4
280K
1.0
13K
Le premier élément de chaque case est le numéro de version ; le second est la taille, exprimée
en lignes de code, avec K = 1 000 et M = 1 000 000. Les comparaisons à l’intérieur d’une même
colonne sont pleinement significatives, ce qui n’est pas nécessairement le cas entre colonnes,
comme nous l’avons dit.
814 ◆ Systèmes d’exploitation
Figure 11.3 • Une comparaison de la taille de quelques systèmes d’exploitation (suite).
Année
AT&T
BSD
MINIX
1991
Solaris
Windows NT
5.3
850K
3.1
6M
0.01
10K
1993
Free 1.0
235K
1994
4.4 Lite
743K
1996
1997
1.0
165K
3.5
10M
2.0
470K
4.0
16M
2.0
62K
1999
2000
Linux
5.6
1,4M
2.2
1M
Free 4.0
1,4M
5,8
2,0M
2000
29M
Le premier élément de chaque case est le numéro de version ; le second est la taille, exprimée
en lignes de code, avec K = 1 000 et M = 1 000 000. Les comparaisons à l’intérieur d’une même
colonne sont pleinement significatives, ce qui n’est pas nécessairement le cas entre colonnes,
comme nous l’avons dit.
Bien que Windows 2000 soit déjà champion du monde, catégorie poids lourds, il est
toujours en expansion, que ce soit à cause des corrections de bogues ou de l’ajout de
nouvelles caractéristiques. Le mode de développement chez Microsoft mérite une
petite description. Des centaines de programmeurs travaillent toute la journée sur les
différents aspects de Windows 2000. Quand un module de code est terminé, le développeur l’envoie électroniquement à l’équipe de construction. À 18 h, tous les jours,
le système est reconstruit — compilation et édition des liens. Chaque construction
reçoit un numéro de séquence unique, visible dans le numéro de version de ntoskrnl.exe. (La première version publique de Windows 2000 portait le numéro de
séquence 2195.)
Le nouveau système est distribué électroniquement à des milliers de machines dans le
campus Microsoft de Redmond, aux États-Unis, où il est soumis à des tests intensifs
pendant la nuit. Le lendemain matin, le résultat des tests est envoyé très tôt aux équipes concernées afin qu’elles vérifient l’adéquation du nouveau code. Chaque équipe
décide alors de la version du code qu’elle désire soumettre ce jour-là. Pendant la
journée, l’équipe travaille sur le code choisi et à 18h, le cycle reprend.
Étude de cas n˚2 : Windows 2000 ◆ 815
11.2 La programmation de Windows 2000
Il est temps maintenant d’aborder l’étude technique de Windows 2000. Cependant,
avant d’entrer dans les détails de sa structure interne, nous jetterons un coup d’œil à
son interface de programmation (API) et à le Registre, une petite base de données
chargée en mémoire vive.
11.2.1 L’API Win32
Comme tous les autres systèmes d’exploitation, Windows 2000 possède un ensemble
d’appels système. Mais Microsoft n’en a jamais rendu la liste publique, et la change de
version en version. Mais Microsoft a défini un ensemble de fonctions, l’API Win32
(l’interface de programmation d’application Windows 32 bits) qui sont définies et
documentées de façon complète. Ce sont des fonctions de bibliothèque qui soit provoquent des appels système, soit effectuent directement leur tâche dans l’espace utilisateur. Les fonctions existantes de l’API ne changent pas d’une version à l’autre, mais
de nouvelles fonctions apparaissent régulièrement.
Les codes binaires pour l’Intel x86 qui utilisent exclusivement l’API Win32 fonctionneront de la même façon sur toute version de Windows, depuis Windows 95. Comme
le montre la figure 11.4, une bibliothèque supplémentaire est nécessaire si l’on veut,
sous Windows 3.x, utiliser (un sous-ensemble de) l’API Win32 sur ce système 16 bits,
mais aucune adaptation n’est nécessaire pour les autres versions. Il faut mentionner
que Windows 2000 ajoute un nombre important de fonctionnalités à l’API, et que de
nombreux appels ne fonctionneront donc pas pour les versions précédentes du
système.
Application
Win32
Interface de programmation d'applications (API) Win32
Win32s
Windows 3.x
Windows
95/98/Me
Windows NT
Windows 2000
Figure 11.4 • L’API Win32 permet aux programmes de s’exécuter sous presque toutes
les versions de Windows.
La philosophie de l’API Win32 est totalement différente de la philosophie UNIX.
Dans cette dernière, les appels système sont publics et forment l’interface minimale
de programmation : le retrait d’un seul élément réduit les fonctionnalités du système.
816 ◆ Systèmes d’exploitation
La philosophie Win32 est de fournir une interface complète, avec souvent trois ou
quatre méthodes distinctes pour aboutir au même résultat, et contenant de nombreuses fonctions qui ne doivent clairement pas être (et ne sont d’ailleurs pas) des
appels système (comme par exemple une fonction pour copier un fichier entier).
De nombreux appels de l’API Win32 créent des objets du noyau de tout type (fichier,
processus, thread, tube, etc.). Chaque appel qui crée un tel objet renvoie un handle
(poignée) à l’appelant. Ce handle permet ensuite d’effectuer des opérations sur
l’objet créé. Les handles sont spécifiques au processus qui a créé l’objet qu’ils référencent. Ils ne peuvent être passés directement à un autre processus (de même que les
descripteurs de fichier UNIX ne peuvent être passés à un autre processus UNIX).
Cependant, dans certains cas, il est possible de dupliquer un handle et de passer la
copie à un autre processus de manière sûre, lui permettant ainsi un accès à un objet
du processus courant. Chaque objet est en outre muni d’un descripteur de sécurité
contenant des listes d’autorisations à effectuer telle ou telle opération sur l’objet.
Toutes les structures de données créées par le système ne sont pas des objets, et tous
les objets ne sont pas internes au noyau. Les seuls vrais objets du noyau sont ceux qui
doivent être nommés, protégés ou partagés d’une façon ou d’une autre. Chaque objet
du noyau appartient à un type défini au niveau du système tout entier avec un ensemble d’opérations associées à ce type ; il occupe une place en mémoire noyau. Même si
les utilisateurs peuvent déclencher ces opérations (en utilisant l’API Win32), ils ne
peuvent accéder directement à la structure de l’objet.
Le système d’exploitation lui-même peut créer et utiliser de tels objets, et il ne s’en
prive pas. La plupart de ces objets sont créés pour permettre à un composant du système de stocker durablement de l’information ou pour passer des données à un autre
composant. Ainsi, quand un pilote de périphérique est chargé, il y a création d’un
objet contenant ses propriétés, ainsi que des pointeurs sur les fonctions associées. À
l’intérieur du système d’exploitation, le pilote est alors référencé par l’intermédiaire
de cet objet.
Windows 2000 est souvent qualifié de système orienté objet parce que la seule façon de
manipuler des objets est d’invoquer des opérations sur leur handle au moyen de l’API
Win32. Cependant, il lui manque des caractéristiques fondamentales de l’orienté objet,
telles que l’héritage et le polymorphisme.
L’API Win32 couvre tous les domaines gérables par un système d’exploitation, et un
certain nombre d’aspects dont il ne devrait pas s’occuper. Bien entendu, certains
appels permettent la création de processus et de threads. De nombreux autres sont
relatifs à la communication interprocessus (en fait, interthread), comme la création,
la destruction et l’utilisation de mutex, de sémaphores, d’événements et d’autres
objets IPC.
Bien qu’une bonne part du système de gestion de la mémoire soit invisible pour l’utilisateur (à la base, il s’agit d’une simple pagination à la demande), une caractéristique
importante est accessible : la possibilité pour un processus d’associer un fichier à une
région de sa mémoire virtuelle. Cela permet au processus de lire et d’écrire des parties
du fichier comme s’il s’agissait de mots mémoire.
Étude de cas n˚2 : Windows 2000 ◆ 817
Les entrées/sorties sont un domaine important pour les programmeurs. Dans
l’approche Win32, un fichier est simplement une suite linéaire d’octets. Win32 fournit plus de 60 fonctions pour la création et la destruction de fichiers et de répertoires,
l’ouverture et la fermeture de fichiers, leur lecture et leur écriture, la gestion de leurs
attributs, et beaucoup d’autres aspects liés.
La sécurité est un autre domaine couvert par les appels Win32. Chaque processus
possède une ID l’identifiant de manière unique et chaque objet peut s’adjoindre une
ACL (Access Control List, liste de contrôles d’accès) décrivant précisément quels utilisateurs peuvent accéder à l’objet et les opérations qu’ils peuvent effectuer. Cette
approche permet une granularité très fine de la gestion de la sécurité, chaque individu pouvant se voir autoriser ou refuser tout type d’accès sur tout objet.
Les processus, les threads, la synchronisation, la gestion de la mémoire, les entrées/sorties fichiers et la sécurité ne sont pas des concepts nouveaux. D’autres systèmes
d’exploitation les incluent également ; pas de manière aussi complète, cependant, que
Win32. Ce sont les milliers de fonctions de gestion de l’interface graphique de Win32
qui en sont la caractéristique principale. On trouve des fonctions pour créer, détruire et
gérer des fenêtres, des menus, des barres d’outils, de statut et de défilement, des boîtes
de dialogue, des icônes et de nombreux autres objets pouvant apparaître sur l’écran. Il y
a des fonctions pour dessiner des figures géométriques, les remplir, gérer leur palette de
couleurs, les polices de caractères et le placement des objets sur l’écran. Enfin, on trouve
des fonctions pour la gestion du clavier, de la souris et d’autres périphériques d’entrées,
ainsi que pour la gestion de l’audio, de l’impression et d’autres périphériques de sortie.
Bref, l’API Win32 (sa partie IHM, notamment) est énorme : il est impensable de la
décrire dans ce chapitre, même superficiellement.
Bien que l’API Win32 soit accessible sous Windows 98 (ainsi que sous sa version Windows CE destinée aux agendas électroniques), toutes les versions de Windows n’implémentent pas la totalité de ses fonctions, et il peut y avoir quelques différences mineures
entre ces versions. Ainsi, Windows 98 n’a pas de gestion de la sécurité, tous les appels
relatifs à ce domaine, dans cette version, se contentent donc de renvoyer un code
d’erreur. De même, les noms de fichiers Windows 2000 utilisent les caractères de l’Unicode, qui ne sont pas disponibles sous Windows 98, et sont sensibles à la casse, alors que
ceux de Windows 98 ne le sont pas pas. On trouve également des différences dans les
paramètres de certaines fonctions. Sous Windows 2000 par exemple, les coordonnées
écran des fonctions graphiques sont des nombres sur 32 bits ; sous Windows 98, seuls
les 16 bits de poids faible sont utilisés, car une grande partie du sous-système graphique
est encore constituée de code 16 bits. L’existence de l’API Win32 sur plusieurs systèmes
facilite le portage d’applications entre ces systèmes, mais à cause de ces petites variations, le portage doit être fait avec beaucoup d’attention.
11.2.2 Le Registre
Windows doit garder trace d’une grande quantité d’informations sur le matériel, le
logiciel et les utilisateurs. Sous Windows 3.x, ces informations étaient réparties dans
des centaines de fichiers « .ini » (initialisation) répartis sur tout le disque. À partir de
818 ◆ Systèmes d’exploitation
Windows 95, à peu près toutes les informations nécessaires à l’amorce (boot) et à la
configuration du système, ainsi qu’à son adaptation à l’utilisateur courant, ont été
rassemblées dans une base de données centralisée appelée registry. Dans cette section, nous allons brièvement aborder le Registre de Windows 2000.
Il faut noter pour commencer que, bien que de nombreuses parties de Windows 2000
soient complexes et mal conçues, le Registre est l’une des pires, et sa nomenclature
absconse n’arrange rien. Cela dit, son principe est très simple. Elle est constituée d’un
ensemble de répertoires pouvant contenir des sous-répertoires ou des entrées simples. Il s’agit en somme d’une sorte de système de fichiers pour de très petits fichiers,
chaque entrée représentant un « fichier ».
Le problème est que Microsoft appelle un répertoire une key (clé), ce qui n’a rien à
voir. En outre, tous les répertoires de premier niveau ont un nom qui commence par
HKEY, ce qui signifie « handle sur une clé ». Leurs sous-répertoires ont en général des
noms plus significatifs, mais ce n’est pas systématique.
Au dernier niveau de l’arborescence se trouvent les entrées, appelées values (valeurs),
qui contiennent l’information. Chaque value est structurée en trois parties : un nom,
un type et la donnée. Le nom est une simple chaîne de caractères Unicode (qui sera
souvent default si le répertoire ne contient qu’une value). Le type est l’un des
11 types standards (parmi lesquels les chaînes Unicode, les listes de chaînes, les
entiers 32 bits, les nombres binaires de longueur quelconque et les liens symboliques
vers un autre répertoire/entrée du Registre). Les noms symboliques sont parfaitement identiques aux liens symboliques dans les systèmes de fichiers UNIX ou aux
raccourcis Windows : ils permettent à une entrée de pointer sur une autre entrée ou
un autre répertoire. Un nom symbolique peut également être utilisé en tant que
répertoire, indiquant ainsi que le (pseudo-) répertoire est en fait simplement un
pointeur vers un autre répertoire. Dorénavant, nous utiliserons exclusivement
« répertoire » pour key, et « entrée » pour value.
Au premier niveau, le Registre Windows 2000 a six répertoires, appelées root keys
(clés racines), comme l’indique la figure 11.5. Quelques sous-répertoires intéressants
y figurent également. Pour consulter cette liste sur son système, on utilise l’un des
éditeurs de registry regedit ou regedt32, qui malheureusement ne donnent pas la
même information, et pas sous le même format. Ils permettent également de modifier les entrées. Les amateurs devraient s’abstenir de modifier le Registre d’un système
qu’ils envisagent de continuer à utiliser. En revanche, la consultation en mode lecture
seule est sûre. Le lecteur est prévenu…
Le premier répertoire, HKEY_LOCAL_MACHINE, est probablement le plus important,
puisqu’il renferme toute l’information sur le système courant. Il possède cinq sousrépertoires. Le sous-répertoire HARDWARE contient de nombreux sous-répertoires
décrivant complètement le matériel et les pilotes qui contrôlent chaque élément de ce
matériel. Cette information est construite à la volée par le gestionnaire de plug and
play, au moment de l’amorçage. À la différence des autres sous-répertoires, il n’est pas
stocké sur disque.
Étude de cas n˚2 : Windows 2000 ◆ 819
Le sous-répertoire SAM (Security Account Manager, gestionnaire de sécurité des comptes) contient le nom des utilisateurs, les groupes, les mots de passe et toute l’information relative aux comptes nécessaire à la connexion au système. Le sous-répertoire
SECURITY contient des informations générales sur la politique de sécurité, comme la
longueur minimale des mots de passe, le nombre maximal de tentatives de connexion
erronées tolérées par le système avant qu’il n’appelle la police, etc.
Le sous-répertoire SOFTWARE est l’endroit où les distributeurs de logiciels stockent
leurs paramètres, leurs préférences, etc. Par exemple, si un utilisateur a installé Acrobat, Photoshop et Premiere, de la société Adobe, on trouvera un sous-répertoire Adobe
sous SOFTWARE, et, sous ce sous-répertoire Adobe, un sous-répertoire pour chaque
produit installé. Ces sous-répertoires peuvent contenir tout ce que les programmeurs
de ces logiciels souhaitent y placer : des propriétés au niveau système, comme le
numéro de version et de construction du binaire, des informations relatives à la
désinstallation du module, les pilotes à utiliser, etc. La registry leur épargne de devoir
gérer le stockage de cette information de manière locale.
Figure 11.5 • Les répertoires de premier niveau du Registre et quelques sous-répertoires
significatifs.
HKEY_LOCAL_MACHINE
Propriétés du matériel et du logiciel
HARDWARE
Description du matériel et mappage du matériel sur les pilotes
SAM
Informations de sécurité sur les utilisateurs
SECURITY
Règles de sécurité globales du système
SOFTWARE
Informations sur les applications installées
SYSTEM
Informations sur l’amorçage du système
HKEY_USERS
Informations sur les utilisateurs (un répertoire par utilisateur)
USER-AST-ID
Profil de l’utilisateur
AppEvents
Liaison de sons aux événements graphiques (ex : arrivée de mail)
Console
Caractéristiques du prompt (couleur, police, …)
Control Panel
Apparence du Bureau, sensibilité de la souris, économiseur
d’écran, …
Environment
Variables d’environnement
Keyboard Layout
Nature du clavier : AZERTY, QWERTY, DVORAK, …
Printers
Informations sur les imprimantes installées
Software
Préférences utilisateur (logiciels Microsoft et autres)
La mise en majuscules est sans signification, mais suit la convention Microsoft.
820 ◆ Systèmes d’exploitation
Figure 11.5 • Les répertoires de premier niveau du Registre et quelques sous-répertoires
significatifs (suite).
HKEY_PERFORMANCE_DATA
Centaines de compteurs mesurant les performances du système
HKEY_CLASSES_ROOT
Lien vers HKEY_LOCAL_MACHINE\SOFTWARE\CLASSES
HKEY_CURRENT_CONFIG
Lien vers le profil matériel courant
HKEY_CURRENT_USER
Lien vers le profil utilisateur courant
La mise en majuscules est sans signification, mais suit la convention Microsoft.
L’information spécifique à chaque utilisateur est également stockée dans le Registre,
mais sous HKEY_USERS.
Le répertoire SYSTEM contient principalement l’information nécessaire au démarrage
du système, notamment la liste des pilotes à charger. Il contient également la liste des
services (démons) qui doivent être lancés au boot, ainsi que les informations de
configuration les concernant.
Le répertoire suivant, HKEY_USERS, contient le profil de tous les utilisateurs définis.
Quand un utilisateur change une de ses préférences à l’aide du panneau de contrôle,
par exemple la palette de couleurs de son Bureau, les nouveaux paramètres sont
stockés à cet endroit. En fait, un bon nombre de programmes du panneau de contrôle
ne font que saisir l’information auprès de l’utilisateur et la stocker dans le Registre.
Quelques-uns des sous-répertoires de HKEY_USERS sont mentionnés dans la figure 1.5
et ne nécessitent pas davantage de commentaires. Certains, tel Software, contiennent
un nombre surprenant de sous-répertoires, même si aucun logiciel n’est installé.
Le répertoire HKEY_PERFORMANCE_DATA contient des données qui ne sont ni lues depuis le
disque, ni rassemblées par le gestionnaire de plug and play. Il s’agit plutôt d’une vue sur
le système d’exploitation. Ce dernier contient des centaines de compteurs pour mesurer
ses performances. Ces compteurs sont accessibles via ce répertoire. Quand l’un de ses
sous-répertoires est consulté, une procédure est déclenchée qui calcule et renvoie
l’information souhaitée, au besoin en consultant un ou plusieurs compteurs. Ce répertoire n’est pas visible sous les éditeurs regedit et regedt32. Pour le consulter, on doit
utiliser des outils de gestion des performances, comme pfmon, perfmon et pview. Il y en a
beaucoup d’autres, certains se trouvant sur le CD-ROM de Windows 2000, d’autres
dans les divers kits de ressources, d’autres enfin dans des logiciels tiers.
Les trois répertoires suivants du premier niveau n’existent pas réellement. Chacun
d’eux est un lien symbolique vers un autre emplacement du Registre. Le répertoire
HKEY_CLASSES_ROOT est le plus intéressant. Il pointe vers le répertoire gérant les objets
COM (Component Object Model, le modèle objet de composants Microsoft), ainsi
que les associations entre extensions des noms de fichiers et les applications à déclencher. Ainsi, quand un utilisateur double-clique sur un fichier de type « .doc », le programme qui gère le clic de souris consulte ce répertoire pour savoir quelle application
lancer (probablement Microsoft Word). Toutes les extensions reconnues et les applications associées sont stockées à cet endroit.
Étude de cas n˚2 : Windows 2000 ◆ 821
Le répertoire HKEY_CURRENT_CONFIG pointe sur la configuration matérielle courante.
Un utilisateur peut définir plusieurs configurations matérielles, notamment pour
désactiver certains périphériques soupçonnés d’être la cause d’un éventuel dysfonctionnement. De même, HKEY_CURRENT_USER pointe sur le profil de l’utilisateur courant,
ce qui permet de retrouver rapidement l’ensemble de ses préférences.
Ces trois derniers répertoires n’ajoutent donc pas vraiment d’information, ils se contentent de faciliter l’accès à certains éléments importants. Ainsi, bien que regedit et
regedt32 affichent 5 répertoires au premier niveau, il n’y en a réellement que 3, dont
un est caché à l’utilisateur par ces deux outils.
Le Registre est totalement accessible au programmeur Win32. L’API fournit des fonctions pour créer et détruire des répertoires, chercher des entrées de répertoires, etc.
Quelques-unes des plus utilisées sont présentées à la figure 11.6.
Figure 11.6 • Quelques appels de l’API Win32 pour utiliser le Registre.
RegCreateKey
Crée un nouveau répertoire dans le Registre
RegDeleteKey
Détruit un répertoire existant
RegOpenKeyEx
Ouvre un répertoire (pour récupérer un handle)
RegEnumKeyEx
Énumère les sous-répertoires du répertoire pointé par le handle
RegQueryValueEx
Recherche les données correspondant à une entrée dans le répertoire
Quand le système s’arrête, la plupart (et non la totalité, comme nous l’avons vu) des
informations du Registre sont placées dans des fichiers appelés hives (ruches). On en
trouve la plus grande partie dans \winnt\system32\config. Comme leur intégrité est
une condition sine qua non au bon fonctionnement du système, une copie de sauvegarde est faite automatiquement à chaque mise à jour, et les accès en écriture font
l’objet de transactions non-interruptibles (atomiques) pour éviter la corruption des
fichiers en cas de panne du système en cours d’écriture. La perte du Registre rend la
réinstallation de la totalité du système nécessaire.
11.3 L’architecture du système
Dans les sections précédentes, nous avons abordé Windows 2000 du point de vue du
programmeur. Nous allons maintenant explorer l’envers du décor, c’est-à-dire l’organisation interne du système, pour voir ce que font ses divers composants et comment
ils interagissent, entre eux et avec les programmes utilisateur. Il existe de nombreux
ouvrages sur l’utilisation de Windows 2000, mais très peu qui décrivent son fonctionnement. La meilleure référence en la matière est Inside Windows 2000, 3e édition, par
Salomon et Russinovitch (2000). Une partie de ce chapitre puise ses informations
dans cet ouvrage. Microsoft a également fourni des informations cruciales.
822 ◆ Systèmes d’exploitation
11.3.1 La structure du système d’exploitation
Windows 2000 est composé de deux parties principales : le système d’exploitation
lui-même, qui fonctionne en mode noyau, et les sous-systèmes d’environnement, qui
tournent en mode utilisateur. Le noyau est classique : il gère les processus, la
mémoire, les systèmes de fichiers, etc. Les sous-systèmes d’environnement sont plus
originaux, dans la mesure où ce sont des processus utilisateur distincts qui aident les
applications à remplir certaines fonctions système. Les sections qui suivent présentent
chacune de ces parties.
L’une des nombreuses améliorations de NT par rapport à Windows 3.x a été sa structure modulaire. Elle était constituée d’un noyau de taille raisonnable qui tournait en
mode noyau et d’un ensemble de processus serveur qui, eux, tournaient en mode utilisateur. Les applications interagissaient avec ces processus sur un mode client-serveur : un client envoie un message de requête au serveur, qui effectue la tâche
demandée et renvoie le résultat au client, via un message de retour. Cette structure
modulaire a facilité le portage sur différentes plates-formes, qu’il s’agisse du DEC
Alpha, du PowerPC d’IBM ou du MIPS de SGI. Elle épargnait aussi au noyau les
bogues des processus serveur. Cependant, pour des raisons de performance, à partir
de NT 4.0, la grande majorité des responsabilités du système d’exploitation (notamment la gestion des appels système et du graphisme) a été réintégrée en mode noyau.
Ce choix de conception est également celui de Windows 2000.
Néanmoins, une certaine structuration subsiste dans Windows 2000. Le système est
divisé en couches, chaque couche pouvant utiliser les services des couches inférieures.
Sa structure est présentée à la figure 11.7. Une des couches est divisée horizontalement
en de nombreux modules. Chaque module remplit une fonction particulière et présente une interface clairement définie pour communiquer avec les autres modules.
Les deux couches inférieures, HAL (Hardware Abstraction Layer, couche d’abstraction
du matériel) et le noyau, sont écrites en C et dans le langage d’assemblage et sont partiellement dépendantes de la plate-forme. Les couches supérieures sont écrites en C et
sont presque totalement indépendantes de la plate-forme. Les pilotes sont écrits en C,
dans quelques cas en C++. Nous allons maintenant examiner les différents
composants du système, en allant du bas vers le haut.
Couche d’abstraction du matériel
L’un des buts objectifs, dans la conception de Windows 2000 (et avant lui de Windows NT), était la portabilité. Dans l’idéal, quand une nouvelle plate-forme apparaît, il devrait être possible de recompiler simplement le système d’exploitation
avec le compilateur dédié à la plate-forme pour que ce dernier fonctionne au
premier essai. Malheureusement, ce n’est pas si simple. Même si les couches supérieures du système d’exploitation peuvent effectivement être rendues totalement
portables (puisqu’elles manipulent essentiellement des structures de données
internes), les couches basses travaillent sur des registres, des interruptions, le
DMA (Direct Memory Access, accès direct à la mémoire) et d’autres caractéristiques matérielles qui varient considérablement d’une plate-forme à l’autre.
Processus
service
Programme POSIX
Programme Win32
Programme OS/2
Sous-système POSIX
Sous-système Win32
Sous-système OS/2
Mode utilisateur
Étude de cas n˚2 : Windows 2000 ◆ 823
Interface du système (NTDLL.DLL)
Système
de fichiers
D
Gestion.
d'objets
Gestion. Gestion.
de
de
processus mémoire
Gestion.
de
sécurité
Gestion. Gestion.
de
de plug
cache and play
Gestion.
Gestion.
d'alimende confitation
guration
électrique
Noyau
Gestion.
de LPC
Win32
GDI
Pilote
vidéo
Mode noyau
Services système
Gestion.
d'E/S
Couche d'abstraction du matériel (HAL)
Matériel
Figure 11.7 • La structure de Windows 2000 (légèrement simplifiée). La zone grisée est
l’exécutif. Les boîtes contenant un « D » représentent les pilotes de périphériques (device
drivers). Les processus serveurs sont des démons système.
Bien que la plus grande partie du code de bas niveau soit écrite en C, il ne peut être
extrait d’une plate-forme de type Pentium et plongé dans un environnement
Alpha pour être recompilé directement, compte tenu du grand nombre de petites
différences matérielles entre les deux processeurs qui ne peuvent être gommées
par le compilateur seul.
Pour résoudre ce problème, Microsoft fournit un sérieux effort pour masquer la plus
grande partie de ces variations dans une couche fine de bas niveau appelée HAL
(Hardware Abstraction Layer). Ce nom est sans nul doute inspiré de celui de l’ordinateur HAL, dans le célèbre film de Stanley Kubrick 2001, l’Odyssée de l’espace. La
légende veut que Kubrick ait obtenu ce nom en retirant 1 à chacune des trois lettres
IBM…
Le rôle de HAL est de fournir au reste du système d’exploitation des périphériques
abstraits, vierges de toute verrue spécifique à telle ou telle plate-forme. Ces périphériques ont l’aspect de services indépendants de la machine (donc des appels de fonctions et des macros) utilisables par le reste du système et par les pilotes de
périphériques. Utiliser les services HAL, qui sont identiques sur toute plate-forme
supportée par Windows 2000, au lieu de recourir directement au matériel permet aux
pilotes et au noyau un portage simplifié sur toute nouvelle plate-forme. Le portage de
HAL lui-même est simple : le code dépendant de la plate-forme est très localisé et les
besoins (l’implémentation de l’ensemble des services HAL) sont clairement définis.
Les services qu’on a choisi d’inclure dans HAL sont ceux relatifs au chipset sur la carte
mère et qui varient d’une plate-forme à l’autre dans des limites raisonnablement
824 ◆ Systèmes d’exploitation
prévisibles. En d’autres termes, il est conçu pour cacher les différences entre fournisseurs de cartes mères et non les différences entre processeurs. Les services HAL comprennent l’accès aux registres de périphériques, l’adressage des périphériques
indépendamment du bus, la gestion des interruptions, les transferts DMA, le
contrôle des timers (compteurs de temps) et de l’horloge temps réel, les verrous bas
niveau et la synchronisation multiprocesseur, l’interfaçage avec le BIOS et sa configuration mémoire CMOS. HAL ne fournit pas de services pour des périphériques
d’entrées/sorties spécifiques, comme les claviers, les souris ou les disques, ni pour
l’unité de gestion de la mémoire.
Afin de mieux comprendre ce que fait réellement HAL, penchons-nous sur l’alternative entre entrées/sorties mappées en mémoire d’une part, et ports d’entrées/sorties
d’autre part. Certaines machines optent pour la première solution, d’autres choisissent la seconde. Comment programmer un pilote ? Doit-il utiliser la première approche, ou non ? Plutôt que de forcer le choix, ce qui aurait pour conséquence de rendre
le pilote non portable sur les machines ayant opté pour le choix opposé, HAL propose trois procédures pour lire les registres de périphériques et trois autres pour les
écrire :
• Uc = READ_PORT_UCHAR(port); WRITE_PORT_UCHAR(port, uc);
• Us = READ_PORT_USHORT(port); WRITE_PORT_USHORT(port, us);
• Ul = READ_PORT_ULONG(port); WRITE_PORT_ULONG(port, ul);
Ces procédures lisent et écrivent des entiers non signés sur 8, 16 et 32 bits sur le port
spécifié. C’est à HAL de choisir si des entrées/sorties mappées en mémoire doivent ou
non être utilisées. De cette façon, un pilote peut être porté sans modification sur des
plates-formes ayant fait un choix différent à ce niveau.
Les pilotes ont fréquemment besoin d’accéder à des périphériques d’entrées/sorties
spécifiques. Au niveau matériel, un périphérique possède une ou plusieurs adresses sur
un certain bus. Comme les architectures modernes utilisent plusieurs bus (ISA, PCI,
SCSI, USB, 1394, etc.), il peut arriver que plusieurs périphériques aient la même
adresse : il faut donc les distinguer. HAL fournit un service d’identification des périphériques qui mappe leurs adresses (dépendantes du bus concerné) sur des adresses logiques au niveau du système. Ainsi, les pilotes n’ont plus besoin de savoir quel
périphérique est sur tel bus. Ces adresses logiques sont analogues aux handles que le
système d’exploitation fournit aux applications pour la gestion des fichiers et autres ressources système. Ce système protège en outre les couches supérieures des spécificités de
certaines structures de bus ou de conventions d’adressage.
Les interruptions posent le même type de problème — elles sont également dépendantes du bus. Là encore, HAL propose un service de nommage global des interruptions et permet aux pilotes de lier des routines aux interruptions de façon portable,
sans qu’il soit nécessaire de connaître le vecteur d’interruptions ou le bus concernés.
La gestion du niveau des requêtes d’interruption est également effectuée par HAL.
Autre tâche de HAL, la gestion des transferts DMA indépendamment du périphérique.
Le moteur DMA système, comme les moteurs propres à des cartes d’entrées/sorties spécifiques, sont gérables. L’adressage des périphériques se fait par leur adresse logique.
Étude de cas n˚2 : Windows 2000 ◆ 825
HAL implémente également le scatter/gather (écriture ou lecture de blocs non contigus
en mémoire).
HAL gère aussi les horloges et les timers (temporisateurs) de manière portable. Le
temps est décompté en unités de 100 nanosecondes à dater du 1er janvier 1601, ce qui
est beaucoup plus précis que sous MS-DOS, avec ses unités de 2 secondes à dater du
1er janvier 1980. Ce service permet l’indépendance des pilotes vis-à-vis des fréquences
physiques de fonctionnement des horloges.
Les composants du noyau doivent parfois être synchronisés à bas niveau, notamment
pour la prévention des risques de blocage lors de la compétition dans les systèmes
multiprocesseurs. HAL fournit quelques primitives de gestion de cette synchronisation, comme les spin locks (verrous pivotants) qui permettent à un processus d’attendre une ressource détenue par un autre, notamment dans le cas où la ressource n’est
détenue que le temps d’un petit nombre d’instructions.
Enfin, une fois le boot terminé, HAL consulte le BIOS et examine éventuellement la
configuration mémoire CMOS, pour découvrir les bus et les périphériques
d’entrées/sorties du système, ainsi que leur configuration. Cette information est alors
stockée dans le Registre, de sorte qu’elle soit utilisable par les autres composants système, sans que ces derniers aient à dialoguer avec le BIOS ou à connaître le mode de
fonctionnement de la configuration mémoire.
La figure 11.8 présente un résumé de quelques-unes des tâches de HAL.
Dans la mesure où il est hautement dépendant de la plate-forme, HAL doit parfaitement
correspondre au système sur lequel il est installé. C’est pourquoi on trouve plusieurs
HAL sur le CD-ROM d’installation de Windows 2000. Lors de l’installation du système,
la version adéquate est sélectionnée et placée dans le répertoire \winnt\system32 sous le
nom hal.dll. Tous les boots suivants de la machine utiliseront cette version de HAL. La
suppression de ce fichier rend le système indémarrable.
Bien que HAL soit raisonnablement efficace, il peut se révéler insuffisamment performant pour des applications multimédias. C’est pour cette raison que Microsoft propose
également un paquetage logiciel appelé DirectX, qui étend HAL en permettant aux
processus utilisateur un accès plus direct au matériel. DirectX étant plutôt spécialisé,
nous n’en dirons rien de plus dans ce chapitre.
Couche noyau
Au-dessus de la couche d’abstraction du matériel, on trouve ce que Microsoft
appelle le noyau, ainsi que les pilotes de périphériques. Quelques documents
datant des débuts du développement font mention d’un « micro-noyau », mais il
n’en a jamais été réellement question, dans la mesure où la gestion de la mémoire,
des systèmes de fichiers et d’autres composants fondamentaux fonctionnaient dès
le premier jour en mode noyau. En fait, on ne peut parler de micro-noyau, puisque
pratiquement tout le système d’exploitation fut ramené dans l’espace d’adressage
du noyau à partir de NT 4.0.
826 ◆ Systèmes d’exploitation
Adresses de
périphériques
DMA
1.
Verrous
RAM
11 12 1
10
2
9
3
8
4
7 6 5
2.
3.
MOV EAX,ABC
ADD EAX,BAX
BNE LABEL
MOV EAX,ABC
MOV EAX,ABC
ADD EAX,BAX
BNE LABEL
MOVE AX,ABC
ADD EAX,BAX
BNE LABEL
Disque
Imprimante
Registres de
périphériques
Interruptions
Timers
BIOS
Couche d'abstraction du matériel
Figure 11.8 • Quelques-unes des tâches gérées par HAL.
Dans le chapitre consacré à UNIX, nous avons utilisé le terme « noyau » pour
désigner tout ce qui tourne en mode noyau. Ici, nous réserverons ce terme — à
contrecœur — pour la partie ainsi désignée dans la figure 11.7, et nous appellerons
l’ensemble de ce qui tourne en mode noyau « système d’exploitation ». Une partie
du noyau (et une grosse part de HAL) réside en permanence en mémoire principale
(autrement dit, n’est pas paginée). Si l’on définit sa priorité, elle peut contrôler la
préemption éventuelle des interruptions d’entrées/sorties. Bien qu’une part non
négligeable du noyau soit dépendante de la plate-forme, il est en grande partie écrit
en C, sauf dans les passages où la performance prime toute autre considération.
Le rôle du noyau est de rendre le reste du système d’exploitation complètement
indépendant du matériel, et donc facilement portable. Il commence là où HAL
finit. Il accède au matériel via HAL et construit, au-dessus des services bas niveau
de ce dernier, des abstractions de plus haut niveau. Ainsi, HAL propose des appels
pour associer des routines de service aux différentes interruptions, et leur donner
une priorité, mais rien de plus. Le noyau fournit quant à lui un mécanisme complet
de commutation de contexte. Il sauvegarde l’ensemble des registres CPU, modifie
la table des pages, vide le cache CPU, etc., de telle sorte que le thread qui était en
cours d’exécution est complètement sauvegardé dans des tables mémoire. Il initialise alors l’environnement mémoire du nouveau thread courant afin qu’il puisse
(re-)démarrer.
Le code gérant l’ordonnancement des threads est également dans le noyau. Quand il
doit vérifier si un nouveau thread doit s’exécuter — par exemple à l’expiration d’un
quantum de temps ou à la fin d’une interruption d’entrée/sortie — le noyau choisit le
thread et déclenche la commutation de contexte appropriée. Vue du reste du système
d’exploitation, la commutation est prise en main automatiquement par les couches
inférieures, de façon transparente et portable. L’algorithme d’ordonnancement luimême sera décrit plus loin dans ce chapitre, quand nous aborderons les processus et
les threads.
Étude de cas n˚2 : Windows 2000 ◆ 827
Outre la fourniture d’une abstraction de plus haut niveau pour l’accès au matériel et
la gestion de la commutation de contexte, le noyau se charge d’une tâche fondamentale, celle de fournir un support bas niveau pour deux types d’objets : les objets de
contrôle et les objets de dispatch. Ces objets ne sont pas ceux sur lesquels les applications peuvent obtenir des handles, ce sont des objets internes sur lesquels l’exécutif
construit les objets « utilisateurs ».
Les objets de contrôle se composent notamment des processus primitifs, des interruptions, et de deux objets étranges appelés DPC (Deferred Procedure Call, appel de
procédure retardé) et APC (Asynchronous Procedure Call, appel de procédure asynchrone). Un DPC sert à scinder la partie non critique — sur le plan temporel —
d’une procédure de service d’interruption de sa partie critique. En général, une telle
procédure sauvegarde quelques registres matériels volatils associés au périphérique
concerné afin qu’ils ne soient pas écrasés, et rend le matériel à nouveau disponible,
mais laisse le cœur du traitement pour plus tard.
Par exemple, après l’enfoncement d’une touche du clavier, la routine de service des
interruptions clavier lit le code de la touche dans un registre et ré-enclenche l’interruption clavier, mais elle n’a pas besoin de gérer la frappe immédiatement, surtout si
quelque chose de plus important (c’est-à-dire de plus grande priorité) est arrivé
entre-temps. Du moment que la touche est gérée en moins de 100 ms, l’utilisateur
n’en aura pas conscience. Les DPC gèrent aussi les expirations de timers et d’autres
activités qui ne réclament pas un traitement complet immédiat. La file d’attente DPC
renseigne le système sur la présence de travaux en attente.
Les APC sont analogues aux DPC, à ceci près qu’ils s’exécutent dans le contexte d’un
processus particulier. Quand on traite l’enfoncement d’une touche, le contexte dans
lequel le DPC s’exécute importe peu : le seul travail effectué est la lecture du code de
la touche et son placement probable dans un buffer (tampon) noyau. Cependant, si
une interruption nécessite la recopie d’un buffer noyau dans un buffer utilisateur
(comme c’est notamment le cas à l’issue d’une lecture depuis un modem), alors la
routine associée doit s’exécuter dans le contexte du processus utilisateur concerné
(puisque la table des pages doit contenir le buffer noyau et le buffer utilisateur —
nous verrons plus loin que tous les processus contiennent le noyau entier dans leur
espace d’adressage). D’où la distinction entre DPC et APC.
L’autre catégorie d’objets noyau est constitué des objets de dispatch. Elle inclut les
sémaphores, les mutex, les événements, les timers et d’autres objets sur lesquels les
threads peuvent se bloquer en attente. La raison pour laquelle ils doivent être (partiellement) gérés par le noyau est qu’ils sont étroitement liés à la gestion de l’ordonnancement des threads, qui est une tâche noyau. Pour l’anecdote, les mutex sont appelés
« mutants » dans le code parce qu’ils devaient initialement respecter la sémantique
OS/2, qui veut qu’ils ne se déverrouillent pas automatiquement quand un thread qui
les détenait se termine, ce qui a beaucoup étonné les concepteurs de Windows 2000.
La sémantique d’OS/2 devait être prise en compte dans la mesure où NT était au
départ destiné à remplacer OS/2, le système d’exploitation qui équipait les PS/2
d’IBM.
828 ◆ Systèmes d’exploitation
Exécutif
Au-dessus du noyau et des pilotes de périphériques, on trouve la partie supérieure du
système d’exploitation appelée exécutif, repérée par la zone grisée de la figure 11.7.
L’exécutif est écrit en C ; il est indépendant de l’architecture et peut être porté sur de
nouvelles plates-formes assez simplement. Il est formé de dix composants, euxmêmes constitués d’une dizaine de procédures qui collaborent à la réalisation d’une
tâche donnée. Il n’y a pas de frontière immuable entre ces composants, certains
auteurs amenés à décrire l’exécutif pourraient même grouper différemment les procédures dans les composants. Il est à noter que des composants de même niveau sont
régulièrement amenés à faire appel les uns aux autres.
Le gestionnaire d’objets prend en charge tous les objets connus du système d’exploitation, notamment les processus, les threads, les fichiers, les répertoires, les
sémaphores, les périphériques d’entrée/sortie, les timers, etc. Le gestionnaire alloue
une zone de mémoire virtuelle de l’espace d’adressage du noyau quand un tel objet
est créé et le replace dans la liste des blocs libres lorsque l’objet est désalloué. Il doit
donc garder trace de tous les objets.
Afin d’éviter toute confusion, la plupart des composants nommés « manager » (gestionnaire) dans la figure 11.7 ne sont pas constitués de processus ou de threads, mais
de collections de procédures que d’autres threads peuvent exécuter quand ils sont en
mode noyau. Un petit nombre d’entre eux, cependant, comme le gestionnaire d’alimentation et le gestionnaire de plug and play, sont effectivement des threads indépendants.
Le gestionnaire d’objets est également responsable d’un espace de nommage où il
place les nouveaux objets créés pour qu’on puisse les référencer par la suite. Tous les
autres composants de l’exécutif utilisent abondamment des objets pour accomplir
leur tâche. Les objets sont si étroitement liés au fonctionnement de Windows 2000
que la prochaine section leur est consacrée.
Le gestionnaire d’entrées/sorties fournit un cadre de manipulation des périphériques d’entrées/sorties et un ensemble de services d’entrées/sorties génériques. Il permet au reste du système de réaliser des entrées/sorties indépendamment du
périphérique physique utilisé, en faisant appel au pilote adéquat pour réaliser les
entrées/sorties physiques. Il accueille également l’ensemble des pilotes de périphériques (indiqués par un D dans la figure 11.7). Les systèmes de fichiers, d’un point de
vue technique, sont des pilotes de périphériques sous le contrôle du gestionnaire
d’entrées/sorties. On en compte un pour le système FAT (File Allocation Table, table
d’allocation des fichiers) et un pour le système NTFS (New Technology File System) ;
ils sont indépendants l’un de l’autre et contrôlent des partitions différentes du disque.
Tous les systèmes de fichiers FAT sont gérés par un seul pilote. Nous étudierons
les entrées/sorties à la section 11.6 et l’un des systèmes de fichiers, NTFS, à la
section 11.7.
Le gestionnaire de processus est chargé de la gestion des processus et des threads, y
compris en ce qui concerne leur création et leur terminaison. Il s’occupe des
mécanismes à mettre en œuvre pour les gérer plutôt que des stratégies définissant
Étude de cas n˚2 : Windows 2000 ◆ 829
leur mode d’utilisation. Il est construit au-dessus du noyau des objets de type thread
et processus et leur ajoute des fonctionnalités. C’est la clé de voûte de la multiprogrammation sous Windows 2000. Nous étudierons la gestion des processus et des
threads à la section 11.4.
Le gestionnaire de mémoire met en œuvre l’architecture Windows 2000 de pagination à la demande pour la mémoire virtuelle. Il gère le lien entre les pages virtuelles et
les pages de la mémoire physique. Il participe ainsi au respect des règles de protection
qui limitent l’accessibilité d’un thread donné aux pages appartenant à son propre
espace d’adressage (sauf cas très particulier). Il prend également en charge quelques
appels système liés à la mémoire virtuelle. Nous étudierons la gestion de la mémoire
dans la section 11.5.
Le gestionnaire de sécurité met en œuvre les mécanismes de sécurité sophistiqués de
Windows 2000, qui répond aux contraintes du niveau C2 du livre orange du DOD
(Department of Defense, le ministère américain de la Défense). Le livre orange énonce
un grand nombre de règles auxquelles doit se plier tout système conforme, de l’identification de l’utilisateur au moment de la connexion jusqu’à l’obligation de remettre
à zéro toute page virtuelle avant une nouvelle utilisation, en passant par le mode de
gestion du contrôle d’accès. Nous étudierons le gestionnaire de sécurité dans la
section 11.8.
Le gestionnaire de cache garde en mémoire les blocs de disque les plus récemment utilisés pour accélérer l’accès aux informations qu’ils contiennent dans le cas, probable, où
ils seraient de nouveau requis. Sa tâche est de distinguer les blocs qui ont le plus de
chances d’être rapidement réutilisés. Il est possible de configurer Windows 2000 avec
plusieurs systèmes de fichiers ; dans ce cas, le gestionnaire de cache doit fonctionner
pour l’ensemble, afin d’éviter que chacun d’eux fasse sa propre gestion de cache. Quand
un bloc est requis, on en demande la fourniture au gestionnaire de cache. S’il ne possède pas ce bloc, il fait appel au système de fichiers adéquat. Dans la mesure où les
fichiers peuvent être mappés en mémoire dans l’espace d’adressage des processus qui les
utilisent, le gestionnaire de cache doit collaborer avec le gestionnaire de mémoire pour
préserver la cohérence nécessaire. La quantité de mémoire dévolue au système de cache
peut augmenter ou diminuer dynamiquement en fonction des demandes. Nous étudierons le gestionnaire de cache en section 11.9.
Le gestionnaire de plug and play reçoit toute information relative à la connexion
d’un nouveau périphérique. La présence de certains périphériques n’est vérifiée qu’au
démarrage du système. Pour d’autres (les périphériques USB, par exemple) l’attachement peut se faire n’importe quand et déclenche un message à destination du gestionnaire de plug and play, qui retrouve et charge alors le pilote adéquat.
Le gestionnaire d’alimentation électrique s’occupe de tout ce qui est relatif à l’énergie. Cela consiste par exemple à éteindre le moniteur ou les disques après un certain
temps d’inactivité. Sur les portables, il gère également le niveau de charge des batteries et déclenche un arrêt propre du système quand ces dernières sont presque
épuisées, après avoir commandé à chaque application en cours de sauvegarder les
fichiers qu’elle manipule.
830 ◆ Systèmes d’exploitation
Le gestionnaire de configuration s’occupe de le Registre. Il ajoute de nouvelles
entrées et y recherche l’information éventuellement demandée par un programme.
Le gestionnaire d’appels de procédures locales fournit un système de communication interprocessus très efficace entre les processus et leurs sous-systèmes. Dans la
mesure où cet intermédiaire est indispensable à la réalisation de certains appels système, l’efficacité est ici cruciale, ce qui explique que le système standard de communication interprocessus ne soit pas utilisé.
Le module GDI (Graphic Device Interface, interface de pilote graphique) prend en
charge certains appels système (pas tous). Au début, il travaillait dans l’espace
d’adressage utilisateur, mais il a été déplacé dans l’espace d’adressage noyau avec NT 4.0
pour des raisons de performances. Il fournit des appels système permettant aux programmes utilisateur d’écrire sur l’écran et vers les imprimantes indépendamment du
périphérique physique. Il contient également le gestionnaire de fenêtres et le pilote
d’affichage. Avant NT 4.0, il travaillait également dans l’espace utilisateur, mais ses
performances décevantes ont conduit Microsoft à le faire migrer dans l’espace noyau
pour le rendre plus rapide. La figure 11.7 n’est absolument pas à l’échelle : le module
GDI Win32 est plus important que tout le reste de l’exécutif.
Au sommet de l’exécutif, on trouve une couche fine appelée services système. Son
rôle est de fournir une interface pour l’exécutif. Elle reçoit les véritables appels système Windows 2000 et charge d’autres parties de l’exécutif de leur exécution.
Au démarrage, Windows 2000 est chargé en mémoire sous la forme d’un ensemble de
fichiers. La partie principale du système d’exploitation, comprenant le noyau et l’exécutif, se trouve dans le fichier ntoskrnl.exe. HAL est une bibliothèque dynamique
placée dans un fichier à part, hal.dll. Win32 et l’interface graphique sont placés
ensemble dans un troisième fichier, win32k.sys. Enfin, de nombreux pilotes de périphériques sont également chargés. La plupart portent l’extension .sys.
En fait, les choses ne sont pas aussi simples. Le fichier ntoskrnl.exe existe en versions
monoprocesseur et multiprocesseur. Il y a également des versions pour le processeur
Xeon, qui peut gérer plus de 4 Go de mémoire vive, et pour le Pentium, qui en est incapable. Enfin, ces versions sont soit des versions commerciales (vendues en magasin ou
préinstallées sur les machines), soit des versions de mise au point. Il pouvait donc se
trouver huit combinaisons, mais un regroupement a permis de n’en laisser que six.
L’une d’elles est effectivement copiée sur disque — sous le nom ntoskrnl.exe — lors de
l’installation du système.
Les versions de mise au point méritent qu’on en dise quelques mots. Quand on installe un nouveau périphérique sur un PC, on doit nécessairement installer en même
temps un pilote fourni par son fabricant. Supposons par exemple qu’une carte
IEEE 1394 soit installée sur un poste et semble fonctionner correctement. Deux
semaines plus tard, le système se bloque soudainement. Qui le propriétaire va-t-il
accuser ? Microsoft.
Le bogue peut effectivement être dû à Microsoft, mais certains sont causés par des
pilotes défaillants, sur lesquels Microsoft n’a aucun contrôle, et qui s’installent en
mémoire noyau, où ils ont accès à toutes les tables mémoire du noyau et à l’ensemble
Étude de cas n˚2 : Windows 2000 ◆ 831
du matériel. Pour réduire le nombre de clients mécontents, Microsoft tente d’aider les
auteurs de pilotes à mettre au point leur code en plaçant des directives du type
• ASSERT(condition)
à l’intérieur du code. Ces instructions effectuent des tests de validité sur les paramètres des procédures internes au noyau (qui peuvent être appelées sans restriction par
les pilotes), ainsi que beaucoup d’autres vérifications. Les autres versions définissent
ASSERT comme une macro qui ne fait rien, supprimant ainsi tous les tests. Sinon, la
macro est de la forme
• #define ASSERT(a) if (!a) error(...)
et permet aux tests de figurer dans le code de ntoskrnl.exe, ainsi que leur gestion
dynamique lors de l’exécution. Bien que cela ralentisse considérablement le système,
c’est une aide importante à la mise au point des pilotes. Ces versions de mise au point
fournissent également d’autres facilités.
Pilotes de périphériques
La dernière partie de la figure 1.7 est relative aux pilotes de périphériques. Chaque
pilote peut contrôler un ou plusieurs périphériques d’entrées/sorties, mais un pilote
peut également prendre en charge des tâches non liées à un périphérique spécifique,
comme le chiffrement d’un flux de données ou un simple accès à des structures de
données noyau. Les pilotes de périphériques ne sont pas inclus dans ntoskrnl.exe.
L’avantage est que quand un pilote vient d’être ajouté au système, il est placé dans une
liste dans le Registre et chargé automatiquement lors des démarrages suivants. Ainsi,
ntoskrnl.exe est le même pour tout le monde, mais chaque système est exactement
configuré pour ses propres périphériques.
On trouve des pilotes pour les périphériques visibles, comme les disques et les imprimantes, mais aussi pour quantité de périphériques internes et de puces dont pratiquement personne n’entend jamais parler. En outre, les systèmes de fichiers sont
présents sous forme de pilotes de périphériques, comme nous l’avons dit. Le plus gros
pilote, celui qui gère Win32, le GDI et la vidéo, se trouve à l’extrémité droite sur la
figure 11.7. Il prend en charge de nombreux appels système et la plus grande partie
du traitement graphique. Comme les clients peuvent installer de nouveaux pilotes, ils
risquent de perturber le noyau et de corrompre le système. C’est pour cette raison
que l’écriture de pilotes réclame un soin particulier.
11.3.2 L’implémentation des objets
Le concept d’objet est probablement le concept le plus important dans Windows 2000. Les objets fournissent une interface cohérente et uniforme à toutes les
ressources système et aux structures de données comme les processus, threads,
sémaphores, etc. Cette uniformité a plusieurs facettes. D’abord, on nomme tous les
objets et on y accède de la même manière, par l’intermédiaire de handles. Ensuite,
comme tous les accès aux objets passent par le gestionnaire d’objets, il est possible de
localiser les contrôles de sécurité dans un endroit unique et de s’assurer qu’aucun
832 ◆ Systèmes d’exploitation
processus n’échappe à ces contrôles. Troisièmement, le partage d’objets est pris en
charge de manière uniforme. Quatrièmement, comme l’ouverture et la fermeture de
tout objet passe par le gestionnaire d’objets, on peut savoir à tout instant quels objets
sont encore utilisés, et quels autres peuvent être détruits sans problème. Cinquièmement, ce modèle uniforme de gestion d’objets rend plus simple la gestion des quotas.
Pour bien comprendre ce qu’est un objet, il faut considérer qu’il n’est en fait qu’un
certain nombre de mots consécutifs dans la mémoire (c’est-à-dire dans l’espace
d’adressage virtuel du noyau). Un objet est une structure de données en RAM, ni plus
ni moins. Un fichier disque n’est pas un objet, bien qu’un objet soit créé lorsque ce
fichier est ouvert. En conséquence, quand le système redémarre (ou se bloque), tous
les objets sont perdus. En fait, au démarrage du système, il n’y a aucun objet (sauf
pour les processus système et le processus d’attente idle, dont les objets sont codés
dans le fichier ntoskrnl.exe). Tous les autres objets sont créés à la volée pendant le
boot et lors de l’exécution des programmes d’initialisation, puis des applications.
Les objets ont une structure, comme l’indique la figure 11.9. Chaque objet contient
un en-tête formé d’informations communes à tous les objets de tout type. Parmi les
champs, citons le nom de l’objet, le répertoire dans lequel il réside dans l’espace des
objets, des informations de sécurité (afin de rendre possibles des contrôles lors de
l’ouverture de l’objet) et une liste des processus ayant un handle ouvert sur l’objet (si
un certain flag de debug est positionné).
En-tête
de l'objet
Données
de l'objet
Nom de l'objet
Répertoire où se trouve l'objet
Information de sécurité
Coût d'accès à l'objet
Liste de processus (avec handles)
Compteurs de références
Pointeur sur l'objet type
Données spécifiques à l'objet
Nom du type
Types d'accès
Droits d'accès
Coût d'accès
Synchronisable ?
Paginable
Méthode d'ouverture
de fermeture
de suppression
de nom de requête
d'exploration
de sécurité
Figure 11.9 • La structure d’un objet.
Chaque en-tête d’objet contient également un champ mentionnant le « coût d’ouverture » de l’objet : si par exemple un objet fichier vaut 1 point, et qu’un processus
donné appartienne à un job qui possède un capital de 10 points, alors ce processus ne
pourra ouvrir que 10 fichiers au maximum. De cette manière, on peut gérer des limites
sur les ressources séparément pour chaque type.
Étude de cas n˚2 : Windows 2000 ◆ 833
Les objets occupent des emplacements coûteux dans l’espace d’adressage du noyau.
Lorsqu’un objet n’est plus nécessaire, il doit donc être détruit et son adresse rendue
libre. Le mécanisme de récupération mémoire se fonde sur un compteur de références placé dans l’en-tête de chaque objet. Quand ce compteur tombe à 0, aucun programme utilisateur ne référence plus l’objet. Un second compteur du même type est
utilisé pour les références par des composants de l’exécutif. Quand ces deux compteurs sont nuls, plus aucun programme ne référençant l’objet, ce dernier peut être
détruit et sa place mémoire libérée.
Le gestionnaire d’objets doit donc maintenir des structures de données dynamiques
(ses objets), mais il n’est pas le seul composant de l’exécutif à avoir ce besoin. D’autres
éléments doivent également allouer et libérer dynamiquement des blocs de mémoire
noyau. Pour rendre cela possible, l’exécutif maintient deux tables des pages dans
l’espace d’adressage du noyau : l’une pour les objets, l’autre pour les autres structures
dynamiques. Ces tables sont en fait des tas (heaps), et fonctionnent comme les fonctions malloc() et free() de la bibliothèque standard du langage C. L’une des deux
tables est paginée, pas la seconde. Les objets souvent utilisés sont placés dans la
seconde. Les autres, comme les répertoires du Registre ou des informations de sécurité, supportent la pagination. Quand la mémoire est proche de la saturation, ces derniers sont déchargés et rechargés à la demande. En fait, une part importante du code
et des données du système d’exploitation est également paginable, afin de réduire le
besoin de mémoire. Les objets dont on peut avoir besoin dans les parties critiques du
code (et quand la pagination est interdite) doivent, bien entendu, résider dans la table
non paginable. Quand une petite quantité de mémoire est nécessaire, une page de
l’une des deux tables peut être retenue et fractionnée en portions de plus petite taille,
jusqu’à 8 octets.
Les objets sont typés, ce qui signifie qu’ils possèdent tous les propriétés communes à
leur type. Ce type est indiqué par un pointeur dans l’en-tête vers un objet « type »,
comme le montre la figure 11.9. L’information contenue dans l’objet type inclut
notamment le nom du type, la possibilité d’une attente sur l’objet (oui pour un
mutex, non pour un fichier ouvert) et la table des pages dans laquelle placer l’objet.
Tout objet pointe donc sur son objet type.
Enfin, et c’est le plus important, l’objet type détient aussi des pointeurs vers le code des
opérations standard du type comme open, close et delete. Quand l’une de ces opérations est invoquée sur un objet, ce dernier suit le lien vers son objet type et exécute le
code correspondant. Ce mécanisme permet au système d’initialiser les nouveaux objets
et de récupérer la mémoire lors de leur destruction.
Les composants de l’exécutif peuvent créer dynamiquement de nouveaux types
d’objets. Il n’y a donc pas de liste exhaustive des types d’objets, mais les plus courants
sont listés en figure 11.10. Passons-les brièvement en revue. Processus et threads sont
triviaux. Il y a un objet par processus (ou par thread), qui contient les principales
informations nécessaires à sa gestion. Les trois objets suivants, sémaphores, mutex et
événements, sont relatifs à la synchronisation entre processus. Les sémaphores et
mutex se comportent de manière classique, mais supportent plusieurs extensions
(comme des bornes supérieures ou des timeouts). Les événements peuvent se trouver
834 ◆ Systèmes d’exploitation
dans deux états : signalés ou non. Si un thread attend un événement signalé, il est
immédiatement libéré. Si l’événement attendu n’est pas signalé, le thread se bloque
jusqu’à ce qu’un autre thread signale l’événement, libérant alors tous les threads bloqués dessus. Un événement peut aussi être configuré de telle sorte qu’il repasse automatiquement dans l’état non signalé après réception d’un signal sur lequel des
threads attendent.
Les ports, les timers et les files sont également des objets relatifs aux problèmes de
communication et de synchonisation. Les ports sont des canaux d’échanges de messages entre processus. Les timers permettent de bloquer un processus pendant une
durée donnée. Les files sont utilisées pour prévenir les threads de la fin d’une opération
d’entrée/sortie asynchrone.
Figure 11.10 • Quelques types d’objets de l’exécutif gérés par le gestionnaire d’objets.
Type
Description
Process
Processus utilisateur
Thread
Thread interne à un processus
Semaphore
Outil de décompte pour la synchronisation des processus
Mutex
Sémaphore binaire pour la gestion de sections critiques
Event
Objet de synchronisation à état (signalé ou non)
Port
Outil pour l’échange de message entre processus
Timer
Objet permettant à un thread de dormir pendant un temps fixé
Queue
Objet permettant la notification de la terminaison d’une opération
d’E/S asynchrone
Open file
Objet associé à un fichier ouvert
Access token
Descripteur de sécurité d’un objet
Profile
Structure de données permettant l’étude de l’utilisation de la CPU
(profiling)
Section
Structure pour le mappage de fichiers sur l’espace d’adressage
virtuel
Key
Répertoire du Registre
Object directory
Répertoire facilitant le regroupement d’objets dans le gestionnaire
d’objets
Symbolic link
Pointeur sur (le nom d’) un autre objet
Device
Objet associé à un périphérique d’E/S
Device driver
Chaque pilote de périphérique possède son propre objet
Étude de cas n˚2 : Windows 2000 ◆ 835
Les objets fichiers sont créés lors de l’ouverture d’un fichier. Les fichiers non ouverts
ne sont associés à aucun objet géré par le gestionnaire d’objets. Les jetons d’accès sont
des objets de sécurité ; ils identifient un utilisateur et précisent ses privilèges éventuels. Les profils sont des structures permettant le stockage d’échantillons périodiques de la valeur du compteur d’instruction d’un thread actif, afin de déterminer les
endroits où le programme passe l’essentiel de son temps.
Les sections sont utilisées par le système de gestion de la mémoire pour traiter les
fichiers mappés en mémoire. Elles gardent trace des fichiers (ou des portions de
fichiers) associé(e)s à telle adresse mémoire. Les clés sont des répertoires du Registre permettant d’associer des noms à des valeurs. Les répertoires d’objets sont
locaux au gestionnaire d’objets. Ils permettent de regrouper des objets liés entre
eux, exactement comme un répertoire regroupe des fichiers dans un système de
fichiers. Les liens symboliques sont également analogues à leurs correspondants
dans un système de fichiers : ils permettent d’associer un objet de l’espace de nommage à un nom situé à un autre endroit de cet espace. Chaque périphérique connu
du système est associé à un objet qui contient l’information nécessaire à la manipulation du périphérique par le système. Enfin, chaque pilote chargé se voit également
associer un objet.
Les utilisateurs peuvent créer de nouveaux objets et ouvrir des objets existants via
des appels Win32 comme CreateSemaphore ou OpenSemaphore. Ce sont des appels à des
procédures de bibliothèques qui se traduisent par l’exécution de l’appel système
correspondant. Le résultat de tout appel correctement exécuté qui ouvre ou crée un
objet est une entrée de table contenant un handle sur 64 bits qui est stocké dans la
table des handles du processus, en mémoire noyau. L’index du handle dans la table
(sur 32 bits) est renvoyé à l’utilisateur, pour qu’il puisse l’utiliser par la suite.
L’entrée de 64 bits contient en fait deux mots de 32 bits. Le premier contient un pointeur (de 29 bits) sur l’en-tête de l’objet. Les 3 bits de poids faible sont utilisés comme
flags (par exemple, pour savoir si le handle doit être hérité par les processus descendants). Ces bits sont masqués avant que le pointeur ne soit effectivement suivi. Le
second mot contient un masque de 32 bits pour les droits. Il est nécessaire, car le
contrôle des permissions n’est fait que lorsque l’objet est créé ou ouvert. Si un processus a seulement un accès en lecture sur un objet, tous les autres bits de droits seront
mis à 0, ce qui permet au système de rejeter toute autre opération que la lecture sur
l’objet.
Les tables de handles pour deux processus et leurs relations avec des objets sont illustrées à la figure 11.11. Dans cet exemple, le processus A a accès aux threads 1 et 2 et
aux mutex 1 et 2. Le processus B accède au thread 3 et aux mutex 2 et 3. Les entrées
correspondantes dans la table des handles décrivent les droits pour chacun de ces
objets. Ainsi, le processus A pourrait être autorisé à (dé)verrouiller ses mutex, mais
pas à les détruire. Remarquons que le mutex 2 est partagé par les deux processus, ce
qui permet à leurs threads de se synchroniser. Les autres mutex ne sont pas partagés,
ce qui pourrait indiquer que les threads de A utilisent le mutex 1 pour leur synchronisation interne, et que ceux de B font de même avec le mutex 3.
836 ◆ Systèmes d’exploitation
Espace de nommage des objets
Certains objets étant créés et détruits à l’exécution, le gestionnaire d’objets doit garder un moyen d’accès à chacun d’eux. À cette fin, il maintient un espace de nommage
où sont référencés tous les objets du système. Tout processus peut avoir recours à cet
espace de nommage pour retrouver un objet appartenant à un autre processus et en
obtenir un handle, à condition qu’il ait droit. Cet espace de nommage est l’un des
trois utilisés par Windows 2000. Les autres concernent le système de fichiers et le
Registre. Ils sont tous trois organisés hiérarchiquement à l’aide d’une arborescence de
répertoires structurant les entrées. Les objets de répertoire listés à la figure 11.10
fournissent la base de la hiérarchie de cet espace de nommage.
Comme les objets de l’exécutif sont volatils (ils disparaissent dès que le système est
interrompu, à la différence des entrées du système de fichiers ou du Registre), l’espace
de nommage des objets est vide au démarrage du système. Pendant le boot, des éléments de l’exécutif créent des répertoires qu’ils remplissent d’objets. Par exemple,
quand le gestionnaire de plug and play découvre un périphérique, il crée un objet
périphérique associé et inclut cet objet dans l’espace de nommage. À l’issue du
démarrage, tous les périphériques d’entrées/sorties, les partitions de disques et autres
découvertes intéressantes figurent dans l’espace de nommage.
Tous les objets ne s’obtiennent cependant pas via cette méthode « à la Colomb »
(« pars, et vois ce que tu trouves en chemin… »). Certains composants de l’exécutif
consultent le Registre pour savoir quoi faire. Ainsi des pilotes de périphériques : pendant le boot, le système consulte le Registre pour connaître les périphériques
nécessaires.
Au fur et à mesure de leur chargement, on crée un objet pour chacun, et son nom est
inséré dans l’espace de nommage. Dans le système, on référence le pilote par un pointeur
sur l’objet associé.
Bien que l’espace de nommage soit un élément crucial pour le bon fonctionnement
du système, peu de gens en connaissent l’existence : des outils spéciaux sont
nécessaires pour le visualiser. L’un de ces outils, winobj, est téléchargeable gratuitement sur www.sysinternals.com. Quand on le lance, il décrit un espace de nommage contenant les répertoires listés à la figure 11.12, ainsi qu’un petit nombre
d’autres.
Le répertoire qui porte le nom bizarre de \?? contient tous les périphériques nommés
« à la MS-DOS », comme A: pour le lecteur de disquettes ou C: pour le premier
disque dur. Ces noms sont en fait des liens symboliques sur le répertoire \Device, où
résident les objets de type périphérique. Ce nom a été choisi de sorte qu’il figure en
tête dans un répertoire trié alphabétiquement, ce qui permet d’accélérer le temps
de consultation des noms commençant par une lettre de périphérique. Le contenu
des autres répertoires se déduit de leur nom.
Étude de cas n˚2 : Windows 2000 ◆ 837
Objet
"type de
thread"
table des
handles du
processus A
Thread
1
Thread
2
Thread
3
Thread
4
table des
handles du
processus B
1110
1010
1011
1111
0101
Objet
"type de
mutex"
Mutex
1
Mutex
2
Mutex
3
Figure 11.11 • Les relations entre les tables de handles, les objets et les types d’objets.
11.3.3 Les sous-systèmes d’environnement
Si l’on examine de nouveau la figure 11.7, on constate que Windows 2000 se compose
d’éléments fonctionnant en mode noyau et d’autres fonctionnant en mode utilisateur. Nous avons fait le tour des premiers ; il est temps maintenant de nous intéresser
aux composants en mode utilisateur, qui sont de trois sortes : les DLL (Dynamically
Linked Library, bibliothèque liée dynamiquement — l’équivalent des bibliothèques
partageables d’UNIX), les sous-systèmes d’environnement et les processus serveurs.
Ces composants collaborent pour fournir aux processus utilisateur une interface,
distincte de l’interface des appels système de Windows 2000.
Figure 11.12 • Quelques répertoires représentatifs de l’espace de nommage des objets.
Contenu
??
Point de départ pour la recherche de périphériques MS-DOS, comme C:
Device
Tous les périphériques d’E/S
Driver
Objets correspondant à chacun des pilotes chargés
Object Types
Types d’objets, comme indiqué à la figure 11.11
Windows
Objets pour envoyer des messages à toutes les fenêtres