---
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.
