chapitre 1 : introduction chapitre 2 : processus chapitre 3

214
Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3 : Ordonnancement Chapitre 4 : Outils de communication centralisés entre processus Chapitre 5 : Allocation mémoire Chapitre 6 : Mémoire virtuelle Chapitre 7 : Interblocage Chapitre 8 : Synchronisation entre processus Chapitre 9 : Inversion de Priorité Chapitre 10 : Communication inter processus sur le réseau Chapitre 11 : Programmation socket Table des figures

Upload: phammien

Post on 05-Jan-2017

293 views

Category:

Documents


16 download

TRANSCRIPT

Page 1: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Chapitre 1 : Introduction

Chapitre 2 : Processus

Chapitre 3 : Ordonnancement

Chapitre 4 : Outils de communication centralisés entre processus

Chapitre 5 : Allocation mémoire

Chapitre 6 : Mémoire virtuelle

Chapitre 7 : Interblocage

Chapitre 8 : Synchronisation entre processus

Chapitre 9 : Inversion de Priorité

Chapitre 10 : Communication inter processus sur le réseau

Chapitre 11 : Programmation socket

Table des figures

Page 2: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Chapitre 1 : Introduction Ce chapitre constitue une introduction aux systèmes d'exploitation multiprogrammés.

Cours : Introduction aux systèmes d'exploitation

Evolution de la prise en compte des entrées sorties par la machine matérielle : vers la machine multiprogrammée

Le mode programmé Les interruptions Le DMA ou Direct Memory Access

Rôles du système d'exploitation dans un environnement multiprogrammé. Définition. Fonctions d'un système d'exploitation Les différentes classes de système

Un peu plus sur les systèmes temps réel Notions de base

Interfaces du système d'exploitation Modes d'exécutions et Commutations de contexte

Un premier test

Table des figures

Page 3: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Introduction>Introduction aux systèmes d'exploitation

1 Cours : Introduction aux systèmes d'exploitation

Ce chapitre constitue une introduction aux systèmes d'exploitation multiprogrammés. Après un rappel sur

l'évolution de la prise en compte des entrées/sorties par la machine physique, nous donnons la définition

d'un système d'exploitation., puis nous listons les différentes fonctions qui le composent. Les différents

types de systèmes d'exploitation sont ensuite présentés et une attention toute particulière est alors portée

sur les systèmes temps réel. Nous finissons par un rappel sur les notions de base.

1.1 Evolution de la prise en compte des entrées sorties par la machine matérielle : vers la machine multiprogrammée

Nous nous intéressons à l'évolution de la prise en charge des entrées/sorties au niveau matériel. Trois

modes sont possibles : le mode programmé, les interruptions et le Dma. Nous allons voir que le dernier

mode libère complètement le processeur de la réalisation d'une entrée sortie et permet alors de faire

exécuter à celui-ci d'autres opérations durant celle-ci : le système pourra alors être multiprogrammé, c'est-à-

dire pourra permettre le traitement de plusieurs programmes : l'une en entrées/sorties, l'autre en calcul.

1.1.1 Le mode programmé Le premier mode de gestion des entrées/sorties est le mode programmé. Dans ce mode, l'unité d'échange

qui interface le périphérique au bus et au processeur, ne sait pas délivrer d'informations sur son état. Pour

savoir si l'unité d'échange est prête à recevoir ou délivrer une nouvelle donnée, le processeur doit

régulièrement lire le contenu du registre d'état RE de l'unité d'échange. Le processeur est donc

complètement occupé durant l'entrée sortie par la réalisation de cette boucle de scrutation dont la logique

est donnée sur la figure 1 en mémoire centrale. On ne peut donc concevoir qu’un système

monoprogrammé.

Page 4: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Fig 1 : Le mode programmé

1.1.2 Les interruptions Une première amélioration provient du mécanisme d'interruption : l'unité d'échange délivre alors une

interruption à destination du processeur lorsqu'elle est prête à délivrer ou recevoir une nouvelle donnée. Le

processeur, à la prise en compte de cette interruption, va exécuter la routine associée qui lit la donnée

présente dans RD (lecture) ou met dans RD la donnée suivante à écrire. Le processeur ne doit donc plus

scruter le registre d'état RE de l'unité d'échange pour savoir si celle-ci est prête. Cependant, il reste le seul

à pouvoir accéder à la mémoire centrale et doit donc encore gérer l'entrée sortie dans le sens où il doit se

charger des transferts mémoire centrale – registre de donnée.

Page 5: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Fig 2 : Entrée/sortie et interruption

Complément : Nous rappelons brièvement le mécanisme des interruptions. Le processeur exécute un programme lambda. L'interruption n°3 survient, déclenchée par une unité périphérique. Le processeur arrête alors l'exécution du programme lambda. Il sauvegarde le contexte du processeur en mémorisant notamment l'adresse de la prochaine instruction à exécuter dans le programme lambda (ici 1002). Puis il détermine grâce à une table appelée la table des vecteurs d'interruptions, l'adresse de la routine associée à l'interruption n°3. : ici 0017. Il charge alors le CO avec cette adresse, exécute la routine d'interruption qui se termine par l'instruction machine RTI (return Interrupt) : cette instruction restaure le contexte sauvegardé lors de la prise en compte de l'interruption : le CO est chargé avec l'adresse 1002 et le programme lambda reprend son exécution là où il avait été interrompu.

Page 6: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Traitement d'une interruption

1.1.3 Le DMA ou Direct Memory Access L'ajout d'un dispositif de DMA (Direct Memory Access) permet à l'unité d'échange d'accéder par elle-même

à la mémoire centrale. Le DMA com port e quatre registres qui servent à décrire l'opération

d'entrées/sorties à réaliser (nombre d'octets à transférer nb, adresse du premier octet @m, sens de

l'opération, .adresse du périphérique concerné @p). Combiner au mécanisme d'interruption, le DMA

permet de décharger le processeur de toute la réalisation des entrées/sorties. Celui-ci n'a plus qu'à

initialiser l'opération en initialisant le contenu des registres du DMA tel que cela est décrit sur la figure 3. Le

DMA se charge ensuite d'effectuer l'opération d'entrées/sorties, en lisant ou écrivant lui-même en mémoire

centrale les données transférées par l'unité périphérique. Lorsque l'opération d'entrées sorties est terminée,

le DMA émet une interruption pour le signaler au processeur.

Fig 3 : DMA et entrée sortie

Le chronogramme de la figure 4 illustre l'activité du processeur et du DMA lorsque le système est

monoprogrammé. Le programme Prog s'exécute et fait du calcul : le processeur est actif et le DMA inactif.

A un instant t, le programme Prog demande une opération d'entrées/sorties (Lire/Ecrire). Le Dma, une fois

initialisé par le processeur, devient actif et le processeur inactif, puisqu'il n'y a qu'un seul programme en

mémoire centrale et que le DMA gère seul l'opération d'entrées sorties. Le processeur demeure donc inactif

jusqu’à ce que le DMA ait fini l'entrée sortie; à ce moment là le processeur reçoit l'interruption émise par le

DMA, redevient actif, traite l'interruption puis reprend l'exécution du programme Prog..

Page 7: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Fig 4 : Activité du CPU et du DMA sur un système monoprogrammé

Laisser le processeur inactif conduit à une mauvaise utilisation de cette ressource matérielle. On choisit

donc de "monter" plusieurs programmes en mémoire centrale : ainsi comme le montre la figure 5, lorsque le

DMA réalise une opération d'entrées sorties pour le programme ProgA, le processeur peut exécuter un

autre programme qui demande du calcul : ProgB. Le système est devenu multiprogrammé.

Page 8: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Fig 5 : Activité du CPU et du DMA sur un système multiprogrammé

Page 9: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Introduction>Introduction aux systèmes d'exploitation>Role du systeme

1.2 Rôles du système d'exploitation dans un environnement multiprogrammé. Définition.

Le système est multiprogrammé : plusieurs programmes coexistent en mémoire centrale et utilisent le

processeur pour effectuer des calculs et le DMA pour réaliser les opérations d'entrées sorties. On distingue

en mémoire centrale un programme particulier : le système d'exploitation, chargé en mémoire haute avec le

vecteur d'interruptions. Le premier rôle du système d'exploitation dans un système multiprogrammé est de

gérer le partage de la machine physique et des ressources matérielles entre les différents programmes.

Cette gestion doit assurer l'équité d'accès aux ressources matérielles et assurer également que les accès

des programmes à ces ressources s'effectuent correctement (protection des ressource s). Plus

précisément, la gestion du partage de la machine physique et des ressources matérielles doit permettre de

répondre aux questions suivantes :

● partage du processeur unique : parmi tous les programmes chargés en mémoire centrale, lequel doit

s'exécuter ?

● partage de la mémoire centrale : comment allouer la mémoire centrale aux différents programmes.

Comment assurer la protection entre ces différents programmes utilisateurs ? Comment protéger le

système d'exploitation des programmes utilisateurs ? Par protection, on entend ici veiller à ce qu'un

programme donné n'accède pas à une plage mémoire allouée à un autre programme.

● partage des périphériques

Le second rôle du système d'exploitation est de faciliter l'accès à la machine physique pour l'utilisateur de

celle-ci. En effet sur une machine physique brute, pour réaliser par exemple une opération d'entrées

sorties, il est nécessaire de connaître la manière dont le périphérique est géré (DMA, interruption ?). De

même pour faire exécuter un programme, il faut charger celui-ci en mémoire centrale et connaître la

manière dont l'allocation des mots mémoire est gérée. Ceci est fastidieux et compliqué pour l'utilisateur de

la machine. Le système d'exploitation offre à l'utilisateur une interface destinée à masquer les caractéristiques

matérielles. Cette interface est composée d'un ensemble de primitives qui gèrent elles-mêmes les

caractéristiques matérielles sous-jacentes et offrent un service à l'utilisateur. Un utilisateur souhaitant

réaliser une opération d'entrées sorties fait appel à une primitive unique ECRIRE sans se soucier du type

de gestion associée au périphérique. C'est la primitive qui prendra en charge la spécificité du périphérique.

L'ensemble des primitives offertes par le système d'exploitation crée une machine virtuelle au dessus de la

machine physique plus simple d'emploi et plus conviviale. On distingue deux types de primitives : les

appels systèmes et les commandes.

Page 10: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Définition : Système d'exploitationC'est un ensemble de programmes qui réalise l'interface entre le matériel de l'ordinateur et les utilisateurs. Il a deux objectifs principaux :

● construction au-dessus du matériel d'une machine virtuelle plus facile d'emploi et plus conviviale ● prise en charge de la gestion de plus en plus complexe des ressources et partage de celle-ci

Comme son nom le suggère, le système d’exploitation a en charge l'exploitation de la machine pour en faciliter l'accès, le partage et pour l'optimiser

Page 11: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Introduction>Introduction aux systèmes d'exploitation>Fonction du système

1.3 Fonctions d'un système d'exploitation

Le système d'exploitation (figure 6) présente donc comme une couche logicielle placée entre la machine

matérielle et les applications. Il s'interface avec la couche matérielle, notamment par le biais du mécanisme

des interruptions. Il s'interface avec les applications par le biais des primitives qu'il offre : appels système et

commandes. Le système d'exploitation peut être découpé en plusieurs grandes fonctions présentées sur la

figure ci-dessous Dans une première approche, ces fonctions qui seront étudiées plus en détails dans les

chapitres suivants du cours sont :

● Gestion du processeur : le système doit gérer l'allocation du processeur aux différents programmes

pouvant s'exécuter. Cette allocation se fait par le biais d'un algorithme d'ordonnancement qui planifie

l'exécution des programmes. Selon le type de système d'exploitation, l'algorithme d'

ordonnancement répond à des objectifs différents

● Gestion de la mémoire : le système doit gérer l'allocation de la mémoire centrale entre les différents

programmes pouvant s'exécuter (pagination/segmentation). Comme la mémoire physique est

souvent trop petite pour contenir la totalité des programmes, la gestion de la mémoire se fait selon le

principe de la mémoire virtuelle : à un instant donné, seules sont chargées en mémoire centrale, les

parties de code et données utiles à l'exécution

● Gestion des entrées/sorties : le système doit gérer l'accès aux périphériques, c'est-à-dire faire la

liaison entre les appels de haut niveau des programmes utilisateurs (exemple getchar()) et les

opérations de bas niveau de l'unité d’échange responsable du périphérique (unité d’échange clavier)

: c'est le pilote d'entrées/sorties (driver) qui assure cette correspondance

● Gestion de la concurrence : Comme plusieurs programmes coexistent en mémoire centrale, ceux-ci

peuvent vouloir communiquer pour échanger des données. Par ailleurs, il faut synchroniser l'accès

aux données partagées afin de maintenir leur cohérence. Le système offre des outils de

communication et de synchronisation entre programmes

● Gestion des objets externes : La mémoire centrale est une mémoire volatile. Aussi, toutes les

données devant être conservées au-delà de l'arrêt de la machine, doivent être stockées sur

une mémoire de masse ( disque dur, disquette, cédérom...). La gestion de l'allocation des mémoires

de masse ainsi que l'accès aux données stockées s'appuient sur la notion de fichiers et de système

de gestion de fichiers (SGF).

● Gestion de la protection : le système doit fournir des mécanismes garantissant que ses ressource s

(CPU, mémoire, fichiers) ne peuvent être utilisées que par les programmes auxquels les droits

nécessaires ont été accordés. Il faut notamment protéger le système et la machine des programmes

utilisateurs (mode d'exécution utilisateur et superviseur)

Page 12: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Fig 6 : Les grandes fonctions du système d’exploitation

Page 13: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Introduction>Introduction aux systèmes d'exploitation>Classe des systèmes

1.4 Les différentes classes de système

Les systèmes d'exploitation multiprogrammés peuvent être classés selon différents types qui dépendent

des buts et des services offerts par les systèmes. On distingue principalement trois grandes classes de

systèmes

● les systèmes multiutilisateurs interactifs : l'utilisateur est "derrière son clavier et son écran" ; il

soumet des exécutions et attend les résultats : il faut donc réduire au maximum le temps d'attente et

faire croire à l'utilisateur qu'il est seul à utiliser la machine. Ce sont des systèmes adaptés à la mise

au point de programmes (exemple : UNIX, Linux)

● les systèmes à traitements par lots (batch) : les programmes sont exécutés en différé, les uns à la

suite des autres. Ce sont des systèmes dédiés aux travaux de production (exemple : MVS...). On

peut noter que beaucoup de systèmes offrent simultanément un service de temps partagé et un

service de traitement par lots (VMS)

● les systèmes temps réels : les programmes en exécution sont soumis à des contraintes de temps,

c'est-à-dire que leurs exécutions doivent être impérativement achevées à une date butoir appelée

échéance. Comme ces systèmes sont souvent interfacés à un environnement dynamique (procédé)

délivrant des événements synchrones ou asynchrones auxquels ils doivent réagir, on parle aussi de

systèmes réactifs. Ce sont des systèmes adaptés à la commande de procédé (exemple : LynxOS...)

1.4.1 Un peu plus sur les systèmes temps réel

Définition : Application temps réelUne application temps réel est une application pour laquelle le facteur temps est la principale contrainte à respecter. L' application doit fournir un résultat juste mais si celui-ci est fourni hors délai, alors il ne pourra pas être validé.

Il ne s’agit pas par contre, de rendre le résultat le plus vite possible, mais simplement à temps. L’échelle du

temps relative à la contrainte temporelle varie d’une application à l’autre : elle peut être par exemple de

l’ordre de la micro-seconde dans des applications de contrôle radars, mais peut être de l’ordre de l’heure

pour une application de contrôle chimique. Par ailleurs, le système temps réel peut être qualifié de système

embarqué ou enfouis (embedded system). On distingue deux types de contraintes temporelles:

● Les contraintes temporelles strictes : les fautes temporelles (non-respect d’une échéance) sont

intolérables pour la validité du système. Elles mettent en péril le système temps réel lui-même voire

son environnement.

● Les contraintes temporelles relatives : quelques fautes temporelles peuvent être sup port ées sans

remettre en cause la validité du système. Il s'ensuit généralement une baisse de performance du

système.

Page 14: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Un système temps réel se divise en deux parties qui interagissent entre elles (figure 7):

● D’un côté, un procédé, en général industriel, muni de capteurs, envoie des mesures ou déclenche

des événements avec une occurrence périodique ou aléatoire.

● De l’autre côté, un système informatique, connecté au procédé. Ce système informatique

commande et contrôle de manière dynamique le comportement du procédé, en respectant les

contraintes de temps qui lui sont associées. Il réagit dans un temps contraint aux événements et

mesures prélevées sur le procédé : on dit alors que le système est réactif. Le système informatique

se décompose lui-même en deux parties : l'application temps réel proprement dite construite par un

ensemble de tâches, qui sont activées de manière périodique pour réaliser les prises de mesures ou

de manière apériodique pour prendre en compte les événements du procédé. Ces tâches utilisent

les services d'un noyau temps réel appelé généralement exécutif temps réel pour s'exécuter et

utiliser les ressource s matérielles ou périphériques.

Fig 7 : Système temps réel

Exemple d’une application temps réel On considère un procédé chimique d’extraction de matière grasse à l’aide d’un solvant tel que l’hexane piloté par ordinateur. La colonne d’extraction comporte un robinet pour amener le solvant, un capteur de température et un ph-mètre. La colonne de distillation est munie également d’un capteur de température. Les deux dispositifs sont chauffés à l’aide de deux becs bunsen. L’application lit régulièrement les températures de la colonne de distillation et de la colonne d’extraction. Si une température devient anormale, par exemple, trop élevée, l’application doit réagir dans un délai maximum, qui garantit que la réaction chimique peut se poursuivre, pour agir sur le bec bunsen associé (le baisser).

Page 15: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Introduction>Introduction aux systèmes d'exploitation>Notions de base

1.5 Notions de base

Nous révisons ci-dessous quelques notions de base.

1.5.1 Interfaces du système d'exploitation

Définition : Commandes et appels systèmesLes appels systèmes constituent l'interface du système d'exploitation et sont les points d'entrées permettant l'exécution d'une fonction du système. Les appels système sont directement appelables depuis un programme. Les commandes permettent elles d'appeler les fonctions du système depuis le prompt de l'interpréteur de commande (shell, invite du dos).

1.5.2 Modes d'exécutions et Commutations de contexte Lorsqu'un programme utilisateur fait un appel système (ici open), il va exécuter une fonction du système

d'exploitation. Il quitte alors son mode courant d'exécution (mode utilisateur) pour passer dans un mode

d'exécution privilégié qui est le mode d'exécution du système d'exploitation (mode superviseur) : ce mode

est privilégié dans le sens où il donne accès à un plus grand nombre d'instructions machine que le mode

utilisateur (notamment il permet l'exécution des instructions de masquage et démasquage d’interruptions

interdites en mode utilisateur). Ce passage du mode utilisateur au mode superviseur constitue une

commutation de contexte : elles s'accompagne d'une opération de sauvegarde du contexte utilisateur.

Page 16: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Fig 8 : Commutation de contexte

Lorsque l'exécution de la fonction système est achevée, le programme repasse du mode superviseur au

mode utilisateur. Il y a de nouveau une opération de commutation de contexte avec restauration du

contexte utilisateur sauvegardé lors de l'appel système.

Fig 9 : Commutation de contexte

Il y a trois grandes causes de passage du mode utilisateur au mode superviseur :

● le fait que le programme utilisateur appelle une fonction du système. C'est une demande explicite de

passe en mode superviseur.

● l'exécution par le programme utilisateur d'une opération illicite (division par 0, instruction machine

interdite, violation mémoire…) : c'est la trappe. L'exécution du programme utilisateur est alors

arrêtée.

● la prise en compte d'une interruption par le matériel et le système d'exploitation. le programme

utilisateur est alors stoppé et l'exécution de la routine d'interruption associée à l'interruption

survenue exécutée en mode superviseur.

Définition : Mode superviseurLe mode superviseur est le mode d'exécution du système. C'est un mode d'exécution privilégié qui autorise notamment l'appel à des instructions interdites en mode utilisateur (manipulation des interruptions). Ce mode assure la protection du système d'exploitation. Le passage du mode utilisateur vers le mode superviseur est soit provoqué par un appel système, soit par une trappe, soit par l'arrivée d'une interruption

Définition : Commutations de contexte

Page 17: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Le passage entre les modes utilisateur / superviseur s'accompagne de commutations de contexte (sauvegarde du contexte utilisateur - changement de mode d'exécution - restitution du contexte utilisateur)

Page 18: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Introduction>qcm : Un premier test

2 Un premier test

Exercice

Pour en finir avec le chapitre introductif…

Question : Quel est le rôle du système d’exploitation ?

Compiler un programme et construire un exécutable Gérer les accès disque Partager la machine physique entre les différents programmes et bâtir une machine virtuelle plus accessible à l’utilisateur

Réponse :

Partager la machine physique entre les différents programmes et bâtir une machine virtuelle plus accessible à l’utilisateur

Question : Un système est dit réactif

Car il doit offrir le plus petit temps de réponse possible à l’utilisateur Car il doit réagir à des événements délivrés par des périphériques externes Car il est rapide

Réponse :

Car il doit réagir à des événements délivrés par des périphériques externes

Question : Une interruption

Permet de séquencer l’exécution des programmes Est un signal externe au processeur obligeant celui-ci à interrompre son traitement en cours pour traiter la cause de

l’événement

Réponse :

Est un signal externe au processeur obligeant celui-ci à interrompre son traitement en cours pour traiter la cause de l’événement

Question : Une commutation de contexte intervient

à chaque changement de mode d’exécution Sur l’occurrence d’une interruption Lorsque le programme commet une erreur grave

Réponse :

à chaque changement de mode d’exécution

Page 19: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Chapitre 2 : Processus Ce chapitre est consacré à l'étude des processus

Cours : Processus

Notion de processus Rappel sur la chaine de production d'un programme exécutable. L'exécution d'un programme Le processus

Les états du processus Le bloc de contrôle du processus (PCB)

Processus Unix : création, vie et mort Création d'un processus Unix Terminaison d'un processus Unix Les primitives de recouvrement Architecture du système Unix

La notion de processus léger Définition Primitives associées aux processus léger

Table des figures

Page 20: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Processus >Processus

1 Cours : Processus

Nous commençons à présent l'étude du fonctionnement d'un système multiprogrammé. Dans ce cours,

nous nous intéressons à la fonction d'exécution : elle recouvre principalement deux notions : celle de

processus ,qui correspond à l'image d'un programme qui s'exécute et celle d' ordonnancement qui

correspond au problème de l'allocation du processeur et donc du partage du processeur entre différents

processus .

1.1 Notion de processus

1.1.1 Rappel sur la chaine de production d'un programme exécutable. La chaine de production de programme (figure 1) transforme un programme écrit dans un langage de haut

niveau en un programme dit exécutable, écrit en langage machine. Cette transformation s'effectue à l'aide

de plusieurs étapes : L'utilisateur saisit son programme à l'aide de l'éditeur de texte qui crée un fichier sur le

disque que l'on appelle le fichier source. Ce fichier source est ensuite compilé à l'aide d'un compilateur

dépendant du langage de programmation utilisé et dont le rôle est de vérifier que la syntaxe du langage est

respectée et de générer alors l'équivalent du programme source en langage machine : on obtient alors sur

disque le fichier objet. Ce fichier objet est ensuite soumis à l'éditeur de liens dont le rôle est de résoudre les

références externes, c'est-à-dire par exemple, d'associer les appels système inclus dans le programme à

leur adresse dans le système. L'éditeur de liens produit sur disque le fichier exécutable. Lorsque l'utilisateur

demande l'exécution de son programme, le fichier exécutable est alors monté en mémoire centrale : c'est

l'étape de chargement. Le système alloue de la place mémoire pour placer le code et les données du

programme.

Page 21: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Fig 1 : Chaine de production de programme

Exemple sur Unix : Pour le système d'exploitation Unix, les étapes suivantes permettent de passer d'un fichier source à un processus

● saisie du fichier source avec un éditeur tel que vi : programme.c ● compilation et production du fichier programme.o : cc -c programme.c <return> ● édition des liens et production du fichier exécutable programme : ld -o programme programme.o <return> ● chargement et création du processus : programme <return>

1.1.2 L'exécution d'un programme La figure 2 représente l'exécution du programme à un niveau qui est celui de l'architecture matérielle de la

machine. Le programme à exécuter est placé en mémoire centrale à partir de l'emplacement d'adresse

102. Le processeur a commencé l'exécution du programme : la première instruction de celui-ci a été

chargée dans le registre instruction (RI) et le Compteur Ordinal (CO) contient l'adresse de la prochaine

instruction à exécuter soit 103. Lorsque l'instruction courante aura été exécutée, le processeur chargera

dans la registre RI l'instruction pointée par le CO, soit add Imm R1 5 et le compteur ordinal prendra la

valeur 104. L'exécution de l'instruction add Imm R1 5 va modifier le contenu du registre PSW puisque c'est

une instruction arithmétique : les flags de signe, de nullité etc... sont mis à jour. On voit donc qu'à chaque

étape d'exécution du programme, le contenu des registres du compteur ordinal évolue. De même le

contenu de la mémoire centrale peut être modifié : écriture, lecture dans la pile (Push, Pop), modification

des données par une instruction Store. On appelle processus l'image de l'état du processeur et de la

mémoire au cours de l'exécution d'un programme. Le programme est statique et le processus représente la

dynamique de son exécution. Lors de l'étape de chargement, le système alloue de la place mémoire pour

placer le code et les données du programme et crée la pile d'exécution de ce qui est en train de devenir un

Page 22: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

processus. Il alloue alors une structure de donnée descriptive du processus destinée à regrouper toutes les

informations du contexte du processus : le bloc de contrôle du processus. Le processus est alors créé. Il

est prêt à s'exécuter.

Fig 2 : Exécution d'un programme

1.1.3 Le processus

Définition : Processus● Un processus est un programme en cours d'exécution auquel est associé un environnement

processeur (CO, PSW, RSP, registres généraux) et un environnement mémoire appelés contexte du processus.

● Un processus est l'instance dynamique d'un programme et incarne le fil d'exécution de celui-ci dans un espace d'adressage protégé (objets propres : ensemble des instructions et données accessibles)

● Un programme réentrant est un programme pour lequel il peut exister plusieurs processus en même temps

1.1.3.1 Les états du processus Lors de son exécution, un processus est caractérisé par un état :

● lorsque le processus obtient le processeur et s'exécute, il est dans l'état élu. L'état élu est l'état

d'exécution du processus.

● lors de cette exécution, le processus peut demander à accéder à une ressource (réalisation d'une

entrée/sortie, accès à une variable protégée) qui n'est pas immédiatement disponible : le processus

ne peut pas poursuivre son exécution tant qu'il n'a pas obtenu la ressource (par exemple, le

processus doit attendre la fin de l'entrée/sortie qui lui délivre les données sur lesquelles il réalise les

calculs suivants dans son code) : le processus quitte alors le processeur et passe dans l'état bloqué.

L'état bloqué est l'état d'attente d'une ressource autre que le processeur.

Page 23: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

● lorsque le processus a enfin obtenu la ressource qu'il attendait, celui-ci peut potentiellement

reprendre son exécution. Cependant, nous nous sommes placés dans le cadre de systèmes

multiprogrammés, c'est-à-dire qu'il y a plusieurs programmes en mémoire centrale et donc plusieurs

processus.. Lorsque le processus est passé dans l'état bloqué, le processeur a été alloué à un autre

processus. Le processeur n'est donc pas forcément libre. Le processus passe alors dans l'état prêt.

L'état Prêt est l'état d'attente du processeur.

● Le passage de l'état prêt vers l'état élu constitue l'opération d'élection. Le passage de l'état élu vers

l'état bloqué est l'opération de blocage. Le passage de l'état bloqué vers l'état prêt est l'opération de

déblocage.

Un processus est toujours crée dans l'état prêt. Un processus se termine toujours à partir de l'état élu (sauf

anomalie).

Fig 3 : Etats d'un processus

Exemple sous Unix La figure ci-dessous présente le graphe d'états simplifié pour un processus Unix. Un processus évolue entre trois modes au cours de son exécution : le mode utilisateur qui est le mode normal d'exécution, le mode noyau en mémoire qui est le mode dans lequel se trouve un processus prêt ou bloqué (endormi) et le mode swappé qui est le mode dans lequel se trouve un processus bloqué (endormi) déchargé de la mémoire centrale. En effet, le système Unix décharge de la mémoire centrale les processus endormis depuis trop longtemps (ils sont alors dans l'état Endormi swappé). Ces processus réintègrent la mémoire centrale lorsqu'ils redeviennent prêts (transition de l'état prêt swappé vers l'état prêt). Un processus qui se termine passe dans un état dit zombi. Il y reste tant que son PCB n'est pas entièrement démantelé par le système.

Page 24: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Etats d'un processus Unix -graphe simplifié-

1.1.3.2 Le bloc de contrôle du processus (PCB) Le bloc de contrôle d'un processus (PCB) contient les informations suivantes :

● un identificateur unique du processus (un entier) : le PID

● l'état courant du processus (élu, prêt, bloqué)

● le contexte processeur du processus : la valeur du CO, la valeur des autres registres du processeur

● le contexte mémoire : ce sont des informations mémoire qui permettent de trouver le code et les

données du processus en mémoire centrale

● des informations diverses de comptabilisation pour les statistiques sur les performances système

● des informations liées à l' ordonnancement du processus. Le PCB permet la sauvegarde et la

restauration du contexte mémoire et du contexte processeur lors des opérations de commutations

de contexte .

Page 25: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Fig 4 : Bloc de contrôle d'un processus

Exemple sous Unix Nous prenons en exemple la structure du bloc de contrôle de processus dans le système d'exploitation Unix. Le bloc de contrôle du processus est divisé en deux parties :

● chaque processus dispose d'une entrée dans une table générale du système, la table des processus. Cette entrée contient les informations sur le processus qui sont toujours utiles au système quel que soit l'état du processus : l'identificateur du processus (pid), l'état du processus, les informations d'ordonnancement, les informations mémoire, c'est-à-dire l'adresse des régions mémoire allouées au processus.

● chaque processus dispose également d'une autre structure, la Zone U. Cette Zone U contient d'autres informations concernant le processus, mais ce sont des informations qui peuvent être temporairement "swappées" sur le disque.

Page 26: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Bloc de contrôle d'un processus Unix

Page 27: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Processus >Processus>Les processus Unix

1.2 Processus Unix : création, vie et mort

1.2.1 Création d'un processus Unix La primitive FORK permet la création dynamique d'un nouveau processus qui s'exécute de manière

concurrente avec le processus qui l'a créé. Tout processus Unix hormis le processus 0 est crée à l'aide de

cette primitive. Le processus créateur (le père) par un appel à la primitive FORK crée un processus fils qui

est une copie exacte de lui-même (code est données).

Définition : Primitive FORKpid_t fork (void) : permet la création d'un processus Unix par duplication complète du créateur.

Lors de l'exécution de l'appel système Fork, si les ressource s noyaux sont disponibles, le système effectue

les opérations suivantes :

● le système alloue une entrée à la table des processus pour le nouveau processus

● le système alloue un PID unique au nouveau processus

● le système duplique le contexte du processus parent : le code, les données, et la pile

● le système retourne au processus père le PID du processus crée et au nouveau processus (le fils) la

valeur 0.

Lors de cette création le processus fils hérite de tous les attributs de son père sauf :

● l'identificateur de son père

● son propre identificateur

● les temps d'exécution du nouveau processus sont à nuls.

Notamment, le fils hérite de tous les descripteurs de fichiers ouverts par son père et partage donc ces

fichiers avec lui. A l'issue de l'exécution de la primitive Fork, chaque processus, le père et le fils, reprend son exécution

après le Fork. Le code et les données étant strictement identiques, il est nécessaire de disposer d'un

mécanisme pour différencier le com port ement des deux processus après le Fork. On utilise pour cela le

code retour du Fork qui est différent chez le fils (toujours 0) et le père (PID du fils créé). Dans l'exemple de la figure ci-dessous, le processus n°12222 est le processus père. Le processus 12224

est le processus fils crée par le processus de 12222 à l'issue de l'appel à la primitive Fork. Une fois la

primitive Fork exécutée par le père, les deux processus (le père et le fils) reprennent leur exécution de

manière concurrente. Le processus fils a pour retour de la primitive Fork la valeur 0. Il va donc exécuter la

Page 28: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

partie de l'alternative pour laquelle (if pid = = 0) est vrai. Le processus père au contraire a pour retour de la

primitive Fork la valeur du PID du processus créé c'est-à-dire une valeur positive. Il va donc exécuter l'autre

partie de l'alternative. Dans cet exemple, la primitive GETPID retourne à un processus la valeur de son

PID.. La primitive GETPPID retourne au processus le PID de son père.

Exemple de création d'un processus avec la primitive Fork

1.2.2 Terminaison d'un processus Unix Un appel à la primitive EXIT provoque la terminaison du processus effectuant l'appel avec un code retour

valeur. Un processus qui se termine passe dans l'état zombi et reste dans cet état tant que son père n'a

pas pris en compte sa terminaison.

Définition : Primitive EXITvoid exit (int valeur) : provoque la terminaison du processus appelant

Lorsqu'un processus se termine, le système démantèle tout son contexte, sauf l'entrée de la table des

processus le concernant. Le processus père, par un appel à la primitive WAIT, récupère la mort de son fils,

cumule les statistiques de celui-ci avec les siennes et détruit l'entrée de la table des processus concernant

son fils défunt. Le processus fils disparaît complètement. La communication entre le fils zombi et le père

s'effectue par le biais d'un signal transmis du fils vers le père (signal SIGCHLD ou mort du fils). Si le père

n'attend pas la mort de son fils par un appel à la primitive WAIT alors la table des processus n'est pas

libérée et il y a risque de saturation de cette table.

Définition : Primitive WAITpid_t wait (int *status) : attente de la mort du fils

Page 29: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Synchronisation père fils assurant une bonne libération des ressources du système à

la mort du fils.

Un processus fils défunt reste zombi jusqu'à ce que son père ait pris connaissance de sa mort. Un

processus fils orphelin, suite au décès de son père (le processus père s'est terminé avant son fils) est

toujours adopté par le processus numéro 1 (INIT) et non par son grand-père.

1.2.3 Les primitives de recouvrement Il s'agit d'un ensemble de primitives (famille EXEC) permettant à un processus de charger en mémoire, un

nouveau code exécutable. L'exécution d'une de ces primitives EXEC entraîne l'écrasement du code hérité

au moment de la création ( primitive Fork ) par le code exécutable spécifié en paramètre de la primitive.

Des données peuvent être passées au nouveau code exécutable via les arguments de la primitive EXEC.

Définition : Primitives EXECEnsemble de primitives (famille EXEC) permettant à un processus de charger en mémoire, un nouveau code exécutable :

● int execl (const char *ref, const char *arg, ..., NULL) : ref est le chemin d'un exécutable à partir du répertoire courant, const char *arg, ..., NULL est la liste des arguments.

● int execlp (const char *ref, const char arg, ..., NULL) : ref est le chemin d'un exécutable à partir de la variable d'environnement PATH, const char *arg, ..., NULL est la liste des arguments.

● int execv (const char *ref, const char *arg[]) : ref est le chemin d'un exécutable à partir du répertoire courant, const char *arg [] est un tableau contenant les arguments.

● int execvp (const char *ref, const char *arg[]) : ref est le chemin d'un exécutable à partir de la variable d'environnement PATH, const char *arg [] est un tableau contenant les arguments.

L'interface du programme principal d'un exécutable (le main) se présente toujours de la manière suivante :

Page 30: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

int main (int argc, char *argv[], char *arge[]);

● argc est le nombre de composants de la commande

● argv est un tableau de pointeurs de caractères donnant accès aux différentes composantes de la

commande

● arge est un tableau de pointeurs de caractères donnant accès à l'environnement du processus.

Exemple

calcul 3 4

on a argc = 3, argv[0]="calcul",argv[1]="3"

et argv[2] = "4"

Calcul.c

main(argc,argv)

{

int somme;

if (argc <> 3) {printf("erreur"); exit();}

somme = atoi(argv[1]) + atoi(argv[2]); -- atoi : conversion caractère -->

entier

exit();

}

La figure ci-dessous donne un exemple de l'utilisation d'une des primitives EXEC : la primitive EXECL.

Dans l'exemple ci-dessous, le processus fils résultant de l'exécution du Fork va exécuter la partie de

l'alternative pour laquelle PID == 0 est vrai. Dans cette alternative, le processus fils fait appel à la primitive

EXECL en demandant l'exécution d'un programme exécutable qui a pour nom calcul et qui admet pour

paramètre argv[0] = "calcul", argv [1] = "3"et argv[2] ="2". Il faut noter que les arguments sont transmis

comme des chaînes de caractères. C'est pourquoi une conversion de caractère vers entier est nécessaire.

Dans l'exemple donné, cette conversion est réalisée à l'aide de la primitive atoi.

Page 31: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Exemple de programmation avec une primitive de la famille Exec..

1.2.4 Architecture du système Unix Tout le système UNIX repose sur ce concept arborescent de processus père et de processus fils créé par

duplication du processus père puis par recouvrement par un code exécutable spécifique du code hérité du

processus père. Le seul processus Unix qui n'est pas crée par un appel à la primitive Fork puis par un

appel à une des primitives Exec est le premier processus de la machine c'est-à-dire le processus 0. Une

fois créé, le processus 0 crée un deuxième processus, le processus 1 appelé également processus INIT.

Le processus 0 devient alors le processus Swapper c'est-à-dire le processus responsable de swapper hors

de la mémoire centrale les processus utilisateurs endormis depuis trop longtemps. Le processus INIT va

créer à son tour un certain nombre de processus : d'une part les processus DEMONS responsable de

fonctions système telles que la surveillance du réseau (inetd), la gestion de l'imprimante (lpd)..., d'autre part

des processus GETTY chargés de la surveillance des terminaux. Le processus INIT trouve l'ensemble des

processus à créer dans un fichier qui est fichier /etc/inittab. Lorsqu'un utilisateur vient pour utiliser la

machine, il se logue sur un terminal. Le processus GETTY crée alors un nouveau processus, le processus

LOGIN chargé de lire le login de l'utilisateur, son mot de passe et de vérifier dans le fichier /etc/passwd que

l'utilisateur est autorisé à se connecter à la machine. Si l'utilisateur est autorisé à se connecter à la

machine, alors le processus LOGIN va créer à son tour un nouveau processus le processus SHELL c'est-à-

dire l'interpréteur de commande pour le nouvel utilisateur. Cet interpréteur de commandes va prendre en

charge les commandes tapées par l'utilisateur et pour chacune d'elles il va crée un nouveau processus

chargé d'exécuter la commande. Ce processus existera le temps de l'exécution du programme ou de la

commande.

Page 32: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

L'arborescence de processus Unix

Page 33: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Processus >Processus>Les processus légers

1.3 La notion de processus léger

1.3.1 Définition Le processus léger constitue une extension du modèle traditionnel de processus. Un processus classique

est constitué d'un espace d'adressage avec un seul fil d'exécution, ce fil d'exécution étant représenté par

une valeur de compteur ordinal et une pile d'exécution. Un processus léger est un espace d'adressage

dans lequel plusieurs fils d'exécution peuvent évoluer en parallèle. Ces fils d'exécution sont chacun

caractérisés par une valeur de compteur ordinal propre et une pile d'exécution privée.

Définition : Processus légerUn processus léger est un espace d'adressage dans lequel plusieurs fils d'exécution peuvent évoluer en parallèle. Ces fils d'exécution sont chacun caractérisés par une valeur de compteur ordinal propre et une pile d'exécution privée.

Le gros avantage lié à la notion de processus léger est un allègement des opérations de commutations de

contexte : en effet lorsque le processeur commute d'un fil d'exécution à un autre fil et que ces deux fils

appartiennent au même espace d'adressage, alors la commutation est très allégée puisqu'elle consiste

seulement à changer de pile et de valeur de compteur ordinal, le contexte mémoire restant le même. Un

inconvénient majeur est que l'espace d'adressage est complètement partagé entre tous les fils d'exécution

qui évoluent au sein de celui-ci et donc les opérations de synchronisation sont plus souvent requises. .

1.3.2 Primitives associées aux processus léger ● int pthread_create (pthread_t *thread, const pthread_attr_t *attr, void * (*start_routine)(void *), void

*arg) : Création d'un processus léger : un processus léger identifié par l'identificateur *thread est

crée et attaché à l'exécution de la routine (*start_routine)(void *), void *arg).

● nt pthread_join ( pthread_t thread, void **value_ptr) : synchronisation entre processus légers :

permet au processus léger principal d'attendre la terminaison de ses fils.

● int pthread_exit (void **value_ptr) : terminaison de processus léger

La figure ci-dessous donne un exemple d'un programme dans lequel un processus léger principal (le main)

crée 4 autres processus légers qui sont chacun rattachés à la fonction void t_pthread(int i). Chaque

processus léger affiche le pid du processus auquel il appartient (l'espace d'adressage) par l'appel à la

fonction getpid, puis son propre identifiant par l'appel à la fonction pthread_self().

Page 34: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Exemple de programmation avec les processus légers. .

Page 35: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Chapitre 3 : Ordonnancement Ce chapitre est consacré à l'étude de l' ordonnancement

Cours : Ordonnancement de processus

Notions liées à l'ordonnancement de processus Qu'est-ce que l'ordonnancement de processus ? Ordonnancement préemptif ou non préemptif Les entités système responsables de la fonction d'ordonnancement

Les politiques d'ordonnancement Objectifs des politiques d'ordonnancement Les principales politiques d'ordonnancement

Politique d'ordonnancement "premier arrivé, premier servi" (FIFO). Politique d'ordonnancement "plus court d'abord" Politique d'ordonnancement par tourniquet (Round Robin) Politique d'ordonnancement par priorités constantes Politique d'ordonnancement par files de priorités constantes multiniveaux avec ou sans extinction de priorité Les politiques d'ordonnancement sous le système Linux

Ordonnancement sous le système Unix Structures de données liées à l'ordonnancement Algorithmes d'ordonnancement mis en oeuvre

Ordonnancement temps réel Caractéristiques de l'ordonnancement temps réel Les algorithmes d'ordonnancement temps réel

Classification des algorithmes d'ordonnancement temps réel Modélisation de l'application temps réel pour la certification

Les tâches périodiques Les tâches apériodiques

Ordonnancement en ligne préemptifs pour des tâches périodiques indépendantes. L'ordonnancement Rate Monotonic (RM) Ordonnancement Inverse Deadline (ID) Ordonnancement Earliest Deadline (EDF)

Une application temps réel sous RT-Linux Les tâches RT-Linux.

Mise en œuvre. Format d'une application.

L'ordonnancement.

Exercices dirigés

Primitives de recouvrement Exec Exercice 1 Exercice 2 Exercice 3

Page 36: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Ordonnancement>Ordonnancement de processus

1 Cours : Ordonnancement de processus

1.1 Notions liées à l'ordonnancement de processus

1.1.1 Qu'est-ce que l'ordonnancement de processus ? La figure 1 schématise le fonctionnement d'une machine multiprocessus. Plusieurs processus sont

présents en mémoire centrale. P1 est élu et s'exécute sur le processeur. P2 et P4 sont dans l'état bloqué

car ils attentent tous les deux une fin d'entrée/sortie avec le disque. Les processus P3, P5 et P6 quant à

eux sont dans l'état prêt : ils pourraient s'exécuter (ils ont à leur disposition toutes les ressource s

nécessaires) mais ils ne le peuvent pas car le processeur est occupé par P1. Lorsque P1 quittera le

processeur parce qu'il a terminé son exécution, les trois processus P3, P5 et P6 auront tous les trois le droit

d'obtenir le processeur. Mais le processeur ne peut être alloué qu'à un seul processus à la fois : il faudra

donc choisir entre P3, P5 et P6 : c'est le rôle de l' ordonnancement qui élira un des trois processus .

Fig 1 : qu'est-ce l'ordonnancement ?

Définition : OrdonnancementLa fonction d'ordonnancement gère le partage du processeur entre les différents processus en attente pour s'exécuter, c'est-à-dire entre les différents processus qui sont dans l'état prêt. L'opération d'élection consiste à allouer le processeur à un processus.

Page 37: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

1.1.2 Ordonnancement préemptif ou non préemptif La figure 2 reprend le graphe d'états tel qu'il a été vu à la leçon précédente concernant les processus. Une

transition a été cependant ajoutée entre l'état élu et l'état prêt : c'est la transition de préemption. La

préemption correspond à une opération de réquisition du processeur, c'est-à-dire que le processeur est

retiré au processus élu alors que celui-ci dispose de toutes les ressources nécessaires à la poursuite de

son exécution. Cette réquisition port e le nom de préemption .

Fig 2 : Ordonnancement préemptif

Définition : PréemptionSelon si l'opération de réquisition du processeur est autorisée ou non, l'ordonnancement sera qualifié d'ordonnancement préemptif ou non préemptif :

● si l'ordonnancement est non préemptif, la transition de l'état élu vers l'état prêt est interdite : un processus quitte le processeur si il a terminé son exécution ou si il se bloque.

● si l'ordonnancement est préemptif, la transition de l'état élu vers l'état prêt est autorisée : un processus quitte le processeur si il a terminé son exécution , si il se bloque ou si le processeur est réquisitionné.

1.1.3 Les entités système responsables de la fonction d'ordonnancement Le système d'ordonnancement gère une file des processus (bloc de contrôle) prêts et une file des

processus bloqués. La file des processus prêts est classée selon une politique d'ordonnancement qui

assure que le processus en tête de file est le prochain processus à élire au regard de la politique mise en

oeuvre. Ce classement est effectué par l' ordonnanceur et est réactualisé à chaque fois qu'un processus

est préempté ou qu'un processus est débloqué (ajout d'un nouvel élément prêt). C'est le répartiteur qui élit

le processus en tête de file, c'est-à-dire qui lui alloue un processeur libre. La figure montre le cas d'un

système multiprocesseur ; le répartiteur doit alors choisir quel processeur allouer. Dans un système

monoprocesseur, cette question ne se pose évidemment pas et souvent alors, ordonnanceur et répartiteur

Page 38: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

sont confondus. Une fois élu par le répartiteur, le processus s'exécute. S'il quitte le processeur, sur

préemption, il réintègre la file des processus prêts ; au contraire, s'il quitte le processeur sur un blocage, il

intègre la file des processus bloqués. Dans les deux cas, un nouveau processus est élu (la tête de file des

processus prêts). Plus précisément, la file d'attente des processus bloqués est souvent organisée en files

multiniveaux, chaque niveau correspondant à une attente d'un événement/ ressource particulier auquel

peut être éventuellement associé une priorité. Dans le cas où des événements se produisent

simultanément, cette priorité sert alors à déterminer quel événement doit être traité en premier.

Fig 3 : Ordonnanceur et répartiteur

Définition : OrdonnanceurL'ordonnanceur est un programme système dont le rôle est d'allouer le processeur à un processus prêt.

Page 39: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Ordonnancement>Ordonnancement de processus>Algorithmes d'ordonnancement

1.2 Les politiques d'ordonnancement

Définition : Politique d'ordonnancementLa politique d'ordonnancement détermine le choix d'un processus à élire parmi tous ceux qui sont prêts.

1.2.1 Objectifs des politiques d'ordonnancement Les buts de l' ordonnancement diffèrent en fonction du type de systèmes.

● Systèmes de traitements par lots. Le but est de maximiser le débit du processeur ; c'est-à-dire le

nombre moyen de processus traités par unité de temps.

● Systèmes interactifs. Les buts principaux de l'ordonnancement sont premièrement de maximiser le

taux d'occupation du processeur, c'est-à-dire le rap port entre le temps où le processeur est actif et

le temps total. En théorie, ce taux peut varier entre 0% et 100 % ; dans la pratique, on peut observer

un taux d'occupation variant entre 40 % et 95 %. Deuxièmement, on va chercher à minimiser le

temps de réponse des processus, c'est-à-dire la durée séparant l'instant de soumission du

processus au système de la fin d'exécution du processus. Au mieux, le temps de réponse peut être

exactement égal au temps d'exécution du processus, lorsque le processus a immédiatement été élu

et s'est exécuté sans être préempté.

● Systèmes temps réel. Le but principal est d'assurer le respect des contraintes de temps liées aux

processus.

1.2.2 Les principales politiques d'ordonnancement Nous décrivons ci-dessous les politiques mises en oeuvre dans des systèmes interactifs ou à traitements

par lots. Les politiques spécifiques aux systèmes temps réel sont abordées dans la leçon suivante.

1.2.2.1 Politique d'ordonnancement "premier arrivé, premier servi" (FIFO).

C'est une politique à l'ancienneté, sans réquisition ; l'unité centrale est allouée selon l'ordre de soumission

des processus. Dans cette politique, des processus de faible temps d'exécution peuvent être pénalisés

parce qu'un processus de longue durée les précède dans la file.

Page 40: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Fig 4 : Politique Premier Arrivé, Premier Servi

1.2.2.2 Politique d'ordonnancement "plus court d'abord"

Cette politique tente de remédier à l'inconvénient mentionné pour la politique précédente. Maintenant,

l'unité centrale est allouée au processus de plus petit temps d'exécution. Cette politique est également une

politique sans réquisition. Elle a la propriété de minimiser le temps de réponse moyen pour l'ensemble des

algorithmes d'ordonnancement sans réquisition. Elle pénalise les travaux longs. Elle impose également

d'estimer la durée des processus ce qu'on ne connaît pas habituellement. Il existe une version avec

réquisition de cette politique appelée "temps restant le plus court d'abord" : dans ce cas, le processus en

exécution restitue le processeur lorsqu'un nouveau processus de temps d'exécution inférieur à son temps

d'exécution restant devient prêt.

1.2.2.3 Politique d'ordonnancement par tourniquet (Round Robin)

On définit une tranche de temps appelée quantum qui peut varier de 10 ms à 100 ms. Chaque processus

présent dans la file des processus prêts acquiert le processeur à tour de rôle, et ce pour au maximum un

temps égal au quantum de temps. Si le processus a terminé son exécution avant la fin du quantum, il libère

le processeur et le processus suivant dans la file des processus prêts est élu. Si le processus n'a pas

terminé son exécution avant la fin du quantum, il perd le processeur et est réinséré en fin de file des

processus prêts. Cette politique du tourniquet est usuellement utilisée dans les systèmes en temps partagé.

Sa performance dépend largement de la taille du quantum. Un quantum trop grand augmente les temps de

réponse alors qu'un quantum trop petit multiplie les commutations de contexte jusqu'à les rendre non

négligeables.

Page 41: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Fig 6 : Politique par tourniquet

1.2.2.4 Politique d'ordonnancement par priorités constantes

Un niveau de priorité constant est affecté à chaque processus et à un instant donné, le processus élu est

toujours celui de plus forte priorité. Cet algorithme présente une version sans réquisition et une version

avec réquisition. Le défaut de cette politique est le risque de famine encouru par le processus de faible

priorité. Une solution à ce problème est de faire "vieillir" la priorité des processus en attente, c'est-à-dire

d'augmenter celle-ci en fonction du temps d'attente. La priorité des processus devient ainsi variable.

Complément : La famine est la situation où un processus ne peut disposer d'une ressource qu'il demande (ici le processeur)

Page 42: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Fig 7 : Politique par priorités constantes

1.2.2.5 Politique d'ordonnancement par files de priorités constantes multiniveaux avec ou sans extinction de priorité Les politiques présentées jusqu'à présent utilisent une seule file d'attente des processus prêts. On choisit

ici de définir plusieurs files de processus prêts, chaque file correspondant à un niveau de priorité ; on peut

alors avoir n files de priorités différentes variant de 0 à n-1. Dans une file donnée, tous les processus ont la

même priorité et sont servis soit selon une politique à l'ancienneté sans préemption , soit selon une

politique de tourniquet. Le quantum peut être différent selon le niveau de priorité de la file. L' ordonnanceur

sert d'abord tous les processus de la file de priorité n, puis ceux de priorité n-1 dès que la file de niveau n

est vide et ainsi de suite...

On peut définir deux variantes de l'algorithme, fonction de l'évolution de la priorité des processus :

● les priorités des processus sont constantes tout au long de leur exécution. A ce moment-là, un

processus en fin de quantum est toujours réinséré dans la même file d'attente, celle correspondant à

son niveau de priorité.

● les priorités des processus évoluent dynamiquement en fonction du temps de service dont a

bénéficié le processus. Ainsi un processus de priorité n, à la fin du quantum de la file n, si il n'a pas

terminé son exécution, n'est pas réinséré dans la file de priorité n, mais dans la file de priorité n-1. Et

ainsi de suite... On cherche ici à minimiser les risques de famine pour les processus de faible priorité

en faisant petit à petit baisser la priorité des processus de plus forte priorité.

Page 43: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Fig 8 : Politique par priorités multiniveaux avec ou sans extinction de priorité

1.2.2.6 Les politiques d'ordonnancement sous le système Linux Il existe trois politiques (classes) d' ordonnancement dans Linux qui correspondent aux politiques définies

dans la norme pour les systèmes d'exploitation ouverts, POSIX.: deux sont dites temps réel, SCHED_FIFO

et SCHED_RR, la troisième est classique SCHED_OTHER. On peut attribuer un processus à l'une de ces

trois classes grâce à l'appel système : sched_setscheduler(). Comme dans les systèmes Unix, la politique

d'ordonnancement SCHED_OTHER de Linux a plusieurs objectifs : avoir de bons temps de réponses,

assurer une bonne capacité de traitement et essayer de répartir équitablement le temps CPU entre les

différents processus. Elle correspond à ce qu'on appelle la politique "en temps partagé". Chaque processus

Linux a une priorité variable qui dépend de nombreux facteurs parmi lesquels nous pouvons citer le taux

d'utilisation de la CPU et l'intensité des entrées / sorties.

Il existe deux autres classes d'ordonnancement dites temps réel : SCHED_FIFO et SCHED_RR.Les

rubriques "policy" et "rt_priority" stockées dans la structure des processus Linux permettent de leur attribuer

une classe d'ordonnancement et une priorité temps réel. SCHED_FIFO et SCHED_RR ne sont pas des

classes temps réel strictes, mais relatives. Quand un processus temps réel est prêt, tous les processus de

priorité inférieure sont mis de côté. Un processus de la classe SCHED_FIFO peut s'exécuter jusqu'à la fin

de son traitement ou jusqu'à ce qu'un processus de la classe SCHED_FIFO de priorité temps réel plus forte

soit prêt. Un processus de la classe SCHED_RR est interrompu à la fin de son quantum de temps ou dès

qu'un processus de priorité temps réel (SCHED_FIFO ou SCHED_RR) supérieure est prêt. Il existe dans

cette classe un principe de tourniquet parmi les processus de même priorité.

Page 44: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Ordonnancement>Ordonnancement de processus>Un petit test ordonnancement

Exercice

Quelques questions d'ordonnancement

Question : Soient trois processus A, B, C soumis dans cet ordre de temps d'exécution respectif 4, 2, 9. Quelles propositions

sont exactes ?

Avec une politique FIFO, l'ordre d'exécution est C, B puis A Avec une politique Plus Court d'Abord, l'ordre d'exécution est B, A, puis C Avec une politique par priorité fixe, l'ordre d'exécution est A, B, puis C

Réponse :

Avec une politique Plus Court d'Abord, l'ordre d'exécution est B, A, puis C

Question : Le processus A de priorité 5 s'exécute. Le processus B de priorité 7 se réveille. Quelles sont les propositions

justes ?

B interrompt l'exécution de A car B est plus prioritaire et l'ordonnancement est préemptif A continue son exécution car il est plus prioritaire et l'ordonnancement est préemptif A continue son exécution car l'ordonnancement est non préemptif B interrompt l'exécution de A car B est plus prioritaire et l'ordonnancement est non préemptif

Réponse :

A continue son exécution car il est plus prioritaire et l'ordonnancement est préemptif ou A continue son exécution car l' ordonnancement est non préemptif

Question : Un processus réentrant est :

Un programme exécutable lancé plusieurs fois en parallèle Un processus qui a terminé son exécution Un programme exécutable qui modifie son code.

Réponse :

un programme exécutable lancé plusieurs fois en parallèle

Page 45: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Ordonnancement>Ordonnancement de processus>Ordonnancement sous Unix

1.3 Ordonnancement sous le système Unix

Les opérations relatives à l' ordonnancement sont réalisées par le système Unix à chaque fois qu'un

processus s'apprête à passer du mode système au mode utilisateur, donc à la suite de l'occurrence soit

d'une interruption, d'un appel système ou d'une trappe. La politique d'ordonnancement du système Unix est

une politique à multiples niveaux de priorité avec quantum de temps. Le noyau recalcule la priorité d'un

processus quand il passe du mode noyau au mode utilisateur. Dans une première approche, ce calcul

permet une extinction de priorité des processus qui s'exécutent de manière à permettre l'exécution de tous

les processus et à éviter les problèmes de famine

1.3.1 Structures de données liées à l'ordonnancement La priorité de chaque processus est codée dans l'entrée de la table des processus qui lui correspond. La

plage des priorités des processus est partitionnée en deux sous-ensembles. Chaque ensemble contient

plusieurs niveaux de priorités et à chaque niveau de priorité est associé une file d'attente (fig 9) :

Fig 9 : Files de priorités pour l'ordonnancement sous Unix

● les priorités utilisateurs : ce sont les processus préemptés par l' ordonnanceur au moment de leur

retour en mode utilisateur. Un processus qui se réveille quitte la priorité noyau pour réintégrer les

Page 46: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

priorités utilisateur. La procédure de traitement de l'interruption horloge ajuste les priorités des

processus en mode utilisateur toutes les secondes et fait entrer le noyau dans son algorithme

d'ordonnancement pour éviter qu'un processus monopolise l'unité centrale

● les priorités noyau : ce sont des processus endormis en attente d'un événement. La file où le

processus s'endort est fonction de l'événement attendu et la priorité correspond à une "préférence"

sur les réveils suite à un événement. Un processus endormi en priorité noyau demeure toujours

dans la file où il s'est endormi

1.3.2 Algorithmes d'ordonnancement mis en oeuvre L'algorithme mis en œuvre s'appuie sur la routine d'interruption horloge qui fait régulièrement passer le

processus en mode noyau. Plus précisément :

● A chaque interruption horloge, le système incrémente de une unité la valeur du champ "utilisation du

processeur" pour le processus élu.

● Toutes les secondes c'est-à-dire toutes les 50 à 100 interruptions horloge, la valeur du

champ "utilisation du processeur" pour tous les processus est divisée par deux. Puis la priorité des

processus est recalculée selon la formule suivante : priorité du processus = Utilisation du processeur

/2 + (priorité de base du niveau utilisateur) Du fait de ce recalcul des priorités; les processus se

déplacent dans les files de priorité

Un exemple de mise en œuvre On considère trois processus A, B et C qui ont chacun une priorité initiale de 60. La priorité de base du niveau utilisateur est également la priorité 60. L'interruption horloge se déclenche 60 fois par seconde. A l'instant 0, le processus A est élu. Chaque seconde, l'interruption horloge survient et le champ "utilisation du processeur " (compte UC sur la figure 10) est incrémenté de une unité. Au bout de 60 secondes (instant t = 1), la priorité des processus est recalculée. Du fait que les processus B et C ne se sont pas encore exécutés leur priorité est inchangée (le compte UC est nul pour ces processus). La priorité du processus A par contre baisse et devient égale à 75 (60 + 30/2). C'est donc le processus B qui est à présent élu. A l'instant 2, de nouveau les priorités sont recalculées. Le compte UC du processus C étant toujours nul , la priorité de ce processus n'est pas modifiée. Les priorités du processus A et B par contre évoluent et deviennent respectivement égales à 67 pour le processus A (60 + 15/2) et à 75 pour le processus B. On voit à présent que le processus A a une priorité qui remonte du fait qu'il n'a pas pu utiliser l'unité centrale.

Page 47: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Fig 10 : Exemple de mise en œuvre de l'ordonnancement sous Unix

Page 48: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Ordonnancement>Ordonnancement de processus>Ordonnancement temps réel

1.4 Ordonnancement temps réel

1.4.1 Caractéristiques de l'ordonnancement temps réel Dans un système temps réel, le but principal de l'ordonnancement est de permettre le respect des

contraintes temporelles associées à l'application et aux tâches. Chaque tâche possède un délai critique qui

est le temps maximal dont elle dispose pour s'exécuter depuis sa date de réveil. La date butoir résultante

est appelée échéance. Le dépassement d'une échéance est appelé faute temporelle. Beaucoup

d'applications temps réel sont des applications embarquées et critiques et il est nécessaire de certifier l'

ordonnancement réalisé, c'est-à-dire de vérifier avant le lancement de l'application (hors ligne) le respect

des contraintes temporelles. Cette certification s'effectue à l'aide de tests d'acceptabilité qui prennent en

compte les paramètres temporels des tâches et notamment les temps d'exécutions des tâches. Il faut donc

pouvoir connaître ces temps d'exécutions et surtout pouvoir les borner. Pour cela l'exécutif doit être

déterministe. Les tests d'acceptabilité sont rarement des conditions nécessaires et suffisantes. On établit

donc soit des conditions nécessaires, soit des conditions suffisantes. Les tests d'acceptabilité utilisent les

temps d'exécution maximum des tâches pour pouvoir certifier les exécutions. Il faut donc être capable de

calculer ces temps d'exécutions maximums. Pour cela, le sup port d'exécution, l'exécutif temps réel, doit

être déterministe. Un exécutif temps réel déterministe est un exécutif pour lequel les temps de certaines

opérations système et matérielles élémentaires peuvent être bornés : temps de commutation, temps de

prise en compte des interruptions, etc...

1.4.2 Les algorithmes d'ordonnancement temps réel

1.4.2.1 Classification des algorithmes d'ordonnancement temps réel L'ordonnancement peut être en ligne ou hors ligne. Un ordonnancement hors ligne établit avant le

lancement de l'application une séquence fixe d'exécution des tâches à partir de tous les paramètres de

celles-ci. Cette séquence est rangée dans une table et exécutée en ligne par un automate. Avec un

ordonnancement en ligne, la séquence d'exécution des tâches est établie dynamiquement par

l'ordonnanceur au cours de la vie de l'application en fonction des événements qui surviennent (réveils des

tâches, blocage, etc...). Dans ce dernier cas, l' ordonnanceur choisit le prochaine tâche à élire en fonction

d'un critère de priorité.

1.4.2.2 Modélisation de l'application temps réel pour la certification On distingue deux types de tâches pour la modélisation de l'application :

● les tâches périodiques : Elles correspondent aux mesures sur le procédé ; elles se réveillent

Page 49: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

régulièrement (toutes les P unités de temps)

● les tâches apériodiques : Elles correspondent aux événements ; elles se réveillent de manière

aléatoire

Les tâches périodiques

On distingue :

● périodiques strictes : contraintes temporelles dures à respecter absolument

● périodiques relatives : contraintes temporelles molles qui peuvent être non respectées de temps à

autre (sans échéance)

● périodiques à échéance sur requête (délai critique = période)

Une tâche périodique Tp (r0, C, R, P) est caractérisée par les paramètres temporels suivants avec 0 ???C

?????R ????P :

● r0, sa date de premier réveil

● P, sa période

● rk, la date de réveil de la kème requête, rk = r0 + kP

● C, son temps d'exécution maximum

● R, son délai critique et dk, sa date d'échéance qui est telle que échéance d = rk + R

● C(t) : le temps d'exécution restant à t

● R(t) : le délai critique dynamique c'est-à dire le temps restant à t jusqu'à d.

Une tâche périodique relative n'a pas de paramètre R défini. Une tâche périodique à échéance sur requête

est une tâche pour laquelle R = P.

Page 50: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Fig 11 : Modèle de tâche périodique

Les tâches apériodiques

On distingue :

● apériodiques strictes : contraintes temporelles dures à respecter absolument

● apériodiques relatives : contraintes temporelles molles qui peuvent être non respectées de temps à

autre (sans échéance)

Une tâche apériodique Tap (r0, C, R) est caractérisée par les paramètres temporels suivants avec 0 ???C

????R :

● r, sa date de réveil

● C, son temps d'exécution maximum

● R, son délai critique et dk, sa date d'échéance qui est telle que échéance d = rk + R

● C(t) : le temps d'exécution restant à t

● R(t) : le délai critique dynamique c'est-à dire le temps restant à t jusqu'à d.

Une tâche apériodique relative n'a pas de paramètre R défini.

Fig 12 : Modèle de tâche apériodique

1.4.2.3 Ordonnancement en ligne préemptifs pour des tâches périodiques indépendantes. Nous ordonnançons un ensemble de tâches périodiques (configuration). Les priorités affectées aux tâches

Page 51: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

sont soit constantes (évaluées hors ligne et fixes par la suite), soit dynamiques (elles changent dans la vie

de la tâche) L'ordonnancement d'un ensemble de tâches périodiques est cyclique et la séquence se répète

de manière similaire sur ce que l'on appelle la période d'étude. Pour un ensemble de tâches à départ

simultanée (t = 0), la période d'étude est : [0, PPCM(Pi)]

L'ordonnancement Rate Monotonic (RM) Avec cet algorithme, la priorité d'une tâche est fonction de sa période, de telle sorte que la tâche de plus

petite période est la tâche la plus prioritaire. Pour un ensemble de tâches à échéance sur requête, le test

d'acceptabilité d'une configuration de n tâches est (condition suffisante) donné sur la figure qui suit. La

figure ci-dessous donne un exemple pour trois tâches périodiques à échéance sur requête, Tp1(r0=0, C=3,

P=20), Tp2(r0=0, C=2, P=5) et Tp3(r0=0, C=2, P=10). La tâche la plus prioritaire est la tâche Tp2 et la

tâche la moins prioritaire est la tâche Tp1. La séquence est décrite sur la période d'étude, soit l'intervalle [0,

20]. Les trois tâches respectent leurs contraintes temporelles. La condition suffisante est vérifiée ; on a

: 3/20 + 2/5 + 2/10 = 0.75 < 0.77

Fig 13 : Ordonnancement Rate Monotonic

Ordonnancement Inverse Deadline (ID) Avec cet algorithme, la priorité d'une tâche est fonction de son délai critique. La tâche la plus prioritaire est

la tâche de plus petit délai critique. Cet algorithme constitue une généralisation de l'algorithme Rate

Monotonic à des tâches quelconques. Une condition suffisante d'acceptabilité de tâches est donné sur la

figure qui suit : La figure ci-dessous donne un exemple pour trois tâches périodiques Tp1(r0=0, C=3, R=7,

P=20), Tp2(r0=0, C=2, R=4, P=5) et Tp3(r0=0, C=2, R=9, P=10). La tâche la plus prioritaire est la tâche

Tp2 et la tâche la moins prioritaire est la tâche Tp3. La condition suffisante n'est pas vérifiée ; en effet on a :

Page 52: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

3/7 + 2/4 + 2/9 = 1.14 > 1. Mais le chronogramme construit sur la période d'étude de la configuration prouve

que l' ordonnancement des trois tâches s'effectue sans faute temporelle.

Fig 14 : Ordonnancement Inverse Deadline

Ordonnancement Earliest Deadline (EDF) La priorité maximale à l'instant t est accordée à la tâche dont l'échéance est la plus proche. La priorité des

tâches est maintenant dynamique. La figure ci-dessous donne un exemple pour trois tâches périodiques

Tp1(r0=0, C=3, R=7, P=20), Tp2(r0=1, C=2, R=4, P=5) et Tp3(r0=0, C=1, R=8, P=10). À l'instant t=0, les

trois tâches sont réveillées et la tâche pour laquelle l'échéance est la plus proche est la tâche Tp2, qui donc

s'exécute. À l'instant t=2, la tâche Tp2 a terminé son exécution et c'est maintenant la tâche Tp1 qui est la

plus prioritaire. À l'instant t=5, la tâche Tp1 se termine et la tâche Tp2 se réveille de nouveau. Mais, c'est

maintenant la tâche Tp3 pour laquelle la date d'échéance est à t=8 qui est devenue la plus prioritaire. C'est

donc elle qui s'exécute. On voit donc que contrairement à ce qui se passe avec les algorithmes à priorité

fixe où les priorités des tâches sont calculées une fois pour toutes à l'initialisation du système, ici les

priorités des tâches évoluent les unes par rapport aux autres en fonction de leur urgence. Ainsi, à l'instant

t=0, la tâche Tp2 est plus prioritaire que la tâche Tp3, mais le rap port d'urgence est inversé à l'instant t=5.

Page 53: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Fig 15 : Ordonnancement Earliest Deadline

Page 54: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Ordonnancement>Ordonnancement de processus>LinuxRT

1.5 Une application temps réel sous RT-Linux

Le système RT-Linux est une extension du système Linux classique vers le temps réel. Il est constitué par

un noyau temps réel Rt-Linux qui partage le processeur avec le noyau de base Linux et exécute des tâches

temps réel.

1.5.1 Les tâches RT-Linux.

1.5.1.1 Mise en œuvre. Les tâches RT-Linux s'exécutent dans l'espace adresse du noyau Linux au même titre que l' ordonnanceur

temps réel. Il existe dans les versions récentes de Linux la possibilité de charger dynamiquement des

modules dans l'espace adresse du noyau et de les "lier" au code du noyau. On parle de "modules

chargeables". En effet, pour limiter la taille du noyau Linux et libérer ainsi plus de place mémoire pour

l'utilisateur, on évite de compiler le noyau avec des composants qui ne sont pas nécessaires à tout

moment. Ces composants sont par exemple des gestionnaires de périphériques. Lorsqu'on a besoin

d'ajouter ou de supprimer l'un de ces composants, on n'a plus besoin de recompiler tout le noyau comme

cela était le cas auparavant: le composant est chargé ou déchargé dynamiquement à l'aide d'un module. Il

faut simplement configurer le noyau avant sa compilation pour qu'il accepte de gérer les modules

chargeables: Ainsi, les tâches RT-Linux sont créées à l'aide d'un "module chargeable". Il y a plusieurs avantages à mettre les tâches dans l'espace adresse du noyau:

● Elles partagent le même espace adresse.

● Comme elles sont dans l'espace adresse du noyau, on élimine la charge occasionnée par tout

changement de niveau de protection.

● Un avantage plus pratique que performant. Dans l'espace adresse du noyau, il est permis de faire

référence aux objets et aux fonctions par leur nom plutôt que par leur descripteur. C'est l'édition de

lien dynamique des "modules chargeables" qui résoudra les symboles en adresse.

En conclusion cette solution, qui consiste à mettre les tâches temps réel dans l'espace adresse du noyau

permet d'optimiser les performances de RT-Linux. En contrepartie, cette solution com port e un risque :

Un "Bug" dans une tâche temps réel peut mettre en danger tout le système. Ce risque est renforcé avec

l'utilisation du langage C, notamment les pointeurs. La seule garantie à ce niveau est la rigueur du

programmeur.

Page 55: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Le mode opératoire consiste en général à créer toutes les tâches lors du chargement du module (fonction

init_module()) et à les supprimer uniquement au déchargement (fonction cleanup_module). La structure

"rt_task_struct" (ou RT_TASK par " typedef struct rt_task_struct RT_TASK ;") des tâches temps réel dans

RT-Linux est la suivante :

● int *stack ; /* hardcoded */

● int uses_fp ; /* this one is too*/

● int magic ;

● int state; Etat de la tâche : RT_TASK_READY,...

● int *stack_bottom ; Pointeur de pile.

● int priority ; Politique d' ordonnancement basée sur la priorité seule.

● RTIME period; P : Période si la tâche est périodique.

● RTIME resume_time; r : Prochaine heure de réveil .

● struct rt_task_struct *next; Chaînage simple de l'ensemble des tâches créées dans l'application.

● RTL_FPU_CONTEXT fpu_regs ;

Les fonctions de gestion des tâches temps réel dans RT-Linux sont listées ci-dessous. Les états évoqués

sont présentés dans les figures qui suivent.

● int rt_task_init (RT_TASK *task, void (fn)(int data), int data, int stack_size, int priority) : Création

d'une tâche RT_Linux pointée par "task". Il y a réservation d'espace mémoire dans le noyau

(kmalloc) à hauteur de stack_size. "fn" est le code exécuté par la tâche. "data" est le paramètre

passé à "fn" au démarrage. "priority" est la priorité de la tâche dont la valeur peut aller de 1, la plus

forte priorité, à "RT_LOWEST_PRIORITY" (1000000), la plus faible.

● int rt_task_delete (RT_TASK *task) : Suppression logique d'une tâche : Pas de libération de l'espace

mémoire "kfree" à ce niveau. Son état passe à RT_TASK_ZOMBIE.

● int rtl_delete_zombies (void) : Suppression réelle de l'ensemble des tâches logiquement supprimées

par "rt_task_delete". "kfree" est effectué ici.

● int rt_task_make_periodic (RT_TASK *task, RTIME start_time, RTIME period) : Rend la tâche "*task"

périodique avec une période "period" à partir de la première date de réveil "start_time".

● int rt_task_wait (void) : Suspension de la tâche périodique jusqu'à sa prochaine date de réveil. Elle

met la tâche qui l'a appelée dans l'état RT_TASK_DELAYED. En général toute tâche périodique

indique par cette fonction qu'elle a terminé son traitement : ce doit donc être la dernière instruction

du code de la tâche. Les tâches apériodiques ne s'en servent pas car l'état RT_TASK_DELAYED ne

leur correspond pas.

● int rt_task_wakeup (RT_TASK *task) : Déclenche la tâche "task", c'est-à-dire la met dans l'état prêt

RT_TASK_READY.

● int rt_task_suspend (RT_TASK *task) : Inactive la tâche "task", c'est-à-dire la met dans l'état

RT_TASK_DORMANT. En général, cette routine est utilisée par les tâches apériodiques à la place

de rt_task_wait pour signaler la fin de leur traitement. Une tâche périodique ou apériodique peut être

Page 56: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

suspendue par une autre tâche ou par l' ordonnanceur .

Les figures jointes donnent les graphes d'état des tâches périodiques et apériodiques pour Rt-Linux

Tâches périodiques.

Tâches apériodiques.

Page 57: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

1.5.1.2 Format d'une application. La déclaration des tâches périodiques et apériodiques dans RT-Linux se fait à l'aide d'un module noyau

dont nous présentons le squelette ci-dessous : On suppose que ce module s'appelle RT_PROCESS.C et qu'il va créer deux tâches : T1 et T2. T1 est une

tâche périodique et T2 apériodique. Le module s'installe dans le noyau par : insmod RT_PROCESS.O On le décharge du noyau par : rmmod

RT_PROCESS

#define MODULE

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/version.h>

#include <linux/errno.h>

#include <rtl_sched.h >

#include <linux/arch/i386/kernel/irq.h>

#define NTASKS 2

RT_TASK tasks[NTASKS]

Fonction 1 /*-- Fonction 1 = traitement de la tâche périodique 1 --*/ Code de la fonction exécutée par la tâche périodique 1; l'affectation de cette fonction à la tâche 1 est faite par rt_task_init (Cf. plus bas). Toute tâche périodique doit terminer son traitement par l'appel à la fonction rt_task_wait(). Cette routine met la tâche dans l'état RT_TASK_DELAYED, réinitialise la prochaine date de réveil et fait appel à l'ordonnanceur rtl_schedule().

void f_tâche_1(int par1) {

Page 58: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

rt_task_wait() ; } Fonction 2 /*-- Fonction 2 = traitement de la tâche apériodique 2 --*/ Code de la fonction exécutée par la tâche apériodique 2. Toute tâche apériodique doit terminer son traitement par l'appel à la fonction rt_task_suspend() sur elle-même. Cette routine met la tâche dans l'état RT_TASK_DORMANT et fait appel à l'ordonnanceur rtl_schedule(). void f_tâche_2(int par2) { rt_task_suspend(&(tasks[2])) ; } Interruption Tache 2 /*-- Gestionnaire de l'interruption associée à la tâche 2 --*/ La tâche apériodique 2 est associée à un niveau d'interruption dont l'apparition active le gestionnaire T2_Handler(). L'association entre le numéro d'interruption et le gestionnaire sera vue plus bas. Ce gestionnaire a au moins pour rôle de réveiller la tâche apériodique concernée. Cette fonction met la tâche dans l'état RT_TASK_READY et appelle l'ordonnanceur. int T2_handler() { rt_task_wakeup(&(tasks[2])) ; } /*-- Ordres lancés à l'installation du module: RT_PROCESS --*/ Lorsqu'un module est chargé sous Linux (>insmmod RT_Process.o), il commence par effectuer la fonction init_module(). Cette fonction doit donc figurer dans tous les modules chargeables. Dans RT-Linux, on lui attribue le rôle de l'initialisation des tâches temps réel (rt_task_init), de l'initialisation des périodes et des dates de réveil pour les tâches périodiques (rt_task_make_periodic) et de l'association des niveaux d'interruptions aux gestionnaires associés (request_RTirq). int init_module(void) {

Page 59: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

rt_task_init(&(tasks[0]), f_tâche_1, 0, 3000, 4) ; rt_task_init(&(tasks[1]), f_tâche_2, 1, 3000, 5) ; rt_task_make_periodic(&(tasks[0]), 5, 10) ; request_RTirq(2, &T2_handler) ; return 0 ; } /*-- Ordres lancés à la suppression du module: RT_PROCESS --*/ void cleanup_module(void) Lorsqu'un module est déchargé sous Linux, il exécute systématiquement la fonction cleanup_module(). Cette fonction doit donc figurer dans tous les modules chargeables. Dans RT-Linux, on lui attribue le rôle de suppression des tâches temps réel (rt_task_delete) ainsi que celui de la libération des interruptions (free_Rtirq). { rt_task_delete(&(tasks[0])) ; rt_task_delete(&(tasks[1])) ; free_Rtirq(2) ; }

1.5.2 L'ordonnancement. Dans la version 1.2 de RT-Linux, présentée sur le site officiel du système, trois ordonnanceur s préemptifs

ont déjà été installés :

● Un ordonnanceur "à priorité fixe" : il s'appuie sur le paramètre "priority" défini au niveau de chacune

des tâches. Lorsque plusieurs tâches sont prêtes, celle qui a la priorité la plus forte (la valeur la plus

petite) est élue. Une tâche qui se réveille (RT_TASK_READY) pourra préempter la tâche active si sa

priorité est plus forte.

● Un ordonnanceur "Rate-Monotonic" : nous ne nous y intéresserons pas, compte-tenu des limites de

cette politique vis-à-vis des tâches apériodiques.

● Un ordonnanceur "EDF" ("Earliest Deadline First") : Il utilise l'urgence comme critère de sélection de

la prochaine tâche à élire.

Page 60: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Ordonnancement>Exercices dirigés

2 Exercices dirigés

2.1 Primitives de recouvrement Exec

Préambule : rappel du format des primitives de la famille exec

2.1.1 Exercice 1 Ecrivez un programme où un processus crée un fils, ce fils exécutant la commande ls –l :. Le père attend la

fin de son fils. Par ailleurs, chacun des processus affiche son pid ; le fils affiche le pid de son père et le père

affiche le pid de son fils.

2.1.2 Exercice 2 On reprend le programme essai.c de l'exercice 4 de l'ed 2, dont le code est redonné ci-après.

On souhaite modifier ce code de manière à remplacer la partie fils par un appel à une primitive exec.

Donnez la modification du programme essai.c résultante et le code du programme lire_fichier.c exécuté par

le fils.

2.1.3 Exercice 3 On souhaite écrire un pseudo-code correspondant à l’algorithme suivi par le shell. les étapes (simplifiées)

du shell sont les suivantes :

● Le shell lit une ligne de commande sur son entrée standard et l'interprète selon un ensemble de

règles fixées (on ne s'intéresse pas au détail de cette analyse)

● le shell gère deux types de commandes :

● les commandes en premier plan qui correspondent à des processus pouvant lire et écrire sur le

Page 61: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

terminal.

● les commandes en arrière plan qui sont "détachées" du terminal, c'est-à-dire qui ne peuvent plus lire

et écrire sur celui-ci.

Lorsqu'il traite une commande en premier plan, le shell attend la fin de celle-ci avant de prendre en compte

une nouvelle commande. Au contraire lorsque le shell traite une commande en second plan, il n'attend pas

la fin de celle-ci pour prendre en compte une nouvelle commande.

Ecrivez la boucle d'exécution du shell. Voir la correction

Page 62: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Chapitre 4 : Outils de communication centralisés entre processus

Ce chapitre est consacré à l'étude de deux outils de communication centralisés entre processus Unix : les tubes anonymes et les messages queues.

Cours : Les tubes anonymes

Définition et propriétés d'un tube anonyme. Les primitives associées au tube anonyme

La création d'un tube La fermeture du tube Lecture dans le tube p Ecriture dans le tube p

Un exemple de programmation avec les tubes anonymes. Définition et propriétés des MSQ Les primitives associées aux MSQ

Création et accès à une MSQ Envoi d'un message dans la file de messages Réception d'un message depuis la file de messages Destruction d'une file de messages

Un exemple de programmation avec les MSQ.

Exercices dirigés

Primitives d'utilisation des tubes Exercice 1 Exercice 2

Table des figures

Page 63: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Outils de communication centralisés entre processus>Les tubes anomymes

1 Cours : Les tubes anonymes

Ce cours s'intéresse à un premier outil de communication entre processus placés sur une même machine :

les tubes anonymes. Un tube (pipe en anglais) est un fichier particulier. Il existe deux types de tube: . les tubes anonymes . les tubes nommés Le fichier tube est un outil de communication unidirectionnel qui est géré par le noyau comme une file de

données FIFO. Les lectures sont destructives car tout caractère lu est extrait de la file d'attente. Deux

nombres im port ants caractérisent le tube:

● le nombre de lecteurs : si ce nombre est nul, les lectures ne sont pas autorisées dans tube.

● le nombre d'écrivains : si ce nombre est nul, . il est équivalent à une fin de fichier.

1.1 Définition et propriétés d'un tube anonyme.

Un tube anonyme encore appeler tube ordinaire est associé à un nœud du système de gestion de fichiers,

mais il représente un fichier qui n'a pas de nom. Ce fichier est supprimé dès qu'il n'y a plus de processus

pour l'utiliser. Un tube anonyme n'ayant pas de nom, il ne peut être connu que par la possession des

descripteurs qui lui donnent accès. La connaissance de ces descripteurs par un processus s'effectue de

deux moyens:

● Le processus connaît les descripteurs associés au tube car il a créé le tube.

● Le processus connaît les descripteurs associés au tube car il a hérité de ces descripteurs. On voit

donc que seuls les processus de la descendance du créateur du tube dont les ancêtres connaissent

eux-mêmes les descripteurs sont habilités à utiliser le tube. Un tube anonyme permet donc la

communication entre des processus de même filiation père-fils ou frères s'exécutant sur une même

machine. Il faut noter également que les tubes ne préservent pas la limite des messages postés

dans le tube.

Définition : Tube anonymeUn tube anonyme est un outil de communication unidirectionnel permettant de faire communiquer entre eux des processus de même filiation, ayant connaissance des descripteurs permettant l'accès au tube.

1.2 Les primitives associées au tube anonyme

Page 64: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

1.2.1 La création d'un tube En créant le tube le processus1 se dote de 2 descripteurs:

● . un descripteur en Lecture sur le tube

● . un descripteur en Ecriture sur le tube

La primitive pipe (p) où le paramètre p est un tableau de deux entiers correspondant aux deux descripteurs

d'accès au tube permet la création du tube anonyme p, avec p[0] comme descripteurs en lecture et p[1]

comme descripteur en écriture.

Définition : Primitive pipeint pipe (int p[2]) : création du tube p.

1.2.2 La fermeture du tube La fermeture du tube et sa destruction ont lieu lorsqu'il n'existe plus aucun processus utilisant le tube, c'est-

à-dire qu'il n'existe plus aucun descripteurs ouverts pour ce tube, que ce soit en lecture ou en écriture. La

primitive close (idf) permet de fermer le descripteur idf .

Définition : Primitive closeint close (int idf) : fermeture du descripteur idf.

1.2.3 Lecture dans le tube p La primitive nb_lu = read (p[0], buf, nb) permet la lecture dans le tube p d'au plus nb caractères. Elle

retourne les caractères dans le tampon buf et nb_lu contient le nombre de caractères effectivement lus.

L'opération de lecture répond à la sémantique suivante :

● si le tube n'est pas vide, et contient N caractères, le nombre de caractères effectivement lus, nb_lu

est égal à min (N, nb).

● Si le tube est vide et que le nombre d'écrivains sur le tube est nul, la fin de fichier est atteinte. Nb_lu

= 0.

● Si le tube est vide et que le nombre d'écrivains sur le tube n'est pas nul, le processus est bloqué

jusqu'à ce le tube ne soit plus vite (un écrivain a écrit). En effet, par défaut, l'opération de lecture sur

un tube vide est bloquante. Il est possible de rendre la lecture non bloquante, mais nous n'abordons

pas cette option dans le cours.

Définition : Primitive read (tube)nb_lu = read (p[0], buf, nb) : lecture dans le tube p d'au plus nb caractères. Les caractères lus sont retournés dans le tampon buf. nb_lu contient le nombre de caractères effectivement lus. Par défaut, la lecture sur un tube vide est bloquante.

1.2.4 Ecriture dans le tube p La primitive nb_écrit = write (p[1], buf, nb) permet l'écriture dans le tube p des nb caractères placés dans le

Page 65: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

tampon buf et nb_lu contient le nombre de caractères effectivement écrits. L'opération d'écriture répond à la

sémantique suivante :

● Si le nombre de lecteurs dans le tube est nul, une erreur est levée par le système (signal SIGPIPE

levé) et le processus écrivain est terminé. En effet, le système considère que si il n'y a plus de

lecteurs dans le tube, il est inutile d'y écrire puisque les caractères écrits ne pourront jamais être lus.

● Si le nombre de lecteurs dans le tube n'est pas nul, le retour de la primitive n'a lieu que lorsque les

nb caractères ont effectivement été écrits. En effet par défaut, l'écriture sur le tube est bloquante. Il

est possible de rendre l'écriture non bloquante, mais nous n'abordons pas cette option dans le

cours.

Définition : Primitive write (tube)nb_écrit = write (p[1], buf, nb) : écriture dans le tube p des nb caractères placés dans le tampon buf. nb_lu contient le nombre de caractères effectivement écrits.

1.3 Un exemple de programmation avec les tubes anonymes.

#include <stdio.h>

int pip[2]; /* descripteur de pipe */

char buf [6];

{ main()

pipe(pip); /* creation pipe */

switch (fork())

{case -1: perror("fork"); exit(1);

case 0: fils();

default: pere();}

pere(){close pip[0]; write (pip[1],"hello",5); exit(0);} /* écriture pipe */

fils() {close pip[1]; read (pip[0],buf,5); exit(0);} /* lecture pipe */

}

Page 66: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

On voit dans l'exemple ci-dessus :

● La création du tube a lieu avant la création du processus fils, afin que celui-ci puisse hériter des

descripteurs du tube et donc l'utiliser pour communiquer avec son père.

● Chacun des processus, le père et le fils, ferme le descripteur qui lui est inutile : ainsi le père ferme

pip[0], le descripteur en lecture puisqu'il est écrivain sur le tube. Inversement, le fils ferme pip[1], le

descripteur en écriture puisqu'il est lecture sur le tube.

Page 67: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Outils de communication centralisés entre processus>Les tubes anomymes>Les files de message ou MSQ

Nous présentons à présent un second outil de communication entre processus Unix : les files de messages

ou MSQ.

1.4 Définition et propriétés des MSQ

Les files de messages UNIX constitue un autre mécanisme de communication entre processus UNIX

placés sur une même machine. À la différence du mécanisme des tubes, le mécanisme des files de

message est externe au système de gestion de fichiers du système UNIX. Le système gère une table

spécifique pour cet outil, qui est identifié par une clé de type key_t. Cette clé est une valeur numérique et

tout processus ayant connaissance de la clef peut utiliser la file de messages pour envoyer ou recevoir des

messages. On voit donc qu'à la différence des tubes, la file de message peut être utilisée par des

processus non nécessairement affiliés entre eux. La file de message constitue l'implémentation UNIX du

concept de boîtes aux lettres. Il offre deux propriétés : la préservation de la limite des messages ce qui est

différent des tubes, et la possibilité de multiplexage c'est-à-dire qu'une seule file peut servir pour plusieurs

destinataires. Les messages postés dans une file de messages sont obligatoirement constitués de deux

parties:

● Un type qui est un entier long. Ce type est utile pour réaliser le multiplexage.

● Les données

Un exemple de messages

struct message {

long mtype; -- le type

float n1 ;

int tab[4];

}

Définition : File de messages (MSQ)Une file de messages est un outil de communication entre processus Unix non nécessairement affiliés placés sur une même machine qui implémente le concept de boîtes aux lettres. Une file de message ou MSQ est identifiée par une clé.

1.5 Les primitives associées aux MSQ

Page 68: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

1.5.1 Création et accès à une MSQ La primitive MSGGET permet à un processus soit de créer une nouvelle file de messages, soit de

récupérer une file de messages existantes afin de l'utiliser.

Définition : Primitive msggetint msgid = msgget (key_t cle, int option) : permet de créer une nouvelle file de messages ou de récupérer une file de messages existante.

Le premier paramètre de la primitive, clé, permet de spécifier l'identifiant de la MSQ. Le second paramètre,

option, est une combinaison des constantes IPC_CREAT, IPC_EXCL et des droits d'accès associés à la

file. La primitive en cas de succès retourne un identifiant interne au programme msgid. Plus précisément, la

création d'une file est obtenue grâce aux combinaisons suivantes :

● Positionnement de IPC_CREAT et de IPC_EXCL : création d'une nouvelle file de messages avec les

droits spécifiés dans option, à condition que la file ayant pour référence clé n'existe pas déjà. Sinon,

il y a erreur

● Positionnement de IPC_CREAT seul: création d'une nouvelle file de messages avec les droits

spécifiés dans option, si celle-ci n'existe pas

La récupération d'une file existante s'obtient en mettant les options à 0.

CREATION D'UNE FILE de clé 17 en lecture écriture pour tous

#define CLE 17

int msqid;

msqid = msgget (CLE, IPC_CREAT | IPC_EXCL | 0666);

RECUPERATION D'UNE FILE de clé 17

#define CLE 17

int msqid;

msqid = msgget (CLE,0);

1.5.2 Envoi d'un message dans la file de messages La primitive msgsnd permet l'envoi d'un message dans une file de messages.

Page 69: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Définition : Primitive msgsndint msgsnd (int msgid, struct message *buf, int lg, int option) : envoi du message buf de taille lg octets dans la MSQ d'identifiant msgid. Les options permettent de rendre l'envoi non bloquant si la file est pleine.

1.5.3 Réception d'un message depuis la file de messages La primitive msgrcv permet de recevoir un message depuis une file de messages. C'est à ce niveau

qu'intervient le multiplexage dont nous avons parlé précédemment, c'est-à-dire que le récepteur peut

désigner parmi les messages présents dans le file, un message qu'il souhaite plus spécifiquement

recevoir.

Définition : Primitive msgrcvint msgrcv (int msgid, struct message *buf, int lg, long montre, int option) : réception du message buf de taille lg octets dans la MSQ d'identifiant msgid. Le paramètre montre spécifie le type de message à extraire. Les options permettent de rendre la réception non bloquante si la file ne contient pas de message avec le type attendu.

Le multiplexage utilise le paramètre type des messages. Les interprétations possibles du paramètre montre

spécifié dans la primitive msgrcv sont les suivantes :

● Si montre > 0 alors le message le plus vieux dont le type est égal à montre est extrait.

● Si montre = 0 alors le message le plus vieux, quel que soit son type,, est extrait.

● Si montre < 0, alors le message le plus vieux de type le plus petit inférieur ou égal à |montre| est

extrait.

1.5.4 Destruction d'une file de messages La primitive msgctl permet l'accès et la modification des informations contenues dans la table des files de

messages gérée par le système. Plus précisément, utilisée avec comme opération la valeur IPC_RMID, elle

permet la destruction de la file de message dont l'identifiant est passé en paramètre.

Définition : Primitive msgctlint msgctl (int msgid, int op, struct msgid_ds *buf) : accès et modification des informations contenues dans la table des files de messages gérée par le système int msgctl (msgid, IPC_RMID, NULL) : destruction de la file de message identifiée par msgid.

1.6 Un exemple de programmation avec les MSQ.

Processus 1 : crée la file et envoi un message dans cette file

/* création d'une MSQ et envoi message*/

#include <sys/types;h>

#include <ipc.h>

Page 70: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

#include <msqg.h>

#define CLE 17

struct msgbuf msgp;

char *msg="ceci est un message";

main()

{ int msqid; /* identificateur msq */

msqid = msgget((key_t)CLE,0666| IPC_CREAT | IPC_EXCL);/* creation msq */

msgp.mtype=12; /* le type */

strcpy(msgp.mtext,msg); /* le message */

msgsnd(msqid, &msgp, strlen(msg), 0) /* envoi message */

exit(0); }

Processus 2 : reçoit un message depuis la file 17 puis la détruit.

/* lecture message et destruction msq*/

#include <sys/types;h>

#include <ipc.h>

#include <msqg.h>

#define CLE 17

struct msgbuf msgp;

main()

Page 71: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

{ int msqid; int x;

msqid = msgget((key_t)CLE, 0); /* récup msqid */

x = msgrcv (msqid, &msgp, 19, (long)12, 0) /* lecture type 12*/

msgp.text[x] = 0;

printf ("message lu %s÷n",msgp.mtext);

msgctl(msqid,IPC_RMID, NULL); /* destruction */

exit(0); }

Page 72: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Outils de communication centralisés entre processus>Exercices dirigés

2 Exercices dirigés

2.1 Primitives d'utilisation des tubes

Préambule : rappel du format des primitives permettant l'utilisation des tubes

2.1.1 Exercice 1 Ecrivez un programme où un père crée un fils.

Le fils et le père communiquent par l'intermédiaire de deux tubes pip1 et pip2. Le père écrit dans pip1, qui

est lu par le fils, le message "hello"et attend sur pip2 une réponse du fils. Le fils lit dans pip1 le message du

père et lui renvoie la chaine"bonjour" dans pip2.

2.1.2 Exercice 2 Programmez la commande ps -e | wc -l qui compte le nombre de lignes de la commande ps -e, laquelle

affiche les caractéristiques de tous les processus . Voir la correction

Page 73: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Chapitre 5 : Allocation mémoire Ce chapitre est consacré à l'étude de l'allocation de la mémoire centrale

Cours : Allocation mémoire

Allocation de la mémoire centrale et multiprogrammation. Présentation du problème Différentes méthodes d'allocation mémoire

Allocation mémoire d'un seul tenant. La pagination

Principe Traduction de l'adresse paginée vers l'adresse physique.

La segmentation Principe Traduction de l'adresse segmentée vers l'adresse physique.

Segmentation et Pagination

Exercices dirigés

Primitives d'utilisation des messages queues Exercice 1 Exercice 2

Table des figures

Page 74: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Allocation mémoire >Allocation mémoire

1 Cours : Allocation mémoire

Ce cours s'intéresse aux différentes méthodes permettant l'allocation de l'espace mémoire aux

programmes à exécuter. Il aborde les différentes méthodes d'allocation et notamment les mécanismes de

pagination et de segmentation

1.1 Allocation de la mémoire centrale et multiprogrammation.

1.1.1 Présentation du problème Nous commençons par un bref rappel du concept de mémoire au niveau matériel. La mémoire physique est

constituée d'un ensemble de mots mémoire contigus désignés chacun par une adresse physique. Le

processeur accède aux mots de la mémoire centrale par le biais de deux registres, le registre Adresse RAD

et le registre donnée RDO. Le registre RAD contient l'adresse du mot à lire ou l'adresse du mot où écrire

tandis que le registre RDO contient soit la donnée à écrire, soit la donnée lue. Les lignes d'adresses, de

données et de commandes du bus font la liaison entre les registres processeur et la mémoire. Dans un

système en monoprogrammation, la mémoire centrale est occupée d'une part par les procédures du

système d'exploitation , d'autre part par un seul programme utilisateur . Comme nous l'avons vu plusieurs

fois déjà, l'occurrence d'opérations d'entrées/sorties demandées par le processus peut entrainer une

inactivité du processeur si celles-ci sont gérées par DMA. Par exemple, avec un processus effectuant 50 %

de calcul et 50 % d'entrées/sorties, le processeur est inactif durant 50% du temps. Une telle inactivité du

processeur n'est pas souhaitable : on place donc un second programme en mémoire centrale de même

profil. Le processeur est maintenant occupé à 100 % (cas idéal et non réel) car il exécute le second

processus durant l'entrée/sortie du premier processus . Le système est maintenant multiprogrammé.

Définition : Degré de multiprogrammationon définit le degré de multiprogrammation comme étant le nombre de processus présents en mémoire centrale.

Le schéma ci-dessous représente le taux d'activité du processeur en fonction du nombre de processus

présents en mémoire centrale et en fonction du temps d'entrée-sortie de ces processus.

Page 75: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Fig 1 : Degré de multiprogrammation

Dans un système multiprogrammé, trois problèmes sont à résoudre vis-à-vis de la mémoire centrale :

● Il faut définir un espace d'adressage indépendant pour chaque processus

● Il faut protéger les espaces d'adressage des processus les uns vis-à-vis des autres

● Il faut allouer de la mémoire physique à chaque espace d'adressage.

1.1.2 Différentes méthodes d'allocation mémoire Les méthodes d'allocation mémoire peuvent être divisées en deux grandes familles :

● pour la première famille, un programme est un ensemble de mots contigus insécable. L'espace

d'adressage du processus est linéaire. On trouve ici les méthodes d'allocations en partitions

variables que nous allons étudier en premier.

● pour la seconde famille, un programme est un ensemble de mots contigus sécable, c'est-à-dire que

le programme peut être divisé en plus petits morceaux, chaque morceau étant lui-même un

ensemble de mots contigus. Chaque morceau peut alors être alloué de manière indépendante. On

trouve ici les mécanismes de segmentation et de pagination..

1.2 Allocation mémoire d'un seul tenant.

Dans cette méthode d'allocation, le programme est considéré comme un espace d'adressage insécable. La

mémoire physique est découpée en zones disjointes de taille variable, adaptables à la taille des

Page 76: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

programmes : ces zones sont appelés des partitions . Initialement, la mémoire centrale est uniquement

occupée par les procédures du système d'exploitation. La zone réservée aux programmes utilisateurs est

vide et constitue une unique zone libre. Au fur et à mesure des chargements de programmes, la zone libre

va se réduire et à l'instant t, elle n'occupe plus qu'une fraction de la mémoire centrale (mémoire basse).

Lorsque l'exécution des programmes se termine (ici P2 et P4), la mémoire est libérée : il se crée alors pour

chaque zone libérée, une nouvelle zone libre. Finalement, la mémoire centrale se retrouve constituée d'une

ensemble de zones allouées et de zones libres réparties dans toute la mémoire. Les zones libres sont

organisées en une liste chaînée de zones libres repérée par une tête de liste.

Fig 2 : Allocation en partitions variables

Dans ce contexte, charger un nouveau programme consiste à trouver une zone libre suffisamment grande

pour pouvoir y placer le programme. Une première stratégie pour trouver et choisir cette zone libre est de

prendre la première zone libre suffisamment grande trouvée au cours du parcours de la mémoire (parcours

de la liste chaînée) : c'est la stratégie First Fit. Ici, donc, le programme 7 est placée dans la zone libre de

120K ce qui crée une nouvelle zone libre résiduelle de 40K.

Page 77: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Fig 3 : Stratégie First Fit

Une seconde stratégie pour trouver et choisir cette zone libre est de prendre la zone libre dont la taille est la

plus proche de celle du programme à allouer, donc celle engendrant le plus petit trou résiduel : c'est la

stratégie Best Fit. Ici, donc, le programme 7 est placée dans la zone libre de 100K ce qui crée une nouvelle

zone libre résiduelle de 20K.

Page 78: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Fig 4 : Stratégie Best Fit

Au fur et à mesure des opérations d'allocations et de désallocations, la mémoire centrale devient composée

d'un ensemble de zones occupées et de zones libres éparpillées dans toute l'étendue de la mémoire

centrale. Ces zones libres peuvent devenir trop petites pour permettre l'allocation de nouveaux

programmes (problème de fragmentation de la mémoire). Par exemple, sur la figure 5, la mémoire centrale

com port e 3 zones libres mais aucune d'elles n'est assez grande pour contenir un programme 8 de 180K.

Pourtant l'ensemble des 3 zones libres forme un espace de 120 + 20 + 150 = 350K suffisant pour le

programme 8. Pour permettre l'allocation du programme 8, il faut donc réunir l'ensemble des zones libres

pour ne former plus qu'une zone libre suffisante : c'est l'opération de compactage de la mémoire centrale .

Fig 5 : Fragmentation et compactage de la mémoire centrale

Définition : FragmentationAllocations et désallocations successives des programmes en mémoire centrale créent des trous, c'est-à-dire des zones libres de taille insuffisante en mémoire centrale : la mémoire centrale est alors fragmentée.

Définition : Compactage de la mémoire centraleLe compactage de la mémoire centrale consiste à déplacer les programmes en mémoire centrale de manière à ne créer qu'une seule et unique zone libre.

Le compactage de la mémoire centrale est une opération coûteuse. Il n'existe pas d'algorithme simple

permettant d'optimiser le nombre d'octets déplacés lors d'une telle opération. Par ailleurs elle suppose une

translation des adresses dynamiques. Dans le mécanisme de chargement dynamique, les adresses du

programme chargé en mémoire centrale ne sont pas translatées de la valeur de l'adresse d'implantation au

moment du chargement, mais seulement au moment de l'exécution. L'adresse d'implantation du

programme est conservée dans un registre processeur - le registre de translation - .Ainsi lors de l'opération

Page 79: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

de compactage, déplacer un programme consiste seulement à changer la valeur d'adresse d'implantation à

charger dans le registre de translation. L'allocation en mémoire centrale d'un seul tenant souffre donc de deux défauts principaux :

● Elle nécessite une opération de compactage de la mémoire qui est une opération très coûteuse

● Elle exige d'allouer le programme en une zone d'un seul tenant.

Une solution est de diviser le programme en portions de taille fixe et égales à l'unité d'allocation de la

mémoire centrale. On dit alors que le programme est découpé en pages. Le mécanisme d'allocation

associé s'appelle la pagination.

1.3 La pagination

1.3.1 Principe Dans le mécanisme de pagination, l'espace d'adressage du programme est découpé en morceaux linéaires

de même de taille : la page.

L'espace de la mémoire physique est lui-même découpé en morceaux linéaires de même taille : la case.

La taille d'une case est égale à la taille d'une page. Dans ce contexte, charger un programme en mémoire

centrale consiste à placer les pages dans n'importe quelle case disponible.

Fig 6 : Principe de la pagination

1.3.2 Traduction de l'adresse paginée vers l'adresse physique. L'espace d'adressage du processus étant découpé en pages, les adresses générées dans cet espace

Page 80: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

d'adressage sont des adresses paginées, c'est-à-dire qu'un octet est repéré par son emplacement

relativement au début de la page à laquelle il appartient. L'adresse d'un octet est donc formé par le couple

<n°de page à laquelle appartient l'octet, déplacement relativement au début de cette page >.

Les octets dans la mémoire physique eux ne peuvent être adressés au niveau matériel que par leur

adresse physique. Pour toute opération concernant la mémoire, il faut donc convertir l'adresse paginée

générée au niveau du processeur en une adresse physique équivalente. L'adresse physique d'un octet

s'obtient à partir de son adresse virtuelle en remplaçant le numéro de page de l'adresse virtuelle par

l'adresse physique d'implantation de la case contenant la page et en ajoutant à cette adresse physique

d'implantation le déplacement de l'octet dans la page. C'est la MMU (Memory Management Unit) qui est

chargée de faire cette conversion. Il faut donc savoir pour toute page, dans quelle case de la mémoire

centrale celle-ci a été placée : cette correspondance s'effectue grâce à une structure particulière appelée la

table de pages. Dans une première approche, la table des pages est une table contenant autant d'entrées que de pages

dans l'espace d'adressage d'un processus. Chaque processus a sa propre table des pages. Chaque entrée

de la table est un couple < n°de page, n°de case physique dans laquelle la page est chargée >. Dans

l'exemple de la figure ci-dessous, le processus a 4 pages dans son espace d'adressage, donc la table des

pages a 4 entrées. Chaque entrée établit l'équivalence n°de page, n°de case relativement au schéma de la

mémoire centrale.

Définition : Table des pagesLa table des pages est une table contenant autant d'entrées que de pages dans l'espace d'adressage d'un processus. Chaque processus a sa propre table des pages. Chaque entrée de la table est un couple < n°de page, n°de case physique dans laquelle la page est chargée >.

Page 81: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Fig 7 : Table des pages

Puisque chaque processus dispose de sa propre table des pages, chaque opération de commutation de

contexte se traduit également par un changement de table des pages, de manière à ce que la "table active"

corresponde à celle du processus élu. Deux approches existent pour la réalisation de la table des pages :

● à l'aide de registres du processeur : la table des pages est sauvegardée avec le contexte processeur

dans le PCB du processus.

● placer les tables des pages en mémoire centrale : la table active est repérée par un registre spécial

du processeur le PTBR. Chaque processus sauvegarde dans son PCB la valeur de PTBR

correspondant à sa table.

Dans la première approche accéder à un emplacement mémoire nécessite seulement un accès à la

mémoire : celui nécessaire à la lecture ou l'écriture de l'octet recherché puisque la table des pages est

stockée dans des registres du processeur. Dans la deuxième approche accéder à un emplacement

mémoire à partir d'une adresse paginée <p,d> nécessite au contraire deux accès à la mémoire :

● un premier accès permet de lire l'entrée de la table des pages correspondant à la page cherchée :

c'est l'opération (p + adresse table) qui délivre une adresse physique de page dans la mémoire

centrale.

● un second accès est nécessaire à la lecture ou l'écriture de l'octet recherché à l'adresse <adresse

physique > + d.

Fig 8 : Traduction d'une adresse paginée en adresse physique

Page 82: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Pour accélérer les accès à la mémoire centrale et compenser le coût lié à la pagination, un cache associatif

est placé en amont de la mémoire centrale. Ce cache associatif contient les couples <n°de page, adresse

d'implantation de la case> les plus récemment accédés. Lorsque la MMU doit effectuer une conversion

d'adresse paginée, elle cherche tout d'abord dans le cache si la correspondance n°de page, adresse

d'implantation de la case recherchée n'est pas dans le cache. Si non, elle accède à la table des pages en

mémoire centrale et place le nouveau couple référencé dans le cache. Si oui, elle effectue directement la

conversion : un seul accès mémoire est alors nécessaire pour accéder à l'octet recherché.

Fig 9 : Traduction d'une adresse paginée en adresse physique avec ajout d'un cache

associatif

1.4 La segmentation

1.4.1 Principe La pagination constitue un découpage de l'espace d'adressage du processus qui ne correspond pas à

l'image que le programmeur a de son programme. Pour le programmeur, un programme est généralement

constitué des données manipulées par ce programme, d'un programme principal, de procédures séparées

et d'une pile d'exécution. La segmentation est un découpage de l'espace d'adressage qui cherche à

conserver cette vue du programmeur. Ainsi, lors de la compilation, le compilateur associe un segment à

chaque morceau du programme compilé. Un segment est un ensemble d'emplacements mémoire

consécutifs non sécable. A la différence des pages, les segments d'un même espace d'adressage peuvent

être de taille différente. D''une manière générale, on trouvera un segment de code, un segment de données

et un segment de pile.

Page 83: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

1.4.2 Traduction de l'adresse segmentée vers l'adresse physique. D'une manière similaire à ce qui se passe avec la pagination, la segmentation de l'espace d'adressage d'un

processus génère des adresses segmentées, c'est-à-dire qu'un octet est repéré par son emplacement

relativement au début du segment auquel il appartient. L'adresse d'un octet est donc formé par le couple

<n°de segment à laquelle appartient l'octet, déplacement relativement au début du segment >. Pour toute opération concernant la mémoire, il faut ici encore convertir l'adresse segmentée générée au

niveau du processeur en une adresse physique équivalente. L'adresse physique d'un octet s'obtient à partir

de son adresse segmentée en remplaçant le numéro de segment de l'adresse segmentée par l'adresse

physique d'implantation du segment en mémoire centrale et en ajoutant à cette adresse physique

d'implantation, le déplacement de l'octet dans le segment. C'est la MMU (Memory Management Unit) qui

est chargée de faire cette conversion. Il faut donc connaitre pour tout segment, l'adresse d'implantation

dans la mémoire centrale du segment : cette correspondance s'effectue grâce à une structure particulière

appelée la table des segments .

Fig 10 : Table des segments

Dans une première approche, la table des segments est une table contenant autant d'entrées que de

segments dans l'espace d'adressage d'un processus. Chaque entrée de la table est un couple < n°de

segment, adresse d'implantation du segment >. Ici le processus a 4 segments dans son espace

d'adressage, donc la table des segments a 4 entrées. Chaque entrée établit l'équivalence n°de segment,

adresse d'implantation du segment relativement au schéma de la mémoire centrale.

Page 84: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Définition : Table des segmentsLa table des segments est une table contenant autant d'entrées que de segments dans l'espace d'adressage d'un processus. Chaque entrée de la table est un couple < n°de segment, adresse d'implantation du segment >.

Plus précisément la conversion d'une adresse segmentée <s,d> avec s, numéro de segment et d

déplacement dans le segment suit les étapes suivantes, en mettant en jeu un registre processeur qui

contient en partie haute, le nombre maximal de segments de l'espace d'adressage couramment actif (LT) et

en partie basse l'adresse de la table des segments de l'espace d'adressage couramment actif.

● s est comparé à LT. Si s >= à LT alors il y a erreur : le segment adressé n'existe pas.

● sinon s est additionné à l'adresse de la table des segments de manière à indexer l'entrée de la table

concernant le segment s. On récupère alors l'adresse d'implantation du segment s en mémoire

centrale (adr début)

● Une information sur la taille du segment peut être conservée dans la table : d est alors comparé à

cette information. Si d est supérieure à l'information taille, alors une erreur est générée car le

déplacement est en dehors du segment. Sinon , le déplacement d est ajouté à l'adresse

d'implantation du segment pour générer l'adresse physique.

Fig 11 : Traduction d'une adresse segmentée en adresse physique

L'allocation des segments en mémoire centrale s'effectue selon le même principe que pour l'allocation de

partitions variables. Pour allouer un segment de taille S, il faut trouver une zone libre dont la taille soit au

moins égale à la taille du segment S. Elle engendre les mêmes problèmes de fragmentation . Une solution,

très largement répandue, est de combiner pagination et segmentation, c'est-à-dire de paginer les

Page 85: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

segments.

1.5 Segmentation et Pagination

Dans la cas où pagination et segmentation sont simultanément employées, une table des segments est

définie pour chaque segment de l'espace d'adressage du processus. Chaque segment est à son tour

paginé, il existe donc une table des pages pour chaque segment. Ainsi une entrée de la table des segment

ne contient plus l'adresse du segment correspondant en mémoire physique mais contient l'adresse de la

table des pages en mémoire physique pour ce segment. L'adresse d'un octet dans l'espace d'adressage du

processus est un couple <s,d>, le déplacement d étant à son tour interprété comme un couple numéro de

page p, déplacement d' dans cette page. Les mécanismes de traduction d'adresses vus dans les

paragraphes précédents se superposent l'un à l'autre.

Fig 12 : Traduction d'une adresse segmentée et paginée en adresse physique

Page 86: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Allocation mémoire >Exercices dirigés

2 Exercices dirigés

2.1 Primitives d'utilisation des messages queues

Préambule : rappel du format des primitives permettant l'utilisation des messages queues

2.1.1 Exercice 1 On considère deux processus A et B communiquant via une MSQ de clé 17. Le processus A crée la MSQ,

puis écrit le message "ceci est un message" à destination du processus B. Le processus B lit le message et

l'afiche, puis détruit la MSQ.

Ecrivez les programmes correspondants.

2.1.2 Exercice 2 On considère l'architecture logicielle suivante :

A - Le serveur attend des questions de la part de clients. Une question correspond à la demande d'envoi

de n nombres tirés au sort par le serveur, n étant un nombre aléatoire compris entre 1 et NMAX tiré au sort

par le client et envoyé par le client au serveur.

Ecrivez les programmes correspondants. On vous donne les fonctions suivantes : Un appel à la fonction

#include <stdlib.h>

Int rand(void); renvoie un nombre pseudo-aléatoire dans l'intervalle [O… RAND-MAX]. Il est nécessaire

d'initialiser le générateur par un appel à la fonction

#include <stdlib.h> void srand (unsigned int val) Cette fonction peut être appelée avec comme paramètre

soit le numéro du processus srand (unsigned int) getpid ()) soit la date courante srand (unsigned int) time

(NULL).

Page 87: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

B - On souhaite remplacer la message queue par des tubes anonymes. Comment faut-il modifier

l'architecture logicielle précédente ?

Donnez les grandes étapes du code en considérant seulement deux clients.

Voir la correction

Page 88: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Chapitre 6 : Mémoire virtuelle Ce chapitre est consacré à l'étude du principe de la mémoire virtuelle.

Cours : La mémoire virtuelle

Principe de la mémoire virtuelle Notion de défaut de pages Les algorithmes de remplacement de pages

Algorithme de remplacement de pages FIFO Algorithme de remplacement de pages LRU Complément sur le format d'une entrée de la table des pages d'un processus.

Algorithme pour la conversion d'une adresse logique paginée en adresse physique. Notion d'écroulement

Un petit test de mémoire

Exercices dirigés

Exercices sur la mémoire virtuelle Exercice 1 : Gestion d'une mémoire par zones

Question 1 Question 2 Question 3 Question 4 Question 5

Exercice 2 : Pagination Question 1 Question 2 Question 3 Question 4 Question 5

Table des figures

Page 89: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Mémoire virtuelle >Mémoire virtuelle

1 Cours : La mémoire virtuelle

Ce cours s'intéresse au principe de la mémoire virtuelle. Il aborde notamment les notions liées aux défauts

de pages.

1.1 Principe de la mémoire virtuelle

La multiprogrammation implique de charger plusieurs programmes en mémoire centrale de manière à

obtenir un bon taux d'utilisation du processeur. Supposons comme sur la figure 1 ici que l'exécution des

programmes 1, 2 et 3 soit nécessaire pour obtenir ce taux d'utilisation du cpu satisfaisant. On peut

remarquer qu'une fois les programmes 1 et 2 chargés dans la mémoire, toutes les cases sont occupées : le

programme 3 ne peut pas être chargé.

Fig 1 : Cas de figure

Lorsque l'on regarde l'exécution d'un processus, on s'aperçoit qu'à un instant donné le processus n'accède

qu'à une partie de son espace d'adressage (par exemple la page de code couramment exécutée par le

processeur et la page de données correspondante). Les autres pages de l'espace d'adressage ne sont pas

Page 90: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

accédées et sont donc inutiles en mémoire centrale. Une solution pour pouvoir charger plus de

programmes dans la mémoire centrale est donc de ne charger pour chaque programme que les pages

couramment utilisées. Ici par exemple, seules les pages 1,2 et 4 du processus 1 sont chargées ainsi que la

page 3 du processus 2 et les pages 1,2,3 du programme 3. Puisque les pages d'un espace d'adressage de processus ne sont pas toutes chargées en mémoire

centrale, il faut que le processeur puisse détecter leur éventuelle absence lorsqu'il cherche à effectuer une

conversion d'adresse paginée vers l'adresse physique. Chaque entrée de la table des pages com port e

alors un champ supplémentaire, le bit Validation V, qui est à vrai (1 ou V) si la page est effectivement

présente en mémoire centrale. La figure 2 montre les valeurs des bits de validation pour les tables des pages des trois processus 1, 2 et 3,

en tenant compte des chargements de leurs pages en mémoire centrale. Ainsi pour le processus 1, la page

1 est chargée dans la case 2, le bit de validation est à vrai (V). La page 2 est chargée dans la case 4, le bit

de validation est à vrai. Par contre la page 3 n'est pas présente en mémoire centrale et donc le bit de

validation est à faux (I pour Invalide) : dans ce cas, le champs n° de case n'a pas de signification.

Fig 2 : Bit de validation

1.2 Notion de défaut de pages

Que se passe-t-il à présent lorsque qu'un processus tente d'accéder à une page de son espace

d'adressage qui n'est pas en mémoire centrale ? Ici le processus 2 génère une adresse paginée portant sur

la page 2. La MMU accède à la table des pages pour effectuer la conversion adresse paginée, adresse

physique et teste la valeur du bit de validation : elle le trouve à faux, ce qui veut dire que la page n'est pas

Page 91: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

chargée dans une case et donc la conversion ne peut être réalisée. Il se produit alors un défaut de page :

c'est un déroutement qui oblige le processeur à suspendre l'exécution du programme en cours pour lancer

une entrée/sortie qui charge la page manquante en mémoire centrale dans une case libre.

Définition : Défaut de pageLe défaut de page est un déroutement qui oblige le processeur à suspendre l'exécution du programme en cours pour lancer une entrée/sortie qui charge la page manquante en mémoire centrale dans une case libre.

Les figures 3 et 4 illustrent le mécanisme de défaut de page. Le processus cherche à convertir l'adresse

logique <p,d>. Il accède donc à l'entrée de sa table des pages correspondant à l'entrée de la page p, et

teste la valeur du bit de validation. Celui-ci est à faux (I) indiquant ainsi que la page n'est pas présente.

Automatiquement, puisque la traduction vers l'adresse physique ne peut pas être faite, le système lève un

défaut de page, qui entraine une entrée/sortie pour charger la page manquante en mémoire centrale. Le

défaut de page charge la page manquante dans une case libre de la mémoire centrale, puis le système met

à jour l'entrée de la table des pages correspondant à la page p : le bit de validation passe à vrai et le

numéro de case physique contenant la page p est renseigné. Enfin, la traduction vers l'adresse physique

reprend.

Fig 3 : Mécanisme du défaut de pages

Page 92: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Fig 4 : Mécanisme du défaut de pages

1.3 Les algorithmes de remplacement de pages

Lors d'un défaut de page, la page manquante est chargée dans une case libre. La totalité des cases de la

mémoire centrale peut être occupée : il faut donc libérer une case de la mémoire physique globalement

(parmi l'ensemble des cases) ou localement (parmi les cases occupées par les pages du processus en

défaut). Le système d'exploitation utilise un algorithme pour choisir une case à libérer. Les deux principaux

algorithmes sont :

● FIFO (First In, First Out)

● LRU (Least Recently Used)

1.3.1 Algorithme de remplacement de pages FIFO Avec cet algorithme, c'est la page la plus anciennement chargée qui est remplacée. La figure 5 donne un

exemple du fonctionnement de cet algorithme où l'on suppose une mémoire centrale composée de trois

cases initialement vides. La lettre D signale l'occurrence de défaut de pages.

Page 93: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Fig 5 : Remplacement de pages FIFO

1.3.2 Algorithme de remplacement de pages LRU Avec cet algorithme, c'est la page la moins récemment accédée qui est remplacée. La figure 6 donne un

exemple du fonctionnement de cet algorithme où l'on suppose une mémoire centrale composée de trois

cases initialement vides. La lettre D signale l'occurrence de défaut de pages.

Page 94: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Fig 6 : Remplacement de pages LRU

1.3.3 Complément sur le format d'une entrée de la table des pages d'un processus.

Finalement, une entrée de la table des pages d'un processus comprendra souvent les champs suivants :

● le bit V de validation pour indiquer si la page est présente ou non en mémoire centrale

● le champ A pour Accès qui contient les informations pour l'algorithme de remplacement de pages.

Par exemple, pour l'algorithme FIFO, ce champ contiendra la date de chargement de la page; pour

l'algorithme LRU, la date du dernier accès à la page.

● Le bit M pour Modification permet de savoir si la page a été modifiée lors de sa présence en

mémoire centrale. Si oui, il faudra réécrire cette page sur le disque avant de l'écraser par une

nouvelle page.

● Le champ D pour Droits, qui contient la définition des droits d'accès à la page en lecture / écriture /

exécution.

● Le champ n° de case physique, pour la conversion vers l'adresse physique.

1.4 Algorithme pour la conversion d'une adresse logique paginée en adresse physique.

Nous donnons ci-dessous le schéma général de l'algorithme suivi par le système d'exploitation lors de la

conversion d'une adresse logique vers une adresse physique. Une daresse est un couple constitué des

champs page et déplacement.

Procedure Conversion (in adresse_virtuelle, out adresse_physique)

debut

entrée := adresse_virtuelle.page + adresse_table(processus)

Si (entrée.V = FAUX)

alors

-- defaut de page

Charger_page(adresse_virtuelle.page, adresse_case);

entrée.V = vrai;

Page 95: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

entrée. case := adresse_case;

fsi

adresse physique := adresse_case + adresse_virtuelle.deplacement;

return (adresse_physique);

fin

Procedure Charger_Page (in page, out case)

debut

Si (Trouver_case_Libre( ) = FAUX)

alors

Choisir_case_à_libérer (case_à_liberer, page_victime);

si (page_victime.M = Vrai)

alors

Ecrire_Disque(page_victime)

Fsi

Fsi

Lire_Disque(case_à_liberer, page)

return (case_à_liberer)

fin

1.5 Notion d'écroulement

Page 96: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Définition : EcroulementOn appelle Ecroulement, une haute activité de pagination. Un processus s'écroule lorsqu'il passe plus de temps à paginer qu'à s'exécuter.

La figure 7 illustre ce phénomène. Elle représente le taux d'utilisation du processeur en fonction du degré

de multiprogrammation , c'est-à-dire en fonction du nombre de processus chargés en mémoire centrale.

Sur cette figure, on voit clairement que l'utilisation du processeur augmente jusqu'à un certain seuil au delà

duquel cette utilisation chute complètement : cette chute correspond à une trop grande activité de

pagination des processus qui passent le plus clair de leur temps en entrée/sortie car ils n'ont pas

suffisamment de cases mémoires disponibles pour contenir les pages relatives à leur espace de travail

courant.

Fig 7 : Phénomène d'écroulement

Page 97: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Mémoire virtuelle >QCM : un petit test de mémoire

2 Un petit test de mémoire

Exercice

Quelques questions sur la gestion mémoire et la mémoire virtuelle

Question : Le compactage de la mémoire centrale a pour objet

De charger des pages en mémoire centrale De rassembler des zones libres éparses pour en générer une seule et unique De charger un segment dans une zone libre de la mémoire centrale

Réponse :

De rassembler des zones libres éparses pour en générer une seule et unique

Question : Un processus dispose d'une table des pages a trois entrées. Deux pages de son espace d'adressage, les pages 1

et 3 sont chargées en mémoire centrale dans les cases 4 et 8. Il accède à l'adresse logique <page 3, deplacement 10>

Il se produit un défaut de page L'adresse physique générée est <case 5, deplacement 10 > L'adresse physique générée est <case 8, deplacement 10 > Il y a remplacement de pages

Réponse :

L'adresse physique générée est <case 8, deplacement 10 >

Question : Le même processus génère maintenant l'adresse logique <page 2, dep 24>

Il se produit un défaut de page L'adresse physique générée est <case 5, deplacement 10 > L'adresse physique générée est <case 8, deplacement 10 > Il y a remplacement de pages

Réponse :

Il se produit un défaut de pages.

Question : Une mémoire centrale comporte 3 cases libres initialement vides. Un processus effectue les accès suivants à ses

pages : 1,2,3,1, 4. Lors de l'accès à la page 4 :

Avec une stratégie FIFO, la page 1 est remplacée Avec une stratégie FIFO, la page 3est remplacée Avec une stratégie LRU, la page 1 est remplacée Avec une stratégie LRU, la page 3 est remplacée

Réponse :

Avec une stratégie FIFO, la page 1 est remplacée

Page 98: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Mémoire virtuelle >Exercices dirigés

3 Exercices dirigés

3.1 Exercices sur la mémoire virtuelle

3.1.1 Exercice 1 : Gestion d'une mémoire par zones On se propose de définir des algorithmes de gestion par zones d'une mémoire. Cette mémoire est gérée

par un allocateur qui utilise les deux procédures ALLOUER_ZONE(T,A) et LIBERER_ZONE(T,A) où T est

la taille de la zone et A l'adresse d'implantation en mémoire de la zone. L'allocateur entretient une liste des

zones non utilisées de la mémoire (appelées zones libres). Chaque zone libre com port e un en-tête de

deux mots qui contient la taille de la zone et l'adresse de la zone libre suivante dans la liste. On suppose

que cette liste est ordonnée suivant les adresses croissantes d'implantation en mémoire de ces zones.

Pour que l'allocateur puisse allouer une zone libre, il faut évidemment que cette zone soit de taille

supérieure ou égale à celle qui est requise. D'autre part, quand la taille de la zone libre est supérieure à la

taille demandée, le résidu est récupéré et forme une nouvelle zone libre.

3.1.1.1 Question 1

Représenter sous forme d'un schéma la structure de la liste des zones libres en mémoire.

3.1.1.2 Question 2

On suppose que l'algorithme d'allocation est "first fit", c'est-à-dire que la zone libre choisie est celle qui

apparaît la première dans la liste. Ecrire la procédure ALLOUER_ZONE.

3.1.1.3 Question 3

On suppose que l'algorithme d'allocation est "best fit", c'est-à-dire que la zone libre choisie est celle dont la

taille est la plus proche de celle demandée. Ecrire la procédure ALLOUER ZONE.

3.1.1.4 Question 4

A un instant t, la configuration de la mémoire est la suivante et on souhaite encore exécuter le processus

P5 de taille égale à 500K. Que convient-il de faire ?

3.1.1.5 Question 5

On impose qu'il n'y ait pas de zones contiguës en mémoire. Lorsqu'une zone est libérée et qu'il s'avère

qu'elle est contigüe à une zone libre, elles sont fusionnées pour ne plus en former qu'une.

Ecrire la procédure LIBERER_ZONE.

Page 99: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

3.1.2 Exercice 2 : Pagination

3.1.2.1 Question 1

Décrivez le format d'une entrée de la table des pages d'un processus.

3.1.2.2 Question 2

Décrivez sous forme algorithmique les opérations réalisées lors d'un accès à une adresse virtuelle de type

<n° de page, déplacement dans la page>

3.1.2.3 Question 3

Soit la liste des pages virtuelles référencées aux instants t = 1, 2,..., 11

3 5 6 8 3 9 6 12 3 6 10 La mémoire centrale est composée de 4 cases initialement vides. Représentez l'évolution de la mémoire

centrale au fur et à mesure des accès pour chacune des deux politiques de remplacement de pages FIFO

et LRU. Notez les défauts de pages éventuels.

3.1.2.4 Question 4

Décrivez le principe de la pagination appliquée à la segmentation de la mémoire. Comment s'effectue la

traduction d'une adresse virtuelle ?

3.1.2.5 Question 5

On considère une mémoire segmentée paginée pour laquelle les cases en mémoire centrale sont de 4Ko.

La mémoire centrale compte au total 15 cases numérotées de 1 à 15. Dans ce contexte, on considère deux

processus A et B.

● Le processus A a un espace d'adressage composé de trois segments S1A, S2A et S3A qui sont

respectivement de 8 Ko, 12 Ko et 4 Ko.

● Le processus B a un espace d'adressage composé de deux segments S1B et S2B qui sont

respectivement de 16 Ko et 8 Ko. Pour le processus A, seules les pages 1 et 2 du segment S1A, la

page 2 du segment S2A et la page 1 du segment S3A sont chargées en mémoire centrale

respectivement dans les cases 4, 5, 10, 6. Pour le processus B, seules les pages 2 et 3 du segment

S1B et la page 1 du segment S2B sont chargées en mémoire centrale respectivement dans les

cases 11, 2 et 15.

Représentez sur un dessin les structures allouées (table des segments, tables des pages) et la mémoire

centrale correspondant à l'allocation décrite. Voir la correction

Page 100: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Chapitre 7 : Interblocage Ce chapitre est consacré à l'étude de la synchronisation entre les processus . Il aborde tout d'abord la notion d’ interblocage .

Cours : Interblocage

Définitions des situations d’interblocage, de famine et de coalition Conditions nécessaires à l’obtention d’un interblocage Les différentes méthodes de traitement des interblocages

Les politiques de guérison Les politiques de prévention Les politiques d’évitement La politique de l’autruche

Table des figures

Page 101: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Interblocage>Interblocage

1 Cours : Interblocage

Dans un système multiprocessus, l' ordonnanceur alloue le processeur à chaque processus selon un

algorithme d' ordonnancement : la politique choisie conditionne l'ordre d'exécution des processus et très

souvent, les exécutions des processus s'entrelacent les unes avec les autres. Chaque processus dispose

d'un espace d'adressage propre et indépendant, protégé par rap port aux autres processus. Malgré tout, les

processus peuvent avoir besoin de communiquer entre eux pour échanger des données par exemple : ils

ne sont donc pas totalement indépendants et effectuent des accès concurrents aux ressource s logicielles

ou matérielles.

Définition : RessourceUne ressource désigne toute entité dont a besoin un processus pour s'exécuter. La ressource peut être matérielle comme le processeur ou en périphérique ou elle peut être logicielle comme une variable. Une ressource est caractérisée :

● Par un état : elle est libre ou occupée ● Par son nombre de points d'accès, c'est-à-dire le nombre de processus pouvant l’utiliser en même

temps. L’utilisation d'une ressource par un processus s’effectue en trois étapes : lorsque le processus a besoin de

la ressource il s'alloue cette ressource : c'est l'étape d'allocation de ressources. Une fois que le processus a

pu obtenir la ressource, il utilise la ressource durant un certain temps puis il rend la ressource : c'est l'étape

de restitution de la ressource. Les phases d'allocation et de restitution d'une ressources doivent assurer

que la ressource est utilisée conformément à son nombre de points d’accès

1.1 Définitions des situations d’interblocage, de famine et de coalition

Définition : InterblocageUn ensemble de n processus est dit en situation d’interblocage lorsque l'ensemble de ces n processus attend chacun une ressource déjà possédée par un autre processus de l'ensemble. Dans une telle situation aucun processus ne peut poursuivre son exécution. L'attente des processus est infinie.

Considérons un exemple. Soient deux ressources R1 et R2 qui sont toutes les deux à un seul point d’accès

c’est-à-dire que seul un processus à la fois a le droit d’utiliser la ressource. Soient également deux

processus P1 et P2. Ils utilisent tous les deux les ressources R1 et R2 pour effectuer un traitement. Les

processus P1 et P2 sont programmés tels que P1 demande d'abord à s'allouer R1 puis R2 avant de

commencer son traitement tandis que le processus P2 demande d'abord à s'allouer la ressource R2 puis la

ressource R1 avant de commencer son traitement. Les deux processus sont prêts à s'exécuter.

L'ordonnanceur choisit d'abord d’exécuter P1. P1 demande à prendre la ressource R1 et comme les

ressources sont initialement libres, P1 obtient la ressource R1. Puis l’ordonnanceur commute et choisit

maintenant d'exécuter le processus P2. P2 demande à s'allouer la ressource R2 et puisque la ressource R2

Page 102: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

est libre, P2 obtient la ressource R2. Maintenant P2 continue son exécution et demande à accéder à la

ressource R1. P2 est bloqué puisque R1 a été allouée au processus P1. Puisque P2 est bloqué, l'

ordonnanceur reprend l'exécution de P1 qui demande pour sa part maintenant à accéder à la ressource

R2. Comme R2 a été allouée au processus P2, P1 est à son tour bloqué. Les deux processus P1 et P2

sont maintenant en situation d'interblocage : en effet le processus P1 attend le processus P2 pour disposer

de la ressource R2 tandis que le processus P2 attend le processus P1 pour disposer de la ressource

R1.Comme aucun des deux processus ne peut poursuivre son exécution et donc rendre les ressource s

qu'il possède, le blocage est permanent : on dit que les processus P1 et P2 sont en situation d'interblocage

(ou étreinte fatale).

Définition : Coalition et famineOn parle de coalition de n processus contre p autres processus lorsqu’ un ensemble de n processus monopolisent des ressources au détriment des p autres processus. On dit également que les p processus qui ne peuvent pas s'exécuter faute de ressources sont en situation de famine.

1.2 Conditions nécessaires à l’obtention d’un interblocage

Les quatre conditions listées ci-dessus doivent être simultanément vérifiées pour qu'un interblocage puisse

se produire :

● Exclusion mutuelle : une ressource au moins doit se trouver dans un mode non partageable

● Occupation et attente : un processus au moins occupant une ressource attend d'acquérir des

ressources supplémentaires détenues par d'autres processus. Les processus demandent les

ressources au fur et à mesure de leur exécution.

● Pas de réquisition : les ressources sont libérées sur seule volonté des processus les détenant

● Attente circulaire : il existe un cycle d’attente entre au moins deux processus. Les processus

impliqués dans ce cycle sont en interblocage.

La figure 1 donne un exemple d’attente circulaire entre deux processus P1 et P2. Les deux processus P1 et

P2 utilisent les trois mêmes ressources : un lecteur de bandes magnétiques, un disque dur et une

imprimante. Le processus P1 commence par demander la bande magnétique puis l'imprimante et enfin le

disque avant d'effectuer son traitement. Le processus P2 commence par demander le disque puis

l'imprimante et enfin la bande magnétique avant de commencer son traitement. Sur le schéma de la figure

1, le processus P1 s'est exécuté et a obtenu la bande magnétique ainsi que l'imprimante. Le processus P2

lui a obtenu le disque et il demande maintenant à obtenir l'imprimante. L'imprimante a déjà été allouée au

processus P1, donc le processus P2 est bloqué. Le processus P1 ne peut pas poursuivre son exécution car

il est en attente du disque qui a déjà été alloué au processus P2. On a donc une attente circulaire entre P1

et P2 : en effet le processus P2 attend l'imprimante détenue par le processus P1 tandis que le processus

P1 attend le disque détenu par le processus P2. .

Page 103: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Fig 1 : Un exemple d’attente circulaire

1.3 Les différentes méthodes de traitement des interblocages

Il y a 4 méthodes de traitement des situations d'interblocage : les politiques de guérison, les politiques de

prévention ou d'évitements et la politique de "l'autruche".

1.3.1 Les politiques de guérison Une première politique est celle de détection/guérison des interblocages. Dans cette politique on autorise

les interblocages à se produire, on les détecte et on les résout. Pour cette politique, le système maintient un

graphe représentant l'allocation des ressources et les attentes des processus. Dans ce graphe dont un

exemple est donné sur la figure 2, on distingue deux types de sommets : les processus figurés par un rond

et les ressources figurées par un rectangle. Une flèche depuis un rectangle vers un rond indique que la

ressource a été allouée au processus. A contrario une flèche depuis un rond vers un rectangle indique que

le processus attend la ressource . Un cycle dans le graphe indique la présence d'un interblocage et tous les

processus appartenant à ce cycle sont en interblocage. Ainsi dans la figure 2, le graphe présente deux

cycles. Un premier cycle existe entre le processus P1, le processus P2, la ressource R1 et la ressource R2.

Un second cycle existe qui englobe le processus P1, le processus P2, le processus P3 ainsi que les

ressources R1, R3 et R2.

Le système met à jour le graphe à chaque nouvelle allocation de ressources ou demande d'allocation de

ressources. Régulièrement le système parcourt le graphe à la recherche de cycle. Si un cycle est

découvert, celui-ci est cassé en avortant les processus en interblocage appartenant au cycle. Ainsi sur

Page 104: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

l'exemple de la figure 2, l' interblocage est cassé en avortant le processus P2 et en réallouant la ressource

R1 au processus P1.

Ce type de politique présente plusieurs difficultés. Sa mise en oeuvre est coûteuse. Il faut maintenir le

graphe d'allocation, régulièrement parcourir le graphe à la recherche de cycles et enfin remédier à

l'interblocage par destruction de processus. Une autre difficulté tient à la période de parcours du graphe : si

cette période est petite, le graphe est parcouru souvent et consomme ainsi les ressources du système

inutilement car la probabilité d’un interblocage est faible. Si la période de parcours est grande, le graphe

sera parcouru moins souvent et la probabilité de trouver un interblocage sera plus forte. Mais, le nombre de

processus impliqués dans un l’interblocage risque d’être d’autant plus grand que la période de parcours du

graphe est grande. Par ailleurs le choix des processus à avorter pour remédier à un interblocage n’est pas

forcément facile. Une solution est de systématiquement détruire tous les processus impliqués dans

l’interblocage mais on peut essayer de raffiner cette solution en choisissant les processus à avorter : se

pose ici le problème du choix qui va conduire à éliminer l’interblocage en minimisant le nombre de

processus avortés ou le coût pour le système de ces avortements. Ainsi dans le cas de la figure 1, on

pourra choisir d'avorter P2 plutôt que P1 car P1 détient déjà deux ressources sur trois.

Fig 2 : Graphe pour la politique de guérison

1.3.2 Les politiques de prévention Dans les politiques de prévention, on ajoute des contraintes sur l'allocation des ressources afin de faire en

sorte qu'au moins une des 4 conditions nécessaires à l'interblocage ne sera jamais vérifiée. Les deux

seules conditions nécessaires sur lesquelles il est possible d'agir sont la condition d'occupation et d'attente

ainsi que la condition d'attente circulaire.

Page 105: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Pour la condition d’occupation et d’attente, on interdit à un processus de demander les ressources au fur et

à mesure de ses besoins. Un processus ne peut démarrer son exécution que lorsque toutes les ressources

ont pu lui être allouées (phase 0). Ainsi la deuxième condition d'occupation et attente ne peut jamais se

produire. Cependant l'utilisation résultante des ressources est mauvaise puisqu'un processus dispose des

ressources durant toute son exécution même s'il n'utilise celles-ci que pour un très petit temps. Pour la condition d’attente circulaire, une solution est d'imposer un ordre total sur l'ordre d'allocations des

différentes ressources du système : ainsi par exemple l'unité de bande doit toujours être demandée avant

le disque et le disque doit lui-même être toujours demandé avant l'imprimante. Ainsi il ne peut pas se

produire d'attente circulaire.

1.3.3 Les politiques d’évitement La troisième catégorie de solutions est celle des politiques d'évitement : ici, à chaque demande d'allocation

de ressource faite par un processus, le système déroule un algorithme appelé algorithme de sureté qui

regarde si cette allocation peut mener le système en interblocage. L’algorithme de sureté utilise des

informations fournies par les processus notamment pour chaque processus, leur plus grand besoin possible

en ressources. Si tel est le cas, l'allocation est retardée. C’est une vision pessimiste qui prédomine car

l’allocation est interdite dès que la possibilité de l’interblocage est détectée. Mais cela ne veut pas dire que

cet interblocage aura réellement lieu. Un exemple de la mise en œuvre de cette politique est l’algorithme

appelé algorithme du banquier.

1.3.4 La politique de l’autruche Une dernière solution, très simple, est de nier l'existence des interblocages et donc de ne rien prévoir pour

les traiter. Simplement la machine est redémarrée lorsque trop de processus sont en interblocage. Les trois

premières stratégies évoquées (prévention, évitement détection/guérison) sont des politiques qui coûtent

excessivement chères à mettre en œuvre. Aussi, comme la fréquence des interblocages dans un système

est relativement faible, la politique de l'autruche qui paraît dans un premier abord très "curieuse" se justifie

souvent.

Page 106: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Chapitre 8 : Synchronisation entre processus Ce chapitre est consacré à l'étude de la synchronisation entre les processus . Il présentela programmation des différents schémas de synchronisation à l'aide de l'outil sémaphore.

Cours 1 : Exclusion mutuelle

Un exemple simple pour définir le problème Réalisation d'une section critique à l'aide des interruptions matérielles L'outil sémaphore. Utilisation de cet outil pour réaliser l'exclusion mutuelle

Présentation de l'outil sémaphore L'opération INIT (Sem, Val) L'opération P (Sem) L'opération V (Sem) Signification de la valeur du compteur K

Réalisation d'une section critique à l'aide des sémaphores

Cours 2 : Allocation de ressources

Le schéma de l'allocation de ressources

Cours 3 : Le schéma lecteurs/rédacteurs.

Le schéma lecteurs/rédacteurs Le rédacteur (écrivain) Le lecteur Fonctionnement du schéma

Cours 4 : Le schéma producteur/consommateur.

Le schéma producteur/consommateur Programmation du schéma producteur/consommateur Fonctionnement du schéma producteur/consommateur

Exercices dirigés

Les processus Exercice 1

Question 1 Question 2

Exercice 2 Question 1 Question 2 Question 3 Question 4 Question 5

Exercice 3 Question 1 Question 2 Question 3

Page 107: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Exercice 4 Question 1 Question 2

Table des figures

Page 108: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Synchronisation entre processus>Exclusion mutuelle

1 Cours 1 : Exclusion mutuelle

Les processus disposent chacun d'un espace d'adressage protégé inaccessible aux autres processus. Pour

communiquer et s'échanger des données, les processus peuvent utiliser des outils de communications

offerts par le système. La manipulation de ces outils de communication doit se faire dans le respect de

règles de synchronisation qui vont assurer que les données échangées entre les processus restent

cohérentes et ne sont pas perdues. Un premier problème de synchronisation est celui de l'accès par un

ensemble de processus à une ressource critique , c'est-à-dire une ressource à un seul point d'accès donc

utilisable par un seul processus à la fois.

1.1 Un exemple simple pour définir le problème

Nous allons mettre en lumière le problème qui peut se poser sur un exemple simple : on considère donc un

petit programme de réservation de place (dans un avion, un train, …).

Réservation :

Si nb_place > 0

alors

Réserver une place

nb_place = nb_place - 1

fsi

Ce programme réservation peut être exécuté par plusieurs processus à la fois (autrement dit, le programme

est réentrant). La variable nb_place, qui représente le nombre de place restant dans l'avion par exemple,

est ici une variable d'état du système (de réservation) On considère l'exécution de deux processus Client_1

et Client_2 (figure 1) : Client_1 est commuté par l' ordonnanceur juste après avoir testé la valeur de la

variable nb_place (nb_place = 1). Client_2 s'exécute à son tour, teste nb_place qu'il trouve également

égale à 1 et donc effectue une réservation en décrémentant de une unité la variable nb_place. Nb_Place

devient égale à 0. Comme le processus Client_2 a terminé son exécution, Client_1 reprend la main.

Page 109: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Comme Client_1 avait trouvé la variable nb_place comme étant égale à 1 juste avant d'être commuté, il

continue son exécution en décrémentant à son tour nb_place. De ce fait, nb_place devient égale à –1 ce

qui est incohérent !!! Une même place a été allouée à deux clients différents !

Fig 1 : Un exemple de concurrence

La variable nb_place doit être accédée par un seul processus à la fois pour rester cohérente : ici en

l'occurrence le processus Client_1 qui a commencé la réservation en premier. Nb_Place est donc une

ressource critique.

Définition : Ressource critique Une ressource critique est une ressource accessible par un seul processus à la fois.

Définition : Section critiqueLe code d'utilisation de la ressource critique s'appelle une section critique. La section critique doit offrir au moins une propriété essentielle : celle de l'exclusion mutuelle c'est-à-dire assurer qu'effectivement elle ne sera jamais exécutée par plus d'un processus à la fois. Pour ce faire, la section critique est précédée par un prélude et suivie d'un postlude (le prélude et le postlude sont du code) qui doivent assurer cette propriété d'exclusion mutuelle.

Définition : Exclusion mutuelleLa propriété d'exclusion mutuelle assure qu'une ressource critique ne peut jamais être utilisée par plus de un processus à la fois.

Pour garantir l'exclusion mutuelle, il faut donc entourer l'utilisation de la variable nb_place d'un prélude et

d'un postlude. Le prélude prend la forme d'une "protection"qui empêche un processus de manipuler la

variable nb_place si un autre processus le fait déjà. Ainsi le processus Client_2 est mis en attente dès qu'il

cherche à accéder à la variable nb_place déjà possédée par le processus Client_1. Le postlude prend la

forme d'une"fin de protection" qui libère la ressource nb_place et la rend accessible au processus Client_2.

Page 110: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

1.2 Réalisation d'une section critique à l'aide des interruptions matérielles

Nous rappelons que le mécanisme sous-jacent au ré ordonnancement des processus peut être la survenue

d'une interruption horloge. Aussi une solution pour réaliser l'exclusion mutuelle est de masquer les

interruptions dans le prélude et de les démasquer dans le postlude. Ainsi les interruptions sont masquées

dès qu'un processus accède à la ressource nb_place et aucun événement susceptible d'activer un autre

processus ne peut être pris en compte. Cependant, cette solution est moyennement satisfaisante car elle

empêche l'exécution de tous les processus y compris ceux ne désirant pas accéder à la ressource critique .

De plus, le masquage et le démasquage des interruptions sont des opérations réservées au mode

superviseur et ne sont donc pas accessibles pour les processus utilisateurs.

Une autre solution est d'utiliser un outil de synchronisation offert par le système : les sémaphores.

1.3 L'outil sémaphore. Utilisation de cet outil pour réaliser l'exclusion mutuelle

1.3.1 Présentation de l'outil sémaphore Une sémaphore Sem est une structure système composée d'une file d'attente L de processus et d'un

compteur K, appelé niveau du sémaphore et contenant initialement une valeur Val. Cette structure ne peut

être manipulée que par trois opérations P(Sem), V(Sem) et Init(Sem, Val). Une propriété im port ante de

ces opérations est qu'elles sont indivisibles c'est-à-dire que l'exécution de ces opérations ne peut pas être

interrompues. Un outil sémaphore peut être assimilé à un distributeur de jetons; l'acquisition d'un jeton

donnant le droit à un processus de poursuivre son exécution.

1.3.1.1 L'opération INIT (Sem, Val) L'opération INIT a pour but d'initialiser le sémaphore, c'est-à-dire qu'elle met à vide la file d'attente L et

initialise avec la valeur Val le compteur K : on définit ainsi le nombre de jetons initiaux dans le sémaphore.

Init (Sem, Val)

début

masquer_it

Sem. K := Val;

Sem. L := vide;

demasquer_it

fin

Page 111: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

1.3.1.2 L'opération P (Sem) L'opération P(Sem) "attribue un jeton" au processus appelant si il en reste au moins un et sinon bloque le

processus dans Sem.L. L'opération P est donc une opération éventuellement bloquante pour le processus

élu qui l'effectue. Dans le cas du blocage, il y aura réordonnancement. Concrètement, le compteur K du

sémaphore est décrémenté de une unité. Si la valeur du compteur devient négative, le processus est

bloqué.

P (Sem)

début

masquer_it

Sem.K := Sem.K – 1;

Si Sem.K < 0

alors

ajouter ce processus à Sem.L

bloquer ce processus

fsi

demasquer_it

fin

1.3.1.3 L'opération V (Sem) L'opération V(Sem) a pour but de "rendre un jeton" au sémaphore. De plus, si il y a au moins un processus

bloqué dans la file d'attente L du sémaphore, un processus est réveille. La gestion des réveils s'effectue

généralement en mode FIFO (on réveille le processus le plus anciennement endormi). L'opération V est

une opération qui n'est jamais bloquante pour le processus appelant.

V (Sem)

Début

Page 112: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

masquer_it

Sem.K := Sem.K + 1;

Si Sem.K <= 0

alors

sortir un processus de Sem.L

réveiller ce processus

fsi

démasquer_it

fin

1.3.1.4 Signification de la valeur du compteur K

● Si Sem.K > 0, alors Sem.K est le nombre d'opérations P(Sem) passantes

● Si Sem.K <= 0,alors valeur_absolue(Sem.K) est le nombre de processus bloqués dans Sem.L

1.3.2 Réalisation d'une section critique à l'aide des sémaphores La réalisation d'une section critique à l'aide de l'outil sémaphore s'effectue en utilisant un sémaphore

MUTEX, dont le compteur K est initialisé à 1. Le prélude de la section critique correspond à une opération

P(MUTEX). Le postlude de la section critique correspond à une opération V(MUTEX).

INIT (MUTEX, 1);

P(MUTEX);

Section critique

V(MUTEX)

Page 113: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

La figure 2 illustre le fonctionnement de la section critique : Client_1 effectue en premier la demande de

réservation : le P(Mutex) est passant et le "jeton" est alloué au processus Client_1. Juste après le test de la

valeur de Nb_Place, Client_1 perd donc la main ; Client_2 est élu mais le P(Mutex) est bloquant : il n'y a

plus de jeton disponible dans le compteur du sémaphore. Comme Client_2 est bloqué, Client_1 reprend la

main. Lorsqu'il a achevé sa réservation, Client_1 relâche le jeton par un V(Mutex) : Client_2 est alors

réveillé et le jeton lui est attribué.

Fig 2 : Fonctionnement de la section critique

Page 114: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Synchronisation entre processus>Allocation de ressources

2 Cours 2 : Allocation de ressources

Un deuxième problème de synchronisation est celui de l'accès par un ensemble de processus à un

ensemble de n ressource s critiques : c'est une généralisation du cas précédent.

2.1 Le schéma de l'allocation de ressources

Le schéma d'allocation de N exemplaires de ressources exclusives est une généralisation du schéma

précédent : on considère à présent que l'on a N sections critiques. Le sémaphore d'allocation de

ressources –appelé Res ici – est initialisé au nombre d'exemplaires de ressources initialement disponibles

(N ici). Une opération d'allocation correspond à une opération P(Res) – allocation d'une jeton si il y en a au

moins un disponible – et la restitution de ressource correspond à une opération V(Res).

INIT (Res, N)

Allocation : P(Res)

Utilisation de la ressource Res

Restitution : V(Res)

Page 115: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Fig 3 : Allocation de ressource : P4 est bloqué

Fig 4 : Allocation de ressources : P4 est débloqué

Les figures 3 et 4 montrent sur un exemple le mécanisme d'allocation de ressources. Les processus P1, P2

et P3 demandent successivement à accéder à un exemplaire de ressources. Les trois opérations P sont

Page 116: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

passantes (on a initialement 3 jetons) et chaque processus obtient sa ressource. Cependant, le processus

P4 est bloqué : il n'y a plus de jeton dans le compteur du sémaphore et plus d'exemplaires de ressources.

Lorsque le processus P2 a terminé d'utiliser son exemplaire de ressources, il restitue celle-ci par une

opération V(Res). Le jeton est rendu et attribué au processus P4 ainsi que l'exemplaire de ressource libéré.

Page 117: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Synchronisation entre processus>Le schéma lecteurs/rédacteurs.

3 Cours 3 : Le schéma lecteurs/rédacteurs.

3.1 Le schéma lecteurs/rédacteurs

Dans ce problème, on considère la situation où un fichier ou une zone de mémoire commune (une table par

exemple) est accédée simultanément en lecture et en écriture. On a donc deux types de processus : des

lecteurs et des écrivains (ou des rédacteurs). Le contenu du fichier doit évidemment rester cohérent donc

les écritures ne doivent pas avoir lieu en même temps. Les lecteurs doivent lire une information stable, c'est-

à-dire que les lecteurs ne doivent pas lire une information en cours de modification. Il faut donc :

● une seule écriture en cours

ou

● une ou plusieurs lectures en cours (les lectures simultanées ne gênent pas puisqu'une lecture ne

modifie pas le contenu du fichier)

3.1.1 Le rédacteur (écrivain) Dans ce schéma donc, un écrivain exclut les autres écrivains et les lecteurs : un écrivain accède donc

toujours seul à la ressource , autrement dit il effectue des accès en exclusion mutuelle des autres écrivains

et des lecteurs. L'accès de l'écrivain est donc gérer selon ce schéma d'exclusion mutuelle à l'aide d'un

sémaphore ACCES initialisé à 1.

ECRIVAIN

INIT (Acces, 1);

M'assurer que l'accès au fichier est libre : P(Acces)

entrer en écriture

Libérer l'accès au fichier : V(Acces)

3.1.2 Le lecteur

Page 118: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Un lecteur exclut les écrivains mais pas les autres lecteurs. Il faut donc :

● le premier lecteur doit s'assurer qu'il n'y a pas d'accès en écriture en cours

● le dernier lecteur doit réveiller un éventuel écrivain

On voit donc qu'il faut compter le nombre de lecteurs qui accèdent à la ressource. On utilise pour cela une

variable NL, initialisée à 0. Cette variable NL va être accédée en concurrence par tous les lecteurs qui vont

soit incrémenter cette variable (un lecteur de plus), soit la décrémenter (un lecteur de moins). Pour que le

contenu de la variable reste cohérent, il faut que NL soit accédée en exclusion mutuelle. L'accès à la

variable sera donc gardé par un sémaphore MUTEX initialisé à 1. La figure 5 donne le schéma complet

correspondant au lecteur.

Fig 5 : Le lecteur

3.1.3 Fonctionnement du schéma La figure 6 donne un exemple du fonctionnement du schéma lecteurs/rédacteurs et résume le passage du

jeton entre les lecteurs et les rédacteurs. Le premier lecteur Lecteur_1 obtient le jeton par l'opération

P(ACCES). Les lecteurs suivants accèdent directement au fichier. Un rédacteur survenant à ce moment là

est arrêté par l'opération P(ACCES). Le dernier lecteur libère le jeton par l'opération V(ACCES). Le

rédacteur peut alors l'acquérir. Si pendant l'accès du rédacteur, un lecteur cherche à accéder au fichier,

celui-ci va acquérir le sémaphore Mutex, incrémenter NL et se bloquer sur le P(ACCES). Les lecteurs

suivants seront quant à eux bloqués sur le P(MUTEX), car le sémaphore Mutex n'aura pas été libéré par le

premier lecteur. Il est à noter que la solution donnée ici au schéma lecteurs/rédacteurs favorise la coalition

des lecteurs contre les écrivains. D'autres solutions existent qui interdisent les accès en lecture dès qu'un

écrivain est mis en attente.

Page 119: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Fig 6 : Fonctionnement du schéma lecteurs/rédacteurs

Page 120: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Synchronisation entre processus>Le schéma producteur/consommateur

4 Cours 4 : Le schéma producteur/consommateur.

4.1 Le schéma producteur/consommateur

Le dernier problème de communication / synchronisation que nous aborderons est celui du producteur /

consommateur. On considère ici deux processus communiquant par un tampon de N cases. Le producteur

dépose des messages dans le tampon dans la case pointée par l'index i. Le consommateur prélève les

messages dans la case pointée par l'index j. Le tampon est géré selon un mode FIFO circulaire en

consommation et en dépôt, c'est-à-dire :

● le producteur dépose les messages depuis la case 0 jusqu'à la case N-1, puis revient à la case 0

● le consommateur prélève les messages depuis la case 0 jusqu'à la case N-1, puis revient à la case

0.

Pour qu'aucun message ne soit perdu, les trois règles suivantes doivent être respectées :

● Un producteur ne doit pas produire si le tampon est plein

● Un consommateur ne doit pas faire de retrait si le tampon est vide

● Producteur et consommateur ne doivent jamais travailler dans une même case

4.2 Programmation du schéma producteur/consommateur

Dans ce problème deux types de ressource s distinctes peuvent être mises en avant :

● le producteur consomme des cases vides et fournit des cases pleines

● le consommateur consomme des cases pleines et fournit des cases vides

On a donc des ressources cases vides et des ressources cases pleines. Le problème peut maintenant se

rapprocher d'un problème d'allocation de ressources critiques tel que nous l'avons vu précédemment. On

associe donc un sémaphore à chacune des ressources identifiées et on initialise ce sémaphore au nombre

de cases respectivement vides ou pleines initialement disponibles (N et 0). On a donc deux sémaphores

VIDE initialisé à N et PLEIN initialisé à 0. Un producteur va s'allouer une case vide par une opération P(VIDE), remplir cette case vide et de ce fait

générer une case pleine. Le producteur va signaler cette nouvelle case pleine par une opération V(PLEIN),

cette opération réveillera éventuellement le consommateur en attente d'une case pleine. Un consommateur

va s'allouer une case pleine par une opération P(PLEIN), vider cette case pleine et de ce fait générer une

case vide. Le consommateur va signaler cette nouvelle case vide par une opération V(VIDE), cette

Page 121: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

opération réveillera éventuellement le producteur en attente d'une case vide.

Fig 7 : Schéma producteur / consommateur

4.3 Fonctionnement du schéma producteur/consommateur

Les deux figures ci-dessous illustrent le fonctionnement du schéma sur un tampon de deux cases.

Page 122: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Fig 8 : Fonctionnement du schéma producteur / consommateur

Fig 9 : Fonctionnement du schéma producteur / consommateur

Page 123: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Synchronisation entre processus>Exercices dirigés

5 Exercices dirigés

5.1 Les processus

5.1.1 Exercice 1 Soit un système composé de trois processus cycliques Acquisition, Exécution et Impression, et de deux

tampons Requête et Avis , respectivement composés de M et N cases.

● Le processus Acquisition enregistre chacune des requêtes de travail qui lui sont soumises par des

clients et les place dans le tampon Requête à destination du processus Exécution.

● Le processus Exécution exécute chaque requête de travail et transmet ensuite au processus

Impression un ordre d'impression de résultats déposé dans le tampon Avis.

● Le processus Impression prélève les ordres d'impression déposés dans le tampon Avis et exécute

ceux-ci.

5.1.1.1 Question 1

Programmez la synchronisation des trois processus à l'aide des sémaphores et des variables nécessaires

à la gestion des tampons

5.1.1.2 Question 2

On étend la système à trois processus Acquisition, trois processus Exécution et trois processus Impression.

Complétez la synchronisation précédente pour que celle-ci demeure correcte.

5.1.2 Exercice 2 Dans le service de gestion d'un magasin, un employé est chargé d'enregistrer les commandes des clients

dans un fichier COM, et d'éditer les bons de commandes correspondants, sur une imprimante IMP. D'autre

part, un processus facturation, lancé périodiquement lit les commandes à facturer dans COM et édite les

factures correspondantes sur l'imprimante IMP. Notons qu'une commande peut concerner plusieurs

Page 124: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

articles, et donc qu'un bon de commande ou une facture peut com port er plusieurs lignes.

5.1.2.1 Question 1

Sachant que l'opération de transfert élémentaire (lecture, écriture) concerne une ligne de commande,

montrer que l'état de COM peut être incohérent, de même que les impressions sur IMP. En déduire les

règles d'accès à COM et à IMP.

5.1.2.2 Question 2

Afin de résoudre le problème précédent, on fournit deux procédures qui garantissent un accès exclusif à

une ressource R:

● réserver (R) autorise l'accès à R par le demandeur Si R est libre ou bloque le demandeur Si R est

occupée.

● libérer (R) autorise l'accès à un autre demandeur s'il y en a en attente, sinon indique que R est libre.

On propose la solution suivante processus employé :

5.1.2.3 Question 3

Montrer que cette programmation permet d'éditer de manière consécutive, toutes les factures pour une

période donnée.

5.1.2.4 Question 4

Donner une définition de l'interblocage. Montrer que la programmation risque de conduire à un interblocage

.

Page 125: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

5.1.2.5 Question 5

Modifier le processus de facturation pour supprimer le risque d'interblocage.

5.1.3 Exercice 3 On considère un système de gestion de comptes bancaires permettant d'effectuer les deux opérations

suivantes :

● CredDeb_Compte (Numero_Compte, operation, somme) crédite ou débite le compte

Numero_Compte de la valeur somme en fonction de l'opération demandée (opération = débiter ou

créditer).

● Donner_Solde (Numero_Compte, var Solde) renvoie le solde du compte. Un client peut accéder à

chacune de ces opérations indépendamment les unes des autres et plusieurs clients accèdent en

parallèle au système de gestion des comptes bancaires. On supposera que chaque compte

bancaire est stocké sur disque dans un fichier appelé Fichier_Numéro_Compte.

On donne ci-dessous le pseudo code de ces deux fonctions :

5.1.3.1 Question 1

En prenant un exemple, montrez que des incohérences peuvent survenir sur le solde d'un compte si

plusieurs utilisateurs accèdent à un compte en parallèle.

5.1.3.2 Question 2

On accepte les lectures en parallèle des écritures, mais pas les écritures en parallèle. Ajoutez aux pseudo

code des deux procédures les opérations de synchronisation basées sur les sémaphores qui sont utiles.

5.1.3.3 Question 3

On interdit à présent les lectures en parallèle des écritures et les écritures en parallèle. Modifiez le pseudo

code des deux procédures pour réaliser cette synchronisation.

Page 126: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

5.1.4 Exercice 4 Lors de l'utilisation d'une ressource critique par une tâche, il est habituel d'ajouter aux paramètres de

description de la tâche ceux supplémentaires suivant :

● Cia : séquence d'instructions précédant l'appel de la ressource,

● Cib : séquence d'instruction de la section critique ,

● Cig : séquence d'instruction suivant la libération de la ressource avec Ci = Cia + Cib + Cig.

Toute tâche en cours d'utilisation d'une ressource critique peut être préemptée par une tâche plus prioritaire

qu'elle, et qui n'a pas besoin de cette ressource. On considère l'exemple d'une configuration de trois tâches

périodiques. Les tâches Tp1 et Tp3.

5.1.4.1 Question 1

Décrire graphiquement la séquence obtenue avec l'ordonnancement RM sur une durée correspondant à la

période d'étude. Indiquer l'instant où se produit une inversion de priorité entre Tp1 et Tp2 sur le graphe de

la séquence. Pour éviter cette inversion de priorité, on applique aux tâches se partageant des ressources le

principe de l'héritage de priorité : une tâche qui bloque une tâche plus prioritaire qu'elle, parce qu'elle utilise

une ressource dont elle a besoin, exécute sa section critique avec la priorité de la tâche bloquée.

5.1.4.2 Question 2

En appliquant le principe précédent, décrire graphiquement la séquence obtenue pour l'ordonnancement

RM sur la période d'étude. Indiquer sur le graphe de la séquence l'instant où se produit le blocage de Tp2

dû à l'héritage de priorité, et en conséquence la disparition de l'inversion de priorité Voir la correction

Page 127: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Chapitre 9 : Inversion de Priorité Ce chapitre est consacré à l'étude de la synchronisation entre les processus . Il présente le problème de l' inversion de priorités .

Cours : Inversion de priorité

Rappels Solutions mises en oeuvre

Le protocole de l'héritage de priorité Le protocole de la priorité plafonnée Evitement de l'interblocage

Table des figures

Page 128: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Inversion de Priorité>Inversion de priorité

1 Cours : Inversion de priorité

1.1 Rappels

Définition : Ressource critiqueUne ressource critique est une ressource à un seul point d'accès, c'est-à-dire accessible par un seul processus à la fois. L'utilisation s'effectue en exclusion mutuelle. L'allocation et la désallocation d'une ressource critique peuvent être gérées à l'aide d'un sémaphore R initialisé à 1. L'allocation se traduit alors par un P(R) et la restitution par un V(R).

Définition : Inversion de prioritésL'inversion de priorité est la situation pour laquelle une tâche de priorité intermédiaire (T3) s'exécute à la place d'une tâche de forte priorité (T1) parce que la tâche de forte priorité (T1) est en attente d'une ressource acquise par une tâche de plus faible priorité (T2). A priori, on ne peut pas borner le temps d'attente de la tâche de haute priorité qui risque ainsi de dépasser son échéance car l'exécution de T3 n'était pas prévisible.

Fig 1 : Exemple d'inversion de priorités

1.2 Solutions mises en oeuvre

Les solutions mises en œuvre ne cherchent pas à éviter le phénomène d'inversion de priorité, mais

permettent seulement de borner le temps d'attente des tâches sur l'accès aux ressource s. Ces bornes B,

peuvent ensuite être ajoutées au temps d'exécution des tâches et ainsi être intégrées dans les tests

d'acceptation des configurations. Il existe deux protocoles principaux :

Page 129: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

● le protocole de l'héritage de priorité

● le protocole de la priorité plafonnée

Ces protocoles sont mis en oeuvre de façon standard dans certains exécutifs temps réel dans les

opérations P et V des sémaphores.

1.2.1 Le protocole de l'héritage de priorité Principe : une tâche T détentrice d'une ressource R hérite de la priorité des tâches T' plus prioritaires mises

en attente sur cette ressource R, jusqu'à ce qu'elle libère la ressource R. Ainsi T est ordonnancée au plus

vite pour libérer le plus rapidement possible la ressource R. En effet, les tâches de priorité intermédiaire ne

peuvent plus s'exécuter puisque leur priorité devient inférieure à celle de la tâche T. Inconvénient : ce protocole ne prévient pas les interblocage s

Fig 2 : Protocole de l'héritage de priorités

On voit bien sur la figure 3 que l'exécution de la tâche T3 est repoussée au delà de l'exécution de T2 et T1

et ne vient plus de ce fait retarder l'exécution de T2.

1.2.2 Le protocole de la priorité plafonnée Principe : Chaque ressource R possède une priorité qui est celle de la tâche de plus haute priorité pouvant

demander son accès. Le principe est ensuite similaire au précédent : une tâche T détentrice d'une

ressource R hérite de la priorité des tâches T' plus prioritaires mises en attente sur cette ressource R,

Page 130: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

jusqu'à ce qu'elle libère la ressource R. Ainsi T est ordonnancée au plus vite pour libérer le plus rapidement

possible la ressource R Cependant, la tâche T ne peut obtenir la ressource R que si la priorité de R est

strictement supérieure à celles de toutes les ressources déjà possédées par les autres tâches T''. Par ce

biais, on prévient l'interblocage.

Fig 3 : Protocole de la priorité plafonnée

1.2.3 Evitement de l'interblocage Nous approfondissons ici la manière dont les protocoles que nous venons de voir se com port ent vis-à-vis

du problème de l'interblocage. Nous considérons deux tâches T1 et T2 telles que T1 utilise tout d'abord la

ressource R1 puis la ressource R2 tandis que T2 utilise d'abord la ressource R2 depuis la ressource R1. La

figure 4 décrit ce qui se passe en appliquant la protocole de l'héritage de priorité : ici la tâche T2 s'exécute

et obtient la ressource R2, puis T1 s'exécute et obtient la ressource R1.T1 est ensuite bloquée lorsqu'elle

demande à accéder à la ressource R2 et T2 hérite de la priorité de T1. T2 poursuit son exécution, demande

à accéder à R1 et se bloque à son tour. Rien n'empêche les deux tâches de tomber en interblocage.

Page 131: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Fig 4 : Interblocage et Protocole de l'héritage de priorité

Observons maintenant sur la figure 5 ce qui se passe avec le protocole de la priorité plafonnée. Lorsque T1

demande à accéder à R1, la ressource ne lui est pas accordée car la priorité de R1 n'est pas supérieure à

la priorité de R2, déjà possédée par T2. De cette manière, T2 poursuit son exécution en héritant de la

priorité de T1, libérant au plus vite les ressource s R2 et R1. L'interblocage est évité.

Page 132: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Fig 5 : Interblocage et Protocole de la priorité plafonnée

Page 133: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Chapitre 10 : Communication inter processus sur le réseau Ce chapitre est consacré à l'étude de la programmation socket qui permet de faire communiquer des processus placés sur des machines distantes, en utilisant les protocoles réseau UDP/IP et TCP/IP.

Cours : Rappels sur l'interconnexion de réseau et le protocole TCP/IP

Le modèle client – serveur Introduction Les différents types de serveur

L'interconnexion de réseau L’adressage IP dans le réseau virtuel La couche de protocoles TCP/IP

Exercices dirigés

Gestion du disque Exercice 1 : COMPARAISON DES POLITIOUES DE GESTION DU DISQUE

Question 1 Question 2

Exercice 2 : GESTION DE FICHIERS UNIX Exercice 3

Question 1 Question 2 Question 3

Table des figures

Page 134: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Communication inter processus sur le réseau>Rappels sur l'interconnexion de réseau et le protocole TCP/IP

1 Cours : Rappels sur l'interconnexion de réseau et le protocole TCP/IP

1.1 Le modèle client – serveur

1.1.1 Introduction Le modèle de communication client – serveur est un modèle de communication asymétrique. D'un côté, le

serveur ouvre un service et attend des requêtes provenant de clients. Pour chacune de ces requêtes, le

serveur effectue un traitement puis renvoie une réponse. De l'autre côté, le client émet des requêtes, puis

attend la réponse du serveur. Le client et le serveur étant placés sur des machines différentes, la

communication entre ces deux entités s'effectue par messages.

1.1.2 Les différents types de serveur Il est possible de caractériser un serveur selon deux critères :

● la manière dont il traite les requêtes provenant des clients : on distingue le serveur itératif du serveur

parallèle

● la manière dont il réagit vis-à-vis des pannes. On distingue le serveur sans état et le serveur à états .

Définition : Serveur itératif Un serveur itératif est un serveur qui ne peut traiter qu'une seule requête client à la fois. Il est composé d'un processus unique qui effectue les opérations de réception de requêtes, de traitement de requêtes et d'émission des réponses.

Définition : Serveur parallèleUn serveur parallèle est un serveur qui peut traiter plusieurs requêtes client à la fois. Il est composé d'un processus père qui effectue les opérations de réception de requêtes et pour chaque requête reçue, d'un processus fils qui effectue le traitement de la requête et l'émission de la réponse.

Définition : Serveur sans étatUn serveur sans état est un serveur qui ne conserve aucune informations sur les requêtes en cours de traitements. C'est un serveur sans mémoire qui en cas de panne, repart "à zéro".

Définition : Serveur à étatsUn serveur à état est un serveur qui conserve, sur un support non volatile, des informations sur les requêtes en cours de traitements. C'est un serveur à mémoire qui en cas de panne, peut restaurer une partie de son état au moment de la panne et reprendre des requêtes en cours.

1.2 L'interconnexion de réseau

L’interconnexion de réseaux a pour but d’interconnecter entre eux des réseaux hétérogènes. Le but

recherché est de créer un réseau virtuel qui masque complètement les réseaux physiques empruntés. Une

Page 135: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

machine M1 dialoguant avec une machine M2 passe par différents réseaux physiques sans en avoir

conscience. Le masquage des réseaux physiques empruntés suppose de disposer de deux éléments :

● Il faut un adressage des différentes machines appartenant au réseau virtuel construit

indépendamment de l’adressage physique particulier à chaque réseau traversé : C’est l’adressage

IP.

● Il faut un protocole de trans port de bout en bout permettant d’acheminer les données d’une machine

M1 vers la machine M2 à travers le réseau virtuel : c’est la pile de protocoles TCP/IP.

1.2.1 L’adressage IP dans le réseau virtuel Dans le réseau virtuel, une machine est désignée par une adresse que l’on appelle l’adresse IP. Une

adresse IP est un entier 32 bits divisé en deux parties : une partie adresse du réseau et une partie adresse

de la machine dans le réseau. Comme le montre la figure ci-dessous, il existe trois classes d’adresse IP, la

classe A, le la classe B, et la classe C, qui permettent chacune d’adresser plus ou moins de machines dans

un réseau. On associe à une adresse IP une notation que l’on appelle la notation décimale pointée qui

associe à chacun des octets composant l’entier de 32 bits sa valeur décimale séparé par un point.

Définition : Adresse IPUne adresse IP est un entier 32 bits divisé en deux parties : une partie adresse du réseau et une partie adresse de la machine dans le réseau. Elle permet de désigner une machine dans l’interconnexion de réseau. On associe à une adresse IP une notation que l’on appelle la notation décimale pointée qui associe à chacun des octets composant l’entier de 32 bits sa valeur décimale séparé par un point. Exemple : 128.163.10.12.

Fig 1 : L’adressage IP

Page 136: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

À chaque adresse IP, correspond un nom symbolique formé de caractères alphanumériques. La

composition du nom symbolique repose sur la notion de domaine. Le domaine correspond à un groupe

géographique (fr, jp) ou institutionnel (com , mil, edu). Le nom symbolique d’une machine est alors formé de

son nom suivi de l’ensemble des domaines auxquels elle appartient, chaque élément étant séparés par un

point. Ainsi la machine fermi.cnam.fr correspond à la machine de nom fermi dans le domaine cnam,

appartenant lui-même au domaine fr. Le nom symbolique est utilisé pour désigner une machine parce

qu’étant composés de caractères alphanumériques, il est plus aisément manipulable par un être humain.

Ce nom symbolique ne correspond à rien pour le réseau virtuel et doit être converti en l’adresse IP

correspondante. Cette conversion s’effectue grâce à des serveurs de nom et s’appelle la résolution de

noms. Enfin lors de la traversée des réseaux physiques, l’adresse IP doit être reconvertie vers l’adresse

physique du réseau, par exemple l’adresse ethernet pour un réseau de type Ethernet. Cette conversion de

l’adresse IP vers l’adresse physique s'appelle la résolution d’adresses.

Définition : Nom symboliqueÀ chaque adresse IP, correspond au nom symbolique formé de caractères alphanumériques. La composition du nom symbolique repose sur la notion de domaine. Le domaine correspond à un groupe géographique (fr, jp) ou institutionnel (com , mil, edu). Le nom symbolique d'une machine est alors formé de son nom suivi de l'ensemble des domaines auxquels elle appartient, chaque élément étant séparés par un point. Exemple : fermi.cnam.fr.

Fig 2 : L’adressage dans l’interconnexion de réseau

1.2.2 La couche de protocoles TCP/IP La figure 3 ci-dessous représente la couche de protocoles pour l’interconnexion de réseaux, cette couche

de protocoles étant couramment appelée couche de protocole TCP/IP. La couche IP correspond au niveau

Page 137: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

réseau. Elle permet de faire un adressage de machine à machine par le biais des adresses IP de chacune

des machines. La communication au niveau de la couche IP est une communication en mode minimal, c’est-

à-dire en mode non connecté et sans fiabilité : il n’y a pas de garantie de bon transfert des datagrammes

IP, lesquels sont acheminés indépendamment les uns des autres. La remise peut être faite dans le

désordre, il peut y avoir perte de datagrammes ainsi que des duplications.

Fig 3 : La pile de protocoles TCP/IP

La couche supérieure UDP/TCP correspond au niveau transport. A ce niveau, l’adressage s’effectue

d’applications à applications par le biais de la notion de port. Un port est un entier de 16 bits qui permet de

désigner une application à travers le réseau. Pour chacun des protocoles transport, il existe des ports.

Parmi ces ports, certains sont réservés aux applications courantes du réseau. Par exemple le port UDP 513

correspond à l’application who. Le port TCP 21 correspond à l’application ftp tandis que le port TCP 23

correspond à l’application telnet.

Définition : PortLe port est un entier de 16 bits permettant d’adresser une application sur la couche de protocole TCP/IP.

Le protocole UDP est en protocole transport non fiable basé sur IP. Les datagrammes UDP sont acheminés

indépendamment les uns des autres, il s’ensuit qu’il peut y avoir perte de datagrammes, duplication de

datagrammes et la remise n’est pas forcément effectuée selon l’ordre d’émission. Le protocole TCP/IP, au

contraire est un protocole de transport fiable orienté connexion, également basé sur IP. Avant l’échange

des données, une connexion est établie entre les deux entités communicantes : cette connexion est fiable :

il ne peut pas y avoir de perte de messages, il n'y a pas de duplication et la remise s’effectue dans l’ordre

d’émission.

Page 138: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Communication inter processus sur le réseau>Exercices dirigés

2 Exercices dirigés

2.1 Gestion du disque

2.1.1 Exercice 1 : COMPARAISON DES POLITIOUES DE GESTION DU DISQUE

2.1.1.1 Question 1

Expliquez comment se décompose l'accès à un secteur du disque. Quelle phase de cet accès est-elle la

plus coûteuse ?

2.1.1.2 Question 2

On considère un disque composé de 300 pistes numérotées de 0 à 299. Le bras est couramment

positionnée sur la piste 50.

La liste des requêtes (n° de piste cherchée) à servir donnée selon l'ordre d'arrivée est la suivante :

62, 200, 150, 60, 12, 120, 250, 45, 10, 100 Donnez l'ordre de service des requêtes et le déplacement de

bras total en résultant dans le cas d'un service FCFS, d'un service SSTF et d'un service SCAN sens initial

montant.

2.1.2 Exercice 2 : GESTION DE FICHIERS UNIX Un processus lit séquentiellement un fichier de 8 Mo, à raison de 256 octets à la fois. On suppose que les

blocs disque sont de 1024 octets et qu'un numéro de bloc occupe 4 octets. par ailleurs, le temps d'accès

moyen au disque est de 40 ms.

1/ Rappelez la structure d'une inode et d'un fichier Unix

2/ Le système ne gère pas de mécanisme de buffer cache.Donnez le nombre total d'accès disque

nécessaire et le temps d'attente en entrées/sorties

3/ Le système gère un mécanisme de buffer cache

3.a/ Rappelez le fonctionnement de ce mécanisme. Pourquoi la gestion de remplacement est-elle LRU

plutôt que FIFO ?

3.b/ Donnez le nombre total d'accès disque nécessaire et le temps d'attente en entrées/sorties

3.c/ L'écriture physique des blocs modifiés ne se fait que lorsqu'un bloc du buffer cache doit être libéré.

Quel avantage et inconvénient cela présente-t-il ? Quel est alors le rôle de la primitive système SYNC ?

2.1.3 Exercice 3 On considère un système de gestion de fichiers qui fait de l'allocation par zone. L'ensemble du disque est

Page 139: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

constitué de 100 blocs, numérotés de 0 à 99. Trois fichiers existent sur le disque, définis comme suit, le

reste de l'espace étant libre.

F1, Début = bloc5, Taille=20blocs,

F2, Début = bloc 25, Taille 5 blocs,

F3, Début = bloc 50, Taille 10 blocs.

Les trois questions sont indépendantes, c'est-à-dire que dans chaque cas, on part de la situation ci dessus.

2.1.3.1 Question 1

On veut rajouter 10 blocs au fichier FI. Quelles solutions proposez-vous suivant que l'implantation

séquentielle est simple ou avec extensions. Justifiez votre raisonnement et évaluez le coût de ces

solutions.

2.1.3.2 Question 2

On veut créer un fichier F4 de 10 blocs. Où proposez-vous de le mettre, en justifiant votre raisonnement.

2.1.3.3 Question 3

On veut créer un fichier F4 de 40 blocs. Quelles solutions proposez-vous suivant que l'implantation

séquentielle est simple ou avec extensions. Justifiez votre raisonnement et évaluez le coût de ces

solutions. Voir la correction

Page 140: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Chapitre 11 : Programmation socket Ce chapitre est consacré à l'étude de la programmation socket qui permet de faire communiquer des processus placés sur des machines distantes, en utilisant les protocoles réseau UDP/IP et TCP/IP.

Cours 1 : Utilitaires pour la programmation socket

Les fichiers de bases La définition d'une adresse machine Les primitives de conversion pour la représentation des entiers Le fichier /etc/hosts

Cours 2 : La création d'une socket

L'interface socket La création d'une socket L'attâchement d'une adresse à une socket Un exemple : la fonction creesock

Cours 3 : La programmation en mode UDP

Création des points de communication Echange de données en mode UDP Un exemple de programmation en mode UDP

Cours 4 : La programmation en mode TCP

Etablissement d'une connexion TCP/IP Echange de données en mode TCP Un exemple de programmation TCP : réalisation d'un serveur itératif Un exemple de programmation TCP : réalisation d'un serveur parallèle

Exercices dirigés

Programmation des sockets Exercice 1 Exercice 2 Exercice 3

Table des figures

Page 141: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Programmation socket>Utilitaires pour la programmation socket

1 Cours 1 : Utilitaires pour la programmation socket

1.1 Les fichiers de bases

Les fichiers suivants doivent être inclus dans tout programme nécessitant l'utilisation des sockets

# include <sys/types.h>

# include <netdb.h>

# include <netinet/in.h>

# include <arpa/inet.h> -- pour les conversions

1.2 La définition d'une adresse machine

Une adresse machine est définie selon le type ci-dessous :

struct in_addr {

u_long s_addr;

}

1.3 Les primitives de conversion pour la représentation des entiers

La manipulation des adresses au niveau des sockets va amener à manipuler deux entiers, l' adresse IP

d'une part qui est un entier de quatre octets, le port qui est un entier de deux octets. Les machines

admettent deux formats de représentation pour les entiers longs et les entiers courts. Dans le format dit Big

Endian, les octets composant un mot de 16 bits ou de 32 bits sont numérotés de la gauche vers la droite,

c'est-à-dire que l'octet de poids faible reçoit le plus gros numéro. Dans le format Little Endian, les octets

composant un mot de 16 bits ou de 32 bits sont numérotés de la droite vers la gauche, c'est-à-dire que

l'octet de poids faible reçoit le plus petit numéro. Une machine travaillant au format Big Endian interprétera

mal les entiers de format Little Endian qu'elle recevra. Aussi il est convenu que toute adresse émise sur le

Page 142: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

réseau doit être émise selon un format dit format réseau, par opposition au format local de la machine. Par

convention ce format réseau est le format Big Endian. Ainsi une machine en émission convertira toujours

les adresses IP et les numéros de ports de son format local vers le format réseau tandis qu'une machine en

réception convertira toujours les adresses IP et les numéros de port du format réseau vers le format local.

Quatre primitives sont fournies pour permettre la conversion des entiers courts et des entiers longs du

format local vers le format réseau et vice versa.

Fonctions de conversion d'entiers du format local au format réseau

-- conversion d'un entier court du format local au format réseau

u_short htons (hostshort)

u_short hostshort;

-- conversion d'un entier long du format local au format réseau

u_long htonl (hostlong)

u_long hostlong;

-- conversion d'un entier court du format réseau au format local

u_short ntohs (netshort)

u_short netshort;

-- conversion d'un entier long du format réseau au format local

u_long ntohl (netlong)

u_long netlong ;

1.4 Le fichier /etc/hosts

Le fichier /etc/hosts permet de faire la correspondance entre le nom symbolique d'une machine et son

adresse IP. Ce fichier contient une ligne par machine et donne pour chaque machine son adresse IP, son

nom symbolique principal, ses alias et éventuellement un commentaire.

/etc/hosts

Page 143: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Correspondance adresse IP et nom symbolique

Adresse IP nom symbolique alias, commentaire

163.173.128.6 Asimov.cnam.fr cnam iris

La fonction gethostbyname(nom) permet d'obtenir les informations relatives à une entrée du fichier

/etc/hosts en spécifiant comme paramètre de recherche le nom symbolique de la machine. Cette fonction

retourne une structure de type hostent dont la structure est donnée ci-après. La ligne #define h_addr

h_addr_list[0] permet de connaître la première adresse IP de la machine.

struct hostent {

char *h_name; /* official name of host */

char **h_aliases; /* alias list */

int h_addrtype; /* host address type */

int h_length; /* length of address */

char **h_addr_list; /* list of addresses from name server */

#define h_addr h_addr_list[0] /* address, for backward compatiblity */

};

-- obtenir l'entrée etc/host de la machine de nom "nom"

struct hostent *gethostbyname (nom)

char *nom;

Page 144: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Programmation socket>La création d'une socket

2 Cours 2 : La création d'une socket

2.1 L'interface socket

Comme le montre la figure 1 ci-dessous, l'interface socket correspond à deux choses :

● L'interface correspond tout d'abord à une interface de programmation, c'est-à-dire à un ensemble de

primitives permettant de programmer au dessus des couches transport TCP et UDP.

● Par ailleurs la socket correspond également à un point de communication sur la pile de protocole

TCP/IP c'est-à-dire qu'elle représente l'adresse d'une application donc un couple adresse IP -

numéro de port.

De plus, l'interface des sockets est compatible avec l'interface des fichiers Unix. Lors de la création d'une

socket, un descripteur de socket est alloué dans la table des fichiers ouverts du processus créateur. Ce

descripteur pointe sur une structure de socket plutôt que de pointer sur une structure de fichiers (inode).

Notamment la socket répond aux critères d'héritage père fils qui s'applique aux descripteurs de fichiers.

Fig 1 : l'interface socket

2.2 La création d'une socket

Page 145: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

La création d'une socket s'effectue par un appel à la primitive SOCKET dont le prototype est donné ci-

dessous. Le premier paramètre domaine prend la valeur AF_INET. Le second paramètre qui correspond au

type de service prend pour valeur soit SOCK_STREAM pour une communication en mode connecté, soit

SOCK_DGRAM pour une communication en mode datagramme. Le troisième paramètre protocole

correspond au protocole utilisé, donc soit TCP, soit UDP. Le champ peut être mis à zéro ce qui laisse alors

le système choisir le bon protocole en fonction du type spécifié.

int socket (domaine, type, protocole)

int domaine ;

int type ;

int protocole ;

La primitive de création socket retourne un entier correspond au descripteur de la socket créée. À l'issue de

l'opération de création, la socket tout comme un fichier est utilisable par le processus créateur et les

processus créés par ce processus créateur après la création de la socket.

Définition : Primitive Socketint socket (int domaine, int type, int protocole) : création d'une socket de communication

2.3 L'attâchement d'une adresse à une socket

Pour que des processus extérieurs à la famille du processus créateur puissent accéder à la socket il faut

maintenant attâcher une adresse à cette socket. La primitive permettant d'attâcher une adresse à une

socket est la primitive BIND . Le prototype de la primitive BIND est donné ci-dessous. Le premier paramètre

correspond au descripteur de socket créé par l'appel socket. Le second paramètre correspond à une

structure de type sockaddr_in. Cette structure permet de stocker une adresse d'application c'est-à-dire

notamment une adresse IP et un numéro de port . Le dernier paramètre lg correspond la taille de la

structure d'adresse en octets.

int bind (sock, p_adresse, lg)

int sock;

struct sockaddr_in *p_adresse;

int lg;

Page 146: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

struct sockaddr_in {

short sin_family; -- AF_INET

ushort sin_port; -- le numéro de port

struct in_adrr sin_addr ; -- l'adresse IP

char sin_zero[8]; -- huit 0

}

Définition : Primitive Bindint bind (int sock, sockaddr_in *p_adresse, int lg) : attâchement de l'adresse p_adresse à la socket sock

2.4 Un exemple : la fonction creesock

int creesock(port, type)

int port; /* le numero de port au format local */

int type ; /* le type de la socket : SOCK_DGRAM ou SOCK_STREAM

{

int sock;

struct sock_addr_in nom;

int longueur;

/* Création de la socket */

if((sock = socket(AF_INET,type,0)) < 0)

Page 147: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

{

perror("socket");

exit(1);

}

/* preparation de l'adresse */

/* la zone mémoire pour l'adresse est mise à 0 */

bzero(&nom,sizeof(nom));

nom.sin_family = AF_INET;

nom.sin_port = htons(port) ; /* passage au format réseau */

nom.sin_addr.s_addr = INADDR_ANY ; /* n'importe quelle adresse IP de la machine

locale */

/attâchement de la socket */

if(bind(sock, &nom, sizeof(nom)) < 0)

{

perror("bind");

exit(2);

}

return(sock) ;

Page 148: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Programmation socket>La programmation en mode UDP

3 Cours 3 : La programmation en mode UDP

La communication en mode UDP est un mode de communication non fiable, sans connexion. Une fois, les

points de communication crées sur la pile de protocole, le client et le serveur s'échangent directement les

données sous forme de datagrammes UDP. Chaque envoi doit contenir l'adresse du destinataire.

3.1 Création des points de communication

Chaque entité communicante, le client et le serveur, commencent par créer une socket de type

SOCK_DGRAM, puis chacun attâche à cette socket son adresse locale, formée du numéro de port de

l'application (client ou serveur) et de l' adresse IP des machines client ou serveur.

Fig 2 : Création des points de communication

3.2 Echange de données en mode UDP

L'échange de données s'effectuent sous forme de datagrammes. Sa structure est préservée à la réception,

c'est-à-dire qu'une opération de réception permet toujours de récupérer l'intégralité d'un datagramme émis

Page 149: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

sur une opération d'envoi. La primitive sendto permet l'envoi d'un datagramme. La primitive recvfrom permet la réception d'un

datagramme. La primitive recvfrom est bloquante, c'est-à-dire qu'un processus effectuant un appel à la

primitive recvfrom est bloqué jusqu'à avoir effectivement reçu un datagramme.

Fig 3 : Echange de datagrammes

int sendto(sock, msg, lg, option, p_dest, lgdest)

int sock; -- socket pour l'émission

char *msg; -- adresse du message émis

int lg; -- taille du message en octets

int option;

struct sockaddr_in *p_dest; -- adresse du destinataire : adresse IP et

n°de port

int lgdest; -- taille de la structure d'adresse en octets

Page 150: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

int recvfrom(sock, msg, lg, option, p_exp, lgexp)

int sock; -- socket de réception

char *msg; -- adresse d'une zone pour recevoir le message

int lg; -- taille en octets de la zone de réception

int option; -- 0 ou MSG_PEEK ( le message est lu sans être extrait de

la socket)

struct sockaddr_in *p_exp; -- structure adresse contenant l'adresse de

l'expéditeur du message au retour de la primitive

int *lgexp; -- taille de la structure adresse

Définition : Primitive Sendtoint sendto (int sock, char *msg, int lg, int option, struct sockaddr_in *p_dest, int lgdest) : envoi d'un datagramme msg à *p_dest.

Définition : Primitive Recvfromint recvfrom (int sock, char *msg, int lg, int option, struct sockaddr_in *p_exp, int *lgexp) : réception d'un datagramme msg envoyé par *p_exp.

3.3 Un exemple de programmation en mode UDP

Nous donnons ci-dessous un exemple de client /serveur en mode UDP. Le client envoie une ligne au

serveur qui l'affiche.

CLIENT

#include <stdio.h>

#include <errno.h>

#include <sys/types.h>

Page 151: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

#include <sys/socket.h>

#include <netinet/in.h>

#include <netdb.h>

#define PORTC 6259 -- port du client

#define PORTS 6260 -- port du serveur

main()

{

struct hostent *h;

struct sockaddr_in sin;

char buf[20];

int sock, portc, ports;

/* création socket et attâchement d'une adresse*/

sock = creesock(portc, SOCK_DGRAM)

/* Construction adresse du serveur */

if(!(h=gethostbyname("dirac.cnam.fr")))

{

perror("dirac.cnam.fr");

Page 152: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

exit(2);

}

bzero(&sin,sizeof(sin));

sin.sin_family = AF_INET;

-- copie de l'adresse IP du serveur dans la structure d'adresse sockaddr_in sin

bcopy(h->h_addr,&sin.sin_addr,h->h_length);

ports = htons(PORTS);

sin.sin_port = port s;

if (sendto(sock, "bonjour, serveur !! ", 20, 0, &sin, sizeof(sin)) == -1)

{

perror ("pb sendto");

exit();

}

printf ("Message envoye \n");

close (sock);

}

Page 153: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

SERVEUR

#include <stdio.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <netdb.h>

#define PORTS 6260 -- port du serveur

main()

{

int namelen, sock;

char buf[20];

struct sockaddr_in adr;

int lg, n;

/* Création socket */

if((sock = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP)) < 0)

{

Page 154: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

perror("socket");

exit(1);

}

/* preparation de l'adresse */

/* la zone mémoire pour l'adresse est mise à 0 */

bzero(&sin,sizeof(sin));

sin.sin_family = AF_INET;

sin.sin_port = htons(PORTS) ; /* passage au format réseau */

sin.sin_addr.s_addr = INADDR_ANY ; /* n'importe quelle adresse IP de la machine

locale */

/* Attâchement socket */

if(bind(sock, &sin, sizeof(sin)) < 0)

{

perror("bind");

exit(2);

}

for (;;)

{

lg = sizeof(adr);

n = recvfrom (sock, buf, 20, 0, &adr, &lg);

Page 155: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

printf("Chaine recu %s\n", buf);

}

close(sock);

Page 156: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Programmation socket>La programmation en mode TCP

4 Cours 4 : La programmation en mode TCP

La communication en mode TCP est un mode de communication fiable, orientée connexion. Une fois, les

points de communication crées sur la pile de protocole, le client et le serveur doivent établir une connexion

avant de pouvoir s'échanger des données sous forme flux d'octets TCP. L'échange des adresses entre

émetteur et récepteur s'effectue au moment de l'établissement de la connexion. Lorsque l'échange est

terminé, la connexion est fermée.

4.1 Etablissement d'une connexion TCP/IP

Fig 4 : Création des points de communication TCP et établissement de connexion

Chaque entité communicante, le client et le serveur, commencent par créer une socket de type

SOCK_STREAM, puis chacun attâche à cette socket son adresse locale, formée du numéro de port de

l'application (client ou serveur) et de l' adresse IP des machines client ou serveur.

Le serveur, ensuite, se prépare en vue de recevoir des connexions de la part de clients. Pour cela, il fait

d'abord appel à la primitive Listen pour dimensionner sa file de connexions pendantes sur sa socket initiale

que nous appelerons la socket d'écoute..

Page 157: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

int listen (sock, nb)

int sock; /* socket d’écoute */

int nb; /* nb, nombre de connexions pendantes maximal */

Le serveur se met alors en attente de connexion par un appel à la primitive accept. Cette primitive est

bloquante, c'est-à-dire que le serveur reste bloqué sur cette primitive jusqu'à ce qu'un client établisse

effectivement une connexion.

int accept (sock, p_adr, p_lgadr)

int sock; /* socket d'écoute */

struct sockaddr_in *p_adr; /* adresse de la socket connectée */

int *p_lgadr; /* taille en octets de la structure adresse */

De son côté, le client demande l'établissement d'une connexion en faisant appel à la primitive connect. De

la même manière, la primitive connect est bloquante pour le client, c'est-à-dire que le client est bloqué

jusqu'à ce que le serveur accepte sa connexion.

int connect (sock, p_adr, lgadr)

int sock; /* socket client */

struct sockaddr_in *p_adr; /* adresse de la socket du serveur */

int lgadr; /* taille en octets de la structure adresse */

Au moment de l'établissement de la connexion, le serveur crée une nouvelle socket que l'on appelle la

socket de service. Le descripteur de cette nouvelle socket est retourné par la primitive accept. Cette socket

de service est la socket sur laquelle s'effectuent ensuite tous les échanges de données entre les deux

entités connectées par le biais des primitives read et write..

Page 158: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Fig 5 : Création de la socket de service au moment de l'établissement de la connexion

Définition : Primitive Listenint listen (int sock, int nb) : création de la file de connexions pendantes (n max) associée à la socket d'écoute sock

Définition : Primitive Connectint connect (int sock, struct sockaddr_in *p_adr, int lgadr) : demande d'établissement de connexion avec la socket distante dont l'adresse est *p_adr

Définition : Primitive Accept sock_service = accept (int sock_ecoute, struct sockaddr_in p_adr, int p_lgadr) : Acceptation de connexion sur la socket locale d'écoute avec la socket distante p_adr. En résultante, création de la socket de service sock_service.

4.2 Echange de données en mode TCP

L'échange de données s'effectuent sous forme de flux d'octets. Il faut prendre garde au fait que la structure

des messages n'est pas conservée et le découpage en messages identifiables correspondant aux

différents envois n’est pas préservé sur la socket destinataire.

● Une opération de lecture peut provenir de différentes opérations d’écriture

● Une opération d’écriture d’une chaine longue peut provoquer son découpage, les différents

fragments étant accessibles sur la socket destinataire

La primitive write permet l'envoi d'un ensemble d'octets constituant un message. La primitive read permet la

réception d'un ensemble d'octets.

int write (sock, msg, lg)

Page 159: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

int sock;

char *msg; /* adresse de la zone mémoire contenant du message à envoyer

*/

int lg; /* taille en octets du message */

int read (sock, msg, lg)

int sock;

char *msg; /* adresse de la zone mémoire pour recevoir le message */

int lg; /* taille en octets du message */

Définition : Primitive Read (réseau)int read (int sock,char *msg, int lg) : lecture du message msg de taille lg octets sur la socket sock en mode connecté

Définition : Primitive Write (réseau)int write (int sock,char *msg, int lg) : écriture du message msg de taille lg octets sur la socket sock en mode connecté

4.3 Un exemple de programmation TCP : réalisation d'un serveur itératif

Nous donnons ci-dessous un premier exemple de programmation d'un client-serveur en mode TCP : le

serveur écrit dans cet exemple correspond à un serveur de type itératif composé d'un seul processus qui

reçoit les connexions, remplit le service demandé, puis rompt la connexion avant d'en accepter une

nouvelle. Le service remplit par le serveur consiste tout simplement à afficher une chaine de caractères

envoyées par le client. On notera la procédure de lecture de la chaine de caractères sur la socket serveur,

qui fait une lecture caractère par caractère jusqu'à trouver le caractère '\n' afin d'être certain d'avoir

récupéré la chaine en entier.

SERVEUR

Page 160: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

************************************************

Serveur affichant à l'écran la ligne envoyée par le client

************************************************

#include <stdio.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <netdb.h>

#include <pwd.h>

#define LGUSER 20

#define LGREP 256

#define PORT 6259 /* port du serveur */#define TRUE 1

main ()

{

int lg, port, sock, nsock, d; /* sock socket d'écoute, nsock socket de service

*/

struct sockaddr_in adr; /* adresse de la socket distante */

Page 161: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

if ((sock = creesock(port,SOCK_STREAM)) == -1) {

fprintf (stderr, "Echec creation/liaison de la socket\n");

return;

}

/* Creation de la file des connexions pendantes */

listen(sock,5);

/* Boucle d'acceptation d'une connexion */

while (TRUE) { /* Attente de question sur la socket */

lg = sizeof(adr);

nsock = accept (sock,&adr,&lg);

service(nsock);

close(nsock);

}

}

************************************************

Fonction service

************************************************

int service (nsock)

Page 162: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

int nsock;

{

int n;

char line[MAXLINE];

n = readline(nsock,line,MAXLINE);

if (n < 0) {

perror ("Pb lecture de ligne");

return; }

fputs (line, stdout);

}

*********************************************************

Fonction readline : lit une ligne sur la socket fd caractère par caractère

*********************************************************

int readline(fd,ptr,maxlen)

int fd;

char *ptr;

int maxlen;

{

Page 163: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

int n, rc;

char c;

for (n=1; n<maxlen; n++)

{

rc = read(fd, &c, 1);

*ptr++ = c;

if (c == '\n')

break;

}

*ptr = '\0';

return(n);

}

CLIENT

************************************************

Client : envoie une ligne à afficher au serveur

************************************************

Page 164: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

#include <stdio.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <netdb.h>

#define PORT 6259 /* port du serveur */

#define PORTC 6260 /* port du client */

#define TRUE 1

main ()

{

int sock;

struct sockaddr_in adr;

struct hostent *hp;

if ((sock = creesock(PORTC,SOCK_STREAM)) == -1) {

fprintf (stderr, "Echec creation/liaison de la socket\n");

return;

}

Page 165: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

/* preparation de l'adresse de la socket destinataire */

if ((hp = gethostbyname("dirac.cnam.fr")) == NULL) {

perror ("Nom de machine irrecuperable");

return;

}

bzero ((char *)&adr,sizeof(adr));

adr.sin_port = htons(PORT);

adr.sin_family = AF_INET;

bcopy (hp->h_addr, &adr.sin_addr, hp->h_length);

connect (sock, (struct sockaddr *)&adr, sizeof(adr));

client(sock);

close(sock);

}

************************************************

Fonction client

************************************************

int client(sock)

Page 166: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

int sock;

{

int n;

char line[MAXLINE];

printf (" Donnez une ligne : \n");

fgets (line, MAXLINE, stdin);

n = strlen (line);

if (write (sock, line, n) != n)

perror ("Pb ecriture de ligne");

}

4.4 Un exemple de programmation TCP : réalisation d'un serveur parallèle

Le serveur donné en exemple dans le paragraphe précédent est un serveur itératif. Une fois que le serveur

a accepté une connexion, de la part d'un client, les clients suivants sont mis en attente jusqu'à ce que le

serveur ait fini de traiter le premier client et revienne sur l'accept pour accepter une nouvelle connexion.

Durant tout le traitement d'un client connecté, le serveur travaille sur la socket de service et la socket

d'écoute ne sert pas.

Il serait ici plus performant de créer un serveur parallèle tel que :

● Le processus père accepte les connexions sur la socket d'écoute et crée une socket de service pour

la connexion acceptée.

● Pour chaque connexion acceptée, le processus père crée un processus fils. Ce fils hérite

naturellement de la socket d'écoute et de la socket de service ouvertes par son père.

● Le fils s'occupe de servir le client : il travaille donc avec la socket de service et ferme la socket

d'écoute.

● Le père est libéré du service du client réalisé par son fils. Il ferme la socket de service et retourne

immédiatement accepter de nouvelles connexions sur la socket d'écoute. Dans ce cas de figure, on

aura donc autant de fils crées que de connexions acceptées par le père.

Page 167: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Nous donnons ci-dessous le code correspondant pour le même exemple que précédemment. Seul le

processus serveur est modifié. SERVEUR #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include

<netdb.h> #include <pwd.h> #include <sys/wait.h> #include <signal.h> #define LGUSER 20 #define

LGREP 256 #define PORT 6259 #define TRUE 1 #define MAXLINE 512 main () { int lg, port, sock, nsock, d, pid; struct sockaddr_in adr; /* adresse de la socket distante */ if ((sock = creesock(port,SOCK_STREAM)) == -1) { fprintf (stderr, "Echec creation/liaison de la

socket\n"); return; } /* Creation de la file des connexions pendantes */ listen(sock,5); /* Boucle d'acceptation d'une connexion */ while (TRUE) { /* Attente de question sur la socket */ lg = sizeof(adr); nsock = accept

(sock,etadr,etlg); /* creation d’un fils pour chaque connexion acceptée */ pid = fork(); if (pid == -1) { fprintf (stderr, "pb fork\n"); return; } else if (pid == 0) {

/* Je suis le fils */ close(sock); service(nsock); close(nsock); exit();

} else close (nsock); } } On notera ici que les processus fils deviennent zombies car le processus père n'effectue pas de wait pour

récupérer la mort de ses fils. Pour éviter cette situation, on peut ajouter la ligne suivante au code du serveur

qui permet au père d'ignorer le signal mort du fils et de détruire automatiquement en conséquence les

processus zombies : Signal (SIGCHLD, SIG_IGN);

Page 168: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Programmation socket>Exercices dirigés

5 Exercices dirigés

5.1 Programmation des sockets

5.1.1 Exercice 1 Création et attâchement d'une socket 1.a/ Rappelez la définition d'une socket et le format de la primitive

permettant sa création.

1.b/ A l'issue de cette création, quels sont les processus pouvant utiliser la socket ?

1.c/ Quelle primitive permet d'associer une adresse réseau à une socket ?

5.1.2 Exercice 2 Communication en mode datagramme ou en mode connecté On considère l'application clients-serveur

répartie suivante : le serveur gère une table annuaire de couple (nom_de_personne, numero_telephone).

Des clients interrogent le serveur en lui transmettant un nom_de_personne. Le serveur répond en envoyant

au client le couple correspondant (nom_de_personne, numero_telephone) si il a été trouvé dans le table,

sinon le message "usager inconnu". 2.a/ Donnez le pseudo-code d'un client et du serveur en supposant une communication en mode

datagramme. Faites apparaitre dans ce pseudo-code les primitives de communication utilisée. 2.b/ Donnez le pseudo-code d'un client et du serveur en supposant une communication en mode connecté.

Faites apparaitre dans ce pseudo-code les primitives de communication utilisée. 2.c/ Le serveur crée un fils pour gérer chacune des requêtes des clients. Donnez le pseudo-code du

serveur et de l'un de ses fils en supposant une communication en mode connecté. 2.d/ On suppose à présent que le serveur peut accepter deux types de requêtes de la part des clients :

● les requêtes de consultation de l'annuaire similaires à celles traitées jusqu'à présent

● les requêtes de modification de l'annuaire : le client fournit alors au serveur un couple

(nom_de_personne, numero_telephone) que le serveur enregistre dans sa table. Chacun des deux

types de requêtes est accepté par le serveur sur deux ports différents : PORTC pour les

consultations et PORT M pour les modifications. Les consultations et les modifications peuvent être

demandées en parallèle par des clients différents. Le serveur doit traiter les demandes comme elles

se présentent. Donnez le pseudo-code du serveur. A votre avis, faut-il protégez l'accès à la table de

l'annuaire par un schéma de synchronisation ?

Page 169: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

5.1.3 Exercice 3 Le démon INETD

3.a/ Quel est le rôle du démon Inetd ?

3.b/ Ecrivez le pseudo-code de ce démon.

Voir la correction

Page 170: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Lexique

Adresse IPUne adresse IP est un entier 32 bits divisé en deux parties : une partie adresse du réseau et une partie adresse de la machine dans le réseau. Elle permet de désigner une machine dans l’interconnexion de réseau. On associe à une adresse IP une notation que l’on appelle la notation décimale pointée qui associe à chacun des octets composant l’entier de 32 bits sa valeur décimale séparé par un point. Exemple : 128.163.10.12.

Application temps réelUne application temps réel est une application pour laquelle le facteur temps est la principale contrainte à respecter. L' application doit fournir un résultat juste mais si celui-ci est fourni hors délai, alors il ne pourra pas être validé.

Coalition et famineOn parle de coalition de n processus contre p autres processus lorsqu’ un ensemble de n processus monopolisent des ressources au détriment des p autres processus. On dit également que les p processus qui ne peuvent pas s'exécuter faute de ressources sont en situation de famine.

Commandes et appels systèmesLes appels systèmes constituent l'interface du système d'exploitation et sont les points d'entrées permettant l'exécution d'une fonction du système. Les appels système sont directement appelables depuis un programme. Les commandes permettent elles d'appeler les fonctions du système depuis le prompt de l'interpréteur de commande (shell, invite du dos).

Commutations de contexteLe passage entre les modes utilisateur / superviseur s'accompagne de commutations de contexte (sauvegarde du contexte utilisateur - changement de mode d'exécution - restitution du contexte utilisateur)

Compactage de la mémoire centraleLe compactage de la mémoire centrale consiste à déplacer les programmes en mémoire centrale de manière à ne créer qu'une seule et unique zone libre.

Défaut de pageLe défaut de page est un déroutement qui oblige le processeur à suspendre l'exécution du programme en cours pour lancer une entrée/sortie qui charge la page manquante en mémoire centrale dans une case libre.

Degré de multiprogrammationon définit le degré de multiprogrammation comme étant le nombre de processus présents en mémoire centrale.

EcroulementOn appelle Ecroulement, une haute activité de pagination. Un processus s'écroule lorsqu'il passe plus de temps à paginer qu'à s'exécuter.

Exclusion mutuelleLa propriété d'exclusion mutuelle assure qu'une ressource critique ne peut jamais être utilisée par plus de un processus à la fois.

Page 171: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

File de messages (MSQ)Une file de messages est un outil de communication entre processus Unix non nécessairement affiliés placés sur une même machine qui implémente le concept de boîtes aux lettres. Une file de message ou MSQ est identifiée par une clé.

FragmentationAllocations et désallocations successives des programmes en mémoire centrale créent des trous, c'est-à-dire des zones libres de taille insuffisante en mémoire centrale : la mémoire centrale est alors fragmentée.

InterblocageUn ensemble de n processus est dit en situation d’interblocage lorsque l'ensemble de ces n processus attend chacun une ressource déjà possédée par un autre processus de l'ensemble. Dans une telle situation aucun processus ne peut poursuivre son exécution. L'attente des processus est infinie.

Inversion de prioritésL'inversion de priorité est la situation pour laquelle une tâche de priorité intermédiaire (T3) s'exécute à la place d'une tâche de forte priorité (T1) parce que la tâche de forte priorité (T1) est en attente d'une ressource acquise par une tâche de plus faible priorité (T2). A priori, on ne peut pas borner le temps d'attente de la tâche de haute priorité qui risque ainsi de dépasser son échéance car l'exécution de T3 n'était pas prévisible.

Mode superviseurLe mode superviseur est le mode d'exécution du système. C'est un mode d'exécution privilégié qui autorise notamment l'appel à des instructions interdites en mode utilisateur (manipulation des interruptions). Ce mode assure la protection du système d'exploitation. Le passage du mode utilisateur vers le mode superviseur est soit provoqué par un appel système, soit par une trappe, soit par l'arrivée d'une interruption

Nom symboliqueÀ chaque adresse IP, correspond au nom symbolique formé de caractères alphanumériques. La composition du nom symbolique repose sur la notion de domaine. Le domaine correspond à un groupe géographique (fr, jp) ou institutionnel (com , mil, edu). Le nom symbolique d'une machine est alors formé de son nom suivi de l'ensemble des domaines auxquels elle appartient, chaque élément étant séparés par un point. Exemple : fermi.cnam.fr.

OrdonnancementLa fonction d'ordonnancement gère le partage du processeur entre les différents processus en attente pour s'exécuter, c'est-à-dire entre les différents processus qui sont dans l'état prêt. L'opération d'élection consiste à allouer le processeur à un processus.

OrdonnanceurL'ordonnanceur est un programme système dont le rôle est d'allouer le processeur à un processus prêt.

Politique d'ordonnancementLa politique d'ordonnancement détermine le choix d'un processus à élire parmi tous ceux qui sont prêts.

PortLe port est un entier de 16 bits permettant d’adresser une application sur la couche de protocole TCP/IP.

Préemption

Page 172: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Selon si l'opération de réquisition du processeur est autorisée ou non, l'ordonnancement sera qualifié d'ordonnancement préemptif ou non préemptif : si l'ordonnancement est non préemptif, la transition de l'état élu vers l'état prêt est interdite : un processus quitte le processeur si il a terminé son exécution ou si il se bloque. si l'ordonnancement est préemptif, la transition de l'état élu vers l'état prêt est autorisée : un processus quitte le processeur si il a terminé son exécution , si il se bloque ou si le processeur est réquisitionné.

Primitive Accept sock_service = accept (int sock_ecoute, struct sockaddr_in p_adr, int p_lgadr) : Acceptation de connexion sur la socket locale d'écoute avec la socket distante p_adr. En résultante, création de la socket de service sock_service.

Primitive Bindint bind (int sock, sockaddr_in *p_adresse, int lg) : attâchement de l'adresse p_adresse à la socket sock

Primitive closeint close (int idf) : fermeture du descripteur idf.

Primitive Connectint connect (int sock, struct sockaddr_in *p_adr, int lgadr) : demande d'établissement de connexion avec la socket distante dont l'adresse est *p_adr

Primitive EXITvoid exit (int valeur) : provoque la terminaison du processus appelant

Primitive FORKpid_t fork (void) : permet la création d'un processus Unix par duplication complète du créateur.

Primitive Listenint listen (int sock, int nb) : création de la file de connexions pendantes (n max) associée à la socket d'écoute sock

Primitive msgctlint msgctl (int msgid, int op, struct msgid_ds *buf) : accès et modification des informations contenues dans la table des files de messages gérée par le système int msgctl (msgid, IPC_RMID, NULL) : destruction de la file de message identifiée par msgid.

Primitive msggetint msgid = msgget (key_t cle, int option) : permet de créer une nouvelle file de messages ou de récupérer une file de messages existante.

Primitive msgrcvint msgrcv (int msgid, struct message *buf, int lg, long montre, int option) : réception du message buf de taille lg octets dans la MSQ d'identifiant msgid. Le paramètre montre spécifie le type de message à extraire. Les options permettent de rendre la réception non bloquante si la file ne contient pas de message avec le type attendu.

Primitive msgsndint msgsnd (int msgid, struct message *buf, int lg, int option) : envoi du message buf de taille lg octets dans la MSQ d'identifiant msgid. Les options permettent de rendre l'envoi non bloquant si la file est pleine.

Primitive pipeint pipe (int p[2]) : création du tube p.

Page 173: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Primitive Read (réseau)int read (int sock,char *msg, int lg) : lecture du message msg de taille lg octets sur la socket sock en mode connecté

Primitive read (tube)nb_lu = read (p[0], buf, nb) : lecture dans le tube p d'au plus nb caractères. Les caractères lus sont retournés dans le tampon buf. nb_lu contient le nombre de caractères effectivement lus. Par défaut, la lecture sur un tube vide est bloquante.

Primitive Recvfromint recvfrom (int sock, char *msg, int lg, int option, struct sockaddr_in *p_exp, int *lgexp) : réception d'un datagramme msg envoyé par *p_exp.

Primitive Sendtoint sendto (int sock, char *msg, int lg, int option, struct sockaddr_in *p_dest, int lgdest) : envoi d'un datagramme msg à *p_dest.

Primitive Socketint socket (int domaine, int type, int protocole) : création d'une socket de communication

Primitive WAITpid_t wait (int *status) : attente de la mort du fils

Primitive Write (réseau)int write (int sock,char *msg, int lg) : écriture du message msg de taille lg octets sur la socket sock en mode connecté

Primitive write (tube)nb_écrit = write (p[1], buf, nb) : écriture dans le tube p des nb caractères placés dans le tampon buf. nb_lu contient le nombre de caractères effectivement écrits.

Primitives EXECEnsemble de primitives (famille EXEC) permettant à un processus de charger en mémoire, un nouveau code exécutable : int execl (const char *ref, const char *arg, ..., NULL) : ref est le chemin d'un exécutable à partir du répertoire courant, const char *arg, ..., NULL est la liste des arguments. int execlp (const char *ref, const char arg, ..., NULL) : ref est le chemin d'un exécutable à partir de la variable d'environnement PATH, const char *arg, ..., NULL est la liste des arguments. int execv (const char *ref, const char *arg[]) : ref est le chemin d'un exécutable à partir du répertoire courant, const char *arg [] est un tableau contenant les arguments. int execvp (const char *ref, const char *arg[]) : ref est le chemin d'un exécutable à partir de la variable d'environnement PATH, const char *arg [] est un tableau contenant les arguments.

ProcessusUn processus est un programme en cours d'exécution auquel est associé un environnement processeur (CO, PSW, RSP, registres généraux) et un environnement mémoire appelés contexte du processus. Un processus est l'instance dynamique d'un programme et incarne le fil d'exécution de celui-ci dans un espace d'adressage protégé (objets propres : ensemble des instructions et données accessibles) Un programme réentrant est un programme pour lequel il peut exister plusieurs processus en même temps

Processus légerUn processus léger est un espace d'adressage dans lequel plusieurs fils d'exécution peuvent évoluer en parallèle. Ces fils d'exécution sont chacun caractérisés par une valeur de compteur ordinal propre et une pile d'exécution privée.

Page 174: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

RessourceUne ressource désigne toute entité dont a besoin un processus pour s'exécuter. La ressource peut être matérielle comme le processeur ou en périphérique ou elle peut être logicielle comme une variable. Une ressource est caractérisée : Par un état : elle est libre ou occupée Par son nombre de points d'accès, c'est-à-dire le nombre de processus pouvant l’utiliser en même temps.

Ressource critiqueUne ressource critique est une ressource à un seul point d'accès, c'est-à-dire accessible par un seul processus à la fois. L'utilisation s'effectue en exclusion mutuelle. L'allocation et la désallocation d'une ressource critique peuvent être gérées à l'aide d'un sémaphore R initialisé à 1. L'allocation se traduit alors par un P(R) et la restitution par un V(R).

Ressource critique Une ressource critique est une ressource accessible par un seul processus à la fois.

Section critiqueLe code d'utilisation de la ressource critique s'appelle une section critique. La section critique doit offrir au moins une propriété essentielle : celle de l'exclusion mutuelle c'est-à-dire assurer qu'effectivement elle ne sera jamais exécutée par plus d'un processus à la fois. Pour ce faire, la section critique est précédée par un prélude et suivie d'un postlude (le prélude et le postlude sont du code) qui doivent assurer cette propriété d'exclusion mutuelle.

Serveur à étatsUn serveur à état est un serveur qui conserve, sur un support non volatile, des informations sur les requêtes en cours de traitements. C'est un serveur à mémoire qui en cas de panne, peut restaurer une partie de son état au moment de la panne et reprendre des requêtes en cours.

Serveur itératif Un serveur itératif est un serveur qui ne peut traiter qu'une seule requête client à la fois. Il est composé d'un processus unique qui effectue les opérations de réception de requêtes, de traitement de requêtes et d'émission des réponses.

Serveur parallèleUn serveur parallèle est un serveur qui peut traiter plusieurs requêtes client à la fois. Il est composé d'un processus père qui effectue les opérations de réception de requêtes et pour chaque requête reçue, d'un processus fils qui effectue le traitement de la requête et l'émission de la réponse.

Serveur sans étatUn serveur sans état est un serveur qui ne conserve aucune informations sur les requêtes en cours de traitements. C'est un serveur sans mémoire qui en cas de panne, repart "à zéro".

Système d'exploitationC'est un ensemble de programmes qui réalise l'interface entre le matériel de l'ordinateur et les utilisateurs. Il a deux objectifs principaux : construction au-dessus du matériel d'une machine virtuelle plus facile d'emploi et plus conviviale prise en charge de la gestion de plus en plus complexe des ressources et partage de celle-ci Comme son nom le suggère, le système d’exploitation a en charge l'exploitation de la machine pour en faciliter l'accès, le partage et pour l'optimiser

Table des pagesLa table des pages est une table contenant autant d'entrées que de pages dans l'espace d'adressage d'un processus. Chaque processus a sa propre table des pages. Chaque entrée de la table est un couple < n°de page, n°de case physique dans laquelle la page est chargée >.

Page 175: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Table des segmentsLa table des segments est une table contenant autant d'entrées que de segments dans l'espace d'adressage d'un processus. Chaque entrée de la table est un couple < n°de segment, adresse d'implantation du segment >.

Tube anonymeUn tube anonyme est un outil de communication unidirectionnel permettant de faire communiquer entre eux des processus de même filiation, ayant connaissance des descripteurs permettant l'accès au tube.

Page 176: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Ressources

Leçons à télécharger

● Sélectionner une leçon à télécharger :

Annales d'examens

● Sélectionner une annale d'examen pour la télécharger :

Schémas de synthèse

Liens

Utilitaires

● Sélectionner un utilitaire pour le télécharger :

Page 177: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Schémas de synthèse

Page 178: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3
Page 179: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3
Page 180: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3
Page 187: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3
Page 188: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Ordonnancement>Exercices dirigés

Préambule : rappel du format des primitives de la famille exec

Exercice 1

Exercice 2

Page 189: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Exercice 3

Processus shell :

Page 190: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3
Page 191: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Outils de communication centralisés entre processus>Exercices dirigés

Préambule : rappel du format des primitives permettant l'utilisation des tubes

Exercice 1

Page 192: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Exercice 2

Page 193: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Fonctionnement de la primitive DUP

La commande ps -e affiche les caractéristiques de tous les processus. la destination naturelle des informations

est la sortie standard STDOUT. La commande wc -l compte les lignes de l'entrée qui lui est fournie,

naturellement l'entrée standard STDIN. Lors de la création d'un processus, 3 fichiers d'entrées sorties sont

automatiquement crées : l'entrée standard STDIN (0), la sortie standard STDOUT(1) et la sortie d'erreur

STDERR (2). La primitive DUP (int dup(int desc)) associe le plus petit descripteur disponible du processus

appelant à la même entrée dans la table des fichiers ouverts que le descripteur desc. Pour rediriger un

descripteur standard sur un descripteur de tube, il faut :

● fermer le descripteur standard

● faire un dup avec comme paramètre le descripteur du tube concerné

● fermer le descripteur du tube.

Page 194: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Allocation mémoire >Exercices dirigés

Préambule : rappel du format des primitives permettant l'utilisation des messages queues

Exercice 1

Page 195: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Exercice 2 On considère l'architecture logicielle suivante : A - CLIENT

B- SERVEUR

Page 196: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

C - Les contraintes :

● il faut que les clients soient tous fils du serveur

● il faut deux tubes anonymes par couple communiquant.

Page 197: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

La difficulté : Attendre les nombres n sur pip1 et pip12 Tirage aléatoire Ecrire le résultat dans pip2 ou pip22

L'opération de lecture sur un tube est une opération bloquante si le tube sur lequel elle est effectuée est un tube

vide. A priori, on ne sait pas lequel des deux tubes sera prêt en lecture en premier. Alors, on peut se mettre en

lecture sur pip1, être bloqué alors que pip12 est prêt.

● Il existe deux solutions :

● rendre la lecture non bloquante et effectuer une attente active sur les deux tubes.

● s'endormir en attente d'un tube prêt. Pour cela on utilise la primitive SELECT.

Utilisation de la primitive SELECT :

1/ On construit un ensemble de descripteurs en lecture sur lequel on souhaite se mettre en attente. ens_r =

Page 198: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

{pip[0], pip12[0]}

Cette construction se fait à l'aide des macros FD_ZERO(ens_r) - mise à vide de l'ensemble - et

FD_SET(ens_r, element) - ajout de l'élément à l'ensemble.

2/ On se met en attente sur ces descripteurs SELECT (ens_r, ….)

3/ On sort du SELECT car un des descripteurs au moins est prêt (le tube n'est plus vide et une lecture peut

être faite sans risque d'être bloqué). On teste chacun des descripteurs par le biais de la primitive

FD_ISSET FD_ISSET(pip1[0]) est vrai ou FD_ISSET(pip12[0]) est vrai. A titre indicatif, voici le code C correspondant.

Page 199: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3
Page 200: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Mémoire virtuelle >Exercices dirigés

Exercice 1 : Gestion d'une mémoire par zones

Question 1

Une zone libre contient un en-tête de deux mots : mot1 : adresse de la zone libre suivante mot2 : taille de la

zone Les zones libres sont organisées selon une liste chainée, repérée par un pointeur de début :

adresse_premiere_zone.

Question 2

Algorithme First Fit : la première zone de taille suffisante est sélectionnée. On choisit par ailleurs

d'implanter le programme en fin de zone libre de manière à faciliter la gestion des pointeurs. gestion du

résidu issu de l'allocation : soit a la taille de la zone résiduelle, a = Ti - T où Ti est la taille de la zone et T la

taille du programme à allouer. Deux cas sont possibles en fonction de e, la taille minimale de zone

autorisée. 1/ a < e, la zone résiduelle est jugée négligeable et elle est alors supprimée de la liste des zones

libres (à modification du chainage des zones libres). a ³ e, la zone résiduelle est conservée (àmodification

de la taille de la zone libre). D'où l'algorithme suivant avec parcours, avant : pointeurs sur zone libre A :

adresse d'implantation du programme de taille T hors cas particulier sur le chainage (zone libre en tête et

fin de liste). Parcours := premiere_zone_libre; avant := premiere_zone_libre; trouve = faux; tant que (parcours <> NULL

et trouve = faux) faire si (parcours.taille >= T) alors

● on a trouvé une zone de taille suffisante parcours.taille = parcours.taille - T; A := parcours +

Page 201: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

parcours.taille; trouve = vrai; si (parcours.taille < e)

● on libère la zone libre résiduelle avant.suivant = parcours.suivant; fsi avant = parcours; parcours =

parcours.suivant; fait

Question 3

parcours = adresse_premiere_zone; avant = adresse_premiere_zone; taille_mem = max; trouve = faux; tant que (parcours <> NULL) faire si (T <= parcours.taille < taille_mem) alors

● on retient la zone zone_trouve = parcours; taille_mem = parcours.taille; zone_avant = avant; trouve

= vrai; fsi avant = parcours; parcours = parcours.suivant; fait

● à la sortie de la boucle zone_trouve contient l'adresse de la zone dont la taille engendre la plus petit

residu sauf si trouve = faux si (trouve == vrai) alors zone_trouve.taille = zone_trouve.taille - T; A =

zone_trouve + zone_trouve.taille; si (zone_trouve.taille < e)

● on libère la zone libre résiduelle zone_avant.suivant = zone_trouve.suivant; fsi fsi

Question 4

Il y a deux espaces libres : la zone (1700K-2000K) et la zone (2300K-2560K) soit un total de 560K ce qui

est supérieur à la taille de P5. Mais ces deux zones ne sont pas contiguës et on ne peut donc pas allouer

l'espace mémoire à P5. Il faut donc compacter l'espace mémoire pour arriver à la configuration

Question 5 Une zone libérée doit être fusionnée à sa voisine si celle-ci est également une zone libre :

différents acas doivent être considérés.

● Cas 1 : la zone libérée est elle-même précédée d'une zone libre ZL : on effectue une fusion et on

modifie la taille de ZL : ZL.taille = ZL.taille + zoneliberee.taille;

● Cas 2 : la zone libérée est cernée par deux zones occupées : on crée une nouvelle zone libre

● Cas 3 : la zone libérée est suivie par une zone libre : on fusionne les deux zones (on ajoute les

tailles des deux zones) et on modifie le chainage des zones libres. les infos de gestion (taille et

suivant) sont "remontées" dans les deux premiers mots de la zone libérée.

● Cas 4 : la zone libérée est cernée par deux zones libres A et C : on fusionne les trois zones libres.

c'est-à-dire : la zone libre C est supprimée du chainage et les infos de gestion sont remontées dans

les deux premiers mots de la zone A.

Exercice 2 : Pagination

Question 1

Décrivez le format d'une entrée de la table des pages d'un processus .

Page 202: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

bit V : indique si la page est prése,nte ou non en mémoire centrale V= 0, page non présente bit M : indique

si la page a été modifiée bit A : champ pour les informations à la apge (algorithmes de remplacement de

pages) protec : champ de protection pour les accès en lecture/écriture/exécution adresse réelle : adresse

de la case contenant la page.

Question 2

adresse_reelle et adresse_virtuelle sont fde type adresse, ce type est formé de deux champs : champ1 et

champ2 instruction est de type type_instruction qui peut prendre pour valeurs : lecture, écriture et

exécution. procedure décodage (adresse_réelle : adresse, adresse_virtuelle : adresse, instruction : type_instruction)

debut A = table_des_pages(adresse_virtuelle.page); si (A.V ==0) alors réveil du processus de défaut de

page : chargement de la page et mise à jour de la table des pages fsi Verifier_droits_accès (instruction, A.

protec); A.A = 1; - on positionne l'accès à la page si (instruction == écriture) alors A.M = 1; fsi

adresse_reelle.champ1 = A.adresse_reelle; adresse_reelle.depl = adresse_virtuelle.depl; fin

Question 3

Soit la liste des pages virtuelles référencées aux instants t = 1, 2,..., 11 3 5 6 8 3 9 6 12 3 6 10

Question 4

à question de cours

Question 5

Page 203: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3
Page 204: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Synchronisation entre processus>Exercices dirigés

Exercice 1

Question 1 On identifie un schéma producteur/consommateur sur chacun des tampon. On utilise pour chacun de ces

schéma un couple de sémaphores : MVIDE initialisé à M et MPLEIN initialisé à 0 (tampon requête) NVIDE

initialisé à N et NPLEIN initialisé à 0 (tampon avis)

Question 2

Page 205: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Il faut maintenant gérer les accès concurrents aux tampons avis et requête. En effet :

● les différents processus Acquisition se partagent l'index i

● les différents processus exécution se partagent l'index j et k

● les différents processus Impression se partagent l'index k les variables i, j, k, l sont maintenant

globales et les accès à ces variables doivent se faire en exclusion mutuelle. On ajoute donc quatre

sémaphores d'exclusion mutuelle initailisés à 1 (un sémaphore par index).

remarque : l'ordre d'appel des sémaphores d'exclusion mutuelle par rapport à ceux du schéma producteur

consommateur n' pas d'importance.

Exercice 2

Question 1

Lorsque l'employé saisit une commande, l'écriture sur disque d'une ligne à la fois, implique que, à un

instant donné, le disque ne contient qu'une partie de la commande. Si le processus de facturation est lancé,

il ne trouvera pas toutes les lignes de la commande pour éditer la facture, qui sera donc partielle. Au

moment où on crée une commande, il faut donc interdire au processus de facturation d'accéder aux

Page 206: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

commandes. Par ailleurs, l'employé édite les commandes saisies sur la même imprimante que le processus de

facturation. Ces éditions se font ligne par ligne, mais toutes les lignes concernant le même bon de

commande ou la même facture doivent se trouver regroupées, et non entremêlées. Il faut donc que

l'imprimante soit en exclusion mutuelle entre les deux processus.

Question 2 - question 3

La solution proposée garantit bien l'accès en exclusion mutuelle à l'imprimante par les deux processus. Le

processus de facturation réserve l'imprimante pendant le traitement des factures d'une période. Ces

factures seront donc bien éditées de manière consécutive. Par ailleurs, comme le processus réserve

également le fichier des commandes, aucune commande ne peut être en cours de saisie pendant l'édition

des factures.

Question 4

L' interblocage est une situation où un ensemble de processus sont bloqués en attente d'une ressource

possédée par un autre processus de l'ensemble. Chacun attend qu'un autre veuille bien libérer la ressource

qu'il attend. Ceci ne peut se faire sans une intervention extérieure, puisqu'ils sont tous bloqués. Or on ne

peut débloquer un processus qu'en lui donnant toutes les ressources nécessaires, et donc en

réquisitionnant celle qu'il attend et qui est possédée par un autre processus de l'ensemble. Dans la solution proposée, on peut imaginer que le processus employé réserve le fichier COM et

commence à saisir la commande. A ce moment le processus de facturation est activé, et réserve

l'imprimante, puis se bloque en attente du fichier COM. Lorsque l'employé a terminé la saisie, il réserve

l'imprimante, mais comme celle-ci est déjà réservée par le processus de facturation, il se bloque en attente

de la libération. Nous avons alors deux processus qui attendent mutuellement la libération d'une ressource

possédée par un autre processus de l'ensemble : ces deux processus sont en interblocage.

Question 5

Pour ne plus avoir d'interblocage, une des solutions est de réserver les ressources dans le même ordre,

puisque, dans ce cas, il ne peut plus y avoir de circularité dans les attentes de ressources. Dans le

processus de facturation, il faut donc réserver le fichier COM en premier.

Exercice 3

Page 207: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Question 1

Deux processus P1 et P2 exécutent chacun de leur côté, la même opération CredDeb_Compte(10,

Crediter,100). le déroulement de ces opérations peut être le suivant. le solde final sera finalement de 1100

à la place de 1200.

Question 2

Il suffit de placer l'exécution de la procédure CredDeb_Compte en exclusion mutuelle, soit avec ACCES

sémaphore initialisé à 1 :

Page 208: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Question 3

Cette fois, il faut mettre en place un schéma lecteurs/rédacteurs. La procédure CredDeb_Compte reste

comme en Q2. La procédure Donner_Solde devient :

Exercice 4

Lors de l'utilisation d'une ressource critique par une tâche, il est habituel d'ajouter aux paramètres de

description de la tâche ceux supplémentaires suivant :

● Cia : séquence d'instructions précédant l'appel de la ressource,

● Cib : séquence d'instruction de la section critique ,

● Cig : séquence d'instruction suivant la libération de la ressource avec Ci = Cia + Cib + Cig.

Toute tâche en cours d'utilisation d'une ressource critique peut être préemptée par une tâche plus prioritaire

qu'elle, et qui n'a pas besoin de cette ressource. On considère l'exemple d'une configuration de trois tâches

périodiques. Les tâches Tp1 et Tp3.

Page 209: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Question 1

Comme le montre la figure, à l'instant t=8, la tâche 3 est bloquée par la tâche 2 parce qu'elle est plus

prioritaire qu'elle. Mais comme la tâche 1 est en attente de la ressource critique (occupée par la tâche 3)

depuis l'instant t=7, on observe donc que la tâche 2 est exécutée avant la tâche 1 : c'est le phénomène

d'inversion de priorité.

Question 2

Pour éviter ce problème d'inversion de priorité, à l'instant t=7, lorsque la tâche 1 se met en attente de la

ressource occupée par la tâche 3, la tâche 3 prend la priorité de la tâche 1. Par conséquent, à l'instant t=8,

la tâche 3 est plus prioritaire que la tâche 2 et continue à s'exécuter. L'exécution de la tâche 2 se trouve

ainsi repoussée à l'instant t=10 après la tâche 1 (cf. figure 3.15).

Page 210: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3
Page 211: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Communication inter processus sur le réseau>Exercices dirigés

Exercice 1 : COMPARAISON DES POLITIOUES DE GESTION DU DISQUE Question 1

à question de cours Question 2

On considère un disque composé de 300 pistes numérotées de 0 à 299. Le bras est couramment

positionnée sur la piste 50.

La liste des requêtes (n°de piste cherchée) à servir donnée selon l'ordre d'arrivée est la suivante :

62, 200, 150, 60, 12, 120, 250, 45, 10, 100 FCFS

Ordre de service : 62, 200, 150, 60, 12, 120, 250, 45, 10, 100

Déplacement du bras : 12 + 138 + 50 + 90 + 48 + 108 + 130 + 205 + 35 + 90 = 906 SSTF

Ordre de service : 45, 60, 62, 100, 120, 150, 200, 250, 12, 10

Déplacement du bras : 5 + 15 + 2 + 38 + 20 + 30 + 50 + 50 + 238 + 2 = 450 SCAN montant

Ordre de service : 60, 62, 100, 120, 150, 200, 250, 10, 12, 45

Déplacement du bras : 15 + 2 + 38 + 20 + 30 + 50 + 50 + 240 + 2 + 38 = 485

Exercice 2 : GESTION DE FICHIERS UNIX

Un processus lit séquentiellement un fichier de 8 Mo, à raison de 256 octets à la fois. On suppose que les

blocs disque sont de 1024 octets et qu'un numéro de bloc occupe 4 octets. par ailleurs, le temps d'accès

moyen au disque est de 40 ms. Le dessin joint montre l'allocation des blocs.

1/ Rappelez la structure d'une inode et d'un fichier Unix à question de cours

2/ Le système ne gère pas de mécanisme de buffer cache. lecture des 10 premiers blocs : 4 * 10 accès

disque lecture des 256 blocs suivants (niveau d'indirection 1) : on a deux accès disque par lecture 8 *

256 accès disque lecture des 7926 blocs restants (niveau d'indirection 2) : on a trois accès disque par

lecture 12 * 7926 accès disque soit un total de 97200 accès disque et une durée moyenne de lecture

égale à 3888 s.

3/ Le système gère un mécanisme de buffer cache

3.a/ à question de cours

Page 212: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

3.b/ on a un accès disque par blocs de données (lors de la lecture des 256 premiers octets du bloc). on a

un total de 33 blocs d'adresse à lire. soit un nombre d'accès disque égal à 8192 + 33 = 9224 et un

temps moyen de lecture égal à seulement 329 s !!!!

3.c/ avantage : on économise les accès disque, inconvénient : risque de perte de données si plantage de la

machine. l'appel SYNC force le vidage du cache sur le disque.

Exercice 3

On considère un système de gestion de fichiers qui fait de l'allocation par zone. L'ensemble du disque est

constitué de 100 blocs, numérotés de 0 à 99. Trois fichiers existent sur le disque, définis comme suit, le

reste de l'espace étant libre.

F1, Début = bloc5, Taille=20blocs,

F2, Début = bloc 25, Taille 5 blocs,

F3, Début = bloc 50, Taille 10 blocs.

Question 1

Si l'implantation est séquentielle simple, un fichier ne peut avoir qu'une seule zone. Or le fichier FI se

termine sur le bloc 24 et F2 commence en 25. Il n'y a donc pas d'espace libre permettant de le rallonger

sans le déplacer. En général, comme le coût d'un tel déplacement est important, il n'est pas fait

automatiquement. Cependant, étudions cette possibilité. Il faut alors trouver un espace contigu de 30 blocs

(20 + 10), ce qui peut se faire en 60, derrière F3. Le déplacement lui-même demande la lecture du fichier et

sa réécriture dans la nouvelle zone.

Si l'implantation est séquentielle avec extensions, il faut trouver un espace libre de 10 blocs consécutifs

correspondant à une nouvelle zone, puisqu'il n'est pas possible de prolonger la première. On peut allouer

en 30, derrière F2, en 40 devant F3, en 60 derrière F3 ou en 90 à la fin. Il est cependant préférable de

garder intacte la plus grande zone pour satisfaire des besoins ultérieurs importants. Allouer derrière F2

empêchera ce dernier de s'étendre par prolongement, mais allouer devant F3 empêchera FI d'étendre

éventuellement sa deuxième zone, et l'obligera à recevoir une troisième zone.

Enfin, on peut noter que les accès à l'intérieur de FI risquent d'être plus perfon-nant si les zones sont les

plus proches possibles, ce qui milite pour une allocation en 30.

Question 2

Il faut trouver une zone de 10 blocs libre. Elle peut être soit entre 30 et 49, soit entre 60 et 99. Le

raisonnement ci-dessus indique une préférence pour la portion 30..49, de façon à conserver la plus grande

portion intacte. Le choix est assez indifférent, même en cas d'implantation séquentielle simple, car il faut

choisir lequel de F2 ou de F4 sera autorisé éventuellement à s'étendre sur 10 blocs.

Question 3

Il faut trouver cette fois une zone de 40 blocs, ce qui ne peut se faire qu'entre 60 et 99. Notons que si on a

une implantation séquentielle avec extensions, il est envisageable de morceler le fichier F4 en plusieurs

zones, ce qui n'est pas possible dans le cas de l'implantation séquentielle simple.

Cependant, le gain immédiat est nul, et, au contraire, les accès au fichier F4 seront pénalisés dans ce cas,

Page 213: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

et le gain hypothétique à venir est non prévisible au moment de la création de F4.

Page 214: Chapitre 1 : Introduction Chapitre 2 : Processus Chapitre 3

Programmation socket>Exercices dirigés

Exercice 1:

Création et attâchement d'une socket : Rappels de cours sur les primitives socket et bind.

Exercice 2:

Communication en mode datagramme ou en mode connecté On considère l'application clients-serveur

répartie suivante : le serveur gère une table annuaire de couple (nom_de_personne, numero_telephone).

Des clients interrogent le serveur en lui transmettant un nom_de_personne. Le serveur répond en envoyant

au client le couple correspondant (nom_de_personne, numero_telephone) si il a été trouvé dans le table,

sinon le message "usager inconnu". 2.a/ On a joint les codes C correspondants : udp_client et udp_serveur (annuaire.c). S'en inspirer pour le

pseudo-code. 2.b/ On a joint les codes C correspondants : tcp_client, tcp_serveur iteratif. S'en inspirer pour le pseudo-

code 2.c/ On a joint les codes C correspondants : tcp_client, tcp_ serveur parallèle . S'en inspirer pour le pseudo-

code 2.d/ Donnez le pseudo-code du serveur à code joint serveur de scrutation. A votre avis, faut-il protégez

l'accès à la table de l'annuaire par un schéma de synchronisation ? Si les services serveur sont itératifs, on

peut mettre en place une exclusion mutuelle pour protéger un accès en lecture vis à vis d'un accès en

écriture (mais ce n'est pas obligatoire) Si les services serveur sont parallèles, on doit mettre en place une

exclusion mutuelle pour protéger un accès en écriture vis à vis d'un autre accès en écriture.

Exercice 3:

Le démon INETD

3.a/ Quel est le rôle du démon Inetd ? question de cours 3.b/ Ecrivez le pseudo-code de ce démon