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