• Aucun résultat trouvé

Particularit´es des services d’Anaxagoros

4.3 Impl´ ementation des sections critiques

5.1.1 Particularit´es des services d’Anaxagoros

Les services con¸cus pour suivre les principes m´ethodologiques d´evelopp´es dans cette th`ese ont un certain nombre de points notables, certains contraignants pour la conception et l’´ecriture du service, d’autres avantageux, que nous r´ecapitulons ici.

5.1.1.1 Gestion de la m´emoire

Nous avons choisi (§ 3.5.2.1) d’allouer la m´emoire des services statiquement, pour des raisons qui ont ´et´e d´evelopp´ees pr´ec´edemment. Cela cause un inconv´enient : les services sont stateless dans le sens o`u on ne stocke pas l’´etat des requˆetes par thread entre plusieurs appels. Mais cela a ´egalement beaucoup d’avantages, en terme de simplification du code : identification des partages sur les donn´ees, absence d’aliasing, pas de gestion de la m´emoire, etc.

5.1.1.2 Ind´ependance du service et de l’ordonnancement et parall´elisme subi

Le service est ind´ependant de l’ordonnancement des threads des clients qui lui soumettent des requˆetes. Cela occasionne deux inconv´enients :

• Le service ne doit pas perturber l’ordonnancement, ni mˆeme l’ex´ecution, des threads prˆet´es par les clients ; i.e. le temps pass´e `a faire des requˆetes devrait ˆetre globalement pr´evisible ;

• Le service n’a pas de contrˆole sur l’ordonnancement des threads, et ceux-ci peuvent mˆeme ˆetre ordonnanc´e de mani`ere adverse.

Les services sont des programmes parall`eles, non parce qu’on veut en obtenir de bonnes performances, mais parce que c’est une cons´equence directe de l’impl´e- mentation du principe d’ind´ependance de l’ordonnancement par le prˆet de temps CPU au service. Le parall´elisme est donc subi, et non choisi. En particulier, nous ne sommes au courant d’aucun programme r´eel multithread´e dont les threads sont ordonnanc´es par des entit´es ext´erieures sans confiance ; cette notion d’ordonnanceur adverse ne peut ˆetre rencontr´ee que pour des ´evaluations au pire cas dans des travaux th´eoriques (e.g. [Her90]).

Les programmes multithread´es standards font g´en´eralement soit du calcul par- all`ele, soit utilisent un thread par tˆache distincte (e.g. une GUI, ou un serveur r´eseau) ; ici nous avons un ensemble de threads qui peuvent concourir pour faire une tˆache parall`element sans que cela n’aie vraiment de sens. Ce type de probl`eme est courant dans les syst`emes d’exploitation ; Ryzhyk [RCKH09] identifie ainsi de nombreux bugs dans l’initialisation parall`ele de drivers dans Linux.

5.1.1.3 Fonctionnement d’un service, s´equencement et automates Le rˆole d’un service est de partager des ressources entre plusieurs clients. Les clients n’acc`edent pas `a la ressource directement, mais passent par le service en lui faisant des requˆetes. Le but de ces requˆetes n’est pas de faire un calcul par le service (ce calcul peut tout aussi bien ˆetre fait par le client), mais de modifier l’´etat d’une ressource servie par le service.

Le client ne peut pas modifier l’´etat des ressources n’importe comment, sinon le service serait inutile. Le but du service est justement d’assurer certaines propri´et´es “n´egatives” sur les ressources [Rus89]. Ces propri´et´es peuvent prendre deux formes :

5.1.1. Particularit´es des services d’Anaxagoros • la propri´et´e peut sp´ecifier un ordre sur le changement des ´etats, ce qu’on peut

sp´ecifier `a l’aide d’un automate ;

• la propri´et´e peut ˆetre une relation sur l’´etat m´emoire de la ressource, i.e. assure que l’´etat est coh´erent.

Cette sp´ecification sous la forme d’un automate se retrouve dans plusieurs travaux. Ainsi, Denning [Den76, p. 19] annonce qu’ “un contrˆole des ressources sˆur est assur´e lorsque les unit´e d’un certain type de ressource sont forc´ees `a se conformer `a un diagramme ´etat-transition bien d´efini”. Plus r´ecemment, la mod´elisation des interactions OS/device driver dans Dingo [RCKH09] suit ´egalement la forme d’un automate.

Le service est donc un programme parall`ele dont les actions doivent suivre un ordre d´efini (ce qu’on appelle ˆetre correctement s´equenc´e) en plus de devoir travailler sur des ´etats coh´erents.

Au niveau des avantages, les requˆetes sont faites essentiellement pour modifier l’´etat interne du service, et renvoient peu de r´esultats. Cela d’autant plus qu’un maximum d’´etat est export´e vers le client (§ 5.3). Cette observation est exploit´ee de plusieurs fa¸cons, par exemple pour l’utilisation du rollforward lock dans les services (§ 5.2.2) ou pour le multicall (§ 3.3.3.2).

5.1.1.4 Minimisation, s´ecurit´e du service et preuve de programme Les services partagent des ressources entre diff´erents clients qui ne se font pas confiance. Les clients ont en g´en´eral confiance dans les services qu’ils utilisent, car ils ont besoin des ressources servies. Les services sont bien souvent les seuls points communs entre deux programmes ind´ependants, et donc les seuls points d’attaques potentiels pour un programme malveillant. Il faut donc bien les s´ecuriser, et pour cela il faut minimiser leur taille (principe 2.2.2.6) : i.e. en faire faire un maximum par les application elle-mˆemes. En minimisant les services, on diminue la probabilit´e d’occurrence de bugs [Ber07], et on facilite les preuves de sˆuret´e.

Un dernier avantage d’avoir des services de taille minimale est que cela permet de relier directement l’impl´ementation aux sp´ecifications (e.g. la relation entre les invariants de l’impl´ementation et les fonctionnalit´es et les propri´et´es de s´ecurit´e du service). Nul besoin de fonctions g´en´eriques qui peuvent faire plus que ce qui leur est demand´e, ce qui se retrouve fr´equemment dans des programmes plus gros. On peut ainsi optimiser le programme. Par exemple, c’est parce que nos services sont petits que leur rˆole est bien compris et qu’on peut se permettre des choses comme l’affaiblissement des invariants (§ 4.1.3.3), la d´el´egation de la gestion de la coh´erence (§ 5.3.2), ou l’utilisation de primitives de synchronisation “exotiques” comme le revocable lock. Enfin, cette relation directe entre l’impl´ementation et les sp´ecifications est utile pour la preuve de programme.

Il est important de comprendre que la preuve du service concerne seulement les propri´et´es de s´ecurit´e que le service doit assurer. En particulier, il est souvent l´egitime que par suite d’une mauvaise utilisation par l’espace utilisateur (e.g. appels simultan´es), le noyau rende certaines donn´ees incoh´erentes : ceci ne constitue pas un comportement incorrect du noyau.

5.1.1.5 Restrictions sur les primitives de synchronisation et minimisa- tion des probl`emes de concurrence

Dans le chapitre pr´ec´edent, nous avons vu que les primitives de synchronisation habituelles ne pouvaient pas ˆetre utilis´ees quand on veut adh´erer au principe d’ind´ependance des politiques. Les primitives que nous avons d´evelopp´ees fournissent une solution, mais nous allons voir qu’il y a des limitations additionnelles dans leur utilisation. De plus, elles ne permettent d’acqu´erir qu’un seul lock simultan´ement. Notons qu’avec un seul lock, on peut r´esoudre tous les probl`emes de concurrence ; mais cela peut poser probl`eme pour faire du verrouillage `a grain fin.

Notre approche est de minimiser les probl`emes de concurrence. Quand on ´elimine un probl`eme de concurrence, il n’y a plus besoin d’employer de primitive de syn- chronisation, et on a un passage `a l’´echelle excellent. La section 5.3 explique cela en d´etail.

Conclusion : globalement simple, localement complexe

Les difficult´es principales concernant l’´ecriture des services sont les probl`emes li´es `a la concurrence et le besoin en synchronisation. Le fait d’avoir des services stateless pose ´egalement probl`eme.

L’approche que nous avons suivie peut s’appeler globalement simple, localement complexe. L’allocation des ressources est extrˆemement simplifi´ee, les services ne bloquent pas de mani`ere impr´evue, les services sont ind´ependants, etc., tout cela simplifie la vision globale du syst`eme. Mais le prix `a payer pour cette simplicit´e globale est une complexit´e accrue au niveau de l’impl´ementation des services. L’interface d’acc`es au service reste toutefois simple.

Cette approche est `a l’oppos´e de l’approche actuelle d’´ecriture des OS, o`u la simplicit´e du code prime [Gab91], mˆeme si le syst`eme en devient globalement complexe (e.g. allocation des ressources impr´evisible).

Dans le reste de ce chapitre, nous voyons comment r´esoudre cette complexit´e accrue des services.