","title":"Protocoles de routage","tagtitle":"h1"},{"edit":"
"}],[{"text":"
Un réseau informatique sert à connecter des machines entre elles pour qu'elles puissent communiquer.
Le mode de communication classiquement mis en place est le modèle client-serveur qui permet à des clients d'échanger des paquets d'informations avec des serveurs.
La notion de «client» est très large.
Elle peut désigner à la fois une application ou la machine sur laquelle cette application s'exécute.
De même, par «serveur» on fait également
référence à l'ordinateur qui héberge un service ou au logiciel qui fournit ce service.
Les paquets échangés sont les entités élémentaires qui transitent sur le réseau physique.
Îls proviennent du découpage en petits morceaux des données à transmettre (comme des pages web, du courrier électronique ou des vidéos).
Ces paquets sont envoyés séparément sur le réseau et l'information initiale est reconstituée quand les paquets arrivent à destination (côté client ou serveur).
En plus des clients et des serveurs, un réseau informatique est constitué de routeurs qui peuvent être de deux types : des routeurs d'accès (en bordure de réseau) ou des routeurs internes. Il s'agit de machines dont le rôle est de relayer les paquets dans le réseau afin de les acheminer vers leur destination finale.
Les clients et les serveurs sont connectés aux routeurs d'accès, habituellement par l'intermédiaire de réseaux locaux (qui peuvent être de diverses technologies telles que Wi-Fi ou Ethernet).
Figure 1 — Topologie d'un réseau.
Les routeurs internes sont connectés entre eux sur de plus longues distances à l'aide de fibres optiques, de câbles téléphoniques, de liaisons par satellite, etc.
L'interconnexion des routeurs via ces liens forment la topologie du réscau.
Par exemple, le schéma de la figure 1 représente la topologie d'un réseau constitué de six routeurs.
Les routeurs R1 et R6 sont des routeurs d'accès. Ils permettent par exemple aux machines Client et Serveur d'accéder au réseau.
Les quatre autres routeurs R2,...,R5 sont des routeurs internes.
Les adresses IP utilisées par les machines sont indiquées par une paire sous-réseau/masque.
Par exemple, les routeurs R1 et R3 sont reliés à un sous-réseau dont l'adresse est 10.1.1.0.
Le masque 30 indique que seuls les deux bits de poids faible de l'adresse du sous-réseau (32 bits de l'adresse IP - 30 bits de masque = 2 bits restants) peuvent être utilisés pour associer des adresses IP aux machines (voir le cadre sur les masques de sous-réseau pour plus de détails).
Ainsi, R1 peut être associé par exemple à l'adresse IP 10.1.1.1 et R3 à l'adresse 10.1.1.2.
Lorsqu'il reçoit un paquet, un routeur l'analyse pour récupérer l'adresse de sa destination. En fonction de cette adresse, il doit choisir vers quel routeur voisin retransmettre ce paquet pour le faire progresser vers sa destination.
Il choisit ce voisin à l'aide de sa table de routage, qui associe les adresses de destination à des adresses de routeurs.
De cette manière, un paquet transite de routeur en routeur jusqu'au client ou au serveur à qui il est destiné.
Par exemple, si le Client veut envoyer un message au Serveur, il le transmet à son routeur d'accès R1 rattaché à son réseau local.
Ce routeur a d'autre choix que de le renvoyer au routeur R3 auquel il est connecté.
Pour faire progresser ce paquet, R3 a plusieurs possibilités. Il peut soit le
transmettre à R2, soit à R4 ou encore à R5.
C'est la table de routage de R3 qui indique quel routeur voisin choisir.
Ce processus est répété de la même manière par chaque routeur et, en progressant de cette façon, le paquet va finir par arriver au routeur R6 qui pourra ainsi le délivrer au Serveur.
Comme on peut le voir sur le réseau de la figure 1, plusieurs routes sont possibles pour acheminer des paquets entre le client et le serveur.
Par exemple, il y a un chemin qui passe successivement par
les routeurs R1, R3, R5, R6
et un autre par les routeurs R1, R3, R2, R5, R6.
S'il existe plusieurs routes pour acheminer un paquet, comment le chemin menant d'un client au serveur est-il choisi?
Est-il toujours le plus court possible?
Est-ce que les tables de routage sont précalculées à l'avance?
Ou au contraire peuvent-elles évoluer de manière dynamique, au gré des changements de la topologie du réseau quand des routeurs tombent en panne, des liaisons sont rompues, ou au contraire quand de nouveaux liens sont mis en place?
Dans cette séquence, nous allons apporter des réponses à ces questions en présentant les algorithmes implémentés dans les routeurs pour configurer leurs tables de routage.
Ces algorithmes, appelés protocoles de routage, permettent aux routeurs de s'échanger des informations pour découvrir la topologie du réseau et choisir ainsi les meilleures routes pour relayer les paquets.
Il s'agit d'un processus qui est à la fois décentralisé et dynamique. c'est-à-dire que les routeurs ont tous la même fonction, aucun d'eux n'a de privilège particulier pour diriger ces configurations et il faut recalculer les tables de routages dès qu'un changement de topologie apparaît dans le réseau.
","title":"Protocoles de routage"},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Masques de sous-réseaux
Il existe deux manières de définir une adresse de sous-réseau.
On peut tout d'abord l'écrire sous la forme d'un couple de deux adresses IP (préfixe, masque).
La première composante est l'adresse du sous-réseau appelée aussi préfixe du sous-réseau (par exemple 10.1.1.0) et la seconde composante est le masque de sous-réseau, par exemple 255.255.255.0.
Ce découpage permet de déterminer facilement si deux machines sont reliées au même sous-réseau.
Par exemple, pour vérifier si les adresses IP 10.1.1.1 et 10.1.1.2 de deux machines sont sur le même sous-réseau 10.1.1.0, il suffit d'effectuer une opération « et » bit à bit entre ces adresses et le masque 255.255.255.0 de ce sous-réseau.
Si le résultat est 10.1.1.0 dans les deux cas, alors ces machines appartiennent bien à ce sous-réseau.
Par le passé, un masque de sous-réseau pouvait être une suite quelconque de bits, comme par exemple 11111100.11111111.10010011.0001110, qui
correspond au masque 252.255.147.14.
Mais cela a été abandonné.
Aujourd'hui, un masque est nécessairement une suite contiguë de bits à 1 suivie par des hits à 0.
Cela permet de simplifier l'écriture des masques en indiquant uniquement leur «longueur», c'est-à-dire le nombre de bits à 1 en partant de la gauche (les bits de poids fort).
Par exemple, le masque 255.255.255.0 correspond à la longueur 24, car il y à exactement 24 bits de poids fort à 1 dans cette adresse.
Cette notion de longueur permet de représenter des adresses de sous-réseaux simplement par des couples préfixe/longueur.
","title":""},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Le nombre de routeurs dans un réseau est généralement trop grand pour envisager de configurer les tables de routage à la main. En effet, chaque fois qu'un élément du réseau tombe en panne ou qu'une modification est apportée à sa topologie (ajout d'une nouvelle liaison ou d'un nouveau routeur), il est nécessaire de recalculer toutes les routes et de mettre à jour les tables de routage de chaque routeur.
Pour que cela soit possible, il faut également que toutes les données relatives à l'état des liaisons et des routeurs soient envoyées vers un unique opérateur qui doit alors se charger de calculer les nouvelles routes.
Outre les inconvénients de centraliser cette tâche, il faut aussi s'assurer que les informations relatives à l'état du réseau puissent être envoyées sans problème à cet opérateur.
Malheureusement, les moyens de communication utilisés pour cela peuvent également tomber en panne.
Pour toutes ces raisons, on a cherché à automatiser ce processus en laissant les routeurs se charger eux-mêmes de mettre à jour leur table de routage, sans aucune intervention humaine.
Aïnsi, en plus de la transmission des paquets, les routeurs s'échangent les informations dont ils disposent sur les routes du réseau, en fonction de l'état de leurs voisins et de leurs liens de communication.
Les règles à suivre pour réaliser ces échanges sont définies par un protocole de routage.
Initialement, les informations dont dispose un routeur sont celles sur ses voisins immédiats ainsi que les sous-réseaux auxquels il est connecté.
En envoyant régulièrement des messages à ses voisins, et en mesurant les temps de réponse de ces machines, il peut ainsi déterminer si un routeur est en paune ou si la liaison entre eux est rompue.
Il peut ensuite propager ces informations à tous ses voisins, qui peuvent eux-mêmes les retransmettre à leurs voisins, et ainsi de suite.
De cette manière, de proche en proche, tous les routeurs vont finir par partager les mêmes connaissances sur la topologie du réseau. Cependant, pour que l'échange de ces informations soit bien coordonné, les routeurs doivent suivre le même protocole.
Dans cette section, nous décrivons le protocole RIP (Protocole d'information de
routage, ou Routing Information Protocol en anglais).
Le principe du protocole RIP est le suivant:
chaque routeur transmet à ses voisins les adresses de ses propres voisins ou celles qu'il a reçues par d'autres routeurs. En plus des adresses, le routeur indique la distance, exprimée en nombre de sauts, qui le sépare d'une machine donnée, c'est-à-dire combien de routeurs il faut traverser pour atteindre cette machine en passant par lui.
Ce sont donc des couples (adresse , distance),appelés vecteurs de distance, qui sont échangés avec ce protocole. C'est grâce à ces indications
de distance qu'un routeur va pouvoir choisir la meilleure route, c'est-à-dire
celle qui traverse le moins de routeurs pour atteindre une machine.
Pour illustrer le fonctionnement du protocole RIP, reprenons l'exemple de la figure 1 et voyons comment évoluent les tables de routage des routeurs R1 et R3 en utilisant ce protocole.
Les tables de routage contiennent quatre colonnes.
La première contient une «destination» sous la forme d'une adresse sous-réseau/masque.
Une deuxième, nommée «passerelle», donne
l'adresse IP du prochain routeur voisin par lequel passer pour atteindre cette destination.
Une colonne «interface» indique l'interface réseau à utiliser pour atteindre la passerelle.
Il peut s'agir par exemple d'une carte Ethernet ou d'une interface sans fil comme une borne Wi-Fi.
Enfin, la dernière colonne contient la distance vers la destination.
Il est important de noter que la colonne «passerelle» est vide quand l'adresse de destination est celle d'un routeur voisin.
Dans ce cas, la colonne «interface» indique par quel moyen atteindre directement le sous-réseau.
Au début du protocole, les tables des routeurs R1 et R3 sont initialisées avec les informations concernant leurs voisins immédiats, à savoir les adresses des sous-réseaux sur lesquels ils sont directement connectés.
Table de routage de R1
destination
passerelle
interface
distance
10.1.1.0/30
eth0
1
192.168.1.0/24
wlan0
1
Table de routage de R3
destination
passerelle
interface
distance
10.1.1.0/30
eth1
1
10.1.2.0/30
eth3
1
10.1.3.0/30
eth2
1
10.1.4.0/30
eth0
1
On voit ainsi que la table du routeur R1 a des informations sur deux destinations:
le sous-réseau 10.1.1.0/30 qui le relie à R3, ainsi que sur le sous-réseau
192.168.1.0/24 grâce auquel il est connecté à la machine Client.
La colonne «passerelle» est vide puisque R1 peut atteindre ces deux destinations directement.
L'interface eth0 signifie que R1 est directement relié au sous-réseau 10.1.1.0/30 et que l'interface réseau qu'il utilise pour transmettre des paquets est une carte Ethernet (eth).
Le numéro associé au nom de l'interface permet de savoir quelle carte utiliser parmi toutes celles disponibles sur un routeur.
De même, wlan0 indique que R1 est connecté au sous-réseau 192.168.1.0/24 via une interface sans fil (wlan).
D'une manière similaire, le routeur R3 initialise sa table avec les informations sur ces quatre voisins R1, R2, R4 et R5 en indiquant qu'il y est relié via des interfaces Ethernet, respectivement eth1, eth3, eth2 et eth0. Puisqu'il s'agit d'information sur des voisins immédiats, les colonnes «distance» de ces deux tables indiquent qu'il n'y à qu'un seul routeur à traverser pour atteindre ces cibles (la distance est de 1).
Après cette phase d'initialisation, un routeur poursuit le protocole en échangeant des demandes RIP avec ses voisins.
Ainsi, lorsqu'un de ses voisins reçoit une telle demande, il doit accuser réception en lui renvoyant sa table en réponse (le protocole prévoit que seule une partie de la table peut être envoyée, selon ce que veut savoir l'émetteur).
Lorsque le routeur reçoit la réponse de son voisin, il y à quatre cas possibles:
Il découvre une nouvelle route vers un sous-réseau qui lui était jusque là inconnu. Il l'inscrit dans sa table,
Il découvre une route plus courte vers un sous-réseau connu mais passant par un autre routeur. Il efface l'ancienne route de sa table et inscrit la nouvelle.
Il reçoit une nouvelle route plus longue. Il l'ignore.
Il reçoit une route existante, mais plus longue, vers un routeur passant par le même voisin. Cela veut dire qu'un problème est apparu sur son ancienne route. Il met donc à jour sa table avec cette nouvelle route.
Lorsqu'un routeur reçoit une route, il doit bien prendre soin de considérer que la distance associée à cette route doit être augmentée de 1 (pour prendre en compte que les paquets devront passer par lui), soit lorsqu'il compare cette distance aux autres, soit quand il décide de l'ajouter dans sa table.
Dans le protocole RIP, la distance maximale d'une route est fixée à 15 routeurs intermédiaires.
Au delà, la route doit être effacée des tables de routage ou simplement ignorée.
Ainsi, après avoir échangé une demande RIP avec R3, la table du routeur de R1 contient les informations suivantes:
Table de routage de R1
destination
passerelle
interface
distance
10.1.1.0/30
eth0
1
192.168.1.0/24
wlan0
1
10.1.2.0/30
10.1.1.2
eth0
2
10.1.3.0/30
10.1.1.2
eth0
2
10.1.4.0/30
10.1.1.2
eth0
2
On voit ainsi que R1 à enrichi sa table avec trois nouvelles routes permettant d'envoyer des paquets vers les sous-réseaux 10.1.2.0/30, 10.1.3.0/30 et 10.1.4.0/30.
La passerelle pour atteindre ces routes est la même, il s'agit du routeur R3 dont l'adresse IP est 10.1.1.2 et qui est accessible via l'interface réseau eth0.
On note également que les distances pour ces nouvelles routes sont toutes augmentées de 1 par rapport à la table de R3, ce qui est normal puisqu'elle nécessitent de passer par R1 pour atteindre R3.
De la même manière, la table du routeur R3 contient les informations suivantes après son échange avec R1.
Table de routage de R3
destination
passerelle
interface
distance
10.1.1.0/30
eth1
1
10.1.2.0/30
eth3
1
10.1.3.0/30
eth2
1
10.1.4.0/30
eth0
1
192.168.1.0/24
10.1.1.1
eth2
2
La seule route ajoutée dans cette table est celle vers le sous-réseau 192.168.1.0/24 sur lequel se trouve la machine Client (avec une distance de 2 lorsqu'on passe par R3).
En répétant ces demandes RIP et en mettant à jour leurs tables de routages selon l'algorithme décrit ci-dessus, les routeurs vont finir au bout d'un certain temps par avoir la même «vision» du réseau et des meilleures routes à suivre pour acheminer un paquet.
Par exemple, la table de routage finale pour le routeur R1 est Ia suivante:
Table de routage de R1
destination
passerelle
interface
distance
10.1.1.0/30
ethO
1
192.168.1.0/24
wlanO
1
10.1.1.0/30
ethO
1
10.1.1.0/30
ethO
1
10.1.1.0/30
ethO
1
10.1.1.0/30
ethO
1
192.168.1.0/24
","title":""},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Détection des pannes
Le protocole RIP doit également permettre de déterminer si une liaison est en panne.
Pour cela, un routeur considère qu'un voisin est en panne s'il ne reçoit pas de réponse à une demande RIP après un certain laps de temps.
Par défaut, ce délai est de trois minutes.
Quand un routeur détecte qu'un sous-réseau devient inaccessible, il envoie à ses voisins cette information sous la forme d'une route avec une distance infinie, qui correspond pour RIP à une valeur de 16.
","title":""},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Délai de convergence
Les distances ne pouvant être supérieures à 15 sauts (ou liaisons traversées), les routeurs ne peuvent pas connaître les plus courts chemins pour atteindre un sous-réseau qui nécessite une route trop longue.
Cela limite de fait l'usage de RIP à des réseaux de petites tailles.
Le choix de cette limite est fait pour diminuer le délai de convergence du protocole, c'est-à-dire le temps nécessaire pour que tous les routeurs aient la même vue de la topologie du réseau.
Plus la limite est haute et plus le temps nécessaire pour converger cst important.
","title":""},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Boucle de routage
Une boucle de routage peut se produire quand les informations contenues dans les tables de routage amènent un paquet à tourner en boucle dans le réseau sans jamais pouvoir atteindre sa destination.
Il y à deux solutions pour empêcher ce phénomène:
faire en sorte que les tables de routage soient toujours bien configurées de manière à ce que ces boucles soient impossibles,
permettre aux routeurs de détecter quand un paquet tourne en rond dans le réseau.
Le protocole RIP est conçu de manière à ce que ces boucles de routage ne puissent
pas être créées lors de la mise à jour des tables.
La limite imposée sur les distances des routes est une première technique.
Cependant, ce n'est pas suffisant et il est nécessaire d'établir d'autres règles pour que cela ne puisse se produire.
Par exemple, la règle du split horizon impose qu'un routeur ne renvoie pas une information à un autre routeur s'il a appris cette information par ce même routeur.
Une autre, appelée temporisateur de retenue (Hold down en anglais), impose à un routeur, qui apprend l'indisponibilité d'une route vers un sous-réseau, d'ignorer toute information concernant des routes vers ce sous réseau pendant une certaine durée (il utilise pour cela un temporisateur).
","title":""},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Dans la section précédente, nous avons vu que le protocole RIP permettait de configurer les tables de routage avec les routes les plus courtes en nombre de routeurs traversés.
Malheureusement, cette notion de distance ne garantit pas que les routes soient les meilleures en terme de débit puisque la nature des liaisons (fibre optique, satellite, sans fil, etc.) n'est pas intégrée dans les messages d'information échangés par ce protocole.
Nous avons vu également que RIP n'était pas adapté aux grands réseaux car il ignore les routes de plus de 15 sauts, ceci afin de limiter son délai de convergence et pour éviter des boucles de routage.
C'est pour pallier ces défauts que le protocole OSPF (pour l'anglais Open Shortest Path First) a été développé dans les années 1990 par l'organisme de normalisation international IETF (Internet Engineering Task Force).
Ce protocole prend en compte la bande passante des liaisons de communication pour calculer les meilleures routes.
La bande passante est la quantité d'information qui peut être transmise par unité de temps.
Habituellement, on mesure ce débit en nombre de bits par seconde, en abrégé bit/s (on trouve aussi la notation bps).
Pour parler de débits élevés, on associe à cette mesure un préfixe du système international comme kilo (kbit/s) pour une bande passante de 10^3 bit/s.
Vous avez les autres multiples dans le tableau ci-dessous.
","title":"Protocole OSPF"},{"edit":"
Notation
Nom
10^x
nb bits/s
kbit/s
kilo-bit/s
10^3 bit/s
1 000 bit/s
10^6 bit/s
10^9 bit/s
10^12 bit/s
"}],[{"text":"
Contrairement au protocole RIP, le nombre de routeurs traversés par un paquet n'a plus d'importance dans le choix de la route.
La notion de distance utilisée dans OSPF est uniquement liée aux coûts des liaisons qu'il faut emprunter pour relier deux routeurs.
Pour pénaliser les liaisons «lentes» (avec une faible bande passante), le coût est fixé à 108/d où d est la bande passante en bit/s de la liaison.
La valeur de 108 a été choisie pour associer un coût de 1 à une liaison FastEthernet avec un débit de 100 Mbit/s.
","title":""},{"edit":"
Calculer le coût pour des liaisons plus lentes:
Pour le satellite, le débit est de 50 Mbits/s, le coût sera de ......
Pour un câble Ethernet à 10 Mbit/s, le coût sera de ......
"},{"solution":"
Satellite 5
Ethernet 10
"}],[{"text":"
Le fonctionnement du protocole OSPF est découpé en deux grandes étapes.
Dans un premier temps, chaque routeur, après avoir été initialisé, tente de découvrir ses voisins afin d'établir une relation de voisinage.
Comme nous le verrons un peu plus loin, les machines dans le protocole OSPF sont classées en différentes zones (une zone est un ensemble de machines) et les
routeurs limitent donc leur recherche de voisins dans la zone qui leur est affectée.
Les détails de cette première étape pour un routeur R sont résumés ci-dessous.
R choisit un identificateur unique, par exemple sa plus grande adresse IP parmi celles de ses sous-réseaux.
Le routeur R poursuit en envoyant des messages de type HELLO à travers toutes ses interfaces réseaux. Ces paquets contiennent (entre autres) son identificateur, le numéro de sa zone et la liste des (identificateurs des} voisins avec qui il a déjà établi une relation de voisinage.
Quand un routeur de la zone reçoit un paquet HELLO de R, il vérifie si son identificateur apparaît déjà dans la liste des voisins. Si c'est le cas, il envoie simplement un accusé de réception à R pour lui signifier qu'il est toujours actif. Dans le cas contraire, il répond en envoyant les informations dont il dispose sur la topologie du réseau. Lorsque R reçoit ces données, il répond en faisant de même. Les messages qui contiennent les états des liens de communication sont appelées LSA (pour l'anglais Link State Advertisement). Il est important de noter que ces messages ne sortent jamais d'une zone.
Plusieurs échanges de messages LSA sont nécessaires pour synchroniser les connaissances de tous les routeurs.
À la suite de ces échanges, les routeurs
d'une même zone doivent tous avoir la même vision de la topologie du réseau pour leur zone.
Le processus mis en place dans cette première étape est une diffusion (en anglais flooding) de l'information de voisinage.
La deuxième étape du protocole OSPF consiste à exécuter, au sein de chaque routeur, un algorithme pour calculer les meilleures routes entre lui n'importe quel autre routeur de la zone.
Le coût d'une route utilisé dans cet algorithme est la somme des coûts des liaisons entre les routeurs traversés.
La meilleure route sera celle ayant le coût le plus faible.
Une fois qu'un routeur a calculé ses meilleures routes, il les enregistre dans sa table de routage.
******
Afin que le protocole OSPF puisse être facilement utilisé dans de grands réseaux, on répartit les routeurs de manière «logique» dans des zones.
La recherche de voisins, l'échange des états de liens et la découverte de la topologie sont alors restreints aux routeurs d'une même zone.
Cela à pour principal avantage de diminuer la charge de calcul des routeurs et de réduire considérablement les échanges de messages pour découvrir la topologie du réseau.
L'organisation de ces zones suit une structure hiérarchique très simple.
Pour commencer, chaque zone a un numéro unique.
La zone 0, obligatoire pour le protocole OSPF, est appelée Backbone.
Il s'agit d'une zone centrale à laquelle toutes les autres zones sont connectées.
Pour être connectée à la Backbone, une zone dispose d'un routeur particulier appelé ABR (pour Area Border Router).
Les routeurs ABR sont les seuls à être rattachés à deux zones (leur zone et la Backbone).
Les autres routeurs sont rattachés à une
seule zone et ils ne communiquent qu'avec les routeurs de cette zone.
Une conséquence immédiate de cette architecture est que pour réaliser des échanges de paquets inter-zones, c'est-à-dire des paquets avec des adresses source et destination qui ne sont pas dans la même zone, il faut nécessairement que les routeurs envoient ces paquets vers leur routeur ABR pour sortir de leur zone.
Cependant, puisque les messages LSA sont restreints à une zone, les routeurs n'ont aucune idée de la topologie du reste du réseau et il leur est donc impossible de connaître la meilleure route pour transmettre un paquet inter-zone.
Pour résoudre ce problème, les routeurs ABR ont une autre fonction dans le protocole OSPF, qui consiste à communiquer les meilleures routes de leur zone (et non les états de liens) à toutes les autres zones.
Ces informations, communiquées via la Backbone, sont essentielles pour que chaque routeur puisse calculer les plus courts chemins de l'ensemble du réseau sans connaître l'intégralité de sa topologie.
Pour illustrer le fonctionnement du protocole OSPF, prenons l'exemple du réseau décrit par la figure 2.
********
Ce réseau est constitué de 7 routeurs (R1,...,R7) et de liaisons de communication dont les bandes passantes sont de 10 ou 100 Mbit/s.
Les routeurs R1, R2 et R3 sont regroupés dans la zone logique appelée Zone 1.
De même, les quatre autre routeurs R4, ..., R7 appartiennent à la zone appelée Zone 2.
La zone 0 est la Backbone. Elle est constituée des routeurs R3 et R4 qui jouent respectivement le rôle de routeur
ABR pour les zones 1 et 2,
Lors de la phase d'initialisation, les routeurs reçoivent leur identificateur.
La valeur choisie est la plus grande adresse IP de leurs interfaces réseaux.
Par exemple, le routeur R1 étant sur les deux sous-réseaux 10.1.1.0/30 et 10.1.2.0/30, son identificateur est l'adresse IP qu'il possède sur l'interface 10.1.2.0/30; supposons qu'il s'agit de 10.1.2.1.
Figure 2 — Topologie d'un réseau de routeurs OSPF répartis dans trois zones et avec des liaisons de communication de 10 où 100 Mbit/s.
De la même manière,R3 ce voit associer son adresse sur le sous-réseau 10.3.1.0/30; on suppose dans notre exemple qu'il a l'adresse 10.3.1.1.
Le tableau ci-dessous contient les identificateurs associés aux routeurs.
routeur
identifiant
R1
10.1.2.1
R2
10.1.3.1
R3
10.3.1.1
R4
10.2.5.1
R5
10.2.5.2
R6
10.2.4.2
R7
10.2.2.2
Dans la suite, pour simplifier notre explication, nous utilisons les noms R1,...,R7 des routeurs plutôt que leurs identificateurs.
","title":""},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Voyons tout d'abord comment le routeur R5 interagit avec ses voisins et la manière dont il construit sa vision de la topologie du réseau.
Au démarrage du protocole, R5 envoie des messages HELLO sur tous les sous-réseaux auxquels îl est connecté.
Comme il ne connaît aucun autre routeur, il initie une relation de voisinage dès qu'il reçoit une réponse à ses messages.
De cette manière, R5 prend connaissance de ses voisins R4, R6 et R7.
Puisqu'il a des informations sur la bande passante des liens vers ces trois routeurs, sa vision de la topologie du réseau est la suivante.
Topologie du réseau pour R5
Lien
sous-réseau
coût
zone
R5 - R4
10.2.5.0/28
10
2
R5 - R6
10.2.3.0/28
1
2
R5 - R7
10.2.1.0/28
1
2
Après cette phase d'initialisation, les routeurs s'échangent des paquets LSA contenant les informations dont ils disposent sur la topologie du réseau.
Ainsi, R5 reçoit de R4 des informations sur sa liaison de communication avec
R6.
De même, il complète sa vision de la topologie avec le lien entre R6 et R7 (dont il reçoit la description par les deux routeurs).
Lorsque tous ces échanges sont terminés, la topologie du réseau vue par RS est la suivante.
Topologie du réseau pour R5
Lien
sous-réseau
coût
zone
R5 - R4
10.2.5.0/28
10
2
R5 - R6
10.2.3.0/28
1
2
R5 - R7
10.2.1.0/28
1
2
R4 - R6
10.2.4.0/28
1
2
R6 - R7
10.2.2.0/28
10
2
Puisque R5 a maintenant la topologie complète de sa zone, il peut passer à la troisième étape du protocole.
Il exécute donc l'algorithme pour déterminer les plus courts chemins entre lui et tous les autres routeurs de la zone 2.
La figure 3 montre le résultat obtenu.
*******
Ainsi, on voit qu'il est plus rapide pour R5 de passer par R6 pour atteindre R4 plutôt que d'utiliser le sous-réseau 10.2.5.0/28.
En effet, le coût du chemin R5 - R6 - R4 est seulement de 2 (coût de 1 pour la liaison 10.2.3.0/28 entre R5 et R6, auquel s'ajoute un coût de 1 pour le sous-réseau 10.2.4.0/28 entre R6 et R4) tandis qu'il est de 10 pour la liaison directe entre R5 et R4.
Ces plus courts chemins ne sont que ceux de la zone 2.
********
Figure 23.3 — Plus courts chemins entre le routeur R5 et les autres routeurs
de la zone 2.
Pour construire une table de routage pour l'ensemble du réseau, le routeur R5 doit tout d'abord connaître quel routeur de sa zone se charge de communiquer avec la Backbone.
C'est lors de la phase d'initialisation de voisinage que R4 informe tous les routeurs de la zone 2 qu'il va jouer le rôle du routeur ABR.
Ainsi, R4 communique à toutes les autres zones (via la zone 0) les plus courts chemins entre lui et les autres routeurs de la zone 2.
Inversement, il reçoit les plus courts chemins de la zone 1, qu'il communique à R5.
De cette manière, R5 apprend par exemple qu'il existe une route avec un coût de 3 pour atteindre R1 en passant par R4.
En intégrant à sa table de routage les informations sur les meïlleures routes de la zone 1, il obtient la table suivante:
Table de routage de R5
destination
interface
liaison
coût
10.2.1.0/28
fasteth1
1
10.2.5.0/28
fasteth0
1
10.2.4.0/28
10.2.3.1
fasteth0
2
10.2.5.0/28
10.2.8.1
fasteth0
2
10.3.1.0/30
10.2.3.1
fasteth0
3
10.1.2.0/30
10.2.3.1
fasteth0
5
10.1.3.0/30
10.2.3.1
fasteth0
4
On voit dans celui-ci qu'il peut atteindre R6 et R7 directement à travers deux interfaces FastEthernet (fasteth0 et fasteth1, respectivement).
Pour atteindre tous les autres routeurs (R4 ou ceux de la zone 1), il passe par le routeur R6 (dont on suppose qu'il a l'adresse IP 10.2.3.1 sur le sous-réseau 10.2.3.0/28).
","title":""},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Multidiffusion
Pour une diffusion plus efficace, un routeur envoie ses messages HELLO en multidiffusion (on dit aussi multicast) vers tous les routeurs de sa zone toutes les 10 secondes.
De cette manière, il évite une transmission point à point plus difficile à mettre en place (en cas de panne par exemple).
En «écoutant» l'adresse 224.0.0.5 (utilisée par défaut par le protocole OSPF), un routeur de la zone voit passer ces messages et peut y répondre s'il le souhaite.
","title":""},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Gestion des pannes et des modifications du réseau
Pour avoir une image fidèle de la topologie du réseau, qui peut évoluer en fonction des pannes ou de l'ajout de nouveaux routeurs ou liens de communications, les routeurs s'échangent régulièrement des messages HELLO et LSA.
Lorsqu'un routeur n'a pas de réponse d'un voisin (qu'il connaît déjà) au bout de 4 messages HELLO, ce voisin est considéré comme étant en panne.
","title":""},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Calcul des plus courts chemins
L'algorithme utilisé par les routeurs pour calculer les meilleures routes est celui inventé par Edsger Dijkstra en 1959.
Cet algorithme permet de trouver le plus court chemin entre deux sommets d'un graphe pondéré, c'est-à-dire un graphe avec des arêtes associées à des valeurs entières. Dans le cadre du protocole OSPF, les noeuds du graphe sont les routeurs, les arêtes sont les liens de communication du réseau et les valeurs avec lesquelles elles sont étiquetées sont les coûts.
Par exemple, pour le réseau de la figure 2, on obtient le graphe pondéré suivant.
La limite du nombre de routeurs traversés n'est donc plus nécessaire comme dans le protocole RIP.
","title":""},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Bande passante
Voici ci-dessous un tableau résumant les bandes passantes (BP) des liaisons de communication les plus classiques.
Certaines technologies sont asymétriques, leur bande passante montante (de l'utilisateur vers le fournisseur) étant généralement plus faible que la bande
passante descendante (du fournisseur vers l'utilisateur).
Technologie
BP descendante BP montante
Modem
56 kbit/s 48 kbit/s
Bluetooth
3 Mbit/s
Ethernet
10 à 100 Mbit/s
Wi-Fi
11 Mbit/s à 10 Gbit/s
ADSL
13 Mbit/s 1Mbit/s
4G
100 Mbit/s 50Mbit/s
Satellite
50 Mbit/s 1Mbit/s
FastEthernet
100 Mbit/s
FFTH (fibre)
10 Gbit/s
","title":""},{"edit":"
"}],[{"text":"
Sous les systèmes Unix, diverses commandes permettent d'afficher et modifier la configuration réseau de la machine.
Nous donnons ici un aperçu des commande ayant trait à la connectivite et au routage.
","title":"Commandes système"},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Champ TTL
Tous les réseaux n'étant pas gérés par des protocoles dynamiques comme RIP ou OSPF, il est possible que des boucles de
routage existent.
C'est le cas par exemple dans des réseaux où les tables sont configurées manuellement (on parle de routage statique).
Pour empêcher un paquet de tourner en rond dans de tels cas, les protocoles de
communication comme IP ont prévu un mécanisme basé sur un compteur de durée de vie.
Ainsi, chaque paquet contient un octet appelé TTL (en anglais, Time To Live) qui indique combien de routeurs un paquet peut encore traverser (la durée de vie initiale du paquet est fixée par le
protocole).
À chaque passage dans un routeur, ce compteur est décrémenté.
Quand un routeur reçoit un paquet avec un compteur TTL à 0, il le détruit.
De cette manière, un paquet ne peut pas tourner en rond trop longtemps dans le réseau.
","title":""},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
La commande ip
La commande ip est fournie en standard sous les systèmes Unix.
C'est un véritable couteau Suisse de l'administration réseau.
Elle remplace plusieurs commandes historiques dont l'utilisation est maintenant déconscillée.
Nous donnons un aperçu des informations accessibles via cette commande, et nous
donnons aussi à titre d'information les commandes qu'elle remplace.
Pour invoquer la commande, il suffit de la saisir dans l'interprète de commandes, suivie de la sous commande désirée, ainsi que des options.
La commande ip addr permet d'afficher la liste des périphériques réseau (ou interfaces) ainsi que les adresses IP qui leur sont associées (IPv4 et IPv6).
Bien d'autres informations sont affichées.
L'option -s affiche également des statistiques sur lc nombre de paquets reçus, émis, perdus ou retransmis.
Pour lancer les lignes de commandes qui vont suivrent, il faut se connecter en ssh au vps suivant: 217.182.207.90
Rappels :
si vous êtes sur Windows lancer le logiciel Putty et taper VotrerNomUtilisateur@217.182.207.90
Ensuite, vous taperez le mot de passe donné par le professeur.
Si vous êtes sur Linux ou Mac Os, vous tapez dans le terminal la commande ssh 217.182.207.90 Ensuite, vous tapez le mot de passe donné par le professeur.
Maintenant que vous êtes connecté en ssh, vous pouvez taper la commande suivante:
$ ip addr
Mettre les résultats ci-dessous.
","title":""},{"edit":"
Mettre le résultat ici de la commande ip addr.
"},{"text":"
La sortie de la commande ip addr ci-dessus nous indique que la machine possède deux interfaces.
L'interface lo, de type loophack (ou boucle locale, une interface fictive) est associée à l'adresse IPv4 127.0.0.1.
La seconde interface, ens3, est de type ethernet (une appellation pour les cartes Ethernet). L'adresse (adresse matérielle ou également appelée adresse physique) de la carte est sous la forme de 6 octets fa:16:3e:xx:xx:xx.
Son adresse IPv4 est 217.182.207.90/32 .
Un affichage similaire peut être obtenu avec la commande ifconfig.
"}],[{"text":"
La commande ip route
Celle-ci affiche la table de routage actuellement configurée par le système d'exploitation.
$ ip route
","title":""},{"edit":"
Tester cette commande sur le vps et mettre le résultat ici.
"},{"text":"
Dans le résultat ci-dessus, on peut voir que la route par défaut est la machine 217.182.204.1. Cette dernière est le routeur d'accès (celui du réseau local).
Tous les paquets qui ne sont pas à destination du réseau local seront transférés au routeur, qui se chargera de les retransmettre.
Un affichage similaire peut être obtenu avec la commande route.
Cette table de routage est une table typique d'un vps connectée à un point d'accès d'un data center.
Les tables de routage des routeurs ont la même structure mais possèdent plus de lignes, qui sont mises à jour régulièrement au fur et à mesure de l'exécution des protocoles de routage (RIP ou OSPF).
"}],[{"text":"
La commande ping
La commande ping, déjà abordée au programme de première, permet d'envoyer un paquet ICMP à une adresse de destination.
Le protocole ICMP (pour l'anglais {
Internet Control Message Protocol) est un protocole «auxiliaire» d'IP dont le but principal est d'échanger des informations d'état et des messages d'erreurs.
Pour effectuer des tests de connectivité, la commande ping envoie périodiquement un paquet IP incluant un message ICMP de demande d'écho et affiche le résultat de la réponse.
$ ping 213.186.33.3
","title":""},{"edit":"
Tester la commande et mettre le résultat ici.
"},{"text":"
Sans aucun autre paramètre que l'adresse IP ou le nom de la machine que l'on souhaite atteindre, la commande s'exécute sans fin, envoyant un message par seconde (on peut alors interrompre avec CTRL-C).
Deux options intéressantes sont l'option -c, qui permet d'indiquer le nombre de tentatives, et l'option -t, qui permet de fixer le TTL du paquet.
$ ping -c 1 -t 5 213.186.33.3
"},{"edit":"
Tester la commande et mettre le résultat ici.
"},{"text":"
Dans l'exemple ci-dessus, on essaye d'envoyer un seul paquet, avec un TTL de 5, à la machine 213.186.33.3.
On obtient une réponse de la machine 213.186.33.3 indiquant le message Time to live exeeded.
Cela signifie que, depuis la machine de l'utilisateur, plus de 5 routeurs doivent être traversés pour atteindre la machine 213.186.33.3.
En effet, comme chaque routeur décrémente le TTL, le 5ème routeur sur la route détruit le paquet car le TTL est arrivé à 0.
"}],[{"text":"
La commande traceroute
La commande traceroute permet de déterminer la route empruntée par un paquet IP pour atteindre une machine cible.
Pour cela, la commande envoie à la machine cible des paquets IP avec un TTL de plus en plus grand.
Elle peut ainsi déterminer la route en utilisant un phénomène similaire à celui décrit dans la section précédente.
En effet, le premier routeur renverra un message ICMP d'erreur indiquant qu'il a détruit le premier paquet (avec un TTL de 1}, le second routeur détruira le second paquet (avec un TTL de 2) et ainsi de suite.
Lorsque le TTL est suffisament grand, le paquet peut atteindre la destination, qui répond.
Cette succession de messages d'erreurs ICMP permet à la commande traceroute de déterminer la route totale.
$ traceroute -n 213.186.33.3
","title":""},{"edit":"
Tester la commande et mettre le résultat ici.
"},{"text":"
L'option -n désactive la résolution de nom, c'est-à-dire que la commande affiche uniquement les adresses IP des machines sans essayer de déterminer leur nom.
Une ligne commençant par « n adresse IP » indique qu'un routeur ayant l'adresse IP donnée se trouve à distance n.
Une ligne commençant par « n * * * » indique qu'à la distance n, aucune machine n'a répondu (le nombre de * indique le nombre de tentatives, qui est par défaut de 3).
Le résultat ci-dessus est donc un peu décevant : à partir de l'étape 12, plus personne ne répond sans pour autant que l'on arrive à atteindre la machine recherchée!
Cela peut s'expliquer par le fait que, sur Internet, pour des raisons de sécurité et de performance, une partie du traffic est filtré. Cela signifie que les routeurs ne laissent passer que les paquets qu'ils estiment légitimes.
Par défaut, la commande traceroute utilise un paquet UDP sur un certain port.
Si les routeurs filtrent sur ce port ce type de paquets, alors la commande ne peut pas fonctionner correctement.
Pour contourner ce problème, la commande possède plusieurs modes de fonctionnement.
L'option -I permet d'utiliser des paquets ICMP (les mêmes que ceux de la commande ping) qui sont généralement moins filtrés.
$ traceroute -n -I 213.186.33.3
"},{"edit":"
Tester la commande et mettre le résultat ici.
"},{"text":"
On obtient bien une route (partielle). Même si certains routeurs intermédiaires ne répondent pas, on atteint notre destination en 6 étapes.
"}],[{"text":"
Pour en savoir plus
Les protocoles de routage comme RIP et OSPF sont principalement utilisés dans les réseaux des systèmes autonomes (SA) comme les FAI (Fournisseurs d'Accès à Internet), les grandes entreprises, les institutions gouvernementales, etc.
Ce sont ces réseaux qui forment Internet.
Il y a aujourd'hui plus de 60 000 systèmes autonomes (source Wikipédia).
Parce que l'interconnexion de ces SA forme un réseau gigantesque, et que les politiques pour acheminer les paquets entre ces réseaux sont bien plus complexes que la recherche d'un plus court chemin, il existe un protocole de routage spécifique hiérarchique entre ces systèmes appelé BGP (en anglais Border Gateway Protocol).
Le rêle de BGP est de permettre à tous les SA de partager leurs informations de routage.
Intuitivement, les SA se comportent comme des «méga-routeurs», ils peuvent indiquer
dans les informations de routage comment atteindre des sous-réseaux qui les constituent, mais aussi comment les traverser pour atteindre d'autres SA distants.
Mais l'interconnexion entre SA ne se limite pas à des informations techniques. Elle est aussi régie par des accords de nature technique, commerciale et juridique entre les SA, appelés accords de peering (ou appairage en français).
Par exemple deux SA ayant un fonctionnement symétriques (disons de FAI A et B pour particuliers) peuvent décider d'un peering gratuit entre eux, c'est à dire que À ne va pas facturer B pour faire transiter des paquets originaires de B sur son réseau, et réciproquement.
À l'inverse, si on considère un FAI A et un fournisseur de contenu C (par exemple une plateforme de vidéo à la demande), alors la situation est asymétrique.
Les utilisateurs de A récupèrent énormément de données depuis C, mais ils n'en envoient pas beaucoup.
Ce déséquilibre fait que, pour soutenir le débit, A peut être dans l'obligation d'investir dans du nouveau matériel (liens à très haut débits supplémentaires).
Il peut alors négocier un accord commercial avec C.
D'une certaine façon, C paye pour avoir accès aux utilisateurs de A.
Si les négociations échouent, alors la qualité de service peut être affectée (car faute de moyen, les liens supplémentaires ne sont pas construits).
","title":""},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Les machines d'un réseau informatique communiquent entre elles grâce à des machines particulières, appelées routeurs, qui acheminent les paquets d'information d'une machine à une autre.
Ces routeurs ont une connaissance de la topologie du réseau informatique, acquise de façon collaborative grâce à des protocoles de routage.
Les protocoles RIP et OSPF en sont deux exemples.
Ces protocoles fonctionnent en permanence, pour prendre en compte les modifications du réseau mais aussi les pannes.
Le protocole OSPF intègre les coûts de connexion pour caleuler des plus courts chemins sur le réseau.
Sous Unix, les commandes route ou ip route permettent d'afficher la
table de routage d'une machine.
La commande traceroute permet d'obtenir la liste des machines reliant la machine locale et une destination.
","title":"Conclusion"},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Considérons le réseau de la figure ci-dessous:
Dans ce réseau, les nœuds A à F sont des routeurs dont on veut calculer les tables de routage.
On suppose que l'on à exécuté le protocole RIP sur ce réseau.
Compléter la table suivante, qui indique pour chaque machine la portion de la table de routage pour la destination G.
","title":"Exercice"},{"edit":"
machine
destination
passerelle
interface
distance
A
G
B
G
C
G
D
G
E
G
F
G
"},{"solution":"
Après stabilisation du protocole RIP, on obtient les informations de routage suivantes:
machine
destination
passerelle
interface
distance
A
G
B
eth0
3
B
G
F
eth0
2
C
G
B
eth2
3
D
G
E
eth1
3
E
G
F
eth1
2
F
G
-
eth1
1
"}],[{"text":"
On considère le réseau de la figure ci-dessus ainsi que le tableau suivant:
machine
destination
passerelle
interface
distance
A
G
B
eth0
3
B
G
F
eth0
2
C
G
B
eth2
3
D
G
E
eth1
3
E
G
F
eth1
2
F
G
-
eth1
1
On suppose maintenant que le lien B-F tombe en panne.
Quel est le vecteur de distance envoyé par B à ses voisins pour atteindre G, une fois qu'il détecte la panne (on suppose que les autres nœuds n'ont pas modifié leurs tables de routage)?
Pour chacun des événements suivants, dire lequel des quatre cas du protocole RIP indiqué ci-dessous ********* page 388 ********* est appliqué. On supposera, pour simplifier, qu'aucun autre événement ne se produit entre-temps et qu'ils sont tous exécutés «en séquence».
(a) Les routeurs A et C reçoivent de B le vecteur trouvé à la question 1.
(b) Le routeur C retransmet ce même vecteur à D.
(c) Le routeur D transmet le vecteur (G,3) à C.
Après le dernier cas ci-dessus, quel vecteur est transmis par C à A et B?
","title":"Exercice"},{"edit":"
Mettre le résultat ici (code et figure).
"},{"solution":"
1. B envoie le vecteur (G, 16), signifiant ainsi que la route qu’il connaissait pour atteindre G est de taille «infinie».
2. (a) A et C reçoivent de B le vecteur (G, 16). Ils reçoivent une route plus longue pour G de la part du voisin censé transmettre les paquets vers G. Nous sommes dans le cas(4). Les routeurs A
et C mettent à jour leur route, pour propager l'information de la
panne.
(b) C envoie le vecteur (G,16) à D. Ce dernier possède une route pour G passant par E de longueur 3. Il ignore cette information.
Nous sommes dans le cas (3).
(c) C reçoit le vecteur (G, 3) de D. Il ne possède que l'entrée (B, 16) pour G; il la remplace par l’entrée (D, 4).
3. Le nœud C envoie (G,4) à A et B. Ces derniers mettent à jour leur table pour inscrire (C,5) comme information de routage pour G.
"}],[{"text":"
On considère le réseau de la figure ci-dessus.
Pour chacun des liens du réseau, proposer une technologie réseau faisant que, pour les nœuds A, B et C, la route pour atteindre G soit différente selon que l'on utilise OSPF ou RIP.
","title":"Exercice"},{"edit":"
Mettre le résultat ici (code et figure).
"},{"solution":"
On rappelle qu’en utilisant RIP les nœuds A et C passent par B pour rejoindre G.
Pour forcer un chemin différent avec OSPF, on peut favoriser les liens C-D, D-E et E-F en leur attribuant une technologie ayant une bande passante plus élevée (et donc un coût moindre).
Pour ces trois liens, on peut choisir une connexion en fibre optique, avec une bande passante de 10 Gbit/s.
Pour tous les autres liens, on peut choisir une technologie moins performante comme de l'ADSL avec un débit compris entre 1 et 10 Mbit/s.
"}],[{"text":"
On considère un réseau ayant les propriétés suivantes:
la distance entre deux nœuds est toujours inférieure à 15;
pour chaque paire de nœuds (A, B), il n'existe pas plusieurs chemin de même taille entre A et B.
On considère ce réseau comme une unique zone backbone OSPF.
Donner une condition suffisante pour qu'OSPF et RIP calculent les même routes.
","title":"Exercice","tagtitle":"h1"},{"edit":"
Mettre le résultat ici (code et figure).
"},{"solution":"
Si tous les liens du réseau utilisent la même technologie, par exemple FastEthernet, alors le coût de chaque lien est le même.
Le chemin qui minimise le coût pour OSPF est celui qui possède le moins de noeuds intermédiaires, ce qui coïncide avec la distance utilisé par des routeurs RIP.
"}],[{"text":"
Montrer comment utiliser la commande ping pour calculer la route prise par un paquet entre sa machine et une machine cible.
","title":"Exercice"},{"edit":"
Mettre le résultat ici (code et figure).
"},{"solution":"
Étant donnée une machine cible, par exemple sciencesappliquees.com, on peut utiliser successivement plusieurs invocations de la commande
ping -c 1 -t n sciencesappliquees.com, en augmentant à chaque fois la valeur de l’entier n, qui représente le TTL du paquet émis.
La plus petite valeur de n pour laquelle la machine cible répond donne la longueur de la route.
"}],[{"text":"
Considérons une machine dont le nom est www.sciencesappliquees.com, est-ce que deux invocations successives de la commande
traceroute -I www.sciencesappliquees.com
donnent toujours le mêrne résultat?Justifier.
","title":"Exercice"},{"edit":"
Mettre le résultat ici (code et figure).
"},{"solution":"
Non, deux invocations de la commande traceroute peuvent afficher des résultats différents.
En effet, si entre les deux appels la route change (par exemple parce qu’un équipement intermédiaire tombe en panne), alors les routes seront recalculées et le paquet empruntera un chemin différent.
"}],[{"text":"
Comme on le sait, les protocoles RIP et OSPF sont implémentés de façon à ne pas introduire de cycles sur le réseau.
Soit une machine, www.sciencesappliquees.com.
Supposons que toutes les routes configurées manuellement entre notre machine et www.sciencesappliquees.com n'introduisent pas de cycle et que les autres informations de routage ont été complétées par l'un des deux protocoles (RIP où OSPF).
Est-il possible d'obtenir la sortie suivante pour la commande
traceroute -I www.unsite.fr ?
$ traceroute -I wuw.sciencesappliquees.com
1 192.168.1.254 (192.168.1.254) 2.666 ms 2.829 ms 2.995 ms
2 194.149.164.66 (194.149.164.66) 9.167 ms 9.832 ms 10.827 ms
3 * * *
4 194.149.10.62 (194.149.10.62) 10.776 ms 10.788 ms 10.786 ns
5 * * *
6 200.14.3.62 (200.14.3.62) 10.764 ms 10.762 ms 10.761 ms
7 * * *
8 200.14.3.62 (200.14.3.62) 10.764 ms 10.762 ms 10.761 ms
9 * * *
10 www.unsite.fr (67.12.19.133) 10.735 ms 65.184 ms 7.179 ms
","title":"Exercice"},{"edit":"
Mettre le résultat ici (code et figure).
"},{"solution":"
Oui, il est possible d'obtenir une telle sortie.
Attention, la répétition du même routeur aux lignes 6 et 8 n’indique pas forcément la présence d’un cycle.
En effet, le fonctionnement de la commande traeroute, fait d’envois sucessifs de paquets avec un TTL croissant, et la nature des réseaux IP font qu’il n’y a aucune garantie que les paquets envoyés prennent le même chemin à chaque fois.
Considérons la situation suivante (rare, mais possible):
Lors du 6° envoi, le paquet arrive sur le routeur 200.14.3.62 avec la route indiquée pour les étapes 1 à 5.
Après ce 6° envoi, un routeur intermédiaire, par exemple 194.149.10.62, tombe en panne. Une nouvelle route est mise en place. Cette nouvelle route peut compter plus de nœuds intermédiaires (soit parce que c’est la seule route dans le cas de RIP, soit parce que passer par ces nœuds est tout de même plus rapide).
Lors du 8° envoi, le paquet prend une route plus longue avant d’atteindre le routeur 200.14.3.62, qui se trouve maintenant à 8 sauts.
Même en l’absence de panne matérielle, cette situation peut être causée par de l’équilibrage de charge (ou load balancing en anglais).
En effet, certains routeurs peuvent être configurés pour avoir plusieurs routes possibles vers une destination et choisir des routes différentes en fonction de l’occupation du réseau ou de la nature des paquets routés (par exemple, envoyer des paquets d’e-mails par un chemin plus lent et utiliser un chemin plus rapide
pour les paquets de voix sur IP ou de vidéo en direct).
Alice rédige un compte-rendu pour un projet informatique. Elle à «ouvert» un logiciel de traitement de texte pour écrire le rapport.
Son navigateur web est aussi ouvert avec divers onglets, l'un pointant vers Wikipedia, l'autre vers un moteur de recherche et un troisième vers un site de réseau social dont elle se sert pour
partager son humeur avec ses camarades.
Elle utilise un logiciel de dessin afin d'ajouter des illustrations à son compte-rendu.
Son projet étant en Python, elle dispose aussi d'une fenêtre avec l'environnement Idle dans lequel elle exécute son programme afin d'en vérifier les résultats.
Enfin, pour ne pas être perturbée, elle a mis des écouteurs sur ses oreilles et écoute de la musique grâce au lecteur de musique de son ordinateur.
Tous ces programmes s'exécutent en même temps.
Pourtant, si on se souvient de la façon dont sont construits les ordinateurs, ils ne disposent que d'un nombre limité de processeurs.
Or, comme on le sait, un programme n'est qu'une suite d'instructions en langage machine, ces dernières étant exécutées une à une par le processeur.
Comment le processeur peut-il donc exécuter en même temps les instructions du programme de traitement de texte et celles du lecteur de musique?
Cette exécution concurrente de programmes est l'une des fonctionnalités de base offertes par les systèmes d'exploitation modernes.
On parle alors de systèmes d'exploitation multitâches.
Nous rappelons d'abord brièvement comment un programme est exécuté par le système d'exploitation, puis nous introduisons le principe de fonctionnement de l'ordonnanceur de processus, la partie du système d'exploitation permettant l'exécution concurrente des Programmes.
","title":"Gestion des processus et des ressources","tagtitle":"h1"},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Rappel sur l'exécution d'un programme
Un exécutable est un fichier (par exemple stocké sur le disque dur) contenant une suite d'instructions en langage machine.
MOVR0,#10\nMOVR1,#20\nADDR2,R1,R0\nADDR2,R2,#5
C'est donc une suite d'octets que le processeur est capable de décoder et exécuter.
Concrètement, lorsque l'on exécute un programme (par exemple en cliquant sur l'icône du fichier exécutable ou en renseignant son chemin dans un terminal), le système d'exploitation effectue les actions suivantes:
le fichier contenant le programme (exécutable) est copié dans la mémoire RAM, à une certaine adresse a;
le système d'exploitation écrit la valeur a dans le registre IP (instruction pointer).
Au prochain cycle d'horloge du processeur, ce dernier va alors lire l'instruction se trouvant à l'adresse a et l'exécuter.
Une fois cela fait, il exécutera ensuite la seconde instruction et ainsi de suite.
On rappelle que l'exécution d'une instruction se décompose elle-même en plusieurs sous-étapes effectuées au sein du processeur:
le chargement (récupérer l'instruction en mémoire), le décodage (déterminer dans la suite d'octets chargés quelle instruction ils encodent) et l'exécution proprement dite.
Même si elle est correcte, la description que nous avons faite de l'exécution d'un programme est incomplète.
En effet, si rien de plus n'est fait, alors la seule chose que l'on peut attendre, c'est que le programme en question s'exécute jusqu'à sa dernière instruction, puis rende la main au système d'exploitation.
Impossible alors de l'interrompre!
Impossible aussi de pouvoir exécuter deux programmes en même temps.
Pour pallier ce problème, les systèmes d'exploitation utilisent une fonctionnalité importante des processeurs modernes:
la notion d'interruption.
Une interruption est un signal envoyé au processeur lorsqu'un événement se produit.
Il existe plusieurs types d'interruptions.
Certaines sont générées par le matériel (par exemple, un disque dur signale qu'il à fini décrire des octets, une carte réseau signale que des paquets de données arrivent, etc.)
Lorsque le processeur reçoit une interruption, il interrompt son exécution à la fin de l'instruction courante et exécute un programme se trouvant à une adresse prédéfinie.
Ce programme reçoit en argument une copie des valeurs courante des registres, ainsi qu'un code numérique lui permettant de savoir à quel type d'interruption il fait face.
Ce programme spécial s'appelle le gestionnaire d'interruption.
Il est installé à une certaine adresse mémoire par le système d'exploitation, très tôt après le démarrage de la machine.
Parmi les interruptions matérielles, on retrouve les interruptions d'horloge.
Le processeur génère de lui-même une interruption matérielle à intervalles de temps fixe.
Historiquement, sur les processeurs Intel, cette interruption était levée toutes les 55 ms (environ 18 fois par seconde).
Le gestionnaire d'interruption était donc appelé au moins toutes les 55 ms.
De nos jours, les processeurs disposent d'horloges de haute précision capables d'émettre des interruptions avec une fréquence de 10 Mhz, donc toutes les 100ns.
Ces interruptions d'horloges, alliées au gestionnaire d'interruption, sont les pièces essentielles permettant d'exécuter des programmes de façon concurrente.
","title":""},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Vocabulaire
Certains termes sont particulièrement importants pour la suite et doivent être définis précisément:
Exécutable : un fichier binaire contenant des instructions machines directement exécutables par le processeur de la machine.
Processus : «programme en cours d'exécution». Un processus est le phénomène dynamique qui correspond à l'exécution d'un programme particulier. Le système d'exploitation identifie généralement les processus par un numéro unique.
Un processus est décrit par:
l'ensemble de la mémoire allouée par le système pour l'exécution de ce programme (ce qui inclut le code exécutable copié en mémoire et toutes les données manipulées par le programme, sur la pile ou dans le tas;
l'ensemble des ressources utilisées par le programme (fichiers ouverts, connexions réseaux, etc.);
les valeurs stockées dans tous les registres du processeur.
Thread ou tâche: exécution d'une suite d'instructions démarrée par un processus.
Deux processus sont l'exécution de deux programmes (par exemple, un traitement de texte et un navigateur web).
Deux threads sont l'exécution concurrente de deux suites d'instructions d'un même
processus.
Par exemple, pour un navigateur web, il peut y avoir un thread dont le rôle est de dessiner la page web dans une fenêtre et un autre thread dont le rôle est de télécharger un fichier sur lequel l'utilisateur à cliqué.
La différence fondamentale entre processus et thread est que les processus ne partagent pas leur mémoire, alors que
les threads, issus d'un même processus, peuvent accéder aux variables globales du programme et occupent le même espace en mémoire.
Exécution concurrente: deux processus ou tâches s'exécutent de manière concurrente si les intervalles de temps entre le début et la fin de leur exécution ont une partie commune.
Exécution parallèle: deux processus où tâches s'exécutent en parallèle s'ils s'exécutent au même instant. Pour que deux processus s'exécutent en parallèle, il faut donc plusieurs processeurs sur la machine.
","title":""},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Ordonnanceur du système d'exploitation
Comme nous l'avons vu, le système d'exploitation peut configurer une horloge et le gestionnaire d'interruption pour «reprendre la main», c'est-à-dire exécuter du code qui lui est propre, à intervalles réguliers.
Lorsqu'il s'exécute, il peut, entre autres choses, décider à quel programme en cours d'exécution il va rendre la main.
Dans le scénario d'utilisation donné en
introduction, l'ordonnanceur fonctionnera donc de la façon suivante:
Le programme «traitement de texte» est en cours d'exécution (l'utilisateur saisit du texte qui est affiché à l'écran, sauvegarde dans un fichier, etc.).
Une interruption d'horloge se déclenche.
Le code du gestionnaire d'interruption est appelé. Il reçoit en argument les valeurs qu'ont tous les registres avant le déclenchement de l'interruption (donc tout l'état «interne» du traitement de texte).
Le gestionnaire d'interruption sauvegarde ces registres à un endroit particulier de la mémoire.
Il choisit dans la liste des processus un autre processus, par exemple celui correspondant au navigateur web.
Il restaure les valeurs de tous les registres du processeur qu'il a sauvegardées la dernière fois qu'il a interrompu le navigateur web. Parmi ces registres sauvegardés, il y a notamment IP, l'adresse de la prochaine instruction à exécuter. Elle pointait alors vers une instruction du programme «navigateur web».
Le gestionnaire d'interruption rend la main. La prochaine instruction à exécuter est celle du processus navigateur web, qui reprend son exécution, jusqu'à ce qu'il soit mis en pause par la prochaine interruption d'horloge.
Le fait que l'ordonnanceur interrompe un processus et sauve son état s'appelle une commutation de conteste.
Afin de pouvoir choisir parmi tous les
processus lequel exécuter lors de la prochaine interruption, le système d'exploitation conserve pour chaque processus une structure de dounées nommée
PCB (pour l'anglais Process Control Bloc ou bloc de contrôle du processus).
Le PCB est simplement une zone mémoire dans laquelle sont stockées diverses informations sur le processus.
Nom
Description
PID
Process ID, l'identifiant numérique du processus
État
l'état dans lequel se trouve le processus
Registres
la valeur des registres lors de sa dernière interruption
Mémoire
zone mémoire (plage 'adresses) allouée par le processus lors de son exécution
Ressources
liste des fichiers ouverts, connexions réseaux en cours d'utilisation, etc.
Pour choisir parmi les processus celui auquel il va donner la main, l'ordonnanceur conserve les PCB dans une structure de donnée, par exemple une file (telles que celles décrites sut les Piles et les Files).
Le premier processus dans la file reprend son exécution.
Lors de la prochaine interruption, il est mis en bout de file.
Cette stratégie simple permet d'éviter qu'un processus monopolise tout le temps de calcul.
En effet, avant qu'un processus mis en bout de file puisse s'exécuter de nouveau, il aura laissé une chance à tous les autres d'avoir la main.
","title":""},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
États des processus
Les interruptions d'horloges ne sont pas les seuls événements permettant d'interrompre les processus.
Considérons le petit programme Python ci-dessous:
texte = input(\"Saisir une phrase : \")
print (\"Votre phrase en majuscules : \", texte.upper())
Ce programme utilise la fonction input pour demander à l'utilisateur de saisir une phrase.
Tant que l'utilisateur ne saisit rien, le programme ne peut pas avancer à l'instruction suivante.
Le système d'exploitation (qui gère aussi
le matériel et en particulier l'accès au clavier) peut suspendre le processus et le mettre en attente.
De plus, il sait aussi qu'il est inutile de réveiller ce processus tant que l'utilisateur n'a pas interagi avec le clavier.
Une telle interaction avec le clavier est signalée au processeur par une interruption, similaire aux interruptions d'horloges.
De manière générale, lorsque des périphériques (carte réseau, disque dur, souris, clavier, etc.) veulent signaler
au processeur qu'un événement est survenu, ils le feront au moyen d'une interruption.
Le système d'exploitation aura alors l'occasion «de réveiller» l'un des processus parmi ceux qui attendaient un tel événement.
On voit donc que les processus peuvent être dans différents états.
La plupart des systèmes d'exploitation utilisent principalement les états suivants:
Nouveau : état d'un processus en cours de création. Le système d'exploitation vient de copier l'exécutable en mémoire et d'initialiser le PCB.
Prêt: le processus peut être le prochain à s'exécuter, Il est dans la file des processus qui «attendent» leur tour et peuvent être choisis par l'ordonnanceur.
En exécution: le processus est en train de s'exécuter.
En attente: le processus est interrompu et en attente d'un événement externe (entrée/sortie, allocation mémoire, etc.).
Terminé: le processus s'est terminé, le système d'exploitation est en train de désallouer les ressources que le processus utilisait,
Les états nouveau et terminé sont éphémères.
En temps normal, l'état d'un processus variera entre prêt, en attente et en exécution.
La figure ci-desssous résume le cycle de vie d'un processus.
Figure 1 — Cycle de vie d'un processus.
On peut noter que quel que soit l'état dans lequel se trouve un processus, il peut se terminer de façon anormale.
S'il est en exécution, cela peut être dû à une erreur provoquée par le programme
(lecture d'une adresse mémoire invalide, division par 0, etc.).
Si le processus est en attente d'une entrée/sortie, il est possible qu'il se produise une erreur matérielle (disque dur défectueux par exemple).
Enfin, si le processus est dans l'état prêt, il peut quand même se terminer de façon anormale si l'utilisateur qui à lancé le programme ou l'administrateur système décide de l'interrompre manuellement.
","title":""},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Dans les systèmes POSIX (Unix, Linux, Mac OSX, Android,...) la commande ps
(pour l'anglais process status ou état des processus) permet d'obtenir des informations sur les processus en cours d'exécution.
$ ps -a -u -x
Connecter vous au vps 217.182.207.90 à l'aide du logiciel Putty sous windows ou avec le terminal sur Linux et Mac à l'aide de la commande :
ssh nomUtilisateur@217.182.207.90
Attention, il faut demander au professeur le nom d'utilisateur et le mot de passe.
Une fois connecté au serveur vps, lancer la commande ps -a -u -x et copier le résultat ci-dessous.
","title":"Commandes Unix de gestion des processus"},{"edit":"
Mettre ici le résultat de la commande ps -a -u -x
"},{"text":"
Les options -a, -u et -x permettent respectivement d'afficher tous les processus (et pas seulement ceux de l'utilisateur qui lance la commande), d'afficher le nom des utilisateurs (plutôt que leur identifiant numérique) et de compter aussi les processus n'ayant pas été lancés depuis un terminal (comme les daemon ou les processus lancés depuis une interface graphique).
Le détaille des colonnes les plus importantes sont ci-dessous. Pour en savoir plus avec la commande ps, il faut aller voir le manuel avec la commande man ps.
La colonne USER indique le nom de l'utilisateur qui a lancé le processus.
La colonne PID donne l'identifiant numérique du processus.
Les colonnes %CPU et %MEM indiquent respectivement le taux d'occupation du processeur et de la mémoire par le processus. Par exemple, dans l'affichage précédant, on peut voir que le processus 6966 occupe 9,8% du temps de calcul du processeur et 2% de la mémoire. En simplifiant un peu, on peut dire que sur les dernières 100 secondes d'utilisation du système, 9,8 secondes ont été passées à exécuter des instruction du processus 6966.
La colonne TTY indique l'identifiant du terminal où le processus à été lancé. Un caractère ? indique que le processus n'a pas été lancé depuis un terminal.
La colonne STAT indique l'état du processus (là première lettre en majuscule).
Sur la plupart des systèmes Unix, les états sont:
R : running ou runnable, le processus est dans l'état prêt ou en exécution (la commande ps ne différencie pas ces deux états);
S : sleeping, le processus est en attente.
Les colonnes START et TIME indiquent respectivement l'heure ou la date à la-
quelle le programme a été lancé et le temps cumulé d'exécution du processus correspondant (c'est-à-dire le temps total pendant lequel le processus était dans l'état « en exécution »).
Enfin, la colonne COMMAND indique la ligne de commande utilisée pour lancer le programme.
"}],[{"text":"
La commande top
Une commande un peu plus conviviale est la commande top. Cette dernière affiche en temps réel des informations similaires à celles affichées par ps.
Lancer la commande top et mettre le résultat ci-dessous.
Attention, pour sortir de la commande top, il faut utiliser la combinaison de touches Ctrl+C.
Ces informations sont rafraîchies toutes les secondes.
Cette commande est particulièrement précieuse pour essayer de déterminer quels processus occupent le plus le processeur ou ont alloué le plus de mémoire,
L'affichage de la commande top est donné à la figure 2.
","title":""},{"edit":"
Mettre le résultat ici de la commande top.
"}],[{"text":"
La commande kill
Si on souhaite interrompre un processus dont on connaît le PID, on peut utiliser la commande kill, en lui spécifiant les numéros des processus que l'on souhaite terminer.
$ kill 69663537
La commande ci-dessus va envoyer un signal de terminaison aux deux processus listés.
Pour les applications graphiques, ce signal est globalement équivalent à fermer la fenêtre principale de l'application.
Pour les applications en ligne de commande (qui s'exécutent dans un terminal en bloquant celui-ci), cela correspond à exécuter la combinaison de touche Ctr1-C.
Ce signal peut être intercepté par l'application et géré par cette dernière.
Par exemple, un logiciel de traitement de texte peut, comme lorqu'on ferme la fenêtre, proposer à l'utilisateur de sauvegarder ses fichiers avant de quitter.
Si on souhaite terminer immédiatement le processus sans que ce dernier puisse intercepter le signal, on peut passer l'option -9 à la commande ki11.
$ kill -9 69663537
******
Figure 22.2 — L'affichage de la commande top.
Dans ce cas, les processus seront immédiatement terminés, sans qu'ils puissent exécuter la moindre instruction supplémentaire. En particulier, les fichiers non écrits seront perdus. Cette option est à utiliser en dernier recours, par exemple lorsque l'application ne «répond plus» et se comporte de manière anarchique (utilisation importante du processeur où consommation de mémoire excessive).
Remarque : Sous le système Microsoft Windows, le gestionnaire de tâches joue un rôle similaire. Il est accessible par la combinaison de touche Ctrl+Alt+Sup.
","title":""},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Processus concurrents
Comme nous l'avons vu, les processus d'un système s'exécutent de manière concurrente:
leurs exécutions sont entrelacées.
De plus, l'ordre dans lequel ils s'exécutent est hors de leur contrôle, car il est décidé par l'ordonnanceur de processus du système d'exploitation.
Cette façon de fonctionner possède de nombreux avantages. Elle permet l'exécution d'un très grand nombre de programmes sur une machine monoprocesseur.
Elle permet aussi d'optimiser les ressources de la machine.
Par exemple, si un processus est en attente d'entrées-sorties, il est simplement mis en pause et le système peut utiliser le processeur pour effectuer un calcul utile. Et même si tous les processus sont en attente d'un événement, le système peut alors décider
dans ce cas de réduire la fréquence du processeur ou de le mettre partiellement en veille, ce qui permet d'économiser de l'énergie.
Cet aspect est particulièrement important pour les systèmes mobiles ou embarqués.
Cependant, l'utilisation de systèmes multitâches n'est pas sans problème.
En effet. lorsqu'un processus est interrompu, il ne s'en «rend pas compte».
Dit autrement, lorsqu'un processus est interrompu, il reprendra son exécution exactement dans l'état où il s'était arrêté. Tant que ce processus manipule des objets visibles de lui seul (par exemple, des variables allouées sur la pile ou dans le tas), tout va bien.
Mais si le processus accède à une ressource partagée, comme un fichier ou un périphérique matériel, alors de nombreux problèmes peuvent se produire.
Considérons le programme ci-dessous.
Programme — Écriture dans un fichier
from os import getpid
pid = str(getpid())
with open (\"test.txt\", \"w\") as fichier:
for i in range (1000):
fichier.write(pid + \" : \" + str(i) + \"\\n\")
fichier.flush()
Ce programme importe la fonction utilitaire getpid du module os. Celle-ci ne prend pas d'argument et renvoie simplement l'identifiant du processus dans lequel on se trouve.
Le programme stocke dans une variable globale son identifiant de processus, converti en chaîne de caractères. Puis il ouvre le fichier test.txt en écriture (mode \"w\", qui autorise l'écriture et vide le fichier s'il existe, plaçant le curseur interne en début de fichier).
Le programme écrit ensuite 1000 lignes de la forme:
12345 : O
12345 : 1
...
12345 : 999
où 12345 est l'identifiant de processus déterminé au début du programme.
La méthode .flush() de l'objet fichier assure que les caractères écrits avec la méthode write sont effectivement écrits sur le disque.
Enregistrer le programme Ecrire dans un fichier dans ecrire_fichier.py.
Exécuter le dans l'Idle python :
>>> ecrire_fichier.py
Ouvrer le fichier test.txt et mettre le numéro du processus ci-dessous.
Lancer dans l'Idle python l'instruction ci-dessus et mettre le résultat ci-dessous.
","title":""},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Comme l'écriture sur un disque dur étant coûteuse en temps, les fonctions d'écriture gardent en interne un tableau de caractères 4 tampon » dans lequel sont accumulés les caractères écrits au moyen de -write(). Seulement lorsque ce tableau est plein, les données sont écritent dans le fichier.
L'utilisation du caractère & à la suite d'une commande bascule directement ce programme en arrière-plan et rend directement la main au terminal.
On a donc lancé trois fois le programme ecrire_fichier.py.
Le shell a indiqué dans la console les identifiants des processus correspondants.
Les trois lignes contenant Done sont l'écrites lorsque les programmes se terminent.
Observer le contenu du fichier test.txt et conclure.
","title":""},{"edit":"
Conclure ici.
"}],[{"text":"
En observant le contenu du fichier test.txt, vous avez vu des lignes telles que:
11104 : 150
11103 : 151
11104 : 152
11105 : 153
11103 : 154
11103 : 155
11105 : 156
11105 : 157
11105 : 158
11105 : 159
11104 : 160
11104 : 161
11104 : 162
11104 : 163
11105 : 164
11105 : 165
11105 : 166
11105 : 167
11104 : 168
11104 : 169
11104 : 170
11104 : 171
11104 : 172
11104 : 173
11103 : 174
11103 : 175
11103 : 176
11103 : 177
11104 : 178
11104 : 179
11104 : 180
On a donc l'impression que certaines lignes ont été écrites par certains processus uniquement.
La réalité est plus complexe. Lors de l'ouverture du fichier, chaque processus initialise dans sa mémoire une variable de position (le curseur) contentant le décalage par rapport au début du fichier.
Lorsque des caractères sont écrits au moyen de .write() le curseur est avancé d'autant de caractères. Un enchaînement d'exécutions de processus provoquant les écritures est représenté à la figure 3.
La figure 3 représente dans trois colonnes les états des trois processus, en particulier les valeurs internes du curseur dans le fichier test.txt et les chaînes qui sont écrites lorsque le processus à la main.
La figure ci-dessous représente l'instant où le processus 11103 a la main,
Ce dernier est à une certaine position dans le fichier (1726) et y écrit deux lignes. Il est alors interrompu par l'ordonnanceur qui provoque un changement de contexte et donne la main au processus 11105.
Ce dernier se trouvait dans la position du curseur 1714 dans le fichier, et donc à indice inférieur (car les trois processus sont partis de l'indice 0 et écrivent exactement les mêmes nombres de caractères dans le fichier).
Le processus 11105 écrit une ligne avant d'être interrompu.
Le processus 11104 passe alors en exécution et peut écrire une ligne.
Comme c'est le dernier processus à écrire le ligne « 143 » qui se retrouve dans le fichier final.
L'ordonnanceur rend la main au processus 11105 qui se retrouve dans l'état où sa variable de curseur vaut 1726 et sa variable i de boucle vaut 153.
Il va donc procéder à l'écriture de la chaîne 11005 : 153\\ndans le fichier, écrasant par là même ligne qui avait été écrite à cette même position par le processus 11103.
Il est important de noter que cet «entrelacement» des instructions des trois processus est «aléatoire» ou plus
exactement non déterministe.
Figure 3 — Commutations de contexte entre processus.
Si on ré-exécute les trois programmes, l'ordonnanceur peut décider de changer de contexte à d'autres moments, en fonction de divers paramètres (nombre de processus total en cours d'exécution, valeur des horloges, etc.) et on obtiendra un fichier différent.
","title":""},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Interblocage
Un tel accès concurrent à une même ressource est souvent problématique.
Certains périphériques matériels, en particulier, nécessitent un accès exclusif.
Nous illustrons ce phénomène complexe avec les deux programmes suivant:
enregistrer_micro: acquiert la carte son en accès exclusif (pour accéder au micro) et écrit les sons enregistrés sous un certain format (par exemple mp3 ou wav) sur sa sortie standard et s'arrête lorsqu'il a écrit l'équivalent de 10 secondes de son.
jouer_son: acquiert la carte son en accès exclusif (pour accéder au haut-parleur) et joue le contenu qu'il reçoit sur son entrée standard.
Même si nous ne donnons pas les détails du code (d'assez bas niveau) permettant de réaliser ces programmes, ils n'en restent pas moins assez réalistes.
Les cartes son sont typiquement des périphériques ne pouvant être utilisés
que par au plus un processus.
Ces deux programmes peuvent être exécutés
séquentiellement de la façon suivante:
$ enregistrer_micro > message.mp3
$ cat message.mp3 | jouer_son
La première ligne de commande appelle le programme enregistrer_micro et enregistre (au moyen d'une redirection) les octets émis sur sa sortie standard dans le fichier message.mp3.
La deuxième commande utilise l'utilitaire cat pour afficher le contenu du fichier message.mp3 sur la sortie standard puis redirige cette dernière au moyen de l'opérateur «|» sur l'entrée standard de la commande jouer_son.
Que se passe-t-il maintenant si on enchaîne directement les deux commandes?
$ enregistrer_micro | jouer_son
Le premier programme ouvre la carte son.
Il commence ensuite à envoyer des données sur sa sortie standard.
Le second programme tente alors d'ouvrir la carte son et se retrouve bloqué.
Il ne peut continuer son exécution tant que la carte son n'est pas disponible.
Comme le programme jouer_son est bloqué, il ne lit aucun octet sur son entrée standard.
Comme il ne lit aucun octet, le premier programme se retrouve lui aussi bloqué lorsqu'il tente d'écrire sur sa sortie standard (puisque personne ne lit, les octets s'accumulent dans une zone mémoire tampon et le programme est bloqué quand cette dernière est pleine).
Les deux processus correspondant à ces deux programmes sont en inter-blocage:
le processus de jouer_son attend que la carte son soit libre pour progresser et lire son entrée standard;
le processus d'enregistrer_micro attend que quelqu'un consomme son entrée standard pour progresser et libérer la carte son.
L'inter-blocage est le grand danger de la programmation concurrente.
Dans notre exemple, l'utilisateur constaterait juste que les deux programmes ne font rien et ne rendent pas la main.
Il existe quatre conditions nécessaires
à la présence d'un inter-blocage, appelées conditions de Coffman, du nom d'Edward Grady Coffman Jr. (1934-), informaticien américain qui les a décrites en premier en 1971.
Exclusion mutuelle: au moins une ressource du système doit être en accès exclusif.
Rétention et attente: un processus détient une ressource et demande une autre ressource détenue par un autre processus.
Non préemption: une ressource ne peut être rendue que par un processus qui la détient (et ne peut pas être «préemptée» ou acquise de force par un autre processus).
Attente circulaire: l'ensemble des processus bloqués P1,...,Pn sont tels que P1 attend une ressource tenue par P2,...,Pn et P2,...,Pn attend une ressource détenue par P1.
Dans notre exemple précédent, les quatre conditions sont remplies.
Il existe une ressource à accès exclusif, la carte son(1).
Le processus du programme enregistrer_micro détient la carte son et veut en plus pouvoir écrire sur sa sortie (2).
Nous avons fait l'hypothèse que jouer_son est bloqué et ne peut pas acquérir la carte son (3).
Le programme enregistrer_micro attend
le déblocage de son entrée standard bloquée par jouer_son et ce dernier
attend le déblocage de la carte son détenue par enregistrer_micro (4).
Il existe plusieurs stratégies permettant d'éviter les inter-blocages où de les détecter et de les résoudre.
Dans le cadre générique des systèmes d'exploitation, et des programmes utilisateurs, la solution souvent retenue est la plus simple, à savoir interrompre les programmes (par exemple au moyen de
la commande kill).
","title":""},{"edit":"
"}],[{"text":"
Programmation concurrente en Python
Afin d'illustrer les problématiques d'inter-blocage dans un cadre plus
contrôlé que dans un système d'exploitation, nous donnons ici une introduction à la programmation multithread en Python.
Comme nous l'avons déjà expliqué, un thread est un «sous-processus» démarré par un processus et s'exécutant de manière concurrente avec le reste du programme.
Le module threading de la bibliothèque standard Python permet de démarrer des threads.
Nous illustrons son utilisation au moyen du programme ci-dessous:
*******57
Ce programme définit une fonction hello prenant en argument un entier représentant l'identifiant du thread dans lequel on se trouve.
Cette fonction effectue ensuite une boucle pour i allant de 0 à 4 et écrit à chaque tour de boucle la valeur de n et de i.
La fonction imprime ensuite un message indiquant qu'elle a terminé la boucle puis se termine.
Le programme principal effectue une boucle et appelle quatre fois (pour n entre 0 et 3) l'expression:
threading.Thread(target=hello, args=[n]).
Cette dernière crée un objet de type Thread.
L'argument nominé target doit être une fonction et l'argument args un tableau des arguments qui seront passés à la fonction.
La variable t contient l'objet Thread créé.
La méthode .start() lance l'exécution de la fonction en tâche de fond.
Cette méthode rend directement la main et le programme principal continue de s'exécuter de façons concurrente au thread démarré.
Programme — Comptage en parallèle
import threading
def hello(n):
for i in range(5):
print (\"Je suis le thread\", n, \"et ma valeur est\", i)
print (\"------ Fin du Thread \", n)
for n in range(4):
t = threading.Thread(target=hello, args=[n])
t.start()
Le programme ci-dessus, une fois exécuté, comporte alors cinq threads:
ceux démarrés par .start() et le
thread principal.
Tester 2 fois le programme et mettre le résultat ci-dessous. Comparer les 2 résultats et conclure sur les threads.
","title":""},{"edit":"
Mettre le résultat ici (code et figure).
"},{"text":"
Comme pour les processus, les threuds alternent leur exécution au gré des commutations de contexte.
Deux exécutions successives donnent des affichages différents.
L'ordre dans lequel sont démarrés les threads ne donne aucune indication sur l'ordre dans lequel ils peuvent se terminer (dans l'exemple ci-dessus, le thread 0, démarré en premier se termine le dernier).
On peut s'étonner que les threads ne s'interrompent pas les un les autres au milieu d'une ligne.
Nous donnerons l'explication de ce phénomène plus loin.
"}],[{"text":"
Les threads peuvent servir à illustrer les problèmes de concurrence et d'interblocage.
Considérons le programme ci-dessous.
Programme — Compteur global partagé
import threading
COMPTEUR = 0
def incrc():
global COMPTEUR
for c in range(100000):
v = COMPTEUR
COMPTEUR = v + 1
th = []
for n in range(4):
t = threading.Thread(target=incrc, args=[])
t.start()
th.append(t)
for t in th:
t.join()
print (\"valeur finale\", COMPTEUR)
Ce dernier définit une variable globale COMPTEUR.
La fonction incre est similaire à la fonction hel1o du programme précédent.
Elle ne prend pas d'argument mais exécute 100 000 itérations d'une boucle qui incrémente la variable globale COMPTEUR.
Le programme principal déclare un tableau vide th.
Il démarre ensuite quatre threads et stocke les objets correspondants dans le tableau th, après les avoir démarrés.
Enfin, pour chacun des objets Thread stockés, la méthode .join() est appelée.
Cette dernière permet d'attendre que le thread auquel on l'applique soit terminé.
Si le thread est déjà terminé, la méthode se termine immédiatement.
Enfin, le programme imprime la valeur finale contenue dans le compteur.
Comme on a démarré quatre threads, et que chacun incrémente la valeur 100 000 fois, on s'attend à ce que l'affichage final soit 400 000.
Cependant, si on exécute le programme plusieurs fois, on peut constater qu'il n'affiche pas toujours le nombre attendu.
Tester plusieurs fois le programme pb_threads.py et conclure.
Que se passe-t-il?
Considérons les quatre threads t0 à t3.
Supposons que t0 soit en exécution et que la valeur de COMPTEUR soit 42. Si t0 est interrompu juste après avoir exécuté
v = COMPTEUR,
alors sa variable locale v contient la valeur 42.
La commutation de contexte donne la main à 41, qui exécute v = COMPTEUR suivi de COMPTEUR = v + 1 avant d'être lui-même interrompu.
La valeur de compteur continue d'augmenter lors des commutations de contexte suivantes avec 2 et t3 jusqu'à avoir COMPTEUR valant 50.
Lorsque ta reprend enfin la main, il continue là où il s'était arrêté et exécute donc COMPTEUR = v + 1, où la valeur de v est 42!
Le thread t0 va donc écraser la valeur 50 avec la valeur 43.
","title":""},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Pour corriger ce problème, il nous faut donc garantir l'accès exclusif à la variable COMPTEUR entre sa lecture et son écriture.
On peut pour cela utiliser un verrou.
Un verrou est un objet que l'on peut essayer d'acquérir.
Si on est le premier à faire cette demande, on acquiert le verrou.
On peut le rendre à tout moment.
Si en revanche quelqu'un d'autre tient le verrou, alors on est bloqué jusqu'à ce qu'il soit libéré.
Des verrous munis de ces deux opérations
sont disponibles dans le module threading avec le constructeur Lock.
Une fois l'objet verrou construit, on peut tenter de l'acquérir avec la méthode
.acquire() et on peut le rendre avec la méthode .release().
Une manière de corriger le programme précédent est la suivante:
import threading
verrou = threading.Lock()
def hello(n):
for c in range(5):
verrou.acquire()
v = COMPTEUR
COMPTEUR = v + 1
verrou.release()
Avant toute tentative de lecture, on essaye d'acquérir le verrou.
Une fois ce dernier acquis, le thread courant a la garantie qu'il est le seul à exécuter son code, jusqu'à l'instruction verrou.release(}).
Une telle portion de code protégée par un verrou s'appelle une section critique.
Attention, cela ne signifie pas que le thread ne peut pas être interrompu entre les lignes
v = COMPTEUR
et
COMPTEUR = v+1.
Cela signifie seulement que les autres threads, s'il reprennent la main, ne sont pas eux-mêmes en section critique
(ils sont forcément ailleurs dans leur code, probablement bloqués sur l'instruction verrou.acquire()).
On remarque qu'il est important que tous les thronds manipulent le même verrou.
C'est pour cela qu'il a été défini dans une variable globale accessible depuis tous les threads.
En dernière remarque, on peut ajouter que le programme aurait été tout aussi faux si on avait écrit COMPTEUR = COMPTEUR + 1 ou même COMPTEUR += 1. En effet, une instruction d'un langage de haut niveau comme Python cst en fait décomposée en de
nombreuses instructions machines:
lecture de la valeur du compteur en mémoire, addition et écriture en mémoire de la nouvelle valeur.
Le programme aurait donc pu être interrompu entre deux de ces instructions machines.
L'utilisation de plusieurs verrons rend les interblocages possibles.
Il conviendra donc d'être très prudent lorsque l'on manipule deux verrous à la fois.
","title":""},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
On illustre le problème des 2 verrous avec le programme suivant:
Programme - Interblocage
import threading
verrou1 = threading.Lock()
verrou2 = threading.Lock()
def f1():
verrou1.acquire()
print (\"Section critique 1.1\")
verrou2.acquire()
print (\"Section critique 1.2\")
verrou2.release()
verrou1.release()
def f2():
verrou2.acquire()
print (\"Section critique 2.1\")
verrou1.acquire()
print (\"Section critique 2.2\")
verrou1.release()
verrou2.release()
t1 = threading.Thread(target=f1, args=[])
t2 = threading.Thread(target=f2, args=[])
t1.start ()
t2.start ()
Tester plusieurs fois le programme ci-dessous et conclure.
Ce dernier déclare deux verrous, utilisés de façon symétrique par deux fonctions f1 et f2.
La fonction f1 essaye d'acquérir d'abord verrou1 puis verrou2, alors que f2 essaye de les acquérir dans l'ordre inverse.
Si on exécute ce programme, il a de grandes chances de se retrouver bloqué.
Considérons l'exécution suivante:
Le thread t1 a la main. Il s'exécute jusqu'à son premier affichage (avant la tentative d'acquisition de verrou2).
Le thread t2 prend la main. Il s'exécute, acquiert verrou2 qui est toujours libre, puis bloque sur l'acquisition de verrou1.
Le thread t1 reprend la main, il bloque alors sur l'acquisition de verrou2 (tenu par t2).
Chaque thread détient un verrou et attend l'autre. Ils sont en interblocage.
Cependant, le problème ne se manifeste que si les exécutions se font dans cet ordre.
Si la commutation de contexte intervient après que f1 a acquis verrou2, alors t1 peut se terminer sans bloquer.
Dans des programmes complexes, les situations d'interblocage sont particulièrement difficile à tester et
à corriger.
En effet, à cause du non déterminisme de l'ordonnancement des threads et des processus, il se peut que le programme se comporte bien lors de la phase de test et ne se bloque que lorsqu'il est exécuté en conditions réelles.
","title":""},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Verrous cachés
Considérons de nouveau le programme ci-dessous.
Programme — Comptage en parallèle
import threading
def hello(n):
for i in range(5):
print (\"Je suis le thread\", n, \"et ma valeur est\", i)
print (\"------ Fin du Thread \", n)
for n in range(4):
t = threading.Thread(target=hello, args=[n])
t.start()
Puisque les commutations de contextes peuvent intervenir à tout moment, il peut
sembler étrange que les affichages des threads s'entrelacent «proprement». En effet, l'affichage du programme donne l'impression que les threads ne sont jamais interrompus au milieu d'une ligne.
Ce comportement est du au fait que par défaut, la sortie standard de Python possède un buffer (ou zone tampon).
Ce dernier n'est rien d'autre qu'un tableau d'octets.
Chaque appel à print n'écrit pas directernent dans la console, ce qui serait inefficace, mais ajoute les caractères à la suite dans le buffer.
Lorsque ce dernier est plein, il est affiché d'un seul coup en utilisant une instruction de bas niveau du système d'exploitation.
Pour les fichiers textes (comme la sortie standard) le buffer est vidé à chaque retour chariot.
Hors, ce buffer n'est rien d'autre qu'un tableau d'octets, accompagnés d'entiers représentant sa taille, et la position du dernier caractère écrit,
Pour éviter le phénomène illustré dans le programme COMPTEUR les mises à jour du buffer ainsi que les modifications de ces entiers sont protégées par un verrou. En effet, il est naturel de vouloir utiliser print depuis plusieurs threads différents.
Si on écrit une chaîne se terminant avec retour chariot (comportement par défaut de print) il se produit donc le phénomène
suivant:
acquisition d'un verrou associé au buffer;
écriture de tous les caractères dans le buffer;
arrivé au retour chariot, envoyer le contenu du buffer dans la console, puis le vider;
relacher le verrou.
Deux threads ne peuvent donc pas mélanger leur ligne, puisque l'écriture d'une ligne devient une section critique.
On peut obtenir un comportement plus «réaliste» en désactivant les buffers d'écriture globalement, avec l'option -u de l'interprète Python:
$ python -u pr_thread.py
Tester le programme ci-dessous avec la commande ci-dessus et mettre le résultat ci-dessous. Conclure.
","title":""},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Les systèmes d'exploitation multitâches sont la norme.
Ils permettent d'exécuter de façon concurrente plusieurs programme.
L'exécution d'un programme s'appelle un processus.
C'est le système d'exploitation, et en particulier l'ordonnanceur, qui détermine quel processus s'exécute à un instant donné.
Le fait pour un processus d'être interrompu s'appelle une commutation de contexte.
Plusieurs processus s'exécutant de façon concurrente peuvent s'interbloquer s'ils
attendent de pouvoir accéder à un même ensemble de ressources en accès exclusif.
Les threads ou processus légers sont des «sous-processus» s'exécutant de manière concurrente.
L'accès à des ressources par plusieurs threads peut être protégé par des verrous.
Une portion de code comprise entre l'acquisition et le relâchement d'un verrou s'appelle une section critique.
","title":"Conclusion","tagtitle":"h1"},{"edit":"
"}],[{"text":"
On suppose qu'Alice exécute dans son terminal la commande
$ ps -a -u -x
Le processus correspondant à cette commande fera partie des processus affichés dans la sortie.
Dire quel sera l'état de ce processus (R ou S) et justifier.
","title":"Exercice"},{"edit":"
Mettre le résultat ici (code et figure).
"},{"solution":"
Le processus correspondant à la commande ps -a -u -x sera toujours dans l’état R. En effet, comme ce processus est, par définition, en train de s’exécuter lorsqu'il demande au système la liste des processus, il est forcément dans l’état R.
"}],[{"text":"
import threading
COMPTEUR = 0
def incrc():
global COMPTEUR
for c in range(100000):
v = COMPTEUR
COMPTEUR = v + 1
th = []
for n in range(4):
t = threading.Thread(target=incrc, args=[])
t.start()
th.append(t)
for t in th:
t.join()
print (\"valeur finale\", COMPTEUR)
On considère le programme ci-dessus (qui n'utilise pas de verrou).
Pour chacune des affirmations suivantes, dire si elle est vraie ou fausse et
justifier.
1. Le programme affiche toujours 400 000.
2. Le programme peut afficher un nombre plus petit que 400 000.
3. Le programme peut afficher un nombre plus grand que 400 000.
4. Si on ajoute t.join() après t.start(), le programme affiche toujours 400 000.
5. Si on transforme le corps de la boucle de la fonction incrc() par
verrou.acquire()
v = COMPTEUR
verrou.release()
verrou.acquire()
COMPTEUR = v + i
verrou.release()
en ayant définie une variable globale verrou = threading.Lock(), alors le programme affiche toujours 400 000.
Solution page 499 D
","title":"Exercice"},{"edit":"
Mettre le résultat ici (code et figure).
"},{"solution":"
1. Faux. Comme on l’a vu, certains incréments peuvent être perdus car écrasés par des valeurs plus anciennes lors d’une commutation de contexte.
2. Vrai. Même justification.
3. Faux. Les seules valeurs que l’on peut écrire dans la variable COMPTEUR sont des valeurs que la variable avait avant un changement de contexte plus 1. On ne peut donc pas dépasser le nombre total qu’on aurait obtenu dans le meilleur des cas.
4. Vrai. Si on ajoute t.join() à cet endroit, alors on attend qu’un thread se termine avant de passer au suivant. Ils ne s’exécutent donc plus de façon concurrente mais séquentielle et ne peuvent plus s’interrompre les uns les autres.
5. Faux. Le thread peut toujours être interrompu entre la lecture du compteur et l'écriture.
"}],[{"text":"
On considère la situation de la figure ci-dessous dans laquelle quatre voitures sont bloquées à une intersection.
Montrer qu'il s'agit d'un interblocage, c'est-à-dire que les quatre conditions de Coffman sont réunies.
On indiquera précisément quelles sont les ressources et les processus dans cette
situation.
On fera l'hypothèse que les conducteurs sont raisonnables, et qu'ils ne veulent pas provoquer d'accident.
Figure 4 — Quatre voitures bloquées à une intersection.
","title":"Exercice"},{"edit":"
Mettre le résultat ici (code et figure).
"},{"solution":"
On identifie quatre processus, les quatre voitures V1, V2, V3 et V4 (V1 en haut à gauche, puis numérotées dans le sens anti-
horaire).
Les quatre ressources sont les quatre portions de routes se trouvant devant chaque voiture (R1 à R4).
Les quatre conditions de Coffman sont bien respectées.
1. Les ressources sont en accès exclusif: deux voitures ne peuvent pas se retrouver sur la même portion de route en même temps.
2. Rétention et attente: chaque voiture détient une portion de route et attend celle qui est devant elle.
3. Non préemption: on a fait l'hypothèse qu’une voiture ne peux pas forcer le passage et pousser la voiture qui est devant elle.
4. Attente cyclique : V1 attend R1 occupée par V2, qui attend R2 occupée
par V3, qui attend R3 occupée par V4, qui attend R4 occupée par V1.
"}],[{"text":"
Considérons un petit système embarqué:
un petit ordinateur relié à trois LED A, B et C.
Une LED peut être éteinte ou allumée et on peut configurer sa couleur.
On dispose de trois programmes qui affichent des signaux lumineux en faisant clignoter les LED.
Chaque programme possède une LED primaire et une LED secondaire.
Le programme P1 affiche ses signaux sur À (primaire) et B (secondaire) en vert.
Le programme P2 affiche ses signaux sur B (primaire) et C (secondaire) en orange.
Le programme P3 affiche ses signaux sur C (primaire) et A (secondaire) en rouge.
Comme les LED ne supportent pas d'être configurées dans deux couleurs en même temps, le système propose deux primitives acquerirLED(nom) et rendreLED(nom) qui permettent respectivement d'acquérir et de relâcher une LED.
Si une LED est déjà acquise, alors acquerirLED() bloque.
On suppose que chacun des trois programmes P1, P2 et P3 effectue les actions suivantes en boucle:
1. acquérir sa LED primaire
2. acquérir sa LED secondaire
3. configurer les couleurs
4. émettre des signaux
5. rendre la LED secondaire
6. rendre la LED primaire
7. recommencer en 1.
Montrer qu'il existe un entrelacement des exécutions qui place P1, P2 et P3 en interblocage.
","title":"Exercice"},{"edit":"
Mettre le résultat ici (code et figure).
"},{"solution":"
Ce problème est une variante du problème du «dîner des philosophes» proposé en 1965 par Edsger Dijkstra.
On considère l’enchaînement suivant:
P1 acquiert A (étape 1) puis est interrompu.
P2 acquiert B (étape 1) puis est interrompu.
P3 acquiert C (étape 1) puis est interrompu.
P1 tente d'acquérir B, sa LED secondaire (étape 2). Il est bloqué car B est tenue par P2.
P2 tente d'acquérir C (étape 2). Il est bloqué car C est tenue par P3.
P3 tente d'acquérir A (étape 2). Il est bloqué car A est tenue par P1.
À ce stade, les trois processus sont bloqués dans une attente circulaire d’une
ressource tenue par un autre processus.
Ils sont donc en interblocage.
"}],[{"text":"
Écrire un programme Python simulant le code de l'exercice précédent. On pourra s'inspirer du programme ci-dessous.
import threading
verrou1 = threading.Lock()
verrou2 = threading.Lock()
def f1():
verrou1.acquire()
print (\"Section critique 1.1\")
verrou2.acquire()
print (\"Section critique 1.2\")
verrou2.release()
verrou1.release()
def f2():
verrou2.acquire()
print (\"Section critique 2.1\")
verrou1.acquire()
print (\"Section critique 2.2\")
verrou1.release()
verrou2.release()
t1 = threading.Thread(target=f1, args=[])
t2 = threading.Thread(target=f2, args=[])
t1.start ()
t2.start ()
Constater qu'en exécutant suffisamment de fois votre prograrame il se bloque.
Indication: afin de laisser plus de chance au système de changer de contexte, on pourra mettre des affichages juste après l'acquisition d'un verrou. En effet, l'écriture dans la console passe
le thread courant en attente, le temps que les écritures soient effectuées, ce
qui laisse une opportunité à l'ordonnanceur de choisir un autre thread où un autre processus.
","title":"Exercice"},{"edit":"
Mettre le résultat ici (code et figure).
"},{"solution":"
On peut utiliser un dictionnaire indicé par les noms des LED pour y stocker les
verrous correspondant.
import threading
VERROU_LED= {}
VERROU_LED[\"A\"] = threading.Lock()
VERROU_LED[\"B\"] = threading.Lock()
VERROU_LED[\"C\"] = threading.Lock()
def acquerirLED(l):
VERROU_LED[l].acquire()
def rendreLED(l):
VERROU_LED[l].release()
def prog(n,lprim, lsec):
whileTrue :
acquerirLED(lprim)
print(\"Acquisition de\", lprim, \"par le programme\", n)
acquerirLED(lsec)
print(\"Acquisition de\", lsec, \"par le programme\", n)
Comment expliqué à la séquence Systèmes de Gestion de Bases de Données, si deux transactions travaillent sur un même objet (par exemple sur une même table), alors la seconde est bloquée jusqu'à ce que la première soit terminée.
Nous pouvons maintenant expliciter ce mécanisme:
lors qu'une transaction accède à une table, elle tente de prendre un verrou sur cette dernière.
Les verrous sont relâchés au moment du COMMIT ou ROLLBACK.
On considère deux tables
CREATE TABLE T (num INTEGER);
CREATE TABLE S (num INTEGER);
INSERT INTO T VALUES(1000);
INSERT INTO S VALUES(1000);
qui peuvent représenter de façon simplifiée des comptes en banque.
Considérons les deux transactions suivantes:
START TRANSACTION;
UPDATE T SET num = num + 100;
UPDATE S SET num = num - 100;
COMMIT;
START TRANSACTION;
UPDATE S SET num = num + 100;
UPDATE T SET num = num - 100;
COMMIT;
La première simule un virement de S vers T et la seconde un virement de T vers S.
Montrer qu'il s'agit d'un interblocage, c'est-à-dire que les quatre conditions de Coffman sont réunies.
","title":"Exercice"},{"edit":"
Mettre le résultat ici (code et figure).
"},{"solution":"
Les deux processus sont les deux transactions Tg et Td (pour transaction de gauche et transaction de droite).
1. Les tables T et S sont des ressources en accès exclusif :d eux transactions ne peuvent pas mettre à jour une même table en même temps.
2. Rétention et attente : chaque transaction a effectué une mise à jour sur une table et attend l’autre.
3. Non préemption : une transaction ne peut pas forcer une autre à relâcher sa table.
4. Attente cyclique : ici, Tg attend S en tenant T et Td attend T en tenant S.
À l'inverse d’un système d'exploitation, un SGBD est un système beaucoup plus «contraint».
La réponse usuelle des SGBD est de terminer en erreur l'une des deux transactions (arbitrairement) lorsqu'un tel interblocage est détecté.
Voici par exemple le message d'erreur affiché par MariaDb après quelques secondes de blocage:
ERROR: deadlock detected
DETAIL: Process 2160 waits for ShareLock on transaction 1000;
blocked by process 2157.
Process 2157 waits for ShareLock on transaction 999;
blocked by process 2161.
HINT: See server log for query details.
CONTEXT: while updating tuple (0,1) in relation \"t\"
Les circuits électroniques d'un ordinateur (ie ménoire, microprocesseur, etc.) manipulent uniquement des chiffres binaires 0 et 1 qui, en interne, sont simplement représentés par des tensions électriques.
","title":"Transistor et portes logiques","tagtitle":"h1"},{"edit":"
une tension basse (proche de 0 volt) et le chiffre 1 par une tension haute (que l'on notera +V volts, car cette tension varie selon les circuits électroniques).
Par exemple, pour le microprocesseur 4004 d'intel (1971) +V=5V et pour le intel i7 (2020) +V=0,6V
"}],[{"text":"
Les opérateurs (logiques ou arithmétiques) sur ces nombres binaires sont construits à partir de circuits électroniques dont les briques élémentaires sont appelés transistors (Tr).
Les transistors que l'on trouvent dans les circuits électroniques des ordinateurs se comportent comme des interrupteurs qui laissent ou non passer un courant électrique, selon le mode du tout ou rien (commutation), comme représenté de la manière suivante:
Dans ce schéma, la commande de l'interrupteur est jouée par la broche B (appelée la Base).
Lorsqu'elle est sous tension haute (niveau logique 1), le courant circule entre la broche C (appelée Collecteur) et la broche E (appelée Emetteur) et la tension entre la borne C et la masse M passe à 0 (niveau logique 0).
Inversement lorsque la base est sous tension basse (niveau logique 0), le courant est bloqué entre la broche C et la broche E et la tension entre la borne C et la masse M passe à +Vcc (niveau logique 1).
Ce simple transistor permet de réaliser une opération élémentaire appelée porte logique NON (appelée NOT en anglais).
Une porte logique est une fonction qui prend un où plusieurs bits en entrée et qui produit un bif en sortie.
La porte NOT implantée par un transistor est la plus simple de toutes les portes.
Elle n'a qu'un seul bit en entrée (P) et sa sortie (Q) vaut 0 quand l'entrée vaut 1, et inversement elle vaut 1 quand son entrée est à 0.
Graphiquement, on représente la porte NOT comme dans le schéma ci-dessous, avec à gauche la notation européenne et à droite la notation américaine.
Symbole européen
Symbole américain
Pour représenter le calcul réalisé par une porte logique, on utilise une table logique (ou table de vérité) qui relie les valeurs des entrées à la valeur du résultat.
La table logique de la porte NOT cst donnée ci-dessous.
Porte NOT
P
R
0
1
1
0
La porte Non a pour équation logique Q=/P.
Vous pouvez vérifier la table de vérité avec l'application ci-dessous:
On peut fabriquer d'autres portes logiques en combinant plusieurs transistors.
Par exemple, en combinant deux transistors en série comme ci-dessous
(schéma de gauche) on peut fabriquer la porte NON ET (appelée NAND en anglais) qui, pour deux entrées P et Q, produit un résultat R dont le calcul est donné par la table de vérité à droite.
Les portes NAND et NOR sont fondamentales dans les circuits électroniques car elles sont complètes, c'est-à-dire que n'importe quel circuit peut être conçu en utilisant uniquement ces deux portes.
Par exemple, la porte NOT peut être fabriquée à partir d'une porte NAND en reliant les deux entrées de cette porte, comme ci-dessous.
","title":""},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Une autre porte logique très inportante est la porte ET (AND en anglais).
Elle peut aussi être construite avec plusieurs portes NOR.
Les grands principes de fonctionnement des ordinateurs tels que nous les connaissons aujourd'hui reposent sur des travaux réalisés au milieu des années 40 par une équipe de chercheurs de l'université de Pennsylvanie.
Ces travaux concernaient la conception d'un ordinateur dans lequel les programmes à exécuter étaient stockés au même endroit que les données qu'ils devaient manipuler, à savoir dans la mémoire de l'ordinateur.
Cette idée d'utiliser une zone de stockage unique pour les programmes et les données est toujours utilisée aujourd'hui.
Cette architecture est appelée modèle de
von Neumann, en l'honneur du mathématicien et physicien John von Neumann qui participa à ces travaux et qui publia en 1945 un rapport sur la conception de l'EDVAC, ce nouvel ordinateur basé sur ce modèle de calcul.
Le schéma de la figure ci-dessous décrit l'organisation des principaux composants d'un ordinateur selon l'architecture de von Neumann.
Ce modèle comporte quatre types de composants:
une unité arithmétique et logique,
une unité de contrôle,
la mémoire de l'ordinateur
et les périphériques d'entrée-sortie.
Figure 1 — Architecture de von Neumann.
Les deux premiers composants sont habituellement rassemblés dans un ensemble de circuit électronique qu'on appelle Unité Centrale de traitement où plus simplement processeur (CPU en anglais, pour Control Processing Unit).
Lorsqu'un processeur rassemble ces deux unités dans un seul et même circuit, on parle alors de microprocesseur.
L'unité arithmétique et logique (Arithinetic Logic Unit en anglais ou ALU) est uu circuit électronique qui effectue à la fois des opérations arithmétiques et des opérations sur les bits de nombres entiers en binaire.
L'unité de contrôle {Control Unit en anglais ou CT) joue le rôle de chef d'orchestre de l'ordinateur. C'est ce composant qui se charge de récupérer en mémoire la prochaine instruction à exécuter et les données sur lesquelles elle doit opérer, puis les envoie à l'unité arithmétique et logique.
La mémoire de l'ordinateur contient à la fois les programmes et les données.
On distingue habituellement deux types de mémoires:
La mémoire vive ou volatile est. celle qui perd son contenu dès que l'ordinateur est éteint. Les données stockées dans la mémoire vive d'un ordinateur peuvent être lues, effacées ou déplacées comme on le souhaite. Le principal avantage de cette mémoire est la rapidité d'accès aux données qu'elle contient, quel que soit l'emplacement mémoire de ces données. On parle souvent de mémoire RAM en anglais, pour Random-Access Memory.
La mémoire non volatile est celle qui conserve ses données quand on coupe l'alimentation électrique de l'ordinateur. Il existe plusieurs types de telles mémoires. Par exemple, la ROM, pour Read-Only Memory en anglais, est une mémoire non modifiable qui contient habituellement des données nécessaires au démarrage d'un ordinateur ou tout autre information dont l'ordinateur à besoin pour fonctionner. La mémoire Flash est un autre exemple de mémoire non volatile. Contrairement à la ROM, cette mémoire est modifiable (un certain nombre de fois) et les informations qu'elle contient sont accessibles de manière uniforme. Contrairement à la RAM, ces mémoires sont souvent beaucoup plus lentes, soit pour lire les données, soit pour les modifier.
Il existe un très grand nombre de périphériques d'entrées/sortie pour un ordinateur.
On peut tenter de les classer par familles. Tout d'abord, les périphériques d'entrée:
les dispositifs de saisie comme les claviers ou les souris,
les manettes de jeu, les lecteurs de code-barres,
les scanners, les appareils photos numériques, les webcams, etc.
Ensuite, les périphériques de sortie comme :
les écrans et vidéo-projecteurs.
les imprimantes,
les haut-parleurs, etc.
Enfin, certains périphériques sont à la fois des dispositifs d'entrée et de sortie, comme par exemple :
les lecteurs de disques (CD, Blue Ray, etc),
les disques durs, les clés USB ou les cartes SD,
les cartes réseaux (modems), etc.
Les flèches du diagramme de la figure ci-dessous décrivent les différentes interactions entre ces composants.
Pour les comprendre, nous allons passer
rapidement en revue le fonctionnement de chaque composant et ses interactions avec les autres composants de l'ordinateur.
Unité de contrôle
Cette unité est essentiellement constituée de trois sous-composants. Tout d'abord, deux registres (mémoires internes très rapides).
Le premier est le registre d'instruction, dénommé IR (car en anglais il se
nomme instruction Register), qui contient l'instruction courante à décoder et exécuter.
Le second registre est le pointe (car en anglais il se nomme Instruction Pointer), qui indique l'emplacement mémoire de la prochaine instruction à exécuter.
Le troisième sous-composant est un programme particulier, appelé micro-programme, qui est exécuté par le CU et qui contrôle presque tous les mouvements de données de la mémoire vers l'ALU (et réciproquement) ou les périphériques d'entrée-sortie.
L'unité de contrôle est donc tout naturellement connectée à tous les autres composants de l'ordinateur.
Unité arithmétique et logique
Cette unité est composée de plusieurs registres, dits registres de données, et d'un registre spécial, appelé accumulateur, dans lequel vont s'effectuer tous les calculs.
À ces registres s'ajoutent tout un tas de circuits électroniques pour réaliser des opérations arithmétiques (addition, soustraction, etc), des opérations logiques (et, ou, complément à un, etc.), des comparaisons (égalité, inférieur, supérieur, etc.), des opérations sur les bits (décalages, rotations) ou des opérations de déplacements mémoire (copie de ou vers la mémoire).
Les entrées d'une ALU sont les données sur lesquelles elle va effectuer une opération (on parle d'opérandes).
Ces registres sont chargés avec des valeurs venant de la mémoire de l'ordinateur et c'est l'unité de contrôle qui indique quelle opération doit être effectuée.
Le résultat d'un calcul (arithmétique ou logique) se trouve dans l'accumulateur.
Cependant, l'ALU peut également envoyer des signaux pour indiquer des erreurs de calcul (division par zéro, dépassement de la mémoire, etc.) où des résultats de comparaison (inférieur, supérieur, etc).
La mémoire
Nous avons vu les mouvements de données entre l'unité centrale de traitement et la mémoire de l'ordinateur.
Ces échanges se font à travers un médium de communication appelé bus.
Mais les périphériques d'entrée-sortie peuvent également lire et écrire directement dans la mémoire à travers ce bus, sans passer par le CPU.
Cet accès direct à la mémoire est réalisé par un circuit électronique spécialisé appelé controleur DMA, pour Direct Memory Access en anglais.
Les dispositifs d'entrée-sortie
Ces composants sont connectés à l'ordinateur par des circuits électroniques appelés ports d'entrée-sortie sur lesquels il est possible d'envoyer ou recevoir des données.
L'accès à ces ports se fait habituellement à travers des emplacements mémoires à des adresses prédéfinies.
Ainsi, l'envoi ou la réception de données revient simplement à lire ou écrire dans ces emplacements réservés.
Pour connaître l'état d'un périphérique, le CPU peut soit périodiquement lire dans ces emplacements mémoires, mais il peut aussi être directement prévenu par un périphérique d'un changement à travers un mécanisme spécial d'interruption prévu à cet effet.
Une fois interrompu, le CPU peut simplement lire le contenu des ports.
","title":"Architecture de von Neumann ","tagtitle":"h1"},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Limitation du modèle de von Neumann
Ce modèle impose un va-et-vient constant entre le CPU et la mémoire, soit pour charger la prochaine instruction à écouter, soit pour récupérer les données sur lesquelles l'instruction courante doit opérer.
Cependant, la différence de vitesse entre les microprocesseurs (nombre d'opérations par seconde) et la mémoire (temps d'accès) est telle qu'aujourd'hui, avec cette architecture, les microprocesseurs modernes passeraient tout leur temps à attendre des données venant de la mémoire, qui, bien qu'ayant aussi gagné en rapidité, reste beaucoup plus lente qu'un CPU.
C'est ce qu'on appelle le goulot d'étranglement du modèle de von Neumann.
Pour tenter de remédier à ce problème, les fabricants d'ordinateurs ont inventé les mémoires caches.
Ce sont des composants très rapides (mais
très chers) qui s'intercalent entre la mémoire principale et le CPU.
L'idée principale pour gagner du temps est qu'une donnée utilisée une fois a de grandes chances d'être utilisée plusieurs fois.
Ainsi, la mémoire cache est chargée avec des données provenant de la RAM quand une instruction en à besoin, ceci afin de diminuer le temps d'accès ultérieurs à ces données.
D'autres pistes ont également été explorées. Il s'agit des architectures
dites parallèles. Le modèle de von Neumann est également appelé SISD (Single Instruction Single Data, une seule instruction et une seule donnéc), le CPU exécutant un seul flot d'instructions sur des données dans une seule mémoire.
Le modèle SIMD (pour Single Instruction Multiple Data). Il s'agit d'une architecture avec un seul CPU où une instruction peut être appliquée en parallèle à plusieurs données, pour produire plusieurs résultats en même temps.
Le modèle MIMD (pour Multiple Instructions Multiple Data). Il s'agit d'une architecture dotée de plusieurs CPU qui exécutent chacun un programme, de manière indépendante, sur des données différentes.
En 1975, Gordon E. Moore (cofondateur de la société Intel) énonça la conjecture suivante sur l'évolution des capacités des circuits intégrés, appelés familièrement puces électroniques:
Dans les microprocesseurs, le nombre de transistors sur une puce va doubler tous les deux ans.
Bien que fondée sur un constat empirique de l'industrie des fabricants de circuits entre les années 1965 et 1975, cette prédiction, qu'on appelle aussi loi de Moore, s'est révélée incroyablement juste.
Intel 4004
2050 Transitores
Gravure 10 micromètres
1971
Apple M1
16 milliards de transistors
Gravure 5 nanomètres
2020
On est ainsi passé de 2250 transistors en 1971 sur un microprocesseur Intel 4004 (un des premiers microprocesseurs) à plusieurs dizaines de milliards aujourd'hui.
Sur les derniers microprocesseurs, où la taille des transistors n'est que de 5 nanomètres (5nm), soit à peine plus que l'épaisseur de quelques dizaines d'atomes de silicium.
Cette loi de Moore, qui est (plus ou moins) généralisable à tous les composants électroniques (mémoire, etc.), à permis non seulement une augmentation de la puissance de calcul des ordinateurs (en augmentant leur fréquence de fonctionnement grâce à une diminution de la distance entre les composants), mais également une baisse des coûts (en rassemblant plusieurs composants en un seul).
La diminution de la taille des transistors a également permis de baisser la tension électrique pour les faire fonctionner, ce qui a engendré une diminution de leur consommation énergétique à puissance de calcul équivalente.
La miniaturisation des circuits électroniques est telle qu'il est possible aujourd'hui de rassembler sur une même puce tous les composants essentiels d'un ordinateur (microprocesseur, mémoire, interfaces d'entrées-sorties, etc.).
Ces systèmes complets, que l'on retrouve dans tous les systèmes embarqués, portent les noms de microcontrôleur, système sur puce (en anglais, System on Chip) ou circuit logique programmable.
","title":"L'évolution des microprocesseurs","tagtitle":"h1"},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Source : https://www.arduino.cc
Les microcontrôleurs sont des circuits intégrés qui regroupent sur une même puce (de quelques centimètres carrés) un microprocesseur, de la mémoire, des ports d'entrée-sortie, des périphériques et des bus de communication.
La puissance de caleul et la capacité en mémoire de ces puces sont bien en dessous des composants que l'on trouve sur les cartes mères des ordinateurs.
Ainsi, la fréquence d'horloge (qui détermine le nombre d'instructions exécutées par seconde) est généralement de quelques dizaines de mégahertz (MHz) et la taille de la mémoire se mesure seulement en kilo octets (Ko).
En revanche, les microcontrôleurs ont une consommation électrique très faible, ce qui leur confère une très grande autonomie lorsqu'ils sont alimentés par des batteries.
Enfin, leur coût de fabrication est aussi très réduit et les microcontrôleurs s'achètent pour seulement quelques euros.
Ces «mini»-ordinateurs sont principalement utilisés dans des systèmes
informatique embarqués, par exemple dans des avions, des voitures, des robots, etc.
Les rôles des microcontrôleurs dans ces systèmes sont très spécifiques mais ils partagent néanmoins certaines caractéristiques:
acquisition de données (grandeurs physiques),
contrôle d'un processus (actions mécaniques ou électroniques),
contraintes de temps.
Prenons l'exemple d'un régulateur de vitesse d'une voiture qui maintient le véhicule à une vitesse définie par le conducteur:
il récupère la vitesse de la voiture via un capteur externe branché à l'un de ses ports d'entrée;
il est en charge de contrôler l'accélération ou le freinage automatique du véhicule pour conserver la vitesse définie par le conducteur et pour cela il envoie des ordres à des actionneurs, externes également, reliés à ses ports de sortie;
il est soumis à des contraintes temporelles (temps de réponse) très fortes pour garantir la sécurité des passagers.
Des périphériques auxiliaires, intégrés dans la puce du microcontrôleur, peuvent être nécessaire pour réaliser cette tâche.
Par exemple, un filtre de conversion analogique/numérique est utilisé pour interpréter les données analogiques en entrée du capteur.
Un timer peut aussi être utilisé pour mesurer le temps afin de s'assurer que les réponses seront envoyées dans un
certain délai.
Étant données les capacités de calcul et de mémoire des microcontrôleurs, il n'est pas envisageable d'y faire tourner un système d'exploitation (OS) qui prendrait beaucoup trop de place et de temps d'exécution.
De plus, un OS complique fortement la mise au point d'applications temps réel car il faut maîtriser par exemple quand celui-ci va interrompre les processus en
activité, combien de temps prend le basculement entre les tâches, etc.
Nous découvrirons ces notion dans la séquence suivante.
","title":"Microcontrôleurs"},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Architecture d'un microcontrôleur
Les microcontrôleurs sont principalement des composants autonomes qui, dès qu'ils sont sous tension, exécutent le programme contenu dans leur mémoire.
Ainsi, la principale différence entre l'architecture d'un microcontrôleur et celle d'un ordinateur basée sur le modèle de von Neumann est que la mémoire qui contient les programmes n'est pas la même que celle qui contient les données.
La mémoire Programme est une mémoire morte, c'est-à-dire une mémoire qui ne
perd pas l'information qu'elle contient quand le microcontrôleur n'est plus alimenté en électricité.
Il y à plusieurs type de mémoire morte:
ROM (Read-Only Memory), désigne une mémoire dont le contenu est enregistré à la construction de la mémoire et ne peut être que lu.
EEPROM (Electrically-Erasable Programmable Read-Onty Memory), désigne une mémoire dont le contenu peut être écrit et effacé plusieurs fois. L'accès en lecture/écriture est assez lent, mais on peut effacer et écrire son contenu octet par octet.
FLASH, désigne un type de mémoire similaire à l'EEPROM mais avec un accès très rapide en lecture et (un peu moins) rapide en écriture.
Contrairement à l'EEPROM, la mémoire FLASH est découpée en secteurs physiques (dont la taille varie de quelques centaines d'octets à plusieurs kilo octets) et ne peut être effacée ou écrite octet par octet comme l'EEPROM, mais uniquement secteur par secteur.
De plus, pour réécrire un octet, on est obligé d'effacer tout le secteur qui le
contient avant d'écrire (un secteur entier).
La mémoire Données (Data) peut être composée d'une partie de mémoire morte et d'une autre de mémoire vive (aussi appelée mémoire RAM, pour Random Access Memory), c'est-à-dire de la mémoire dans laquelle on peut lire et écrire à convenance (avec un temps d'accès identique quelle que soit la partie de la mémoire ), mais qui perd toutes ses données dès qu'elle n'est plus alimentée en électricité.
Certains microcontrôleurs profitent de la séparation des mémoires de programmes et de données pour implémenter une architecture dite de Harvard.
Dans cette architecture, on accède aux mémoires Programme et Données (ou ports d'entrée-sortie) à travers deux bus distincts, ce qui procure plusieurs avantages.
Figure 3 - Architecture de Harvard.
Tout d'abord, cela permet de gagner du temps en transférant simultanément au microprocesseur les instructions et les données sur lesquelles elles agissent.
Ensuite, cela permet d'avoir des mots mémoires différents pour les instructions et les données.
Par exemple, on peut utiliser une mémoire morte avec des mots de 16 bits pour stocker les instructions, et seulement des mots de 8 bits pour la mémoire de données.
","title":""},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Jeu d'instructions
Lorsqu'ils sont basés sur une architecture Harvard, les microcontrôleurs ont un jeu d'instructions réduit (en anglais RISC, Reduced Instruction Set Computer), composé d'instructions simples, avec une longueur et un format d'instruction fixe.
Cela simplifie à la fois le circuit de décodage mais également l'architecture globale.
Par exemple, les instructions pour déplacer directement des données entre deux cellules mémoires sont impossibles (il faut passer par des registres du microprocesseur).
","title":""},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Périphériques
Les microcontrôleurs sont équipés de nombreux périphériques pour effectuer des tâches bien spécifiques.
Voici ci-dessous quatre familles de périphériques couramment utilisés.
Les Timers sont utilisés pour la gestion du temps. Ils servent à déclencher une action au bout d'un laps de temps t. Ceci est réalisé à l'aide d'un compteur, initialisé à t, qui est décrémenté à chaque cycle d'horloge. L'action est déclenchée quand la valeur du compteur atteint 0.
Les modules de capture de signaux permettent de récupérer la valeur d'un timer au moment où un événement extérieur se déclenche, par exemple quand un signal d'entrée change d'état.
Les convertisseurs Analogique/Numérique permettent de transformer une valeur de tension analogique en un nombre binaire.
Les modules de communication permettent de faire communiquer les microcontroleurs entre eux.
","title":""},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Exemple
Les microcontrôleurs PIC sont fabriqués par la société Microchip.
Il s'agit d'une gamme de microcontrôleurs qui s'appuient sur une architecture de Harvard avec un jeu d'instructions RISC.
Il existe plusieurs familles de puces PIC.
Chaque famille se distingue par le type et la quantité de mémoires utilisées, le nombre de bits par instruction, la fréquence d'horloge du CPU, le nombre de ports d'entrée-sortie ou encore les périphériques présents dans la puce.
Par exemple, la figure ci-dessous représente l'architecture de la famille des microcontrôleurs PIC18F
On y distingue les caractéristiques suivantes:
Un CPU avec jeu d'instructions RISC avec des registres de 8 bits et des instructions codées sur 16 bits.
Une mémoire programme de type FLASH dont la capacité peut aller jusqu'à 128 Ko (même si, en théorie, une capacité de 2Mo est possible).
Un bus de communication vers la mémoire programme sur 21 bits d'adressage (permet donc d'accéder jusqu'à 2 Mo de mémoire) et 16 bits de large pour récupérer en un cycle une instruction sur 16 bits.
Une horloge pouvant être cadencée jusqu'à 64 MHz.
Une mémoire de données de type SRAM (Static RAM) de 4 ko. Il s'agit d'un type de mémoire RAM qui ne nécessite pas de rafraîchissement pour maintenir ses informations.
Un bus de communication sur 12 bits d'adressage et 8 bits de données.
Jusqu'à 5 ports d'entrée-sortie bidirectionnels.
Des périphériques comme des timers, des modules de capture de signaux, des convertisseurs A/N (Analogique Numérique) et des modules de communication.
","title":" ","tagtitle":"h1"},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Un système sur puce, appelé System on Chip (SoC) en anglais, rassemble sur un même circuit intégré tous les composants habituellement présents sur la carte mère d'un ordinateur.
Figure 4 — Architecture d'un System on Chip.
Les caractéristiques d'un SoC sont de fait très proches de celles d'un ordinateur.
Ils ont une puissance de calcul comparable qui repose sur des microprocesseurs de dernière génération avec de nombreux cœurs cadencés à plusieurs giga-hertz (GHz), ainsi que des processeurs dédiés (graphique, sécurité).
Leur capacité mémoire se mesure en gigaoctets et ils incluent des mémoires de type RAM et FLASH.
Enfin, ils contiennent de nombreux périphériques.
Tout cela sur une puce d'environ 100mm.
","title":"Système sur puce"},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Architecture d'un système sur puce
Figure 4 — Architecture d'un System on Chip.
Les SoC reposent habituellement sur une architecture comparable à celle donnée dans le schéma ci-dessus.
Contrairement à l'architecture de Harvard sur laquelle repose les microcontrôleurs, on retrouve un modèle de Von Neumann dans lequel il n'y a pas de différence entre la mémoire des programmes et celle de données.
Les unités de calcul sont ainsi connectées à la mémoire (RAM ou ROM) ou aux périphériques par un unique bus de communication.
Les microprocesseurs des SoC sont presque toujours basés sur un jeu d'instructions RISC, avec des registres de 32 ou 64 bits.
Contrairement aux microcontrôleurs, ces CPU sont cadencés à des fréquences tellement élevées qu'ils sont fortement ralentis dès qu'ils lisent ou écrivent des données dans la mémoire ou qu'ils dialoguent avec des périphériques.
Cette différence de vitesse entre unité de calcul, mémoires et périphériques est telle qu'il est crucial de concevoir des moyens de communication entre ces composants qui soient bien calibrés en fonction des vitesses et débits de données de chacun, afin de ralentir le moins possible le microprocesseur.
Ainsi, parce que les composants mémoire ont généralement une fréquence de fonctionnement et des débits de données bien plus importants que les autres périphériques, certaines architectures de SoC mettent en place un système de transfert d'informations reposant sur deux bus de communication.
Un bus haute performance est dédié à l'échange d'information entre le microprocesseur (CPU), les processeurs dédiés à une tâche particulière comme les processeurs graphique (GPU) et les différents composants mémoire.
Un deuxième bus est utilisé pour la communication avec les périphériques plus lents. Ce bus est relié au bus rapide via un pont de communication qui met en œuvre un protocole d'échange.
Cette architecture permet d'éviter que les bus de communication ne deviennent des goulots d'étranglement pour le CPU qui, lorsqu'il souhaite communiquer avec des composants très rapides comme les mémoires, se retrouve bloqué en attente d'une réponse d'un périphérique beaucoup plus lent.
Afin de limiter encore plus l'impact de la différence de vitesse entre le CPU et les autres composants, les SoC disposent d'un mécanisme d'accès direct à la mémoire (en anglais Direct Memory Access — DMA) similaire aux cartes mères des ordinateurs.
Ce mécanisme permet de transférer directement des données de la mémoire vers où depuis un périphérique, sans aucune intervention du CPU.
Pour cela, le CPU ou les périphériques envoient des informations concernant les adresses et la quantité de données à échanger au contrôleur DMA, qui se charge en retour de réaliser ces transferts et d'avertir quand ils sont effectués.
Même en utilisant un bus à haute performance, le microprocesseur risque
d'être fortement ralenti lorsqu'il accède à la mémoire centrale (RAM ou ROM). Pour limiter l'effet de ce goulot d'étranglement, un mécanisme de mémoire cache, identique à celui des microprocesseurs des ordinateurs, est
intégré dans les systèmes sur puce.
Cela consiste à insérer une mémoire très
rapide entre le CPU et la mémoire centrale afin de servir de tampon en lecture et en écriture.
Ainsi, lorsqu'une instruction doit accéder à la mémoire centrale, elle vérifie d'abord si la mémoire cache contient une copie des cellules qu'elle recherche. Si c'est le cas, alors elle écrit ou lit directement dans le cache.
Sinon, il se produit ce qu'on appelle un défaut de cache et les tâches à réaliser dans ce cas sont différentes selon qu'il s'agisse d'un accès en lecture où en écriture.
Accès en lecture. Un défaut de cache va provoquer un chargement des données de la mémoire centrale vers le cache, puis les données seront ensuite envoyées au CPU. Ce chargement nécessite de libérer de l'espace dans le cache pour accueillir les nouvelles données. Le choix de cet espace n'est pas anodin car le CPU pourrait avoir besoin de son contenu quelques instructions après. La politique de remplacement mise en œuvre dans une mémoire cache est donc un point important de son efficacité.
Accès en écriture. Si le cache ne contient pas la partie de la mémoire dans laquelle le CPU souhaite écrire, il commence par la charger puis elle est modifiée dans le cache. Pour qu'il y ait une cohérence entre la mémoire dans le cache et la mémoire centrale, une politique d'écriture est mise en place. Là encore, le choïx de cette politique a une grande influence sur l'efficacité du SoC.
Bien que l'architecture globale d'un SoC soit celle d'un microprocesseur RISC, le CPU peut également contenir des circuits dédiés à certaines opérations afin d'accélérer certains calculs. Par exemple, on peut trouver
aujourd'hui:
Une unité de calcul pour nombres flottants (FPU) simple où double précision.
Un circuit dédié aux opérations sur les matrices, par exemple pour accélérer les algorithmes d'apprentissage automatique (en anglais Machine Learning — ML) très utilisés par les applications d'intelligence artificielle. Ce circuit peut par exemple exploiter les possibilités de parallélisation des opérations sur ces structures.
Un composant pour la sécurité qui implémente des opérations élémentaires pour accélérer les algorithmes cryptographiques.
Enfin, la liste des périphériques présents dans un système sur puce n'est
limitée que par l'espace physique qu'ils occupent sur la puce.
Voici ci-dessous une liste non exhaustive des composants fréquemment présents sur de telles puces:
des modems (2G/3G/4G),
des circuits radio (WiFi, Bluetooth),
une puce GPS,
des ports d'entrée-sortie (USB, Ethernet, HDMI, Audio), des capteurs (CCD).
","title":""},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Avantages et inconvénients
Les SoC sont aujourd'hui les composants
incontournables de l'informatique nomade (smartphones, tablettes). Ils sont également de plus en plus utilisés dans le monde plus large des systèmes embarqués (voitures, robots, etc).
Ce succès est dû aux avantages suivants:
Énergie. Il est admis qu'une grande partie de la consommation électrique d'un circuit est liée au câblage (et donc au transport) entre les composants. Comme tous les composants d'un SoC sont connectés entre eux sur des distances très petites, et de manière interne sans câblage énergivore, les gains sont très importants. Cette faible consommation énergétique implique aussi une très faible perte de chaleur qui évite de recourir à un ventilateur pour refroidir la puce. Les SoC sont donc silencieux.
Coût. Le prix d'un système sur puce est très petit si on le compare à celui d'une carte mère rassemblant les mêmes composants. Ceci s'explique d'une part par son coût de production, très bas, résultant de la forte automatisation du processus de fabrication des puces de l'industrie du hardware et, d'autre part, par des volumes de production importants.
Sécurité. Le circuit d'un SoC est conçu de manière globale, tant pour la partie hardware que pour celle du micro-logiciel (en anglais firmware), c'est-à-dire des programmes intégrés dans les différents composants qui leur permettent d'assurer leurs fonctions. Cette vue d'ensemble du système, sans aucune possibilité d'ajouter ou changer les composants, est un avantage important pour garantir la sécurité du système.
Bien qu'ayant de nombreux avantages, les systèmes sur puce ont aussi quelques inconvénients. Contrairement à un ordinateur équipé d'une carte mère, où chaque composant est connecté et peut être remplacé en cas de panne ou pour une nouvelle version, les SoC ne permettent pas de mise à jour, aucune extension n'est possible et, si un seul transistor est endommagé, il ne sera pas possible de réparer l'unité défaillante.
","title":""},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Exemple
Le système sur puce A13 bionic, disponible sur les téléphones et tablettes de la société Apple, est un SoC qui repose sur une architecture similaire à celle présentée dans cette section.
Cette puce électronique contient 8,5 milliards de transistors gravés avec une précision de 7 nanomètres.
La liste ci-dessous résume ses principaux composants:
Un CPU disposant d'un jeu d'instructions RISC et de registres de 64 bits. Il contient six cœurs dont deux rapides (2.65GHz) et quatre plus lents (1.8GH2), ces derniers étant plus économes en énergie. Les cœurs rapides intègrent un module (AMX) pour accélérer les opérations de Machine Learning comme les multiplications de matrices. Ensemble, ces modules sont capables d'effectuer 1000 milliards d'opérations 8 bits par seconde.
Un processeur graphique (GPU) avec quatre cœurs pour toutes les opérations 3D.
Un processeur avec huit cœurs, appelé Neural Engine, qui s'occupe de tous les traitements d'informations et calculs liés à l'intelligence artificielle, pour la reconnaissance faciale et la réalité augmentée notamment.
Un module spécialisé pour les opérations cryptographiques (AES).
Une capacité mémoire de 4Go de RAM, directement sur la puce.
","title":""},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Intel 4004
2050 Transitores
Gravure 10 micromètres
1971
Apple M1
16 milliards de transistors
Gravure 5 nanomètres
2020
La miniaturisation des circuits intégrés aujourd'hui est telle qu'il est possible d'avoir des ordinateurs sur une unique puce d'une centaine de millimètres carrés. Ces mini-ordinateurs. appelés microcontrôleurs ou System on Chip (SoC), intègrent tous les composants habituellement présents sur la carte mère d'un ordinateur (CPU, mémoire et périphériques).
Les microcontrôleurs, peu coûteux et avec des capacités de calculs et de mémoire limités, reposent souvent sur une architecture de Harvard où la mémoire des programmes est dissociée de
celle des données.
Les SoC sont de véritables ordinateurs avec des CPU très puissants, tellement rapides qu'une architecture avec deux bus est nécessaire pour éviter les goulots d'étranglement.
","title":"Conclusion","tagtitle":"h1"},{"edit":"
Mettre le résultat ici (code et figure).
"}],[{"text":"
Quels sont les spécificités et les avantages de Harvard par rapport à celle de Von Neumann?
","title":"Exercice"},{"edit":"
Mettre le résultat ici (code et figure).
"},{"solution":"
Dans une architecture de Harvard, les mémoires contenant les programmes et les données sont séparées.
Cela permet de transférer simultanément au CPU la prochaine instruction à exécuter ainsi que les données sur lesquelles elle doit opérer.
Par ailleurs, les bus de commu-
nication entre ces mémoires étant aussi distincts, cela permet d’avoir des
tailles de mots mémoire différents pour les deux mémoires.
"}],[{"text":"
Que signifie l'acronyme RISC?
","title":"Exercice"},{"edit":"
Mettre le résultat ici (code et figure).
"},{"solution":"
Cet acronyme signifie Reduced Instruction Set Computer et indique qu'un CPU dispose d’un jeu d’instructions plus simples et
plus uniformes qu’un CPU CISC (pour Compler Instruction Set Computer).
"}],[{"text":"
Quels sont les éléments essentiels d'un microcontrôleur?
","title":"Exercice","tagtitle":"h1"},{"edit":"
Mettre le résultat ici (code et figure).
"},{"solution":"
Un microcontrôleur est constitué d’une unité de calcul (CPU), de deux mémoires (programme et données) et de périphériques (comme des capteurs ou des convertisseurs analogique/numérique).
"}],[{"text":"
Donner quelques critères pour choisir d'utiliser un microcontrôleur.
","title":"Exercice"},{"edit":"
Mettre le résultat ici (code et figure).
"},{"solution":"
Les microcontrôleurs sont adaptés pour réaliser une tâche dans un systèmes embarqués qui ne nécessite pas de calculs trop intensifs ni trop complexes.
Leur petite taille fait aussi qu’ils peuvent être installés sans trop de difficultés dans n'importe quel endroit du système à contrôler.
Leur utilisation simplifie également la construction d’un système plus complexe car ils disposent à la fois d’une mémoire qui contient l'unique programme à exécuter, des bus de communication pour transférer des données et des capteurs nécessaires pour interagir avec leur environnement.
"}],[{"text":"
Quelles sont les spécificités et les avantages d'un système sur puce? So
","title":"Exercice","tagtitle":"h1"},{"edit":"
Mettre le résultat ici (code et figure).
"},{"solution":"
Un système sur puce est composé des mêmes éléments qu’un ordinateur.
La principale différence est qu’ils sont rassemblés sur le même circuit intégré.
Outre le gain de place, les distances réduites entre les composants et le fait qu'ils soient directement connecté dans la puce de silicium fait qu’ils consomment beaucoup moins d'énergie électrique.
Ainsi, ils ont une plus grande autonomie.
"}],[{"text":"
On souhaite écrire un programme Python qui affiche toutes les n secondes la température du CPU d'un ordinateur.
Pour cela, on peut utiliser la bibliothèque psutil qui donne accès à de nombreuses informations concernant les composants de la machine.
En particulier, la fonction sensors_temperatures() renvoie des données renvoyées par les sondes de températures sous la forme d'un dictionnaire.
Une entrée 'coretemp' de ce dictionnaire contient les informations de température pour le CPU rassemblées sous la forme d'un tableau qui décrit chaque cœur du CPU.
L'information d'un cœur est donnée sous forme d'un n-uplet nommé qui contient, entre autres, un champ current qui donne sa température actuelle.
Ainsi, si on appelle tempinfos ce dictionnaire, la température courante du premier cœur sera accessible par tempinfos['coretemp'][0].current .
Indication : Pour installer la bibliothèque psutil, il faut utiliser la commande suivante:
pip3 install psutil
","title":"Exercice"},{"edit":"
Mettre le résultat ici (code et figure).
"},{"solution":"
from time import sleep
import psutil
n = int (input (\"fréquence? \"))
whileTrue:
tempinfos = psutil.sensors_temperatures()
t = tempinfos['coretemp'][0].current
print(\"Température du coeur 0 : \", t)
sleep(n)
"}],[{"text":"
Compléter le schéma du SoC ci-dessus
","title":"Exercice"},{"edit":"
Mettre le résultat ici (code et figure).
"},{"solution":""}],[{"text":"
Parmi les images ci-dessus, la(les)quelle(s) représente(nt) un SoC.
","title":"Exercice"},{"edit":"
Mettre le résultat ici (code et figure).
"},{"solution":""}],[{"text":"
A partir de l'article du site elektormagazine.fr 1) Relevez les differentes caractéristiques du SoC du Raspberry Pi 3 modèle B+. 2) Les comparez au SoC du Raspberry Pi 4. 3) Quelles sont les principales évolutions qui contribuent à ce gain? Une copie de l'article est disponible ici.
","title":"Exercice"},{"edit":"
Mettre le résultat ici (code et figure).
"},{"solution":""}],[{"text":"
La photo ci-dessus montre le détail d'un SoC Kirin 990 Identifier les différentes parties de ce SoC
En poursuivant votre navigation sur mon site,
vous acceptez l’utilisation des Cookies et autres traceurs
pour réaliser des statistiques de visites et enregistrer
sur votre machine vos activités pédagogiques.En savoir plus.