{"id":1792,"date":"2012-03-19T10:00:51","date_gmt":"2012-03-19T09:00:51","guid":{"rendered":"http:\/\/www.blaess.fr\/christophe\/?p=1792"},"modified":"2012-03-19T10:00:51","modified_gmt":"2012-03-19T09:00:51","slug":"mesurons-le-surcout-dun-appel-systeme","status":"publish","type":"post","link":"https:\/\/www.blaess.fr\/christophe\/2012\/03\/19\/mesurons-le-surcout-dun-appel-systeme\/","title":{"rendered":"Mesurons le surco\u00fbt d&rsquo;un appel syst\u00e8me"},"content":{"rendered":"<p style=\"text-align: justify;\">Une application classique a souvent besoin de r\u00e9aliser des appels syst\u00e8me pour acc\u00e9der \u00e0 des ressources mat\u00e9rielles ou logicielles contr\u00f4l\u00e9es par le noyau. Que l&rsquo;on veuille \u00e9crire dans un fichier, communiquer sur une socket, acc\u00e9der \u00e0 un port d&rsquo;entr\u00e9e-sortie, obtenir un suppl\u00e9ment de m\u00e9moire ou m\u00eame simplement lire notre num\u00e9ro de processus (<em>pid<\/em>), il faudra r\u00e9aliser un appel syst\u00e8me. Mais combien de temps durera-t-il&nbsp;?<\/p>\n<p>\n<!--more-->\n<\/p>\n<p style=\"text-align: justify;\">Typiquement un appel syst\u00e8me se mat\u00e9rialise par l&rsquo;invocation explicite d&rsquo;une interruption logicielle (une trappe) c&rsquo;est \u00e0 dire l&rsquo;ex\u00e9cution d&rsquo;une instruction assembleur sp\u00e9cifique qui va interrompre le travail du processeur et effectuer un appel vers une routine sp\u00e9cifique (un <em>handler<\/em>) se trouvant dans la m\u00e9moire du noyau. Cette routine est \u00e9galement ex\u00e9cut\u00e9e avec les privil\u00e8ges du noyau, ce qui lui permet de r\u00e9aliser les op\u00e9rations demand\u00e9es m\u00eame si elles impliquent des actions directes sur le mat\u00e9riel.<\/p>\n<p style=\"text-align: justify;\">Sur une architecture PC, c&rsquo;est classiquement l&rsquo;interruption <code>0x80<\/code> qui est d\u00e9di\u00e9e \u00e0 ce travail, le num\u00e9ro de l&rsquo;appel syst\u00e8me \u00e9tant cod\u00e9 dans un registre du processeur (<code>EAX<\/code>) avant d&rsquo;invoquer l&rsquo;instruction assembleur <code>INTR<\/code> qui d\u00e9clenchera le transfert dans l&rsquo;espace kernel. Sur les syst\u00e8mes r\u00e9cents, les instructions assembleurs <code>SYSENTER<\/code> et <code>SYSEXIT<\/code> permettent d&rsquo;all\u00e9ger ce m\u00e9canisme en simplifiant l&rsquo;entr\u00e9e dans le noyau.<\/p>\n<p style=\"text-align: justify;\">Ce qui m&rsquo;int\u00e9resse dans cet article est de savoir quel est le surco\u00fbt impliqu\u00e9 par un appel syst\u00e8me, ind\u00e9pendamment du travail \u00e0 r\u00e9aliser effectivement dans le noyau. Ceci peut permettre de juger des traitements \u00e0 effectuer dans un driver et de ceux que l&rsquo;on pr\u00e9f\u00e9rera conserver dans l&rsquo;espace utilisateur.<\/p>\n<h1 style=\"text-align: justify;\">Programmes de test<\/h1>\n<p style=\"text-align: justify;\">Pour faire cette mesure, il y a une solution simple&nbsp;: invoquons la m\u00eame routine du kernel dans un programme de l&rsquo;espace utilisatteur (via un appel syst\u00e8me) et dans un module du noyau, puis comparons le temps d&rsquo;ex\u00e9cution. Une fonction est particuli\u00e8rement adapt\u00e9e&nbsp;: <code>do_gettimeofday()<\/code> qui est invoqu\u00e9e par l&rsquo;appel syst\u00e8me <code>gettimeofday()<\/code> impl\u00e9ment\u00e9 dans le fichier <code>kernel\/time.c<\/code> des sources de Linux. Observons la construction de cet appel syst\u00e8me. Voici un extrait (provenant des sources de Linux 3.0.0) que je vous conseille d&rsquo;\u00e9tudier \u00e0 l&rsquo;aide de l&rsquo;excellent <a title=\"LXR Linux - Appel systeme gettimeofday\" href=\"http:\/\/lxr.linux.no\/#linux+v3.0\/kernel\/time.c#L101\" target=\"_blank\">site LXR<\/a>.<\/p>\n<pre><strong>SYSCALL_DEFINE2<\/strong>(gettimeofday, struct timeval __user *, tv,\n                struct timezone __user *, tz)\n{\n        if (likely(tv != NULL)) {\n                struct timeval ktv;\n                <strong>do_gettimeofday<\/strong>(&amp;ktv);\n                if (<strong>copy_to_user<\/strong>(tv, &amp;ktv, sizeof(ktv)))\n                        return -EFAULT;\n        }\n        if (unlikely(tz != NULL)) {\n                if (<strong>copy_to_user<\/strong>(tz, &amp;sys_tz, sizeof(sys_tz)))\n                        return -EFAULT;\n        }\n        return 0;\n}<\/pre>\n<p style=\"text-align: justify;\">La macro <code>SYSCALL_DEFINE2()<\/code> permet de d\u00e9finir un appel syst\u00e8me nomm\u00e9 <code>gettimeofday<\/code>, poss\u00e9dant deux arguments. Le premier, nomm\u00e9 <code>tv<\/code>, est un pointeur sur une structure <code>timeval<\/code> se trouvant dans l&rsquo;espace utilisateur, le second est un pointeur, nomm\u00e9 <code>tz<\/code>, sur une structure <code>timezone<\/code> \u00e9galement dans l&rsquo;espace utilisateur.<\/p>\n<p style=\"text-align: justify;\">Si <code>tv<\/code> est non nul l&rsquo;appel syst\u00e8me invoque la routine <code>do_gettimeofday()<\/code> pour renseigner une structure <code>timeval<\/code> se trouvant dans l&rsquo;espace kernel. Puis la structure est copi\u00e9e dans l&rsquo;espace utilisateur \u00e0 l&#8217;emplacement point\u00e9 par <code>tv<\/code>.<\/p>\n<p style=\"text-align: justify; padding-left: 30px;\">Le fait que <code>tv<\/code> soit non nul est probable (<em>likely<\/em>) car c&rsquo;est une utilisation fr\u00e9quente de cet appel syst\u00e8me. En revanche il est peu probable (<em>unlikely<\/em>) que le pointeur <code>tz<\/code> soit non nul, car il est rare qu&rsquo;une application demande ainsi des informations sur le fuseau horaire dans lequel se trouve le syst\u00e8me. Les directives <code>likely<\/code> et <code>unlikely<\/code> permettent d&rsquo;aider le compilateur \u00e0 optimiser le code en se basant sur les comportements usuels des applications, qu&rsquo;il n&rsquo;aurait pas pu deviner en analysant simplement le code noyau.<\/p>\n<p style=\"text-align: justify;\">Nous allons donc \u00e9crire un petit module qui invoque <code>do_gettimeofday()<\/code> en boucle, et affiche les r\u00e9sultats ensuite. Puis nous r\u00e9aliserons le m\u00eame travail par l&rsquo;interm\u00e9diaire d&rsquo;un appel syst\u00e8me depuis l&rsquo;espace utilisteur. Voici notre module.<\/p>\n<pre><strong>mesure-gettimeofday-kernel.c<\/strong>\n#include &lt;linux\/version.h&gt;\n#include &lt;linux\/module.h&gt;\n#include &lt;linux\/device.h&gt;\n#include &lt;linux\/time.h&gt;\n\n#define NB_MESURES 50\nstatic struct timeval mesures[NB_MESURES];\n\nstatic int __init init_gettimeofday (void)\n{\n    int i;\n\n    for (i = 0; i &lt; NB_MESURES; i++)\n        <strong>do_gettimeofday<\/strong>(&amp;(mesures[i]));\n\n    for (i = 0; i &lt; NB_MESURES; i++) {\n        printk(KERN_INFO \"%ld.%06ldn\", mesures[i].tv_sec, mesures[i].tv_usec);\n    }\n    return 0;\n}\n\nstatic void __exit exit_gettimeofday (void)\n{\n}\n\nmodule_init(init_gettimeofday);\nmodule_exit(exit_gettimeofday);\nMODULE_LICENSE(\"GPL\");<\/pre>\n<p style=\"text-align: justify;\">Ce module appelle donc cinquante fois <code>do_gettimeofday()<\/code> lors de son chargement puis nous affiche les r\u00e9sultats dans les traces du kernel. Chargeons-le et observons les r\u00e9sultats.<\/p>\n<pre># <strong>make modules<\/strong>\nmake -C \/lib\/modules\/3.0.0-16-generic\/build SUBDIRS=\/home\/cpb\/Documents\/Livres\/Articles\/Blog\/article-2012-03-19  modules\nmake[1]: entrant dans le r\u00e9pertoire \u00ab \/usr\/src\/linux-headers-3.0.0-16-generic \u00bb\n  CC [M]  \/home\/cpb\/Documents\/Livres\/Articles\/Blog\/article-2012-03-19\/mesure-gettimeofday-kernel.o\n  Building modules, stage 2.\n  MODPOST 1 modules\n  CC      \/home\/cpb\/Documents\/Livres\/Articles\/Blog\/article-2012-03-19\/mesure-gettimeofday-kernel.mod.o\n  LD [M]  \/home\/cpb\/Documents\/Livres\/Articles\/Blog\/article-2012-03-19\/mesure-gettimeofday-kernel.ko\nmake[1]: quittant le r\u00e9pertoire \u00ab \/usr\/src\/linux-headers-3.0.0-16-generic \u00bb\n# <strong>insmod .\/mesure-gettimeofday-kernel.ko <\/strong>\n# <strong>rmmod mesure_gettimeofday_kernel <\/strong>\n# <strong>dmesg | tail -50 <\/strong>\n[633997.019941] 1332085840.235456\n[633997.019945] 1332085840.235456\n[633997.019947] 1332085840.235456\n[633997.019950] 1332085840.235456\n[633997.019952] 1332085840.235456\n[633997.019954] 1332085840.235456\n[633997.019957] 1332085840.235456\n[633997.019959] 1332085840.235457\n[633997.019962] 1332085840.235457\n[633997.019964] 1332085840.235457\n[633997.019966] 1332085840.235457\n[633997.019969] 1332085840.235457\n[633997.019971] 1332085840.235457\n[633997.019974] 1332085840.235457\n[633997.019976] 1332085840.235457\n[633997.019978] 1332085840.235457\n[633997.019981] 1332085840.235457\n[633997.019983] 1332085840.235457\n[633997.019985] 1332085840.235458\n[633997.019988] 1332085840.235458\n[633997.019990] 1332085840.235458\n[633997.019992] 1332085840.235458\n[633997.019995] 1332085840.235458\n[633997.019998] 1332085840.235458\n[633997.020056] 1332085840.235458\n[633997.020067] 1332085840.235458\n[633997.020076] 1332085840.235458\n[633997.020085] 1332085840.235458\n[633997.020094] 1332085840.235459\n[633997.020103] 1332085840.235459\n[633997.020111] 1332085840.235459\n[633997.020120] 1332085840.235459\n[633997.020129] 1332085840.235459\n[633997.020137] 1332085840.235459\n[633997.020144] 1332085840.235459\n[633997.020153] 1332085840.235459\n[633997.020162] 1332085840.235459\n[633997.020171] 1332085840.235459\n[633997.020178] 1332085840.235459\n[633997.020186] 1332085840.235460\n[633997.020194] 1332085840.235460\n[633997.020202] 1332085840.235460\n[633997.020210] 1332085840.235460\n[633997.020218] 1332085840.235460\n[633997.020226] 1332085840.235460\n[633997.020234] 1332085840.235460\n[633997.020243] 1332085840.235460\n[633997.020251] 1332085840.235460\n[633997.020259] 1332085840.235460\n[633997.020267] 1332085840.235460\n#<\/pre>\n<p style=\"text-align: justify;\">Les lignes de r\u00e9sultats se d\u00e9composent en une premi\u00e8re valeur entre crochets qui correspond au <em>timestamp<\/em> (horodatage) au moment du <code>printk()<\/code>, ce qui ne nous int\u00e9resse pas, suivi de la valeur obtenue avec <code>do_gettimeofday()<\/code> exprim\u00e9e en secondes (depuis le 01\/01\/1970), les d\u00e9cimales \u00e9tant des microsecondes. Nous voyons que les valeurs sont r\u00e9guli\u00e8res et continues, il y a dix \u00e0 onze appels \u00e0 <code>do_gettimeofday()<\/code> par microseconde. L&rsquo;ex\u00e9cution des cinquantes mesures a dur\u00e9 environ cinq microsecondes.<\/p>\n<p style=\"text-align: justify;\">Voici \u00e0 pr\u00e9sent le programme qui effectuera un travail similaire depuis l&rsquo;espace utilisateur<\/p>\n<pre><strong>mesure-gettimeofday-user.c <\/strong>\n#include &lt;sched.h&gt;\n#include &lt;stdio.h&gt;\n#include &lt;stdlib.h&gt;\n#include &lt;unistd.h&gt;\n#include &lt;sys\/time.h&gt;\n\n#define NB_MESURES 50\n\nint main(void)\n{\n    int i;\n    struct sched_param param;\n    struct timeval mesures[NB_MESURES];\n\n    param.sched_priority = 30;\n    if (sched_setscheduler(0, SCHED_FIFO, &amp; param) != 0) {\n        fprintf(stderr, \"Pour des resultats plus precis, executez ce programme sous identite 'root'n\");\n        sleep(6);\n    }\n\n    for (i = 0; i &lt; NB_MESURES; i++)\n        <strong>gettimeofday<\/strong>(&amp;(mesures[i]), NULL);\n\n    for (i = 0; i &lt; NB_MESURES; i++) {\n        fprintf(stdout, \"%ld.%06ldn\", mesures[i].tv_sec, mesures[i].tv_usec);\n    }\n    return 0;\n}<\/pre>\n<p style=\"text-align: justify;\">On notera que le programme essaye si possible de passer en ordonnancement temps r\u00e9el pour \u00e9viter les incertitudes li\u00e9es aux pr\u00e9emptions par d&rsquo;autres t\u00e2ches. Ex\u00e9cutons-le.<\/p>\n<pre># <strong>make<\/strong>\ncc     mesure-gettimeofday-user.c   -o mesure-gettimeofday-user\n# <strong>.\/mesure-gettimeofday-user <\/strong>\n1332086462.451367\n1332086462.451368\n1332086462.451369\n1332086462.451369\n1332086462.451370\n1332086462.451370\n1332086462.451371\n1332086462.451371\n1332086462.451372\n1332086462.451372\n1332086462.451373\n1332086462.451374\n1332086462.451374\n1332086462.451375\n1332086462.451375\n1332086462.451376\n1332086462.451376\n1332086462.451377\n1332086462.451377\n1332086462.451378\n1332086462.451378\n1332086462.451379\n1332086462.451380\n1332086462.451380\n1332086462.451381\n1332086462.451381\n1332086462.451382\n1332086462.451382\n1332086462.451383\n1332086462.451383\n1332086462.451384\n1332086462.451384\n1332086462.451385\n1332086462.451385\n1332086462.451386\n1332086462.451387\n1332086462.451387\n1332086462.451388\n1332086462.451388\n1332086462.451389\n1332086462.451389\n1332086462.451390\n1332086462.451390\n1332086462.451391\n1332086462.451391\n1332086462.451392\n1332086462.451393\n1332086462.451393\n1332086462.451394\n1332086462.451394\n#<\/pre>\n<p style=\"text-align: justify;\">Cette fois, il n&rsquo;y a qu&rsquo;une \u00e0 deux invocations de <code>gettimeofday()<\/code> par microseconde. Au total, l&rsquo;ex\u00e9cution des cinquante appels a pris 28 microsecondes.<\/p>\n<h1>\u00a0R\u00e9sultats<\/h1>\n<p style=\"text-align: justify;\">Nous avons r\u00e9alis\u00e9 cinquante invocations de <code>do_gettimeofday()<\/code> depuis l&rsquo;espace kernel en cinq microsecondes environ. Depuis l&rsquo;espace utilisateur, les m\u00eames invocations ont dur\u00e9 vingt-huit microsecondes. La diff\u00e9rence est de 23 microsecondes pour cinquante it\u00e9rations, ce qui repr\u00e9sente 460 nanosecondes par appel syst\u00e8me.<\/p>\n<p style=\"text-align: justify;\">Sur cette machine, dans cette configuration, le surco\u00fbt d&rsquo;un appel syst\u00e8me (changement de contexte, modification des privil\u00e8ges d&rsquo;ex\u00e9cution, passage par l&rsquo;ordonnanceur en revenant dans l&rsquo;espace utilisateur&#8230;) par rapport au travail effectivement r\u00e9alis\u00e9 est de 460 nanosecondes. Quel est-il sur votre environnement&nbsp;? Vous pouvez le mesurer facilement avec les exemples ci-dessus, regroup\u00e9s dans <a title=\"http:\/\/www.blaess.fr\/christophe\/files\/article-2012-03-19\/mesure-gettimeofday.tar.bz2\" href=\"http:\/\/www.blaess.fr\/christophe\/files\/article-2012-03-19\/mesure-gettimeofday.tar.bz2\" target=\"_blank\">cette archive<\/a>.<\/p>\n<p style=\"text-align: justify;\">Commentaires, remarques, compl\u00e9ments sont les bienvenus&#8230;<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>","protected":false},"excerpt":{"rendered":"<p>Une application classique a souvent besoin de r&eacute;aliser des appels syst&egrave;me pour acc&eacute;der &agrave; des ressources mat&eacute;rielles ou logicielles contr&ocirc;l&eacute;es par le noyau. Que l&rsquo;on veuille &eacute;crire dans un fichier, communiquer sur une socket, acc&eacute;der &agrave; un port d&rsquo;entr&eacute;e-sortie, obtenir un suppl&eacute;ment de m&eacute;moire ou m&ecirc;me simplement lire notre num&eacute;ro de processus (pid), il faudra [&hellip;]<\/p>","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5,8,14],"tags":[],"class_list":["post-1792","post","type-post","status-publish","format-standard","hentry","category-embarque","category-linux-2","category-temps-reel"],"_links":{"self":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/1792","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=1792"}],"version-history":[{"count":0,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/1792\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/media?parent=1792"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/categories?post=1792"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/tags?post=1792"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}