URL: https://linuxfr.org/users/capello/journaux/les-semaphores Title: Les sémaphores Authors: Anthony Jaguenaud Date: 2012年10月15日T00:17:04+02:00 License: CC By-SA Tags: c Score: 13 Bonjour, Je souhaitais écrire un document sur les sémaphores ici. J’ai même commencé une implémentation de FIFO. Mais le temps me manque pour le finaliser. Néanmoins, cette semaine j’ai réagi au fait que je trouve dommage qu’un démon ne rende pas la main, seulement une fois que l'ensemble des processus résidents sont prêts à répondre. On m’a dit que : « Tu forkes pas une fois ton programme en train de fonctionner, mais avant. » [là](http://linuxfr.org/users/arcaik/journaux/archlinux-est-morte#comment-1397140) Cette affirmation, est juste dans une certaine mesure, mais rien ne justifie, à mes yeux, qu’on ne fasse pas meilleure synchronisation. UNIX, dispose d’un nombre impressionnant de moyens de communiquer entre des processus qu’ils soient afiliés ou non (Mémoire partagée, tube, tube nommé, sémaphore, file de messages...). J’ai choisi de poursuivre mon exemple [ici](http://linuxfr.org/users/arcaik/journaux/archlinux-est-morte#comment-1397444), de lui ajouter des démons, et une synchronisation par sémaphore. Je vais essayer d'être didactique. # Sémaphore # [Les sémaphores](https://fr.wikipedia.org/wiki/S%C3%A9maphore_%28informatique%29) sont une manière de gérer une ressource limitée. On pourrait imaginer un moyen de communication, de 100 ko/s divisé en 10 tranches de 10 ko. Pour communiquer, un processus demande une partie de la ressource, si disponible, alors la fonction _P_ rend la main et le processus peut utiliser la ressource demandée. Lorsqu’il a fini, il doit rendre la ressource avec la fonction _V_ pour qu’un autre puisse l’utiliser. En fonction de ces besoins, il peut demander 1, 2, 3... 10 tranches. Pour assurer la cohérence de l’ensemble, les opération _P_ et _V_ sont [atomiques](https://fr.wikipedia.org/wiki/Atomicit%C3%A9_%28informatique%29) Les sémaphores sont souvent utilisés avec une quantité de ressource à 1, de façon à créer des _mutex_ (verrou d’exclusion mutuelle). # Dépôt git # J’ai fait un petit dépôt git, que j’ai compressé un tgz. Il se trouve [ici](http://www.jaguenaud.org/Demon.tgz). Il y a 3 tags : `sans_ipc`, `synchro_1` et `final`. * `sans_ipc` contient le source de base écrit dans le journal précédent. * `synchro_1` est la transformation avec des sémaphores. * `final` est la version finale sans bug. Il y a une version entre `sans_ipc` et `synchro_1`, qui est la modification du squelette de programme. # Architecture du logiciel # Le programme est démarré depuis un shell, celui-ci démarre le `fils`, le `fils` change de session id et démarre le démon1, le démon2 et les démonsN. Chaque démon s’initialise pendant un temps certain, implémenté avec un magnifique `sleep`. Ensuite, chacun appelle la fonction `synchro`. Le rôle de cette dernière est de _prévenir_ le `fils` que le démon est prêt. Nous avons vu qu’un sémaphore compte des ressources. Quelles sont nos ressources et combien en avons nous ? Nos ressources sont un droit de s’initialiser. Chaque démon en a une au départ. Lorsque son initialisation est finie, il rend la ressource. Puis attend une synchronisation. Cette dernière provient d’une particularité des sémaphores sysV, qui n’ont pas 2 mais 3 opérations possibles. La première est de prendre des ressources, opération strictement négative sur le stock. La deuxième est de rendre des ressources, opération strictement positive. La troisième concerne la valeur nulle de l’opération. Dans ce cas, on attend que la ressource (la valeur du sémaphore) soit à 0. Pour résumer, le démon lors de la synchronisation va rendre sa ressource (son droit à s’initialiser) en effectuant une opération positive. Puis, il va attendre que le nombre de ressources repasse a 0. Le `fils`, qui a démarré les processus, essaye de reprendre toutes les ressources d’initialisation. Comme il essaye de tout reprendre, il n’y arrivera que lorsque tous les démons auront rendu la leur. À ce moment, la ressource passera à 0 et tous les démons pourront quitter la `synchro`. Le `fils` détruira le sémaphore et sortira proprement, ne rendant la main que lorsque tout est fini. Cette implémentation est visible avec le label `synchro_1`. ``` shell --| V père | |------| wait V : fils : | : init : sémaphore : |-------......--|-----| : P(-nb_res) V V : : Init Init : : | | : : | V(+1) : : V(+1) S(=0) : : S(=0) : (dernier) : | | | : rm_sem ... ... (travail de démon) |<----- shell <-- ``` Cette implémentation, si elle semble bonne sur le papier, a néanmoins un problème. Peut-être le verrez-vous à l’exécution, mais peut-être pas. En fonction du nombre de cœur du CPU, des options du noyau... ce n’est pas forcément visible. Je ne m’attendais à l’observer, pourtant je l’ai vu en faisant mes tests. Le `fils`, reprend la main, dès que le dernier V(+1) est fait. Si, pour n’importe quelle raison, le S(=0) n’est pas immédiat (reschedule), l’effacement du sémaphore se fera avant et le S(=0) retournera une erreur car le sémaphore n’existe plus. Pour résoudre ce problème, j’ai utilisé deux autres particularités des sémaphores sysV. La première, c’est que nous disposons non pas d’un sémaphore, mais d’un ensemble de sémaphores. Si lors de la création, j’ai demandé un ensemble de 1 élément, nous allons utiliser un ensemble de 2 éléments. La deuxième particularité, c’est qu’on peut réaliser plusieurs opérations sur un même ensemble de manière atomique. En ajoutant un 2° élément à l’ensemble, on ajoute donc une ressource qui sera : « Je suis synchronisé ». Donc la synchronisation se fera de la sorte : ``` V<0>(+1) (Comme avant, pour rendre la ressource d’init) S<0>(=0),V<1>(+1) (Ces deux opérations sont atomiques) ``` Le `fils` quant à lui, va refaire une opération `P<1>(-nb)`. Cette opération ne sera effective que lorsque tous les démons auront quitté la fonction `synchro`. On aurait pu simplifier et ne pas avoir de synchronisation de démarrage. C’est-à-dire, que chaque processus, une fois prêt font le V(+1) et commence son travail immédiatement.

AltStyle によって変換されたページ (->オリジナル) /