{"id":1772,"date":"2012-03-12T16:00:17","date_gmt":"2012-03-12T15:00:17","guid":{"rendered":"http:\/\/www.blaess.fr\/christophe\/?p=1772"},"modified":"2012-03-12T16:00:17","modified_gmt":"2012-03-12T15:00:17","slug":"reveil-dune-tache-utilisateur-depuis-un-timer","status":"publish","type":"post","link":"https:\/\/www.blaess.fr\/christophe\/2012\/03\/12\/reveil-dune-tache-utilisateur-depuis-un-timer\/","title":{"rendered":"R\u00e9veil d&rsquo;une t\u00e2che utilisateur depuis un timer kernel"},"content":{"rendered":"<p style=\"text-align: justify;\">J&rsquo;ai \u00e9t\u00e9 plusieurs fois confront\u00e9 \u00e0 la n\u00e9cessit\u00e9 de d\u00e9terminer le temps de r\u00e9veil d&rsquo;une t\u00e2che utilisateur. La plupart du temps il s&rsquo;agit de borner le temps de r\u00e9action face \u00e0 un \u00e9v\u00e9nement ext\u00e9rieur qui se traduit par une interruption (nous en verrons un exemple dans un prochain article). R\u00e9cemment toutefois, le probl\u00e8me qui se posait \u00e9tait de r\u00e9veiller un processus lorsque le contenu d&rsquo;une adresse m\u00e9moire (projet\u00e9e par une carte d&rsquo;acquisition) \u00e9tait modifi\u00e9. Aucune interruption n&rsquo;\u00e9rait d\u00e9clench\u00e9e \u00e0 cette occasion, aussi la seule solution \u00e9tait de venir scruter en <em>polling<\/em> cette adresse r\u00e9guli\u00e8rement dans un timer du noyau. Une fois la modification d\u00e9tect\u00e9e, il fallait acquiter l&rsquo;\u00e9v\u00e9nement ce qui \u00e9tait r\u00e9alis\u00e9 dans le kernel, sans pr\u00e9senter de caract\u00e8re d&rsquo;urgence. Apr\u00e8s l&rsquo;acquitement il faillait toutefois entamer un traitement dans l&rsquo;espace utilisateur le plus rapidement possible. J&rsquo;avais donc besoin de mesurer le temps de r\u00e9veil d&rsquo;une t\u00e2che depuis un timer du noyau.<\/p>\n<p>\n<!--more-->\n<\/p>\n<h1><em>Driver<\/em> et processus<\/h1>\n<p style=\"text-align: justify;\">J&rsquo;ai donc \u00e9crit un petit driver minimal, qui pr\u00e9sente un fichier sp\u00e9cial dans <code>\/dev<\/code> sur lequel le processus va s&rsquo;endormir dans une fonction de lecture. Lorsque le timer kernel se d\u00e9clenche (toutes les 10 millisecondes dans l&rsquo;exemple ci-dessous) il r\u00e9veille la t\u00e2che en attente en lui transmettant l&rsquo;heure courante mesur\u00e9e en nanoscondes. D\u00e8s que notre processus est r\u00e9veill\u00e9, il consulte \u00e0 son tour l&rsquo;heure et affiche la diff\u00e9rence sur sa sortie standard.<\/p>\n<p style=\"text-align: justify;\">Voici le module pour le noyau. Les fichiers sont tous regroup\u00e9s dans <a title=\"http:\/\/www.blaess.fr\/christophe\/files\/article-2012-03-12\/reveil-sur-timer.tar.bz2\" href=\"http:\/\/www.blaess.fr\/christophe\/files\/article-2012-03-12\/reveil-sur-timer.tar.bz2\">cette archive<\/a>.<\/p>\n<pre><strong>reveil-sur-timer.c<\/strong>:\n#include &lt;linux\/interrupt.h&gt;\n#include &lt;linux\/version.h&gt;\n#include &lt;linux\/device.h&gt;\n#include &lt;linux\/module.h&gt;\n#include &lt;linux\/sched.h&gt;\n#include &lt;linux\/cdev.h&gt;\n#include &lt;linux\/fs.h&gt;\n\n#include &lt;asm\/uaccess.h&gt;\n\nstatic dev_t          dev_reveil;\nstatic struct cdev    cdev_reveil;\nstatic struct class * class_reveil = NULL;\n\nstatic int read_reveil (struct file * filp, char * buffer,\n                          size_t length, loff_t * offset);\n\nstruct timer_list timer_reveil;\nstatic void timer_function(unsigned long);\n\nstatic struct file_operations fops_reveil = {\n    .owner   =  THIS_MODULE,\n    .read    =  read_reveil,\n};\n\nstruct timespec ts_reveil;\nstatic DECLARE_WAIT_QUEUE_HEAD(wq_reveil);\n\nstatic int __init <strong>init_reveil<\/strong>(void)\n{\n    int erreur;\n\n    erreur = alloc_chrdev_region(&amp; dev_reveil, 0, 1, THIS_MODULE-&gt;name);\n    if (erreur &lt; 0)\n        return erreur;\n    class_reveil = class_create(THIS_MODULE, \"classe_reveil\");\n    if (IS_ERR(class_reveil)) {\n        unregister_chrdev_region(dev_reveil, 1);\n        return -EINVAL;\n    }\n    device_create(class_reveil, NULL, dev_reveil,\n                   NULL, THIS_MODULE-&gt;name);\n\n    cdev_init(&amp; cdev_reveil, &amp; fops_reveil);\n\n    erreur = cdev_add(&amp; cdev_reveil, dev_reveil, 1);\n    if (erreur != 0) {\n        device_destroy(class_reveil, dev_reveil);\n        class_destroy(class_reveil);\n        unregister_chrdev_region(dev_reveil, 1);\n        return erreur;\n    }\n    init_timer(&amp; timer_reveil);\n    timer_reveil.function = timer_function;\n    timer_reveil.expires  = HZ;\n    add_timer(&amp; timer_reveil);\n    return 0;\n}\n\nstatic void __exit <strong>exit_reveil<\/strong> (void)\n{\n    del_timer(&amp; timer_reveil);\n    cdev_del(&amp; cdev_reveil);\n    device_destroy(class_reveil, dev_reveil);\n    class_destroy(class_reveil);\n    unregister_chrdev_region(dev_reveil, 1);\n}\n\nstatic int <strong>read_reveil<\/strong>(struct file * filp, char * buffer,\n                        size_t length, loff_t * offset)\n{\n    char chaine[80];\n\n    ts_reveil.tv_sec = 0;\n    if (wait_event_interruptible(wq_reveil, (ts_reveil.tv_sec != 0)) != 0)\n            return -ERESTARTSYS;\n    sprintf(chaine, \"%ld %ldn\", ts_reveil.tv_sec, ts_reveil.tv_nsec);\n    if(copy_to_user(buffer, chaine, strlen(chaine)+1) != 0)\n        return -EFAULT;\n    return strlen(chaine)+1;\n}\n\nstatic void <strong>timer_function<\/strong>(unsigned long unused)\n{\n    ktime_get_real_ts(&amp; ts_reveil);\n    wake_up_interruptible(&amp; wq_reveil);\n    mod_timer(&amp; timer_reveil, jiffies + HZ\/100);\n}\n\nmodule_init(init_reveil);\nmodule_exit(exit_reveil);\nMODULE_LICENSE(\"GPL\");<\/pre>\n<p style=\"text-align: justify;\">Et voici le petit programme qui attend le r\u00e9veil.<\/p>\n<pre><strong>lecture-reveil.c<\/strong>:\n#include &lt;fcntl.h&gt;\n#include &lt;stdio.h&gt;\n#include &lt;stdlib.h&gt;\n#include &lt;time.h&gt;\n#include &lt;unistd.h&gt;\n\n#define LG_BUFFER 64\n\nint <strong>main<\/strong>(int argc, char * argv[])\n{\n\tchar buffer[LG_BUFFER];\n\tint fd;\n\tstruct timespec ts;\n\tlong int sec, nsec;\n\tlong int duree;\n\n\tif ((argc != 2) || ((fd = open(argv[1], O_RDONLY)) &lt; 0)) {\n\t\tfprintf(stderr, \"usage: %s fichier-specialn\", argv[0]);\n\t\texit(EXIT_FAILURE);\n\t}\n\twhile (1) {\n\t\tif (read(fd, buffer, LG_BUFFER) LG_BUFFER) &lt;= 0)\n\t\t\tbreak;\n\t\tclock_gettime(CLOCK_REALTIME, &amp; ts);\n\t\tif (sscanf(buffer, \"%ld %ld\", &amp; sec, &amp; nsec) != 2)\n\t\t\tbreak;\n\t\tduree  = ts.tv_sec - sec;\n\t\tduree *= 1000000000;\n\t\tduree += ts.tv_nsec - nsec;\n\t\tfprintf(stdout, \"%ldn\", duree);\n\t}\n\treturn EXIT_FAILURE;\n}<\/pre>\n<h1>Essais<\/h1>\n<p style=\"text-align: justify;\">Avant de r\u00e9aliser l&rsquo;essai, il convient de s&rsquo;assurer que la fr\u00e9quence du processeur ne fluctue pas trop. Nous allons agir sur les quatre coeurs de cette machine.<\/p>\n<pre>[~]# <strong>cd \/sys\/devices\/system\/cpu\/<\/strong>\n[cpu]# <strong>ls<\/strong>\ncpu0  cpu1  cpu2  cpu3  cpufreq  cpuidle  kernel_max  offline  online  possible  present  probe  release  sched_mc_power_savings\n[cpu]# <strong>echo userspace &gt; cpu<span style=\"text-decoration: underline;\">0<\/span>\/cpufreq\/scaling_governor<\/strong>\n[cpu]# <strong>echo userspace &gt; cpu<span style=\"text-decoration: underline;\">1<\/span>\/cpufreq\/scaling_governor<\/strong>\n[cpu]# <strong>echo userspace &gt; cpu<span style=\"text-decoration: underline;\">2<\/span>\/cpufreq\/scaling_governor<\/strong>\n[cpu]# <strong>echo userspace &gt; cpu<span style=\"text-decoration: underline;\">3<\/span>\/cpufreq\/scaling_governor<\/strong>\n[cpu]# <strong>cat cpu<span style=\"text-decoration: underline;\">0<\/span>\/cpufreq\/scaling_max_freq &gt; cpu<span style=\"text-decoration: underline;\">0<\/span>\/cpufreq\/scaling_setspeed<\/strong>\n[cpu]# <strong>cat cpu<span style=\"text-decoration: underline;\">1<\/span>\/cpufreq\/scaling_max_freq &gt; cpu<span style=\"text-decoration: underline;\">1<\/span>\/cpufreq\/scaling_setspeed<\/strong>\n[cpu]# <strong>cat cpu<span style=\"text-decoration: underline;\">2<\/span>\/cpufreq\/scaling_max_freq &gt; cpu<span style=\"text-decoration: underline;\">2<\/span>\/cpufreq\/scaling_setspeed<\/strong>\n[cpu]# <strong>cat cpu<span style=\"text-decoration: underline;\">3<\/span>\/cpufreq\/scaling_max_freq &gt; cpu<span style=\"text-decoration: underline;\">3<\/span>\/cpufreq\/scaling_setspeed<\/strong>\n[cpu]# <strong>cd<\/strong>\n[~]#<\/pre>\n<p style=\"text-align: justify; padding-left: 60px;\">Il ne faut pas s&rsquo;\u00e9tonner si le volume sonore du ventilateur se met \u00e0 augmenter&#8230;<\/p>\n<p style=\"text-align: justify;\">Chargeons le module. Il faut savoir que la fonction d&rsquo;un timer est trait\u00e9e sur le m\u00eame CPU (processeur, coeur, hyperthread&#8230;) que celui o\u00f9 le timer a \u00e9t\u00e9 enregistr\u00e9. Nous allons donc ex\u00e9cuter la commande <code>insmod<\/code> en la fixant sur un CPU (le num\u00e9ro 0 ici).<\/p>\n<pre>[~]# <strong>taskset -c 0 insmod .\/reveil-sur-timer.ko<\/strong>\n[~]# <strong>ls -l \/dev\/revei*<\/strong>\ncrw------- 1 root root 250, 0 2012-03-13 13:10 \/dev\/reveil_sur_timer\n[~]#<\/pre>\n<p style=\"text-align: justify;\">\u00a0V\u00e9rifions rapidement si le driver nous fournit bien des donn\u00e9es:<\/p>\n<pre>[~]# <strong>hexdump \/dev\/reveil_sur_timer<\/strong>\n0000000 3331 3133 3436 3630 3236 3920 3630 3437\n0000010 3337 3637 000a 3331 3133 3436 3630 3236\n0000020 3920 3131 3435 3734 3339 000a 3331 3133\n0000030 3436 3630 3236 3920 3931 3435 3534 3835\n0000040 000a 3331 3133 3436 3630 3236 3920 3732\n0000050 3435 3939 3631 000a 3331 3133 3436 3630\n[...]\n    <strong>(<em>Contr\u00f4le-C<\/em>)<\/strong>\n[~]#<\/pre>\n<p style=\"text-align: justify;\">Notre programme de lecture va afficher les dur\u00e9es de r\u00e9veil en nanosecondes\u00a0:<\/p>\n<pre>[~]# <strong>.\/lecture-reveil \/dev\/reveil_sur_timer<\/strong>\n19944\n10090\n5688\n9380\n6508\n7255\n4665\n9447\n7417\n9605\n9275\n9737\n7672\n9259\n7414\n9654\n7549\n12199\n7372\n6391\n6997\n8966\n6086\n6707\n8324\n9624\n17831\n8958\n9887\n10383\n[...]\n    <strong>(<em>Contr\u00f4le-C<\/em>)<\/strong>\n[~]#<\/pre>\n<p>Lan\u00e7ons la t\u00e2che en temps-r\u00e9el (Fifo, priorit\u00e9 99) une premi\u00e8re fois sur le CPU 0 puis sur un autre c\u0153ur. Les r\u00e9sultats sont envoy\u00e9s dans un fichier pour \u00eatre analys\u00e9s par la suite.<\/p>\n<pre>[~]# <strong>taskset -c 0 chrt -f 99 .\/lecture-reveil \/dev\/reveil_sur_timer &gt; resultats-sur-cpu-0.txt<\/strong>\n <strong>(Apr\u00e8s dix minutes de fonctionnement : <em>Contr\u00f4le-C<\/em>)<\/strong>\n[~]# <strong>taskset -c 2 chrt -f 99 .\/lecture-reveil \/dev\/reveil_sur_timer &gt; resultats-sur-cpu-2.txt<\/strong>\n <strong>(Apr\u00e8s dix minutes de fonctionnement : <em>Contr\u00f4le-C<\/em>)<\/strong>\n[~]#<\/pre>\n<h1>R\u00e9sultats<\/h1>\n<p style=\"text-align: justify;\">L&rsquo;exp\u00e9rience a \u00e9t\u00e9 men\u00e9e ici sur un noyau \u00ab\u00a0<em>vanilla<\/em>\u00a0\u00bb sans extension temps-r\u00e9el (Linux-rt, Xenomai, etc.). La charge du syst\u00e8me est moyenne (\u00e9dition de fichiers, compilation de projets, serveur HTTP de test). Nous voyons une diff\u00e9rence assez sensible entre le temps de r\u00e9veil lorsque le timer et le processus s&rsquo;ex\u00e9cutent sur le m\u00eame CPU ou sur un autre c\u0153ur.<\/p>\n<p style=\"text-align: justify;\">Voici les mesures (dur\u00e9es en nanosecondes) lorsque les deux op\u00e9rations se font sur le m\u00eame CPU<\/p>\n<pre style=\"padding-left: 60px;\"> Nb mesures = 87296\n Minimum = 2982\n Maximum = 132053\n Moyenne = 8014\n Ecart-type = 2410<\/pre>\n<p style=\"text-align: justify;\">Nous voyons que la dur\u00e9e maximale est de 132 microsecondes, et la valeur moyenne de 8 microsecondes. Les r\u00e9sultats se r\u00e9partissent comme suit (notez que l&rsquo;axe des ordonn\u00e9es est logarithmique afin de mettre en relief les cas extr\u00eames).<\/p>\n<p style=\"text-align: justify;\"><a href=\"http:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2012\/03\/timer-task-same-cpu.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-thumbnail wp-image-1779\" title=\"a Linux timer wakes up a task on the same CPU\" src=\"http:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2012\/03\/timer-task-same-cpu-150x150.png\" alt=\"a Linux timer wakes up a task on the same CPU\" width=\"150\" height=\"150\" \/><\/a><\/p>\n<p style=\"text-align: justify;\">A pr\u00e9sent sur deux CPU diff\u00e9rents.<\/p>\n<pre style=\"padding-left: 60px;\"> Nb mesures = 131998\n Minimum = 5177\n Maximum = 461443\n Moyenne = 10920\n Ecart-type = 4470<\/pre>\n<p style=\"text-align: justify;\">Les dur\u00e9es sont plus longues, non seulement le maximum passe \u00e0 plus de 400 microsecondes, mais la moyenne augmente de deux microsecondes et m\u00eame la dur\u00e9e minimale est sensiblement plus longue. Sur la figure suivante, il a fallu \u00e9tendre l&rsquo;axe des abcisses beaucoup plus loin pour incorporer les cas extr\u00eames.<\/p>\n<p style=\"text-align: justify;\"><a href=\"http:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2012\/03\/timer-task-different-cpu.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-thumbnail wp-image-1780\" title=\"a Linux timer wakes up a task on another CPU\" src=\"http:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2012\/03\/timer-task-different-cpu-150x150.png\" alt=\"a Linux timer wakes up a task on another CPU\" width=\"150\" height=\"150\" \/><\/a><\/p>\n<p style=\"text-align: justify;\">Il serait int\u00e9ressant de r\u00e9it\u00e9rer cette exp\u00e9rience sur un syst\u00e8me <em>patch\u00e9<\/em> Linux-rt ou Xenomai. Ce sera pour un prochain article&#8230;<\/p>\n<p>&nbsp;<\/p>","protected":false},"excerpt":{"rendered":"<p>J&rsquo;ai &eacute;t&eacute; plusieurs fois confront&eacute; &agrave; la n&eacute;cessit&eacute; de d&eacute;terminer le temps de r&eacute;veil d&rsquo;une t&acirc;che utilisateur. La plupart du temps il s&rsquo;agit de borner le temps de r&eacute;action face &agrave; un &eacute;v&eacute;nement ext&eacute;rieur qui se traduit par une interruption (nous en verrons un exemple dans un prochain article). R&eacute;cemment toutefois, le probl&egrave;me qui se [&hellip;]<\/p>","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8,10,14],"tags":[],"class_list":["post-1772","post","type-post","status-publish","format-standard","hentry","category-linux-2","category-microprocesseur","category-temps-reel"],"_links":{"self":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/1772","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=1772"}],"version-history":[{"count":0,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/1772\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/media?parent=1772"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/categories?post=1772"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/tags?post=1772"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}