--- title: "Systèmes d'exploitation : Les processus" date: 2021-09-17 tags: ["système", "appels système", "processus"] categories: ["Systèmes d'exploitation", "Cours"] --- Les processus sont des instances vivants de programmes. Un programme représente du code binaire stocké sur un support de stockage. Un processus est un espace d'adressage en mémoire et d'un contexte d'exécution. Plus d'information est disponible [dans les cours de prog. système]({{< ref "../../progsys/3-processus/index.md">}} "Les processus") ## Accès à la mémoire L'espace d'adressage contient des segments mémoire : * le *segment de texte* / de code: les instructions optimisées par le compilateur, souvent en lecture seule dans les systèmes modernes. * le *segment data* contenant lui même le segment des *données initialisées* et le BSS (données non-initialisées) * le *tas*, zone de mémoire dynamique gérée par la `libc` par l'utilisation de `malloc()` et `free()`. Le système ne peut détecter un accès en dehors de la plage définie par un `malloc()`. Lorsque le tas n'a plus d'espace alors la `libc` effectue un appel système (mais le noyau peut refuser d'allouer) * la *pile d'exécution*, sa taille est de 8MiB maximum sous Linux. Ce segment contient les paramètres des fonctions et leurs variables locales. * les *librairies partagées* mappees à la demande. Il est possible de voir les espaces de mémoire alloués pour un processus donné : ``` cat /proc/self/maps 55fee9705000-55fee9707000 r--p 00000000 fe:01 1979024 /usr/bin/cat 55fee9707000-55fee970c000 r-xp 00002000 fe:01 1979024 /usr/bin/cat 55fee970c000-55fee970f000 r--p 00007000 fe:01 1979024 /usr/bin/cat 55fee970f000-55fee9710000 r--p 00009000 fe:01 1979024 /usr/bin/cat 55fee9710000-55fee9711000 rw-p 0000a000 fe:01 1979024 /usr/bin/cat 55feead5a000-55feead7b000 rw-p 00000000 00:00 0 [heap] 7fbcfa322000-7fbcfa344000 rw-p 00000000 00:00 0 7fbcfa344000-7fbcfa62c000 r--p 00000000 fe:01 1987533 /usr/lib/locale/locale-archive 7fbcfa62c000-7fbcfa62e000 rw-p 00000000 00:00 0 7fbcfa62e000-7fbcfa654000 r--p 00000000 fe:01 1969528 /usr/lib/libc-2.33.so 7fbcfa654000-7fbcfa79f000 r-xp 00026000 fe:01 1969528 /usr/lib/libc-2.33.so 7fbcfa79f000-7fbcfa7eb000 r--p 00171000 fe:01 1969528 /usr/lib/libc-2.33.so 7fbcfa7eb000-7fbcfa7ee000 r--p 001bc000 fe:01 1969528 /usr/lib/libc-2.33.so 7fbcfa7ee000-7fbcfa7f1000 rw-p 001bf000 fe:01 1969528 /usr/lib/libc-2.33.so 7fbcfa7f1000-7fbcfa7fc000 rw-p 00000000 00:00 0 7fbcfa80f000-7fbcfa810000 r--p 00000000 fe:01 1969517 /usr/lib/ld-2.33.so 7fbcfa810000-7fbcfa834000 r-xp 00001000 fe:01 1969517 /usr/lib/ld-2.33.so 7fbcfa834000-7fbcfa83d000 r--p 00025000 fe:01 1969517 /usr/lib/ld-2.33.so 7fbcfa83d000-7fbcfa83f000 r--p 0002d000 fe:01 1969517 /usr/lib/ld-2.33.so 7fbcfa83f000-7fbcfa841000 rw-p 0002f000 fe:01 1969517 /usr/lib/ld-2.33.so 7ffccf574000-7ffccf595000 rw-p 00000000 00:00 0 [stack] 7ffccf5c8000-7ffccf5cc000 r--p 00000000 00:00 0 [vvar] 7ffccf5cc000-7ffccf5ce000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall] ``` On y voit biens les adresses de début, ceux de fin, les droits (`r`ead, `w`rite, e`x`ecute, `p`rivate) L'accès par un processus à un espace mémoire invalide donne lieu à la fameuse `segmentation fault`. Mais il est tout à fait possible de lire et écrire vers une zone non allouée du tas. Par exemple j'initialise un tableau de 10 éléments et les rmpli avec une boule de 15 itérations. ## Attributs d'un processus en plus de l'espace mémoire alloué pour le processus, le noyau stocke en mémoire un ensemble d'attributs : son identitiants (`PID`), sa priorité, l'`UID` (réel/effectif), la table des descripteurs de fichiers, la table des signaux, un espace pour sauvegarder les registres (changement de contexte, reprise sur interruption). ## Création et vie des processus Un processus voulant en créer un autre doit faire un appel système `fork`. Lors du changement de contexte, les registres du processus `p0` sont sauvegardés puis remplacés par ceux de `p1`. Les signaux sont délivrés au processus lors du passage du noyau à l'exécution. ### Processus bloquants Lorsqu'un processus attends un appel bloquant (par exemple `read()`) il est muse en sommeil. Lorque l'interruption est lancée, alors le noyau réveille le processus. ## Ordonnancement L'ordonnancement essaye définir un **fonctionnement universel** visant à organiser l'exécution concurente de processus sur un CPU. Un fonctionnement universel, convenant donc à tous les usages, *est impossible à obtenir*. Il dépend en effet de l'utilisation qui en est fait : interactif, temps-réel etc. Dans le cadre d'un système interactif, la réactivité est la caractéristique la plus importante. ### Stratégie Le type de système influe donc sur la stratégie à adopter. nous allons en détailler certaines. #### FIFO - First In First Out Une liste chainée de processus, on exécute le premier jusquà la fin de son exécution ou qu'il soit bloqué puis le second et ainsi de suite. C'est une technique facile à implémenter, il est très peu couteux en temps processeur (le noyau intervient peu, peu de changement de contexte) mais comporte un gand risque de **famine** : un processus en boucle ne rendrai jamais la main. #### Round-Robin Un **temporisateur** valable pour tous: le changent de contexte intervient toute les 10ms par exemple. C'est une technique facile à implementer, il y a plus de famine mais on ne **gère pas de priorité**. S'il y a beaucoup de processus, alors notre éditeut de texte sera moind réactif. #### Priorité stricte Les processus sont triés par priorité et les plus important son exécutés en premier. contrairement au *Round-Robin* on gère la priorité mais ette technique est discriminatoire. Comment **assigner les priorités**? Au faciès? #### Priorité dynamique La priorité change au cours de la vie du processus car il change de comportement. Dans le cas d'une **opération de compilation** par exemple, le compilateur lit les fichiers sources effectuant beaucoup de `read` et se bloauqnt donc. Ensuite il compile et utilise beaucoup de CPU. L'ordonnanceur observe donc les métriques du passé pour prévoir l'avenir. Dans l'example du compilateur, le noyau observe que sur les 10ms de temporisation, notre processus **s'est bloqué (et change de contexte) au bout de 1ms**. Puis losqu'il compile, notre compilateur va rester sur le CPU **pour toute sa tempotisation**. Mais comment choisir la bonne priorité en fonction de ces métriques? Tout simplement **en choisissant d'abord les processus les plus courts**. C'est la stratégie utilisée en général dans les **système interactifs**. #### La stratégie utilisée dans le noyau Linux 2.4 Cette version du noyau Linux, la gestion de l'ordonnancement se fait par l'attribution de crédits. Un processus utilisant l'UC le fait en dépensant des crédits, plus il l'utilise plus il en dépense. Lorqu'un processus n'a plus de crédit, il ne peut plus utiliser l'UC jusqu'à ce que le noyau en redistribue. Il le fait lorque aucun processus prêt n'a de crédit. Les processus n'ayant pas dépensé tous ses crédits se voit prélever un "impots": ``` Crédit = Cn + (Cn-1/2) + (Cn-2/4) + (Cn-3/8) + ... ``` Dans la limite de 2C. ### Et pour les système multi-cœur Chaque cœur exécute un ordonnanceur de façon asynchrone, la liste de processus peut-être : * partagée entre tous les cœurs * distribuée par cœur Il est bon de noter qu'une **UC peut envoyer une interruption à un autre UC**. ## Threads et processus Un processus est un espace d'adressage plus une pile d'exécution. Un thread est juste un **autre flow d'exécution dans le même espace d'adressage**. La création de threads (aussi appelés processus légers) est dons plus efficace : il n'y a pas de création d'espace d'adressage. Dans les noyaux modernes, tout est thread. ### Accès concurents à la mémoire Les threads partagent donc des espace commun de mémoire, il est donc important de gérer des accès concurrent. En effet l'accès aux mêmes cases mémoires par plusieurs threads peut conduire à des fonctionnements arbitraires. Il est à noter que les accès à la mémoire sont de toute façon atomique: *une opération de lecture ou écriture à la fois*. Mais ce n'est pas suffisant, un example de code est disponible [dans les cours de prog. système]({{< ref "../../progsys/5_les-processus_legers/index.md">}} "Les processus légers") Le noyau doit donc mettre en place des primitive de synchronisation.