{"id":3928,"date":"2013-12-27T18:02:23","date_gmt":"2013-12-27T17:02:23","guid":{"rendered":"http:\/\/www.blaess.fr\/christophe\/?p=3928"},"modified":"2013-12-28T12:18:14","modified_gmt":"2013-12-28T11:18:14","slug":"comprendre-le-fonctionnement-de-select","status":"publish","type":"post","link":"https:\/\/www.blaess.fr\/christophe\/2013\/12\/27\/comprendre-le-fonctionnement-de-select\/","title":{"rendered":"Comprendre le fonctionnement de select()"},"content":{"rendered":"<p><a href=\"http:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2013\/12\/select.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignright size-full wp-image-3942\" alt=\"Fonctionnement de select()\" src=\"http:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2013\/12\/mini-select.png\" width=\"250\" height=\"250\" srcset=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2013\/12\/mini-select.png 250w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2013\/12\/mini-select-150x150.png 150w\" sizes=\"auto, (max-width: 250px) 100vw, 250px\" \/><\/a><\/p>\n<p style=\"text-align: justify;\">Dans un <a title=\"Attentes passives sur GPIO (commentaire du 27\/12\/2013)\" href=\"http:\/\/www.blaess.fr\/christophe\/2013\/04\/15\/attentes-passives-sur-gpio\/#comment-25563\" target=\"_blank\">commentaire r\u00e9cent<\/a>, Thomas m&rsquo;interrogeait sur le fonctionnement de l&rsquo;appel-syst\u00e8me <code>select()<\/code> lorsqu&rsquo;il est invoqu\u00e9 pour surveiller des entr\u00e9es GPIO par l&rsquo;interm\u00e9diaire du syst\u00e8me de fichiers <code>\/sys<\/code>.<\/p>\n<p style=\"text-align: justify;\">C&rsquo;est effectivement un sujet int\u00e9ressant, un peu complexe, que je vais essayer de d\u00e9velopper ici. Nous allons commencer par examiner comment <code>select()<\/code> fonctionne pour des fichiers sp\u00e9ciaux repr\u00e9sentant des p\u00e9riph\u00e9riques classiques puis verrons comment il se comporte lorsqu&rsquo;il est invoqu\u00e9 pour surveiller un fichier de <code>sysfs<\/code>.<br \/>\n<!--more-->\n<\/p>\n<h1>Utilisation de select()<\/h1>\n<p style=\"text-align: justify;\">L&rsquo;appel-syst\u00e8me <code>select()<\/code>, comme son homologue <code>poll()<\/code>, sert \u00e0 attendre passivement qu&rsquo;une condition soit r\u00e9alis\u00e9e sur un ou plusieurs descripteurs de fichier que l&rsquo;on peut surveiller simultan\u00e9ment. Les descripteurs repr\u00e9sentent des fichiers au comportement dynamique (sockets, tubes, p\u00e9riph\u00e9riques, etc.) et les conditions attendues se traduisent par la pr\u00e9sence de donn\u00e9es disponibles pour la lecture, la possibilit\u00e9 d&rsquo;envoyer des donn\u00e9es en \u00e9criture ou l&rsquo;occurrence d&rsquo;une situation exceptionnelle (par exemple l&rsquo;arriv\u00e9e de donn\u00e9es urgentes hors-bande dans un flux TCP\/IP).<\/p>\n<p style=\"text-align: justify;\">La grande force de <code>select()<\/code> est que l&rsquo;attente est passive&nbsp;: tant qu&rsquo;aucune condition n&rsquo;est r\u00e9alis\u00e9e, la t\u00e2che appelante est endormie. Elle peut n\u00e9anmoins indiquer un d\u00e9lai maximal d&rsquo;attente, un <em>timeout,<\/em> au-del\u00e0 de laquelle elle sera automatiquement r\u00e9veill\u00e9e.<\/p>\n<p style=\"text-align: justify;\">Voici un exemple extrait de mon livre \u00ab\u00a0<a title=\"http:\/\/www.blaess.fr\/christophe\/livres\/programmation-systeme-sous-linux\/\" href=\"http:\/\/www.blaess.fr\/christophe\/livres\/programmation-systeme-sous-linux\/\" target=\"_blank\"><em>D\u00e9veloppement syst\u00e8me sous Linux<\/em><\/a>\u00a0\u00bb (chapitre 25) dans lequel un processus cr\u00e9e dix processus enfants qui lui envoient des donn\u00e9es dans des tubes (<em>pipes<\/em>) avec des p\u00e9riodes diff\u00e9rentes. Le processus parent est en attente passive sur les dix descripteurs simultan\u00e9ment.<\/p>\n<pre><strong>exemple-select.c<\/strong>\n#include &lt;stdio.h&gt;\n#include &lt;stdlib.h&gt;\n#include &lt;unistd.h&gt;\n#include &lt;sys\/types.h&gt;\n\n#define NB_FILS 10\n\nint main (void)\n{\n        int tube[NB_FILS][2];\n        int i, fils;\n        char c = 'c';\n        fd_set ensemble;\n\n        for (i = 0; i &lt; NB_FILS; i ++)\n                if (pipe (tube[i]) &lt; 0) {\n                        perror(\"pipe\");\n                        exit(EXIT_FAILURE);\n                }\n        for (fils = 0; fils &lt; NB_FILS; fils ++)\n                if (fork() == 0)\n                        break;\n        for (i = 0; i &lt; NB_FILS; i ++)\n                if (fils == NB_FILS) {\n                        \/\/ On est dans le pere\n                        close(tube[i][1]);\n                } else {\n                        close(tube[i][0]);\n                        if (i != fils)\n                                close(tube[i][1]);\n                }\n        if (fils == NB_FILS) {\n                \/\/ Processus pere\n                while (1) {\n                        FD_ZERO(&amp; ensemble);\n                        for (i = 0; i &lt; NB_FILS; i ++)\n                                FD_SET(tube[i][0], &amp; ensemble);\n                        if (<strong>select<\/strong>(FD_SETSIZE, &amp; ensemble, NULL, NULL, NULL) &lt;= 0) {\n                                perror(\"select\");\n                                break;\n                        }\n                        for (i = 0; i &lt; NB_FILS; i ++)\n                                if (FD_ISSET(tube[i][0], &amp; ensemble)) {\n                                        fprintf(stdout, \"%d \", i);\n                                        fflush(stdout);\n                                        read(tube[i][0], &amp; c, 1);\n                                }\n                }\n        } else {\n                while (1) {\n                        usleep((fils + 1) * 1000000);\n                        write(tube[fils][1], &amp; c, 1);\n                }\n        }\n        return EXIT_SUCCESS;\n}<\/pre>\n<p>&nbsp;<\/p>\n<h1>Les <em>waitqueue<\/em>s du noyau<\/h1>\n<p style=\"text-align: justify;\">Avant de d\u00e9tailler le fonctionnement interne de <code>select()<\/code>, faisons un petit point sur le principe des op\u00e9rations bloquantes que l&rsquo;on peut r\u00e9aliser sur un p\u00e9riph\u00e9rique. Lorsqu&rsquo;une t\u00e2che appelle une fonction de lecture sur un p\u00e9riph\u00e9rique, la m\u00e9thode <code>read()<\/code> du driver correspondant est invoqu\u00e9e.<\/p>\n<p style=\"text-align: justify;\">Si des donn\u00e9es sont d\u00e9j\u00e0 disponibles, la m\u00e9thode <code>read()<\/code> les renvoie imm\u00e9diatement et se termine.<\/p>\n<p style=\"text-align: justify;\">Si aucune donn\u00e9e n&rsquo;est disponible, la fonction <code>read()<\/code> va inscrire la t\u00e2che appelante dans une <em>waitqueue<\/em> (file d&rsquo;attente) et l&rsquo;endormir. Pour cela, elle bascule l&rsquo;\u00e9tat de la t\u00e2che \u00e0 <em>Sleeping<\/em> et invoque directement l&rsquo;ordonnanceur (fonction <code>schedule()<\/code> du kernel). Ce dernier peut alors activer une autre t\u00e2che ou basculer le processeur en mode <em>idle<\/em>.<\/p>\n<p style=\"text-align: justify;\">D\u00e8s que des donn\u00e9es se pr\u00e9sentent en entr\u00e9e sur le p\u00e9riph\u00e9rique, une interruption mat\u00e9rielle est d\u00e9clench\u00e9e. Le handler associ\u00e9 &#8211; qui fait partie du driver &#8211; est invoqu\u00e9, et il r\u00e9veille une t\u00e2che dans sa file d&rsquo;attente (en la passant \u00e0 l&rsquo;\u00e9tat <em>Runnable<\/em>). Lors de la prochaine invocation de l&rsquo;ordonnanceur, ce dernier pourra choisir d&rsquo;activer la t\u00e2che r\u00e9veill\u00e9e, et la fonction <code>read()<\/code> continuera alors son ex\u00e9cution en renvoyant \u00e0 l&rsquo;espace utilisateur les donn\u00e9es fra\u00eechement arriv\u00e9es.<\/p>\n<h1>Impl\u00e9mentation de select()<\/h1>\n<p style=\"text-align: justify;\">L&rsquo;appel-syst\u00e8me <code>select()<\/code> est bien entendu impl\u00e9ment\u00e9 dans le kernel. Lorsqu&rsquo;une t\u00e2che l&rsquo;invoque depuis l&rsquo;espace utilisateur, le processeur bascule en mode kernel et se branche sur la fonction <code>do_select()<\/code> de <code>fs\/select.c<\/code> (noyau 3.12) apr\u00e8s \u00eatre pass\u00e9 par la routine <code>sys_select()<\/code> du m\u00eame fichier &#8211; dont le nom est construit par la macro <code>SYSCALL_DEFINE5(select,...)<\/code>.<\/p>\n<p style=\"text-align: justify;\">Dans cette fonction <code>do_select()<\/code>, le kernel invoque successivement les m\u00e9thodes <code>poll()<\/code> de tous les drivers concern\u00e9s par les descripteurs de fichiers des diff\u00e9rents ensembles.<\/p>\n<p style=\"text-align: justify;\">La m\u00e9thode <code>poll()<\/code> d&rsquo;un driver est une fonction (non-bloquante) qui a deux objectifs&nbsp;:<\/p>\n<ul>\n<li style=\"text-align: justify;\">renvoyer une valeur indiquant l&rsquo;\u00e9tat du p\u00e9riph\u00e9rique quant \u00e0 la disponibilit\u00e9 de donn\u00e9es en entr\u00e9e, la capacit\u00e9 d&rsquo;\u00e9crire des donn\u00e9es en sortie ou l&rsquo;occurrence de conditions exceptionnelles,<\/li>\n<li style=\"text-align: justify;\">inscrire dans une table qui lui a \u00e9t\u00e9 pass\u00e9e en argument la liste des <em>waitqueues<\/em> dans lesquelles il convient de s&rsquo;inscrire pour \u00eatre notifi\u00e9 de la modification de l&rsquo;\u00e9tat de ce p\u00e9riph\u00e9rique.<\/li>\n<\/ul>\n<p style=\"text-align: justify;\">Apr\u00e8s avoir invoqu\u00e9 les m\u00e9thodes <code>poll()<\/code> de tous les p\u00e9riph\u00e9riques, la fonction <code>do_select()<\/code> va endormir passivement la t\u00e2che appelante &#8211; avec <code>poll_schedule_timeout()<\/code> &#8211; en attente d&rsquo;une notification sur l&rsquo;une des <em>waitqueues<\/em> de la table (\u00e0 moins bien entendu qu&rsquo;un des drivers ait indiqu\u00e9 que les conditions attendues sont d\u00e9j\u00e0 r\u00e9alis\u00e9es, auquel cas <code>do_select()<\/code> se termine tout de suite).<\/p>\n<p style=\"text-align: justify;\">D\u00e8s que l&rsquo;un des handlers d&rsquo;interruption des drivers est invoqu\u00e9 par le mat\u00e9riel concern\u00e9, il r\u00e9veille les t\u00e2ches en attente dans sa <em>waitqueue.<\/em> Ce qui r\u00e9veille la t\u00e2che endormie dans <code>do_select()<\/code>. Cette derni\u00e8re re-balaye alors les m\u00e9thodes <code>poll()<\/code> de tous les descripteurs afin de v\u00e9rifier les \u00e9tats des diff\u00e9rents p\u00e9riph\u00e9riques et choisit \u00e9ventuellement de revenir dans l&rsquo;espace utilisateur si les conditions attendues sont r\u00e9unies.<\/p>\n<p style=\"text-align: center;\"><a href=\"http:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2013\/12\/select.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter  wp-image-3941\" alt=\"Impl\u00e9mentation de select()\" src=\"http:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2013\/12\/select.png\" width=\"648\" height=\"648\" srcset=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2013\/12\/select.png 720w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2013\/12\/select-150x150.png 150w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2013\/12\/select-300x300.png 300w\" sizes=\"auto, (max-width: 648px) 100vw, 648px\" \/><\/a><\/p>\n<h1>\u00a0Utilisation de select() dans \/sys<\/h1>\n<p style=\"text-align: justify;\">Le principe de fonctionnement de <code>select()<\/code> lorsqu&rsquo;il sert \u00e0 surveiller les changements d&rsquo;\u00e9tat d&rsquo;une broche GPIO par l&rsquo;interm\u00e9diaire de <code>\/sys<\/code> (comme on le voit dans <a title=\"Attentes passives sur GPIO\" href=\"http:\/\/www.blaess.fr\/christophe\/2013\/04\/15\/attentes-passives-sur-gpio\/\" target=\"_blank\">cet article<\/a>) est identique. L&rsquo;attente se fait en utilisant le descripteur du fichier\u00a0<code>value<\/code> mais celui ci ne sert ici que de support pour acc\u00e9der aux fonctionnalit\u00e9s de <code>select()<\/code>, il n&rsquo;a pas de contenu physique sur le disque.<\/p>\n<p style=\"text-align: justify;\">Dans la fonction <code>gpio_setup_irq()<\/code> du kernel (<code>drivers\/gpio\/gpiolib.c<\/code>) un handler nomm\u00e9\u00a0<code>gpio_sysfs_irq()<\/code> est install\u00e9 qui notifiera les t\u00e2ches en attente de modification de l&rsquo;\u00e9tat du fichier <code>value<\/code> associ\u00e9 au GPIO. Son impl\u00e9mentation (noyau 3.12) est tr\u00e8s simple.<\/p>\n<pre>static irqreturn_t gpio_sysfs_irq(int irq, void *priv)\n{\n    struct sysfs_dirent *value_sd = priv;\n\n    sysfs_notify_dirent(value_sd);\n    return IRQ_HANDLED;\n}<\/pre>\n<p>&nbsp;<\/p>\n<p style=\"text-align: justify;\">La m\u00e9thode <code>poll()<\/code> est celle de <code>sysfs<\/code>, elle est impl\u00e9ment\u00e9e dans <code>fs\/sysfs\/file.c<\/code> et nomm\u00e9e <code>sysfs_poll()<\/code>.\u00a0 On notera que celle-ci renvoie <code>POLLPRI|POLLERR<\/code>, ce qui explique pourquoi c&rsquo;est le troisi\u00e8me ensemble de descripteurs de <code>select()<\/code>, celui qui attend des conditions exceptionnelles qu&rsquo;il convient d&rsquo;utiliser pour les GPIO via <code>\/sys<\/code>.<\/p>\n<h1>Conclusion<\/h1>\n<p style=\"text-align: justify;\">R\u00e9capitulons ce qui se passe lorsqu&rsquo;un programme comme <code>gpio-select.c<\/code> de <a title=\"Attentes passives sur GPIO\" href=\"http:\/\/www.blaess.fr\/christophe\/2013\/04\/15\/attentes-passives-sur-gpio\/\" target=\"_blank\">cet article<\/a> s&rsquo;ex\u00e9cute.<\/p>\n<ul>\n<li style=\"text-align: justify;\">L&rsquo;invocation <code>select()<\/code> du programme transmet le contr\u00f4le \u00e0 l&rsquo;appel-syst\u00e8me <code>sys_select()<\/code> du kernel().<\/li>\n<li style=\"text-align: justify;\">Ce dernier invoque la m\u00e9thode <code>poll()<\/code> de <em>sysfs<\/em> qui lui indique si une condition attendue est r\u00e9alis\u00e9e, et inscrit sa <em>waitqueue<\/em> (<code>od-&gt;poll<\/code>) dans la table <code>wait<\/code> re\u00e7ue en argument.<\/li>\n<li style=\"text-align: justify;\">Si la condition n&rsquo;est pas r\u00e9alis\u00e9e, <code>sys_select()<\/code> endort la t\u00e2che en attente passive d&rsquo;une notification sur l&rsquo;une des <em>waitqueue<\/em> de la table.<\/li>\n<\/ul>\n<p style=\"text-align: justify;\">Lorsqu&rsquo;un changement d&rsquo;\u00e9tat du type attendu (fronts montant et\/ou descendant) se produit&nbsp;:<\/p>\n<ul>\n<li style=\"text-align: justify;\">Une interruption est g\u00e9n\u00e9r\u00e9e par le contr\u00f4leur GPIO.<\/li>\n<li style=\"text-align: justify;\">Dans le handler d&rsquo;interruption, la notification des t\u00e2ches en attente dans la <em>waitqueue<\/em> r\u00e9veille celle se trouvant dans <code>sys_select()<\/code>.<\/li>\n<li style=\"text-align: justify;\">Apr\u00e8s avoir v\u00e9rifi\u00e9 si une condition <code>POLL_PRI<\/code> est bien r\u00e9alis\u00e9e, <code>sys_select()<\/code> revient dans l&rsquo;espace utilisateur, laissant notre programme continuer son ex\u00e9cution.<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<p style=\"text-align: justify;\">Pour conclure, nous voyons que l&rsquo;attente est bien passive, il n&rsquo;y a aucune scrutation active consommant du CPU. Lors d&rsquo;un changement d&rsquo;\u00e9tat d&rsquo;un broche GPIO d&rsquo;entr\u00e9e, les t\u00e2ches en attente dans <code>select()<\/code> sont imm\u00e9diatement r\u00e9veill\u00e9es (leur activation d\u00e9pend de leur priorit\u00e9) par la notification provenant du handler d&rsquo;interruption.<\/p>","protected":false},"excerpt":{"rendered":"<p>Dans un commentaire r&eacute;cent, Thomas m&rsquo;interrogeait sur le fonctionnement de l&rsquo;appel-syst&egrave;me select() lorsqu&rsquo;il est invoqu&eacute; pour surveiller des entr&eacute;es GPIO par l&rsquo;interm&eacute;diaire du syst&egrave;me de fichiers \/sys. C&rsquo;est effectivement un sujet int&eacute;ressant, un peu complexe, que je vais essayer de d&eacute;velopper ici. Nous allons commencer par examiner comment select() fonctionne pour des fichiers sp&eacute;ciaux repr&eacute;sentant [&hellip;]<\/p>","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8],"tags":[],"class_list":["post-3928","post","type-post","status-publish","format-standard","hentry","category-linux-2"],"_links":{"self":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/3928","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=3928"}],"version-history":[{"count":16,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/3928\/revisions"}],"predecessor-version":[{"id":3948,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/3928\/revisions\/3948"}],"wp:attachment":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/media?parent=3928"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/categories?post=3928"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/tags?post=3928"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}