{"id":1182,"date":"2011-10-09T06:50:44","date_gmt":"2011-10-09T05:50:44","guid":{"rendered":"http:\/\/www.blaess.fr\/christophe\/?p=1182"},"modified":"2011-10-09T06:50:44","modified_gmt":"2011-10-09T05:50:44","slug":"efficacite-des-ipc-semaphore-et-memoire-partagee","status":"publish","type":"post","link":"https:\/\/www.blaess.fr\/christophe\/2011\/10\/09\/efficacite-des-ipc-semaphore-et-memoire-partagee\/","title":{"rendered":"Efficacite des IPC&nbsp;: s\u00e9maphore et m\u00e9moire partag\u00e9e"},"content":{"rendered":"<p>La m\u00e9thode de communication entre processus la plus performante lorsqu&rsquo;on doit transf\u00e9rer des donn\u00e9es volumineuses est l&#8217;emploi de zones de m\u00e9moires partag\u00e9es. Standardis\u00e9 par Posix, il s&rsquo;agit d&rsquo;un m\u00e9canisme extr\u00e9mement efficace. Toutefois, il faut penser \u00e0 synchroniser les acc\u00e8s, afin d&rsquo;\u00e9viter les modifications concurrentes des donn\u00e9es partag\u00e9es ou la modification d&rsquo;une zone pendant sa consultation par un autre processus.<br \/>\n<!--more-->\n<\/p>\n<h1>Partage de m\u00e9moire<\/h1>\n<p>Pour partager une zone de m\u00e9moire entre deux processus, chacun doit d&rsquo;abord obtenir un descripteur avec l&rsquo;appel-syst\u00e8me suivant.<\/p>\n<pre>#include &lt;sys\/mman.h&gt;\nint <strong>shm_open<\/strong>(const char * <em>nom,<\/em> int <em>flags<\/em>, mode_t <em>mode<\/em>);<\/pre>\n<p>Le <em><code>nom<\/code><\/em> est celui que nous attribuons \u00e0 la zone m\u00e9moire. Il doit \u00eatre fourni identique dans les deux processus. La seule contrainte est que ce nom doit commencer par un caract\u00e8re <em>slash<\/em> <strong><code>\/<\/code><\/strong>.<br \/>\nLes <em><code>flags<\/code><\/em> sont les m\u00eames que ceux de l&rsquo;appel-syst\u00e8me <code>open()<\/code>, en g\u00e9n\u00e9ral on emploie <code>O_CREAT | O_RDWR<\/code>.<br \/>\nLe <em><code>mode<\/code><\/em> enfin correspond aux droits d&rsquo;acc\u00e8s en lecture, \u00e9criture, ex\u00e9cution par le propri\u00e9taire, le groupe, et les autres utilisateurs. La valeur octale <code>0600<\/code> repr\u00e9sente les droits classiques pour un partage entre processus appartenant tous \u00e0 un m\u00eame utilisateur.<\/p>\n<p>L&rsquo;appel <code>shm_open()<\/code> renvoie un descripteur au m\u00eame titre que les descripteurs de fichiers obtenus avec <code>open()<\/code>, <code>socket()<\/code>, <code>pipe()<\/code>, etc. Nous allons donc dimensionner le bloc de m\u00e9moire partag\u00e9e en utilisant un appel-syst\u00e8me habituellement r\u00e9serv\u00e9 aux fichiers.<\/p>\n<pre>int <strong>ftruncate<\/strong>(int <em>fd<\/em>, off_t <em>longueur<\/em>);<\/pre>\n<p>Ici <em><code>fd<\/code><\/em> est le descripteur fourni par <code>shm_open()<\/code>. Cette op\u00e9ration n&rsquo;est n\u00e9cessaire que pour dimensionner initialement le segment de m\u00e9moire, mais elle est inoffensive si on la r\u00e9p\u00e8te \u00e0 chaque ouverture.<\/p>\n<p>Enfin, nous allons projeter le bloc de m\u00e9moire partag\u00e9e dans notre espace d&rsquo;adressage en utilisant l&rsquo;appel-suivant.<\/p>\n<pre>void * <strong>mmap<\/strong>(void * <em>adresse<\/em>, size_t <em>longueur<\/em>, int <em>droits<\/em>,\n            int <em>flags<\/em>, int <em>fd<\/em>, off_t <em>offset<\/em>);<\/pre>\n<p>L&rsquo;argument <em><code>adresse<\/code><\/em> sera laiss\u00e9 <code>NULL<\/code>, le noyau cherchera une place libre dans notre espace d&rsquo;adressage, et nous renverra l&rsquo;adresse en retour de <code>mmap()<\/code>. La <em><code>longueur<\/code><\/em> est celle de la zone partag\u00e9e. Les droits vaudront <code>PROT_READ | PROT_WRITE<\/code> pour permettre lecture et \u00e9criture sur la m\u00e9moire. L&rsquo;argument <em><code>flags<\/code><\/em> contiendra <code>MAP_SHARED<\/code> pour indiquer que la projection doit \u00eatre partag\u00e9e entre processus. Enfin <em><code>fd<\/code><\/em> et <em><code>offset<\/code><\/em> repr\u00e9senteront le descripteur fourni par <code>shm_open()<\/code> et le d\u00e9calage de la projection par rapport au d\u00e9but de la zone de m\u00e9moire partag\u00e9e (z\u00e9ro bien s\u00fbr).<\/p>\n<p>Dans le programme suivant, nous partagerons une simple structure <code>timeval<\/code> contenant l&rsquo;heure. Cette zone de m\u00e9moire pourrait \u00eatre beaucoup plus grande, des kilo-octets voire des m\u00e9ga-octets sans diff\u00e9rence notable de performance.<\/p>\n<h1>S\u00e9maphores<\/h1>\n<p>Pour prot\u00e9ger les acc\u00e8s concurrents, il est d&rsquo;usage d&#8217;employer un s\u00e9maphore avec un compteur initialis\u00e9 \u00e0 1, chaque processus prenant le s\u00e9maphore (la prise est bloquante quant le compteur est nul), effectuant ses modifications et restituant le s\u00e9maphore. C&rsquo;est le m\u00eame principe que celui des mutex dont on se sert entre threads du m\u00eame processus.<\/p>\n<p>Pour partager un s\u00e9maphore entre processus, on emploie g\u00e9n\u00e9ralement un <em>s\u00e9maphore nomm\u00e9<\/em>. Celui-ci s&rsquo;obtient avec l&rsquo;appel-syst\u00e8me suivant.<\/p>\n<pre>int <strong>sem_open<\/strong>(const char * <em>nom<\/em>, int <em>flags<\/em>,\n             mode_t <em>mode<\/em>, int <em>valeur<\/em>);<\/pre>\n<p>Le <em><code>nom<\/code><\/em> est soumis aux m\u00eames r\u00e8gles que celui utilis\u00e9 par <code>shm_open()<\/code>. Les flags seront <code>O_CREAT | O_RDWR<\/code>, le mode <code>0600<\/code> et la <em><code>valeur<\/code><\/em> est celle du s\u00e9maphore s&rsquo;il y a cr\u00e9ation (sinon l&rsquo;argument est ignor\u00e9).<\/p>\n<p>La prise et le rel\u00e2chement du s\u00e9maphore se font avec<\/p>\n<pre>int <strong>sem_wait<\/strong>(sem_t * <em>sem<\/em>);\nint <strong>sem_post<\/strong>(sem_t * <em>sem<\/em>);<\/pre>\n<p>Il existe des variantes non bloquantes ou temporis\u00e9es \u00e9galement.<\/p>\n<p>Dans le programme suivant nous allons utiliser le s\u00e9maphore d&rsquo;une mani\u00e8re un peu d\u00e9voy\u00e9e, mais souvent employ\u00e9e dans les syst\u00e8mes temps-r\u00e9el&nbsp;: un processus va r\u00e9aliser uniquement des <code>sem_wait()<\/code> (ce qui le bloquera r\u00e9guli\u00e8rement), et l&rsquo;autre uniquement des <code>sem_post()<\/code> (pour d\u00e9bloquer son partenaire).<\/p>\n<h2>Destructions des ressources partag\u00e9es<\/h2>\n<p>Dans les deux programmes que nous emploierons ci-dessous, nous avons pris soin juste apr\u00e8s ouverture d&rsquo;une ressource partag\u00e9e de d\u00e9truire son nom avec <code>shm_unlink()<\/code> ou <code>sem_unlink()<\/code>. Comme les processus qui utiliserons ces ressources sont deux fils du processus original, ils disposeront des descripteurs (de m\u00e9moire ou de s\u00e9maphore) d\u00e9j\u00e0 ouverts. La destruction des ressources ne se produira effectivement que lorsqu&rsquo;il n&rsquo;y aura plus de processus utilisateur &#8211; autrement dit \u00e0 la fin de nos programmes.<\/p>\n<h1>Communications rares par m\u00e9moire partag\u00e9e<\/h1>\n<p>Comme dans nos articles pr\u00e9c\u00e9dents sur les <a title=\"Efficacit\u00e9 des IPC&nbsp;: les files de messages Posix\" href=\"http:\/\/www.blaess.fr\/christophe\/2011\/09\/17\/efficacite-des-ipc-les-files-de-messages-posix\/\">files de messages<\/a> et <a title=\"Efficacit\u00e9 des IPC&nbsp;: les signaux temps-r\u00e9el\" href=\"http:\/\/www.blaess.fr\/christophe\/2011\/10\/02\/efficacite-des-ipc-les-signaux-temps-reel\/\">signaux temps-r\u00e9el<\/a>, nous allons commencer par un \u00e9change sporadique de donn\u00e9es (toutes les 0,5 secondes).<\/p>\n<p>Un processus \u00e9crit l&rsquo;heure actuelle dans la m\u00e9moire partag\u00e9e et d\u00e9bloque le s\u00e9maphore.<\/p>\n<p>L&rsquo;autre processus qui \u00e9tait bloqu\u00e9 sur le s\u00e9maphore est r\u00e9veill\u00e9, et compare l&rsquo;heure actuelle avec celle mesur\u00e9e par son confr\u00e8re. La dur\u00e9e est affich\u00e9e en micro-secondes sur la sortie standard.<\/p>\n<p>Les deux processus sont plac\u00e9s sur deux CPU s\u00e9par\u00e9s (si possible) afin d&rsquo;\u00e9viter les effets d&rsquo;ordonnancement.<\/p>\n<p>Les codes-source des exemples se trouvent dans <a title=\"http:\/\/www.blaess.fr\/christophe\/files\/article-2011-10-09\/Efficacite-memoire-partagee.tar.bz2\" href=\"http:\/\/www.blaess.fr\/christophe\/files\/article-2011-10-09\/Efficacite-memoire-partagee.tar.bz2\">cette archive<\/a>.<\/p>\n<p>&nbsp;<\/p>\n<pre><strong>memoire-partagee-01.c<\/strong>\n#define _GNU_SOURCE\n#include &lt;fcntl.h&gt;\n#include &lt;sched.h&gt;\n#include &lt;semaphore.h&gt;\n#include &lt;stdio.h&gt;\n#include &lt;stdlib.h&gt;\n#include &lt;string.h&gt;\n#include &lt;unistd.h&gt;\n#include &lt;sys\/mman.h&gt;\n#include &lt;sys\/time.h&gt;\n#include &lt;sys\/wait.h&gt;\n\nstatic sem_t * semaphore = NULL;\nstatic struct timeval * avant = NULL;\n\nvoid producteur (void)\n{\n\tsleep(1);\n\twhile (1) {\n\t\t<strong>gettimeofday<\/strong>(avant, NULL);\n\t\t<strong>sem_post<\/strong>(semaphore);\n\t\tusleep(5000000);\n\t}\n}\n\nvoid consommateur(void)\n{\n\tstruct timeval apres;\n\tlong int duree;\n\twhile (1) {\n\t\t<strong>sem_wait<\/strong>(semaphore);\n\t\t<strong>gettimeofday<\/strong>(&amp; apres, NULL);\n\t\tduree  = apres.tv_sec - avant-&gt;tv_sec;\n\t\tduree *= 1000000;\n\t\tduree += apres.tv_usec - avant-&gt;tv_usec;\n\t\tprintf(\"%ldn\", duree);\n\t}\n}\n\nint main(void)\n{\n\tint md;\n\tcpu_set_t ensemble;\n\n\tsemaphore = <strong>sem_open<\/strong>(\"\/semaphore\", O_CREAT | O_RDWR, 0600, 0);\n\tif (semaphore == SEM_FAILED) {\n\t\tperror(\"\/semaphore\");\n\t\texit(EXIT_FAILURE);\n\t}\n\t<strong>sem_unlink<\/strong>(\"\/semaphore\");\n\n\tmd = <strong>shm_open<\/strong>(\"\/memoire\", O_CREAT | O_RDWR, 0600);\n\tif (md &lt; 0) {\n\t\tperror(\"\/memoire\");\n\t\texit(EXIT_FAILURE);\n\t}\n\t<strong>ftruncate<\/strong>(md, sizeof(struct timeval));\n\tavant = <strong>mmap<\/strong>(NULL, sizeof(struct timeval), PROT_READ | PROT_WRITE, MAP_SHARED, md, 0);\n\tif (avant == MAP_FAILED) {\n\t\tperror(\"\/memoire\");\n\t\texit(EXIT_FAILURE);\n\t}\n\t<strong>shm_unlink<\/strong>(\"\/memoire\");\n\n\tCPU_ZERO(&amp; ensemble);\n\n\tif (<strong>fork<\/strong>() == 0) {\n\t\t\/* Premier fils *\/\n\t\tCPU_SET(1, &amp; ensemble);\n\t\tsched_setaffinity(0, sizeof(cpu_set_t), &amp; ensemble);\n\t\tproducteur();\n\t\texit(EXIT_SUCCESS);\n\t}\n\n\tif (<strong>fork<\/strong>() == 0) {\n\t\t\/* Second fils *\/\n\t\tCPU_SET(0, &amp; ensemble);\n\t\tsched_setaffinity(0, sizeof(cpu_set_t), &amp; ensemble);\n\t\tconsommateur();\n\t\texit(EXIT_SUCCESS);\n\t}\n\n\t\/* pere *\/\n\twaitpid(-1 , NULL, 0);\n\twaitpid(-1 , NULL, 0);\n\n\treturn EXIT_SUCCESS;\n}<\/pre>\n<p>Ex\u00e9cutons notre programme&nbsp;:<\/p>\n<pre>$ <strong>.\/memoire-partagee-01<\/strong>\n[...]\n12\n12\n12\n12\n12\n12\n11\n11\n12\n13\n13\n11\n11\n  <strong><em>(Contr\u00f4le-C)<\/em><\/strong>\n$<\/pre>\n<h1>Communication \u00e0 haut d\u00e9bit<\/h1>\n<p>Cette fois nous allons modifier le programme pour que l&rsquo;\u00e9change se fasse aussi vite que possible. D\u00e8s que le producteur a \u00e9crit l&rsquo;heure dans la m\u00e9moire partag\u00e9e et a incr\u00e9ment\u00e9 le s\u00e9maphore initial, il va se mettre en attente sur un second s\u00e9maphore partag\u00e9.<\/p>\n<p>Le deuxi\u00e8me thread sera r\u00e9veill\u00e9 sur le s\u00e9maphore original comme pr\u00e9c\u00e9demment, comparera les heures et stockera les r\u00e9sultats, puis incr\u00e9mentera le second s\u00e9maphore afin de r\u00e9veiller le premier processus. Une fois la table de r\u00e9sultats remplie, il affichera les valeurs sur sa sortie standard.<\/p>\n<pre>#define _GNU_SOURCE\n#include &lt;fcntl.h&gt;\n#include &lt;sched.h&gt;\n#include &lt;semaphore.h&gt;\n#include &lt;stdio.h&gt;\n#include &lt;stdlib.h&gt;\n#include &lt;string.h&gt;\n#include &lt;unistd.h&gt;\n#include &lt;sys\/mman.h&gt;\n#include &lt;sys\/time.h&gt;\n#include &lt;sys\/wait.h&gt;\n\nstatic sem_t * semaphore_1 = NULL;\nstatic sem_t * semaphore_2 = NULL;\nstatic struct timeval * avant = NULL;\n\nvoid producteur (void)\n{\n\tsleep(1);\n\twhile (1) {\n\t\t<strong>gettimeofday<\/strong>(avant, NULL);\n\t\t<strong>sem_post<\/strong>(semaphore_1);\n\t\t<strong>sem_wait<\/strong>(semaphore_2);\n\t}\n}\n\n#define NB_MESURES 1000\n\nvoid consommateur(void)\n{\n\tstruct timeval apres;\n\n\tlong int * durees = NULL;\n\tdurees = calloc(NB_MESURES, sizeof(long int));\n\tif (durees == NULL) {\n\t\tperror(\"malloc\");\n\t\texit(EXIT_FAILURE);\n\t}\n\tint n = 0;\n\twhile (n &lt; NB_MESURES) {\n\t\t<strong>sem_wait<\/strong>(semaphore_1);\n\t\t<strong>gettimeofday<\/strong>(&amp; apres, NULL);\n\t\tdurees[n]  = apres.tv_sec - avant-&gt;tv_sec;\n\t\tdurees[n] *= 1000000;\n\t\tdurees[n] += apres.tv_usec - avant-&gt;tv_usec;\n\t\tn++;\n\t\t<strong>sem_post<\/strong>(semaphore_2);\n\t}\n\tfor (n=0; n &lt; NB_MESURES; n ++)\n\t\tprintf(\"%ldn\", durees[n]);\n}\n\nint main(void)\n{\n\tint md;\n\tcpu_set_t ensemble;\n\n\tsemaphore_1 = <strong>sem_open<\/strong>(\"\/semaphore-1\", O_CREAT | O_RDWR, 0600, 0);\n\tif (semaphore_1 == SEM_FAILED) {\n\t\tperror(\"\/semaphore-1\");\n\t\texit(EXIT_FAILURE);\n\t}\n\t<strong>sem_unlink<\/strong>(\"\/semaphore-1\");\n\n\tsemaphore_2 = <strong>sem_open<\/strong>(\"\/semaphore-2\", O_CREAT | O_RDWR, 0600, 0);\n\tif (semaphore_2 == SEM_FAILED) {\n\t\tperror(\"\/semaphore-2\");\n\t\texit(EXIT_FAILURE);\n\t}\n\t<strong>sem_unlink<\/strong>(\"\/semaphore-2\");\n\n\tmd = <strong>shm_open<\/strong>(\"\/memoire\", O_CREAT | O_RDWR, 0600);\n\tif (md &lt; 0) {\n\t\tperror(\"\/memoire\");\n\t\texit(EXIT_FAILURE);\n\t}\n\t<strong>ftruncate<\/strong>(md, sizeof(struct timeval));\n\tavant = <strong>mmap<\/strong>(NULL, sizeof(struct timeval), PROT_READ | PROT_WRITE, MAP_SHARED, md, 0);\n\tif (avant == MAP_FAILED) {\n\t\tperror(\"\/memoire\");\n\t\texit(EXIT_FAILURE);\n\t}\n\t<strong>shm_unlink<\/strong>(\"\/memoire\");\n\n\tCPU_ZERO(&amp; ensemble);\n\n\tif (fork() == 0) {\n\t\t\/* Premier fils *\/\n\t\tCPU_SET(1, &amp; ensemble);\n\t\tsched_setaffinity(0, sizeof(cpu_set_t), &amp; ensemble);\n\t\tproducteur();\n\t\texit(EXIT_SUCCESS);\n\t}\n\n\tif (fork() == 0) {\n\t\t\/* Second fils *\/\n\t\tCPU_SET(0, &amp; ensemble);\n\t\tsched_setaffinity(0, sizeof(cpu_set_t), &amp; ensemble);\n\t\tconsommateur();\n\t\texit(EXIT_SUCCESS);\n\t}\n\n\t\/* pere *\/\n\twaitpid(-1 , NULL, 0);\n\twaitpid(-1 , NULL, 0);\n\n\treturn EXIT_SUCCESS;\n}<\/pre>\n<p>Voici un exemple d&rsquo;ex\u00e9cution.<\/p>\n<pre>$ .\/memoire_partagee_02\n4\n4\n4\n4\n4\n4\n4\n4\n4\n4\n4\n4\n4\n4\n4\n4\n4\n4\n4\n4\n4\n4\n4\n4\n3\n3\n3\n3\n3\n3\n3\n3\n3\n3\n3\n3\n3\n3\n4\n3\n3\n3\n3\n3\n4\n4\n4\n4\n4\n4\n4\n4\n4\n4\n4\n4\n4\n4\n4\n4\n4\n4\n4\n4\n4\n4\n4\n4\n4\n4\n3\n3\n3\n3\n3\n3\n3\n3\n3\n3\n3\n3\n3\n3\n4\n3\n3\n3\n3\n3\n4\n4\n  <strong><em>(Contr\u00f4le-C)<\/em><\/strong>\n$<\/pre>\n<p>Nous nous rapprochons des valeurs obtenues dans l&rsquo;article pr\u00e9c\u00e9dent avec les signaux temps-r\u00e9el. Mais cette fois la taille des donn\u00e9es transmises n&rsquo;est pas limit\u00e9e.<\/p>\n<h1>Conclusion<\/h1>\n<p>Entre une douzaine de micro-secondes si le processeur \u00e9tait au repos (et demandait un temps de r\u00e9veil non n\u00e9gligeable) et 4 micro-secondes s&rsquo;il est actif, la communication par zone de m\u00e9moire partag\u00e9e est apparemment la plus rapide entre deux processus, \u00e0 condition bien entendu de g\u00e9rer correctement les cas d&rsquo;acc\u00e8s concurrents, \u00e0 l&rsquo;aide de s\u00e9maphores par exemple.<\/p>","protected":false},"excerpt":{"rendered":"<p>La m&eacute;thode de communication entre processus la plus performante lorsqu&rsquo;on doit transf&eacute;rer des donn&eacute;es volumineuses est l&rsquo;emploi de zones de m&eacute;moires partag&eacute;es. Standardis&eacute; par Posix, il s&rsquo;agit d&rsquo;un m&eacute;canisme extr&eacute;mement efficace. Toutefois, il faut penser &agrave; synchroniser les acc&egrave;s, afin d&rsquo;&eacute;viter les modifications concurrentes des donn&eacute;es partag&eacute;es ou la modification d&rsquo;une zone pendant sa consultation [&hellip;]<\/p>","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8,14],"tags":[],"class_list":["post-1182","post","type-post","status-publish","format-standard","hentry","category-linux-2","category-temps-reel"],"_links":{"self":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/1182","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/comments?post=1182"}],"version-history":[{"count":0,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/1182\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/media?parent=1182"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/categories?post=1182"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/tags?post=1182"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}