L'architecture des OS Un système d'exploitation est un logiciel qui a pour objectif de libérer l'utilisateur d'un ordinateur de la complexité (et la redondance) de la programmation du matériel. Pour communiquer le matériel utilise le "bus" par un système d'interruption (IRQ). Un OS joue deux rôles principaux : Un rôle actif : concerne la gestion de l'enchaînement et l'ordonnancement des processus Un rôle passif : qui permet de séparer des actions potentiellement dangereuses (accès aux ressources – mémoire, disque) des actions courantes (calcul) afin de préserver l'intégrité du système. La partie passive regroupe tous les services auxquels les différents processus peuvent faire appel : accès périphérique, demande de ressources (mémoire), création, activation ou arrêt de processus. Ces services sont disponibles, mais ne se déclenchent qu'à la demande expresse d'une application, au travers d'un appel système. On appelle le noyau de l'OS, la partie active (ordonnanceur) ainsi que les parties réalisant les services passifs. Le noyau est lancé au démarrage du PC. Le système d'exploitation à proprement dire, c'est le noyau. Les fonctionnalités du noyau opèrent en mode non-protégé (mode noyau ou réel), gèrent l'ordonnancement des différentes tâches (ou processus), l'allocation de la mémoire et le contrôle des E/S. Les fonctionnalités de plus haut niveau opèrent en mode protégé (mode utilisateur) et constitue une sorte de machine virtuelle ou étendue. On trouve 2 types d'OS : Des systèmes dit monolithique, intègre le maximum de fonctionnalité dans le noyau (linux) Des systèmes dit à micro-noyau, qui tentent de réduire au minimum le noyau (fctlité bas niveau) dans lequel les autres éléments sont intégrés sous forme de services Principe Les processeurs actuels associent un niveau de protection aux différentes zones mémoire (rendu possible grâce à la segmentation cf. . 1.4.6). Lorsque le processeur exécute des instructions provenant de ces zones mémoire, il les exécute dans le mode de protection associé8. Un nombre d'instructions (accès au bus, gestion des interruptions, mémoire brute) ne peuvent être utilisés dans le mode de protection le moins élevé. Par ailleurs, lorsque le processeur est dans un mode particulier, il ne peut accéder qu'aux instructions de mode équivalant ou supérieurs au sien. Mécanisme Le niveau de protection est déterminé par la zone mémoire dans lequel se trouve l'instruction courante. Une instruction de branchement ne peut aboutir que lorsqu'elle débouche dans une zone de mémoire de niveau de protection équivalente ou plus forte. Sinon, elle est refusée et provoque une erreur de segmentation. Comme les outils (programmes, logiciels) qui s'exécutent sur le système sont généralement écrits dans des langages de haut niveau (C, C++, Java ...) il n'est pas très réaliste d'exiger des développeurs de maîtriser les traps et d'effectuer les appels systèmes en langage d'assemblage à chaque fois qu'ils en ont besoin. Le système fournit donc une ou plusieurs bibliothèques (win32.dll sous Windows_nt/xp, libc.so sous Linux, ...) encapsulant tous les arcanes bas niveau dans des fonctions d'un langage de plus haut niveau. Cela permet aussi de décliner un même trap en plusieurs appels spéci_ques, de tester la validité des paramètres, et de garantir de la portabilité des applications, indépendamment, parfois, des choix du système sousjacent. Les processus Le mode de fonctionnement des micro-ordinateurs, tel que nous l'avons vu dans les sections précédentes, présente un obstacle à l'utilisation optimale de la puissance de calcul du processeur. Afin de fonctionner pleinement, celui-ci doit communiquer avec des périphériques qui sont plusieurs ordres de grandeurs plus lentes que lui. Cette communication se fait de façon asynchrone, et le périphérique sollicité prévient le processeur de la fin de la tâche par une interruption. Dans un contexte de traitement par lots, où tout traitement se déroule de façon séquentielle, le processeur doit attendre de précieux cycles afin d'obtenir la réponse de sa requête auprès du périphérique. La solution couramment adoptée pour optimiser l'attente des processus est l'utilisation de processus selon le principe de la multiprogrammation (ou multi-tasking en anglais). Un processus est une unité d'exécution principale et qui correspond à une tâche qui doit être exécutée par le processeur. La multiprogrammation non préemptive. À chaque processus est associé un état par l'OS : élu, prêt, bloqué avec différentes transitions entre les états. Le(s) processus élu(s) (il peut y en avoir plusieurs dans le cas d'un système multiprocesseur) sont exécutés par le(s) processeur(s). Lorsqu'un processus élu doit attendre l'arrivée d'une interruption d'entrée/sortie, le système le bascule dans l'état bloqué et l'ordonnanceur choisit, parmi les processus en attente dans l'état prêt, un autre processus qui passe en état élu et qui s'exécute jusqu'à être bloqué à son tour. La multiprogrammation préemptive Elle introduit une nouvelle transition dans le graphe d'état, permettant de faire passer un processus de l'état élu à l'état prêt sans que celui-ci soit nécessairement bloqué par une demande. Rôle de l'OS Le système d'exploitation, comme décrit dans, fournit d'une part l'interface entre les outils système de haut niveau et des applications utilisateur, et d'autre part l'interface avec les couches matérielles. Il doit donc permettre de lancer et de créer des processus, afin que les couches supérieures puissent s'exécuter. Ceci mène naturellement à une structure arborescente d'hiérarchie de processus. Un processus père peut lancer plusieurs processus fils (p. ex. la fonction système fork sous Unix) qui à leur tour peuvent lancer des processus fils, etc. Ces processus doivent ensuite pouvoir communiquer des informations entre eux. Le système doit également gérer le chargement, le déchargement de processus en mémoire. Il doit pouvoir les commuter sans perte du flot d'exécution intra-processus. Il doit aussi décider le l'ordre d'exécution, la priorité et le temps alloué à chaque processus (quantum) avant de le suspendre, p. ex..Pour cela, il utilise un ordonnanceur qui prend les décisions quant à quel processus relancer, et un noyau d'une taille minimale qui s'occupe de gérer les interruptions et l'occupation en mémoire des processus. - ordonnancement circulaire ordonnancement avec priorité Les Threads (processus léger) Jusqu'à maintenant nous avons considéré qu'un processus était l'équivalent d'un programme comprenant trois parties : les données statiques, les instructions et la pile ; le tout dans un bloc contigu de mémoire. Souvent, il existe une quatrième partie, qui s'appelle le tas, et qui est une zone de mémoire de laquelle le programme peut puiser de façon non organisée des blocs de mémoire, contrairement à la pile, dans laquelle on ne peut qu'empiler ou dépiler des objets par le dessus. C'est là notamment que des langages de haut niveau puisent les ressources lorsqu'ils font appel à malloc() en C, ou encore new en C++ et Java. Le processus dispose en plus d'un nombre d'autres ressources (p. ex. le contenu des registres, les fichiers ouverts, ...) qui résident dans la table de description du processus gérée par le système. Lorsque plusieurs utilisateurs lancent une même application sur une même machine, il est important de distinguer toutes ces zones mémoire, et il est donc important de pouvoir disposer de plusieurs instances (processus) d'un même programme. En revanche, il peut être tout aussi utile de faire effectuer des tâches en parallèle par un processus principal, tout en partageant certaines ressources mémoire entre le processus parent et ses fils, surtout que cela facilite grandement le partage et l'échange de données que, d'un point de vue système, le changement de contexte demande beaucoup moins de surcharge. Dans ce cas, on parle de threads. Chaque thread est un processus, du point de vue du système d'exploitation, mais il ne dispose uniquement de sa propre pile. Tous les autres ressources (zone statique, tas, etc.) sont partagées avec le processus parent et les fils de celui-ci.