197 lines
8.9 KiB
Markdown
197 lines
8.9 KiB
Markdown
---
|
|
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 composé d'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* mappées à 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 le rempli avec une boucle 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, elle est très peu couteuse 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
|
|
bloque régulièrement 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.
|