{"id":4979,"date":"2017-06-06T04:00:41","date_gmt":"2017-06-06T03:00:41","guid":{"rendered":"https:\/\/www.blaess.fr\/christophe\/?p=4979"},"modified":"2017-06-06T04:49:25","modified_gmt":"2017-06-06T03:49:25","slug":"kernel-interruptions-et-tasklets","status":"publish","type":"post","link":"https:\/\/www.blaess.fr\/christophe\/2017\/06\/06\/kernel-interruptions-et-tasklets\/","title":{"rendered":"[Kernel] Interruptions et tasklets"},"content":{"rendered":"<p><a href=\"https:\/\/www.blaess.fr\/christophe\/2017\/06\/06\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2017\/06\/fig-00-Article-2017-06-06.png\" alt=\"[Kernel Interruptions et tasklets\" width=\"250\" height=\"200\" class=\"alignright size-full wp-image-5050\" \/><\/a><\/p>\n<p style=\"text-align: justify;\">Il existe plusieurs m\u00e9canismes propos\u00e9s par le noyau Linux pour programmer un traitement \u00e0 r\u00e9aliser lorsqu&rsquo;un p\u00e9riph\u00e9rique externe d\u00e9clenche une interruption pour nous notifier de l&rsquo;occurrence d&rsquo;un \u00e9v\u00e9nement. Gestionnaire monolithique, <em>tasklet<\/em>, <em>workqueue<\/em>, <em>threaded interrupt<\/em>, chaque solution a des avantages et des inconv\u00e9nients, qu&rsquo;il est int\u00e9ressant de conna\u00eetre pour optimiser l&rsquo;efficacit\u00e9 de nos traitements.<\/p>\n<p>\n<!--more-->\n<\/p>\n<h1>Gestion des interruptions<\/h1>\n<p style=\"text-align: justify;\">R\u00e9capitulons rapidement le principe des interruptions sous Linux. Il convient tout d&rsquo;abord de pr\u00e9ciser que du fait de la portabilit\u00e9 du noyau sur un grand nombre d&rsquo;architectures, les m\u00e9canismes tr\u00e8s bas-niveau sont totalement abstraits pour ce qui concerne la plupart du code kernel classique (dans un <em>driver<\/em> par exemple).<\/p>\n<p style=\"text-align: justify;\">Lorsqu&rsquo;un p\u00e9riph\u00e9rique externe &#8211; disons un contr\u00f4leur d&rsquo;entr\u00e9es-sorties GPIO par exemple &#8211; d\u00e9sire notifier le processeur de l&rsquo;occurrence d&rsquo;une situation int\u00e9ressante (par exemple le changement d&rsquo;\u00e9tat d&rsquo;une broche d&rsquo;entr\u00e9e), il envoie un signal sur une entr\u00e9e du <strong>contr\u00f4leur d&rsquo;interruption<\/strong> <strong>APIC<\/strong> (<em>Advanced Programmable Interrupt Controler<\/em>). De nos jours celui-ci est int\u00e9gr\u00e9 directement dans le microprocesseur, mais dans les anciens PC par exemple, il existait sous forme de composant ind\u00e9pendant.<\/p>\n<p style=\"text-align: justify;\">Le contr\u00f4leur d&rsquo;interruption effectue une demande d&rsquo;interruption <strong>IRQ<\/strong> (<em>Interrupt Request<\/em>) aupr\u00e8s du processeur principal. Ce dernier arr\u00eate le traitement en cours, sauvegarde son \u00e9tat (ses registres) dans la pile et interroge le contr\u00f4leur d&rsquo;interruption pour conna\u00eetre l&rsquo;\u00e9v\u00e9nement survenu. L&rsquo;APIC lui indique la source du signal initial sous forme d&rsquo;un num\u00e9ro. Le processeur d\u00e9route alors son ex\u00e9cution sur une <strong>routine de traitement<\/strong> bas-niveau charg\u00e9e de prendre en compte l&rsquo;interruption et de r\u00e9agir en cons\u00e9quence.<\/p>\n<p style=\"text-align: justify;\">Une fois cette routine de traitement termin\u00e9e le processeur reprend le cours de ses op\u00e9rations pr\u00e9c\u00e9dentes comme si de rien n&rsquo;\u00e9tait.<\/p>\n<div id=\"attachment_4986\" style=\"width: 314px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-4986\" class=\"size-full wp-image-4986\" src=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2017\/06\/fig-01-Declenchement-d-une-interruption.png\" alt=\"fig-01 - D\u00e9clenchement d'une interruption\" width=\"304\" height=\"152\" srcset=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2017\/06\/fig-01-Declenchement-d-une-interruption.png 304w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2017\/06\/fig-01-Declenchement-d-une-interruption-300x150.png 300w\" sizes=\"auto, (max-width: 304px) 100vw, 304px\" \/><p id=\"caption-attachment-4986\" class=\"wp-caption-text\">Fig-01 &#8211; D\u00e9clenchement d&rsquo;une interruption<\/p><\/div>\n<p style=\"text-align: justify;\">On peut voir les diff\u00e9rentes interruptions g\u00e9r\u00e9es par le noyau Linux dans le pseudo-fichier <strong><code>\/proc\/interrupts<\/code><\/strong>. Par exemple sur un Raspberry Pi 3, on observe les donn\u00e9es suivantes.<\/p>\n<pre># <strong>cat \/proc\/interrupts<\/strong> \n           CPU0       CPU1       CPU2       CPU3       \n 16:          0          0          0          0  bcm2836-timer   0 Edge      arch_timer\n 17:        974        466        348       1505  bcm2836-timer   1 Edge      arch_timer\n 23:         18          0          0          0  ARMCTRL-level   1 Edge      3f00b880.mailbox\n 24:          2          0          0          0  ARMCTRL-level   2 Edge      VCHIQ doorbell\n 39:          1          0          0          0  ARMCTRL-level  41 Edge    \n 46:          0          0          0          0  ARMCTRL-level  48 Edge      bcm2708_fb dma\n 48:        267          0          0          0  ARMCTRL-level  50 Edge      DMA IRQ\n 50:          0          0          0          0  ARMCTRL-level  52 Edge      DMA IRQ\n 62:       8865          0          0          0  ARMCTRL-level  64 Edge      dwc_otg, dwc_otg_pcd, dwc_otg_hcd:usb1\n 79:          0          0          0          0  ARMCTRL-level  81 Edge      3f200000.gpio:bank0\n 80:          0          0          0          0  ARMCTRL-level  82 Edge      3f200000.gpio:bank1\n 86:          4          0          0          0  ARMCTRL-level  88 Edge      mmc0\n 87:        116          0          0          0  ARMCTRL-level  89 Edge      uart-pl011\n 92:        456          0          0          0  ARMCTRL-level  94 Edge      mmc1\nFIQ:              usb_fiq\nIPI0:          0          0          0          0  CPU wakeup interrupts\nIPI1:          0          0          0          0  Timer broadcast interrupts\nIPI2:        323        610        369        468  Rescheduling interrupts\nIPI3:          2          5          4          4  Function call interrupts\nIPI4:          2          2          0          0  Single function call interrupts\nIPI5:          0          0          0          0  CPU stop interrupts\nIPI6:          1          0          0          0  IRQ work interrupts\nIPI7:          0          0          0          0  completion interrupts\nErr:          0\n#<\/pre>\n<p style=\"text-align: justify;\">La premi\u00e8re colonne repr\u00e9sente le num\u00e9ro de l&rsquo;interruption (sauf pour celles du bas de la liste, FIQ et IPIx qui repr\u00e9sentent des interruptions internes au processeur). Les quatre colonnes suivantes indiquent le nombre d&rsquo;occurrence de chaque interruption depuis le <em>boot<\/em> du syst\u00e8me sur chacun des quatre c\u0153urs de processeur. Les valeurs sont faibles ici, le Raspberry Pi 3 vient de d\u00e9marrer. Les colonnes suivantes affichent le type de contr\u00f4leur, un num\u00e9ro interne \u00e0 celui-ci, et le driver concern\u00e9.<\/p>\n<p style=\"text-align: justify;\">Les fonctions de traitement bas-niveau sont d\u00e9j\u00e0 \u00e9crites dans le noyau Linux et nous n&rsquo;avons pas \u00e0 y toucher. Elles ont pour r\u00f4le d&rsquo;appeler des fonctions de plus haut-niveau que l&rsquo;on nomme <strong>routines de service<\/strong> (<em>Interrupt Service Routine<\/em> ou <strong>ISR<\/strong>), et ce sont ces routines de service que nous pouvons \u00e9crire dans nos drivers. Une fonction bas-niveau a \u00e9galement pour r\u00f4le de d\u00e9sactiver dans l&rsquo;APIC l&rsquo;interruption qui l&rsquo;a d\u00e9clench\u00e9e avant d&rsquo;appeler la routine de service et de r\u00e9activer l&rsquo;interruption ensuite. Imaginons le d\u00e9clenchement d&rsquo;une hypoth\u00e9tique interruption 100\u00a0:<\/p>\n<div id=\"attachment_4990\" style=\"width: 540px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-4990\" class=\"size-full wp-image-4990\" src=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2017\/06\/fig-02-Handlers-bas-niveau-et-ISR.png\" alt=\"Fig-02 - Handlers bas-niveau et ISR\" width=\"530\" height=\"228\" srcset=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2017\/06\/fig-02-Handlers-bas-niveau-et-ISR.png 530w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2017\/06\/fig-02-Handlers-bas-niveau-et-ISR-300x129.png 300w\" sizes=\"auto, (max-width: 530px) 100vw, 530px\" \/><p id=\"caption-attachment-4990\" class=\"wp-caption-text\">Fig-02 &#8211; Handlers bas-niveau et ISR<\/p><\/div>\n<p style=\"text-align: justify;\">Lorsque, dans le reste de cet article je parlerai &#8211; avec un l\u00e9ger abus de langage &#8211; de gestionnaire (ou de <em>handler<\/em>) d&rsquo;interruption, il s&rsquo;agira toujours d&rsquo;une routine de service invoqu\u00e9e par une fonction de plus bas-niveau. Autrement dit, les sch\u00e9mas \u00e0 venir ne repr\u00e9senteront plus les <em>handlers<\/em> bas-niveau, mais uniquement les routines ISR.<\/p>\n<div id=\"attachment_4994\" style=\"width: 540px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-4994\" class=\"size-full wp-image-4994\" src=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2017\/06\/fig-03-Handler-d-interruption.png\" alt=\"Fig-03 - Handler d'interruption\" width=\"530\" height=\"228\" srcset=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2017\/06\/fig-03-Handler-d-interruption.png 530w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2017\/06\/fig-03-Handler-d-interruption-300x129.png 300w\" sizes=\"auto, (max-width: 530px) 100vw, 530px\" \/><p id=\"caption-attachment-4994\" class=\"wp-caption-text\">Fig-03 &#8211; Handler d&rsquo;interruption<\/p><\/div>\n<p style=\"text-align: justify;\">Pr\u00e9cisons d\u00e8s \u00e0 pr\u00e9sent qu&rsquo;une fonction bas-niveau peut appeler successivement plusieurs routines de service en r\u00e9ponse \u00e0 la m\u00eame interruption. Par exemple dans le fichier <code>\/proc\/interrupts<\/code> ci-dessus, on voit que l&rsquo;interruption 62 est trait\u00e9e conjointement par trois routines de service appartenant aux drivers <code>dwc_otg<\/code>, <code>dwc_otg_pcd<\/code>, et <code>dwc_otg_hcd<\/code> appel\u00e9es successivement. C&rsquo;est ce qu&rsquo;on nomme une <strong>interruption partag\u00e9e<\/strong>.<\/p>\n<div id=\"attachment_4996\" style=\"width: 540px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-4996\" class=\"size-full wp-image-4996\" src=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2017\/06\/fig-04-Interruption-partagee.png\" alt=\"Fig-04 - Interruption partag\u00e9e\" width=\"530\" height=\"130\" srcset=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2017\/06\/fig-04-Interruption-partagee.png 530w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2017\/06\/fig-04-Interruption-partagee-300x74.png 300w\" sizes=\"auto, (max-width: 530px) 100vw, 530px\" \/><p id=\"caption-attachment-4996\" class=\"wp-caption-text\">Fig-04 &#8211; Interruption partag\u00e9e<\/p><\/div>\n<p style=\"text-align: justify;\">Il est possible, lors de l&rsquo;\u00e9criture d&rsquo;un driver d&rsquo;agir de diff\u00e9rentes fa\u00e7ons sur le traitement des interruptions.<br \/>\nOn peut d\u00e9sactiver (on dit g\u00e9n\u00e9ralement \u00ab\u00a0<strong>masquer<\/strong>\u00ab\u00a0) ou activer (\u00ab\u00a0<strong>d\u00e9masquer<\/strong>\u00ab\u00a0) le traitement d&rsquo;une interruption. Si on masque une interruption, et qu&rsquo;elle se produit effectivement, l&rsquo;IRQ restera en attente au niveau de l&rsquo;APIC, jusqu&rsquo;\u00e0 ce que le processeur d\u00e9masque l&rsquo;interruption. C&rsquo;est \u00e0 ce moment seulement qu&rsquo;il recevra la demande en attente.<\/p>\n<div id=\"attachment_5005\" style=\"width: 540px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-5005\" class=\"size-full wp-image-5005\" src=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2017\/06\/fig-05-Masquage-d-interruption.png\" alt=\"Fig-05 - Masquage d'interruption\" width=\"530\" height=\"228\" srcset=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2017\/06\/fig-05-Masquage-d-interruption.png 530w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2017\/06\/fig-05-Masquage-d-interruption-300x129.png 300w\" sizes=\"auto, (max-width: 530px) 100vw, 530px\" \/><p id=\"caption-attachment-5005\" class=\"wp-caption-text\">Fig-05 &#8211; Masquage d&rsquo;interruption<\/p><\/div>\n<p style=\"text-align: justify;\">Il faut bien comprendre qu&rsquo;<strong>une seule instance<\/strong> de l&rsquo;interruption sera d\u00e9livr\u00e9e au moment du d\u00e9blocage, m\u00eame si elle est survenue \u00e0 plusieurs reprises pendant la p\u00e9riode de masquage.<\/p>\n<div id=\"attachment_5001\" style=\"width: 540px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-5001\" class=\"size-full wp-image-5001\" src=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2017\/06\/fig-06-Occurences-d-interruption-masquee.png\" alt=\"Fig-06 - Occurrences d'interruption masqu\u00e9e\" width=\"530\" height=\"228\" srcset=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2017\/06\/fig-06-Occurences-d-interruption-masquee.png 530w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2017\/06\/fig-06-Occurences-d-interruption-masquee-300x129.png 300w\" sizes=\"auto, (max-width: 530px) 100vw, 530px\" \/><p id=\"caption-attachment-5001\" class=\"wp-caption-text\">Fig-06 &#8211; Occurrences d&rsquo;interruption masqu\u00e9e<\/p><\/div>\n<p style=\"text-align: justify;\">Sur certains processeurs, il existe des interruptions <strong>non-masquables<\/strong> servant \u00e0 la notification d&rsquo;\u00e9v\u00e9nements tr\u00e8s urgents.<br \/>\nEn outre certaines interruptions peuvent \u00eatre plus prioritaires que d&rsquo;autres. Du fait de la portabilit\u00e9 de Linux sur de nombreuses architectures, cette notion n&rsquo;est pas prise en compte au niveau de l&rsquo;API propos\u00e9e aux drivers.<\/p>\n<h1>Un peu de pratique<\/h1>\n<p style=\"text-align: justify;\">Nous allons commencer quelques exp\u00e9riences avec les interruptions. J&rsquo;ai choisi comme plate-forme d&rsquo;illustration pour cet article le <strong>Raspberry Pi 3<\/strong> en raison de sa grande disponibilit\u00e9. Toutefois les exemples sont adaptables sur d&rsquo;autres cartes. Durant des sessions de formation je les ai test\u00e9s sur de nombreux autres syst\u00e8mes (BeagleBoneBlack, Pandaboard, IGEPv2, i.MX6 Sabrelite, toute la gamme des Raspberry Pi, etc.) en adaptant les num\u00e9ros de GPIO.<\/p>\n<p style=\"text-align: justify;\">Dans notre premier exemple nous allons installer un petit gestionnaire d&rsquo;interruption tr\u00e8s simple qui inscrira lors de son d\u00e9clenchement un message dans les traces du noyau. L&rsquo;int\u00e9r\u00eat de travailler avec un Raspberry Pi est de pouvoir facilement g\u00e9rer communications avec l&rsquo;ext\u00e9rieur gr\u00e2ce aux <strong>GPIO<\/strong>, et de d\u00e9clencher facilement des interruptions avec un simple morceau de fil \u00e9lectrique.<\/p>\n<p style=\"text-align: justify;\">J&rsquo;ai fait le choix, pour \u00e9viter de mettre en \u0153uvre un environnement de cross-compilation qui compliquerait le propos de cet article, de faire toutes les compilations de modules du kernel directement sur le Raspberry Pi. La compilation d&rsquo;un module prend deux \u00e0 trois secondes sur un Raspberry Pi 3, ce qui est tr\u00e8s raisonnable, m\u00eame lorsque le nombre d&rsquo;exemples est assez \u00e9lev\u00e9. L&rsquo;inconv\u00e9nient, est que la compilation d&rsquo;un module n\u00e9cessite la pr\u00e9sence des fichiers d&rsquo;en-t\u00eate, du fichier de configuration et des Makefile du noyau cible. Or, les distributions Raspbian ne fournissent pas cet ensemble. Il existe bien un package <em>linux-headers<\/em> dans cette distribution mais il ne correspond pas du tout au noyau fourni.<\/p>\n<p style=\"text-align: justify;\">Nous devons donc commencer par <strong>recompiler un noyau<\/strong>, op\u00e9ration tr\u00e8s simple mais qui prend environ deux heures de compilation sur le Raspberry Pi 3. En partant d&rsquo;une distribution Raspbian fra\u00eechement install\u00e9e, voici la suite de commandes \u00e0 ex\u00e9cuter.<\/p>\n<pre>$ <strong>sudo apt update<\/strong>\n$ <strong>sudo apt install -y ncurses-dev  bc<\/strong>\n$ <strong>git clone https:\/\/github.com\/raspberrypi\/linux --depth 1<\/strong>\n$ <strong>cd linux\/<\/strong>\n$ <strong>make bcm2709_defconfig<\/strong>\n$ <strong>make -j 8<\/strong>     <em># C'est cette \u00e9tape qui dure environ deux heures...<\/em>\n$ <strong>sudo make scripts<\/strong>\n$ <strong>sudo make modules_install<\/strong>\n$ <strong>sudo mkdir \/boot\/old-dtbs<\/strong>\n$ <strong>sudo mv \/boot\/*dtb \/boot\/old-dtbs\/<\/strong>\n$ <strong>sudo make INSTALL_DTBS_PATH=\/boot dtbs_install<\/strong>\n$ <strong>sudo cp \/boot\/kernel7.img \/boot\/old-kernel7.img<\/strong>\n$ <strong>sudo cp arch\/arm\/boot\/zImage \/boot\/kernel7.img<\/strong> \n$ <strong>sudo reboot<\/strong><\/pre>\n<p style=\"text-align: justify;\">Apr\u00e8s red\u00e9marrage, on v\u00e9rifie avec\u00a0:<\/p>\n<pre>$ <strong>uname -a<\/strong><\/pre>\n<p style=\"text-align: justify;\">que l&rsquo;on se trouve bien sur notre noyau tout neuf.<\/p>\n<p style=\"text-align: justify;\">Attention, le r\u00e9pertoire qui a servi pour la compilation sera r\u00e9f\u00e9renc\u00e9 pendant la compilation des modules ult\u00e9rieurs, et ne doit donc pas \u00eatre d\u00e9plac\u00e9, ni effac\u00e9. \u00c0 la rigueur, on peut y faire un peu de m\u00e9nage pour gagner de la place\u00a0:<\/p>\n<pre>$ <strong>cd \/lib\/modules\/$(uname -r)\/build<\/strong>                                                                                  \n$ <strong>rm -rf Documentation<\/strong>\n$ <strong>find . -name '*.[coS]' | xargs rm -f<\/strong><\/pre>\n<p style=\"text-align: justify;\">Ceci efface les fichiers sources C et assembleur ainsi que les fichiers objets. Il est important de conserver les fichiers d&rsquo;en-t\u00eate <code>.h<\/code> et les fichiers <code>Makefile<\/code>. On gagne ainsi environ 700\u00a0Mo.<\/p>\n<h2>Handler d&rsquo;interruption monolithique<\/h2>\n<p style=\"text-align: justify;\">Le cas le plus simple &#8211; et le plus courant &#8211; est celui du <strong>handler monolithique<\/strong>. Lorsque l&rsquo;interruption se produit, le handler invoqu\u00e9 fait tout le travail attendu puis se termine et le processeur reprend son activit\u00e9 initiale.<\/p>\n<p style=\"text-align: justify;\">Un handler d&rsquo;interruption s&rsquo;\u00e9crit en respectant un prototype bien d\u00e9fini (il existe d&rsquo;ailleurs un <code>typedef irq_handler_t<\/code> pour repr\u00e9senter ce prototype)\u00a0:<\/p>\n<pre>irqreturn_t <em>irq_handler<\/em>(int <em>irq_num<\/em>, void *<em>irq_id<\/em>);<\/pre>\n<p style=\"text-align: justify;\">Lorsque le handler est appel\u00e9, il recevra automatiquement deux arguments\u00a0: le num\u00e9ro de l&rsquo;interruption qui l&rsquo;a d\u00e9clench\u00e9 (utile lorsque le m\u00eame handler g\u00e8re plusieurs interruptions diff\u00e9rentes), et un identifiant repr\u00e9sent\u00e9 par un pointeur g\u00e9n\u00e9rique. Nous fournirons cet identifiant lors de l&rsquo;installation du handler. En g\u00e9n\u00e9ral il s&rsquo;agit d&rsquo;un pointeur sur une structure de donn\u00e9es personnalis\u00e9es, contenant des informations propres \u00e0 notre instance de driver.<\/p>\n<p style=\"text-align: justify;\">Ce pointeur joue \u00e9galement un second r\u00f4le, notamment dans le cas d&rsquo;une interruption partag\u00e9e entre plusieurs p\u00e9riph\u00e9riques\u00a0: on fournit le m\u00eame pointeur lors du retrait du driver afin d&rsquo;indiquer quel handler doit \u00eatre d\u00e9sinstall\u00e9.<\/p>\n<p style=\"text-align: justify;\">Le handler doit \u00e9galement renvoyer une valeur de type <code>irqreturn_t<\/code>. Il s&rsquo;agit d&rsquo;un type \u00e9num\u00e9r\u00e9 pouvant prendre les valeurs <code>IRQ_NONE<\/code> ou <code>IRQ_HANDLED<\/code> (ainsi que la valeur <code>IRQ_WAKE_THREAD<\/code> que nous verrons dans le prochain article). En principe le handler doit interroger le mat\u00e9riel qu&rsquo;il g\u00e8re afin de savoir si l&rsquo;interruption lui \u00e9tait bien destin\u00e9e. Dans l&rsquo;affirmative il renverra <code>IRQ_HANDLED<\/code>. Sinon, (il est probable que l&rsquo;interruption soit partag\u00e9e entre plusieurs drivers) il renverra <code>IRQ_NONE<\/code>.<\/p>\n<p style=\"text-align: justify;\">Pour installer et d\u00e9sinstaller le handler, on utilise les routines suivantes\u00a0:<\/p>\n<pre>int  <strong>request_irq<\/strong>(unsigned int <em>irq_num<\/em>, irq_handler_t <em>irq_handler<\/em>, unsigned long <em>flags<\/em>, const char *<em>name<\/em>, void *<em>irq_dev<\/em>);\nvoid <strong>free_irq<\/strong>(unsigned int <em>irq_num<\/em>, void *<em>irq_dev<\/em>);<\/pre>\n<p style=\"text-align: justify;\">Il existe divers <em>flags<\/em> pour l&rsquo;installation d&rsquo;un handler, on les trouve dans le fichier <code>&lt;linux\/interrupt.h&gt;<\/code>. En voici quelques-uns utilis\u00e9s r\u00e9guli\u00e8rement\u00a0:<\/p>\n<ul>\n<li style=\"text-align: justify;\"><code>IRQF_TRIGGER_RISING<\/code>\u00a0: d\u00e9clenchement sur le front montant d&rsquo;un signal logique.<\/li>\n<li style=\"text-align: justify;\"><code>IRQF_TRIGGER_FALLING<\/code>\u00a0: d\u00e9clenchement sur le front descendant d&rsquo;un signal logique.<\/li>\n<li style=\"text-align: justify;\"><code>IRQF_TRIGGER_HIGH<\/code>\u00a0: d\u00e9clenchement tant qu&rsquo;un signal logique est au niveau haut.<\/li>\n<li style=\"text-align: justify;\"><code>IRQF_TRIGGER_LOW<\/code>\u00a0: d\u00e9clenchement tant qu&rsquo;un signal logique est au niveau bas.<\/li>\n<li style=\"text-align: justify;\"><code>IRQF_SHARED<\/code>\u00a0: le handler accepte le partage de l&rsquo;interruption avec d&rsquo;autres handlers.<\/li>\n<li style=\"text-align: justify;\"><code>IRQF_NO_THREAD<\/code>\u00a0: l&rsquo;interruption ne peut pas \u00eatre <em>thread\u00e9e<\/em> (nous en reparlerons ult\u00e9rieurement) m\u00eame avec le patch PREEMPT_RT.<\/li>\n<\/ul>\n<h2>Impl\u00e9mentation<\/h2>\n<p style=\"text-align: justify;\">Nous allons programmer un premier module simple, qui installera un handler pour une entr\u00e9e GPIO. Lorsqu&rsquo;un front montant se pr\u00e9sentera sur cette broche, le handler enverra un message dans les traces du noyau. Le num\u00e9ro de l&rsquo;interruption associ\u00e9 \u00e0 une borne GPIO donn\u00e9e est obtenu avec la fonction <code>gpio_to_irq()<\/code>.<\/p>\n<p style=\"text-align: justify;\">J&rsquo;ai choisi arbitrairement la broche 16 (GPIO 23, comme on peut le voir sur ce sch\u00e9ma du <a href=\"https:\/\/www.blaess.fr\/christophe\/files\/article-2014-08-07\/Connecteur_P1.pdf\">connecteur P1 du Raspberry Pi<\/a>).<\/p>\n<p style=\"text-align: justify;\">T\u00e9l\u00e9chargeons et compilons les exemples de cet article\u00a0:<\/p>\n<pre>$ <strong>git clone https:\/\/github.com\/cpb-\/Article-2017-06-06<\/strong>\n$ <strong>cd Article-2017-06-06<\/strong>\n$ <strong>make<\/strong><\/pre>\n<p style=\"text-align: justify;\">Voici le listing du premier exemple\u00a0:<\/p>\n<pre>\/\/\/ \\file <strong>test-irq-01.c<\/strong>\n\/\/\/\n\/\/\/ \\brief Exemples de l'article \"[KERNEL] Interruptions et tasklets\" (https:\/\/www.blaess.fr\/christophe\/2017\/06\/05)\n\/\/\/\n\/\/\/ \\author Christophe Blaess 2017 (https:\/\/www.blaess.fr\/christophe)\n\/\/\/\n\/\/\/ \\license GPL.\n\n        #include &lt;linux\/gpio.h&gt;\n        #include &lt;linux\/interrupt.h&gt;\n        #include &lt;linux\/module.h&gt;\n\n\n        #define IRQ_TEST_GPIO_IN  23\n\n\nstatic <strong>irqreturn_t irq_test_handler(int irq, void * ident)<\/strong>\n{\n        printk(KERN_INFO \"%s: %s()\\n\", THIS_MODULE-&gt;name, __FUNCTION__);\n        <strong>return IRQ_HANDLED<\/strong>;\n}\n\n\nstatic int __init irq_test_init (void)\n{\n        int err;\n\n        if ((err = gpio_request(IRQ_TEST_GPIO_IN,THIS_MODULE-&gt;name)) != 0)\n                return err;\n\n        if ((err = gpio_direction_input(IRQ_TEST_GPIO_IN)) != 0) {\n                gpio_free(IRQ_TEST_GPIO_IN);\n                return err;\n        }\n\n        if ((err = <strong>request_irq<\/strong>(gpio_to_irq(IRQ_TEST_GPIO_IN), irq_test_handler,\n                               IRQF_SHARED | IRQF_TRIGGER_RISING,\n                               THIS_MODULE-&gt;name, THIS_MODULE-&gt;name)) != 0) {\n                gpio_free(IRQ_TEST_GPIO_IN);\n                return err;\n        }\n\n        return 0;\n}\n\n\nstatic void __exit irq_test_exit (void)\n{\n        <strong>free_irq(<\/strong>gpio_to_irq(IRQ_TEST_GPIO_IN), THIS_MODULE-&gt;name);\n        gpio_free(IRQ_TEST_GPIO_IN);\n}\n\n\nmodule_init(irq_test_init);\nmodule_exit(irq_test_exit);\n\n\nMODULE_DESCRIPTION(\"Simple monolithic interrupt handler\");\nMODULE_AUTHOR(\"Christophe Blaess &lt;Christophe.Blaess@Logilin.fr&gt;\");\nMODULE_LICENSE(\"GPL\");<\/pre>\n<p style=\"text-align: justify;\">Chargeons le module, et v\u00e9rifions que le handler est bien install\u00e9\u00a0:<\/p>\n<pre>$ <strong>sudo insmod test-irq-01.ko<\/strong>\n$ <strong>cat \/proc\/interrupts<\/strong> \n           CPU0       CPU1       CPU2       CPU3       \n   [...]\n189:          0          0          0          0  pinctrl-bcm2835  23 Edge      test_irq_01\n   [...]<\/pre>\n<p style=\"text-align: justify;\">On peut alors faire un contact entre la broche 16 (notre GPIO) et la broche 1 (le +3.3V) avec un petit fil par exemple\u00a0:<\/p>\n<pre>$ <strong>cat \/proc\/interrupts<\/strong>\n           CPU0       CPU1       CPU2       CPU3       \n   [...]\n189:         54          0          0          0  pinctrl-bcm2835  23 Edge      test_irq_01\n   [...]<\/pre>\n<p style=\"text-align: justify;\">Cinquante quatre interruptions&nbsp;! Et oui, ce n&rsquo;est pas surprenant, un petit contact sec entre deux fils produit de nombreux rebonds tr\u00e8s brefs. V\u00e9rifions les traces de notre handler\u00a0:<\/p>\n<pre>$ <strong>dmesg<\/strong>\n   [...]\n[  177.947441] test_irq_01: irq_test_handler()\n[  177.947541] test_irq_01: irq_test_handler()\n[  177.947615] test_irq_01: irq_test_handler()\n[  177.947675] test_irq_01: irq_test_handler()\n[  177.947686] test_irq_01: irq_test_handler()\n[  177.947696] test_irq_01: irq_test_handler()\n[  177.947706] test_irq_01: irq_test_handler()\n[  177.947728] test_irq_01: irq_test_handler()\n[  177.947738] test_irq_01: irq_test_handler()\n[  177.947747] test_irq_01: irq_test_handler()\n   [...]\n[  178.107233] test_irq_01: irq_test_handler()\n[  178.107290] test_irq_01: irq_test_handler()\n[  178.107322] test_irq_01: irq_test_handler()\n[  178.107357] test_irq_01: irq_test_handler()\n[  178.107376] test_irq_01: irq_test_handler()\n$ <strong>sudo rmmod test_irq_01<\/strong><\/pre>\n<p style=\"text-align: justify;\">Gr\u00e2ce \u00e0 l&rsquo;horodatage du <code>printk()<\/code>, nous voyons que les petits rebonds sont s\u00e9par\u00e9s par quelques dizaines de microsecondes seulement.<\/p>\n<h2>Diff\u00e9rer un traitement<\/h2>\n<p style=\"text-align: justify;\">Nous avons parfaitement r\u00e9ussi \u00e0 installer un handler simple qui se d\u00e9clenche \u00e0 chaque occurrence de l&rsquo;interruption et s&rsquo;ex\u00e9cute imm\u00e9diatement et enti\u00e8rement. Il est important de se souvenir que pendant toute l&rsquo;ex\u00e9cution d&rsquo;un handler, l&rsquo;interruption qui l&rsquo;a d\u00e9clench\u00e9 est masqu\u00e9e (sur tous les c\u0153urs dans le cas d&rsquo;un processeur multic\u0153ur).<\/p>\n<p style=\"text-align: justify;\">Supposons \u00e0 pr\u00e9sent, que nous ayons un travail un peu plus cons\u00e9quent \u00e0 r\u00e9aliser dans le handler, et que nous souhaitions par ailleurs horodater pr\u00e9cis\u00e9ment l&rsquo;occurrence de l&rsquo;interruption. On pourrait tr\u00e8s bien imaginer un transfert de donn\u00e9es \u00e0 effectuer, une v\u00e9rification de checksum, voire un d\u00e9chiffrement d&rsquo;information encod\u00e9es.<\/p>\n<p style=\"text-align: justify;\">Pour simuler ceci, j&rsquo;ai simplement ajout\u00e9 dans le handler une attente active d&rsquo;une milliseconde avec <code>udelay()<\/code>, apr\u00e8s le <code>printk()<\/code> qui nous sert d&rsquo;horodatage\u00a0:<\/p>\n<pre>\/\/\/ \\file <strong>test-irq-02.c<\/strong>\n\n   [...]\n\n        #include &lt;linux\/delay.h&gt;\n\n\n        #define IRQ_TEST_GPIO_IN  23\n\n\nstatic irqreturn_t irq_test_handler(int irq, void * ident)\n{\n        printk(KERN_INFO \"%s: %s()\\n\", THIS_MODULE-&gt;name, __FUNCTION__);\n        <strong>udelay(1000);<\/strong>\n        return IRQ_HANDLED;\n}\n\n   [...]<\/pre>\n<p style=\"text-align: justify;\">Apr\u00e8s chargement du module je r\u00e9it\u00e8re la m\u00eame exp\u00e9rience en faisant un bref contact entre l&rsquo;entr\u00e9e GPIO et le +3.3V. Voici les traces des messages du kernel\u00a0:<\/p>\n<pre>$ <strong>cat \/proc\/interrupts<\/strong>\n           CPU0       CPU1       CPU2       CPU3       \n   [...]\n189:         57          0          0          0  pinctrl-bcm2835  23 Edge      test_irq_01\n   [...]\n$ <strong>dmesg<\/strong>\n   [...]\n[ 1297.485853] test_irq_02: irq_test_handler()\n[ 1297.486866] test_irq_02: irq_test_handler()\n[ 1297.487876] test_irq_02: irq_test_handler()<\/pre>\n<p style=\"text-align: justify;\">Cette fois nous n&rsquo;avons que trois interruptions prises en compte, toutes s\u00e9par\u00e9es d&rsquo;une milliseconde. Pour simplifier notre propos, je n&rsquo;en ai repr\u00e9sent\u00e9es que deux sur le sch\u00e9ma suivant, mais le raisonnement est tout aussi valable. Nous savons que des rebonds se produisent et qu&rsquo;une cinquantaine d&rsquo;IRQ seront envoy\u00e9es par l&rsquo;APIC, toutes les vingt microsecondes environ. Apr\u00e8s avoir horodat\u00e9 son d\u00e9clenchement, par un <code>printk()<\/code>, le handler monolithique effectue un travail consommant du temps CPU, une boucle active dans <code>udelay()<\/code>, pendant une milliseconde.<\/p>\n<div id=\"attachment_5026\" style=\"width: 540px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-5026\" class=\"size-full wp-image-5026\" src=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2017\/06\/fig-07-Rafales-d-interruptions-monolithiques.png\" alt=\"Fig-07 - Rafales d'interruptions monolithiques\" width=\"530\" height=\"230\" srcset=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2017\/06\/fig-07-Rafales-d-interruptions-monolithiques.png 530w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2017\/06\/fig-07-Rafales-d-interruptions-monolithiques-300x130.png 300w\" sizes=\"auto, (max-width: 530px) 100vw, 530px\" \/><p id=\"caption-attachment-5026\" class=\"wp-caption-text\">Fig-07 &#8211; Rafales d&rsquo;interruptions monolithiques<\/p><\/div>\n<p style=\"text-align: justify;\">Sur le sch\u00e9ma ci-dessus, j&rsquo;ai num\u00e9rot\u00e9 entre parenth\u00e8ses les occurrences des interruptions. Nous voyons que la premi\u00e8re est correctement trait\u00e9e, avec un retard (on parle g\u00e9n\u00e9ralement de <strong>latence<\/strong>) de quelques microsecondes parfaitement justifi\u00e9. Lorsque la deuxi\u00e8me arrive, l&rsquo;interruption 189 \u00e9tant masqu\u00e9e dans l&rsquo;APIC, elle reste en attente et sera d\u00e9livr\u00e9e plusieurs centaines de microsecondes plus tard, une fois que le premier handler sera termin\u00e9. Nous avons donc un probl\u00e8me d&rsquo;horodatage pour cette deuxi\u00e8me occurrence. La situation est pire pour la troisi\u00e8me interruption et les suivantes, puisque le masquage ne conservant qu&rsquo;une seule occurrence d&rsquo;IRQ, elles seront tout simplement perdues.<\/p>\n<p style=\"text-align: justify;\">Il est clair que si une interruption p\u00e9riodique se produit toutes les vingt microsecondes dont le handler dure une milliseconde, le syst\u00e8me n&rsquo;est pas viable. Ce qui m&rsquo;int\u00e9resse, c&rsquo;est le cas o\u00f9 nous avons quelques d\u00e9clenchements occasionnellement tr\u00e8s rapproch\u00e9s, mais que cela se produit suffisamment rarement pour permettre au syst\u00e8me de fonctionner normalement le reste du temps. Et avec un handler monolithique, <strong>le r\u00e9sultat n&rsquo;est pas satisfaisant<\/strong> : seule la premi\u00e8re occurrence est correctement trait\u00e9e, la seconde est mal horodat\u00e9e, et les suivantes sont perdues&nbsp;!<\/p>\n<h1>Top-half et bottom-half<\/h1>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignright size-full wp-image-5028\" src=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2017\/06\/alf.png\" alt=\"Alf\" width=\"150\" height=\"150\" \/><\/p>\n<p style=\"text-align: justify;\">Une autre approche est possible, qui consiste \u00e0 distinguer les op\u00e9rations devant \u00eatre ex\u00e9cut\u00e9es imm\u00e9diatement au d\u00e9clenchement de l&rsquo;IRQ, de celles qui peuvent \u00eatre r\u00e9alis\u00e9es une fois que l&rsquo;interruption aura \u00e9t\u00e9 d\u00e9masqu\u00e9e dans l&rsquo;APIC. Les premi\u00e8res sont regroup\u00e9es dans la partie sup\u00e9rieure (<em><strong>top half<\/strong><\/em>) du traitement, et les secondes dans la partie inf\u00e9rieure (<em><strong>bottom half<\/strong><\/em>).<\/p>\n<p style=\"text-align: justify;\">Il existe plusieurs supports pour impl\u00e9menter <em>top half<\/em> et <em>bottom half<\/em>. Dans cet article nous observerons les <em>tasklets<\/em>, dans le suivant nous verrons les <em>workqueues<\/em> et les <em>threaded interrupts<\/em>.<\/p>\n<h2>Tasklets<\/h2>\n<p style=\"text-align: justify;\">Contrairement \u00e0 ce que leur nom &#8211; particuli\u00e8rement mal choisi &#8211; laisse entendre, les <em><strong>tasklets<\/strong><\/em> ne sont pas des petites t\u00e2ches. Il s&rsquo;agit simplement d&rsquo;un m\u00e9canisme permettant \u00e0 un handler d&rsquo;interruption de programmer l&rsquo;ex\u00e9cution d&rsquo;une fonction apr\u00e8s avoir d\u00e9masqu\u00e9 l&rsquo;IRQ qui l&rsquo;a d\u00e9clench\u00e9. La fonction qui s&rsquo;ex\u00e9cute doit \u00eatre de type\u00a0:<\/p>\n<pre>void <em>tasklet_function<\/em>(unsigned long <em>arg<\/em>);<\/pre>\n<p style=\"text-align: justify;\">On d\u00e9clare une tasklet avec\u00a0:<\/p>\n<pre><strong>DECLARE_TASKLET<\/strong>(<em>tasklet_name<\/em>, <em>tasklet_function<\/em>, <em>tasklet_arg<\/em>)<\/pre>\n<p style=\"text-align: justify;\">La tasklet ainsi d\u00e9clar\u00e9e est impl\u00e9ment\u00e9e par une structure <code>tasklet_struct<\/code>. Puis on peut programmer son ex\u00e9cution avec\u00a0:<\/p>\n<pre>void <strong>tasklet_schedule<\/strong>(struct tasklet_struct *<em>tasklet_name<\/em>);<\/pre>\n<p style=\"text-align: justify;\">Lorsque le handler invoque <code>tasklet_schedule()<\/code>, nous avons plusieurs garanties\u00a0:<\/p>\n<ul>\n<li style=\"text-align: justify;\">La fonction de la <em>tasklet<\/em> sera ex\u00e9cut\u00e9e (par abus de langage, on dit \u00ab\u00a0la <em>tasklet<\/em> sera ex\u00e9cut\u00e9e\u00a0\u00bb) ult\u00e9rieurement, le plus t\u00f4t possible apr\u00e8s la fin du handler, <strong>sans passer par l&rsquo;ordonnanceur<\/strong> (contrairement \u00e0 ce que <em>schedule<\/em> dans <code>tasklet_schedule()<\/code> laisse entendre).<\/li>\n<li style=\"text-align: justify;\">Si la tasklet est d\u00e9j\u00e0 programm\u00e9e, mais n&rsquo;a pas encore d\u00e9but\u00e9, <strong>une seule instance<\/strong> sera ex\u00e9cut\u00e9e.<\/li>\n<li style=\"text-align: justify;\">Si la tasklet est d\u00e9j\u00e0 en cours d&rsquo;ex\u00e9cution, une seconde instance sera ex\u00e9cut\u00e9e apr\u00e8s la fin de la premi\u00e8re, sur le m\u00eame c\u0153ur de CPU que celle-ci.<\/li>\n<li style=\"text-align: justify;\">Si la tasklet n&rsquo;est pas programm\u00e9e ni en cours d&rsquo;ex\u00e9cution, elle sera ex\u00e9cut\u00e9e sur le m\u00eame c\u0153ur de CPU que le handler qui invoque <code>tasklet_schedule()<\/code>.<\/li>\n<\/ul>\n<p style=\"text-align: justify;\">&nbsp;<\/p>\n<p style=\"text-align: justify;\">Attention, il est important au retrait du module de s&rsquo;assurer qu&rsquo;il n&rsquo;y a pas de <em>tasklet<\/em> en cours ou en attente d&rsquo;ex\u00e9cution (sur un autre c\u0153ur). Pour cela il faut appeler\u00a0:<\/p>\n<pre>void <strong>tasklet_kill<\/strong>(struct tasklet_struct * <em>tasklet_name<\/em>);<\/pre>\n<p style=\"text-align: justify;\">Voyons comment se d\u00e9roule le traitement d&rsquo;une occurrence unique de l&rsquo;interruption\u00a0:<\/p>\n<div id=\"attachment_5035\" style=\"width: 540px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-5035\" class=\"size-full wp-image-5035\" src=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2017\/06\/fig-08-Une-interruption-une-tasklet.png\" alt=\"Fig-08 - Une interruption, une tasklet\" width=\"530\" height=\"230\" srcset=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2017\/06\/fig-08-Une-interruption-une-tasklet.png 530w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2017\/06\/fig-08-Une-interruption-une-tasklet-300x130.png 300w\" sizes=\"auto, (max-width: 530px) 100vw, 530px\" \/><p id=\"caption-attachment-5035\" class=\"wp-caption-text\">Fig-08 &#8211; Une interruption, une tasklet<\/p><\/div>\n<p style=\"text-align: justify;\">J&rsquo;ai abr\u00e9g\u00e9 sur le sch\u00e9ma ci-dessus <code>T.H.<\/code> pour <em>Top Half<\/em> (le code qui est ex\u00e9cut\u00e9 directement dans le handler d&rsquo;interruption), et <code>B.H.<\/code> pour <em>Bottom Half<\/em>, le code ult\u00e9rieurement ex\u00e9cut\u00e9 dans la <em>tasklet<\/em>. Pas de surprise, le traitement est identique \u00e0 celui d&rsquo;un handler monolithique, avec un temps d&rsquo;ex\u00e9cution tr\u00e8s l\u00e9g\u00e8rement plus long (non repr\u00e9sent\u00e9 sur ce sch\u00e9ma) d\u00fb au m\u00e9canisme de programmation et d&rsquo;invocation de la <em>tasklet<\/em>.<\/p>\n<p style=\"text-align: justify;\">Supposons maintenant que deux interruptions tr\u00e8s rapproch\u00e9es se d\u00e9clenchent. La <em>Bottom Half<\/em> de la premi\u00e8re est interrompue par la <em>Top Half<\/em> de la seconde, et la deuxi\u00e8me <em>Bottom Half<\/em> s&rsquo;ex\u00e9cutera \u00e0 la suite.<\/p>\n<div id=\"attachment_5039\" style=\"width: 540px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-5039\" class=\"size-full wp-image-5039\" src=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2017\/06\/fig-09-Deux-interruptions-deux-tasklets.png\" alt=\"Fig-09 - Deux interruptions, deux tasklets\" width=\"530\" height=\"230\" srcset=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2017\/06\/fig-09-Deux-interruptions-deux-tasklets.png 530w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2017\/06\/fig-09-Deux-interruptions-deux-tasklets-300x130.png 300w\" sizes=\"auto, (max-width: 530px) 100vw, 530px\" \/><p id=\"caption-attachment-5039\" class=\"wp-caption-text\">Fig-09 &#8211; Deux interruptions, deux tasklets<\/p><\/div>\n<p style=\"text-align: justify;\">L&rsquo;avantage par rapport au handler monolithique, c&rsquo;est que la seconde interruption est correctement horodat\u00e9e. Int\u00e9ressons-nous maintenant au cas de trois interruptions rapproch\u00e9es&nbsp;:<\/p>\n<div id=\"attachment_5041\" style=\"width: 540px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-5041\" class=\"size-full wp-image-5041\" src=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2017\/06\/fig-10-Trois-interruptions-deux-tasklets.png\" alt=\"Fig-10 - Trois interruptions, deux tasklets\" width=\"530\" height=\"232\" srcset=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2017\/06\/fig-10-Trois-interruptions-deux-tasklets.png 530w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2017\/06\/fig-10-Trois-interruptions-deux-tasklets-300x131.png 300w\" sizes=\"auto, (max-width: 530px) 100vw, 530px\" \/><p id=\"caption-attachment-5041\" class=\"wp-caption-text\">Fig-10 &#8211; Trois interruptions, deux tasklets<\/p><\/div>\n<p style=\"text-align: justify;\">\u00c0 nouveau les trois interruptions sont correctement horodat\u00e9es. Mais cette fois, le troisi\u00e8me <code>tasklet_schedule()<\/code> n&rsquo;a pas d&rsquo;effet car la seconde <em>tasklet<\/em> n&rsquo;a pas encore d\u00e9marr\u00e9. C&rsquo;est ce que confirme l&rsquo;exp\u00e9rience suivante&nbsp;:<\/p>\n<pre>\n\/\/\/ \\file <strong>test-irq-03.c<\/strong>\n\n  [...]\n        static void irq_test_tasklet_function(unsigned long);\n\n        static <strong>DECLARE_TASKLET<\/strong>(irq_test_tasklet, irq_test_tasklet_function, 0);\n\n\nstatic irqreturn_t irq_test_handler(int irq, void * ident)\n{\n        printk(KERN_INFO \"%s: %s()\\n\", THIS_MODULE-&gt;name, __FUNCTION__);\n        <strong>tasklet_schedule<\/strong>(&amp;irq_test_tasklet);\n        return IRQ_HANDLED;\n}\n\n\nstatic void irq_test_tasklet_function(unsigned long unused)\n{\n        printk(KERN_INFO \"%s: %s()\\n\", THIS_MODULE-&gt,name, __FUNCTION__);\n        udelay(1000);   \n}\n  [...]<\/pre>\n<p style=\"text-align: justify;\">Lorsqu&rsquo;on fait \u00e0 nouveau un bref contact entre la broche 16 et la broche 1, on observe&nbsp;:<\/p>\n<pre>$ <strong>dmesg<\/strong>\n  [...]\n[130129.690714] test_irq_03: irq_test_handler()\n[130129.690734] test_irq_03: irq_test_handler()\n[130129.690744] test_irq_03: irq_test_handler()\n[130129.690776] test_irq_03: irq_test_tasklet_function()\n[130129.690826] test_irq_03: irq_test_handler()\n[130129.690857] test_irq_03: irq_test_handler()\n[130129.690868] test_irq_03: irq_test_handler()\n[130129.690877] test_irq_03: irq_test_handler()\n[130129.690895] test_irq_03: irq_test_handler()\n[130129.691814] test_irq_03: irq_test_tasklet_function()\n  [...]<\/pre>\n<p style=\"text-align: justify;\">Mais alors, la situation n&rsquo;est pas meilleure qu&rsquo;avec un driver monolithique&nbsp;! Somme nous condamn\u00e9s \u00e0 perdre la troisi\u00e8me interruption et les suivantes jusqu&rsquo;au d\u00e9clenchement de la <em>tasklet<\/em>&nbsp;? Non. La situation n&rsquo;est pas tout \u00e0 fait la m\u00eame. Car dans la <em>Top Half<\/em>, nous avons bri\u00e8vement le contr\u00f4le, et pouvons par exemple incr\u00e9menter un compteur d&rsquo;interruptions. La <em>Bottom Half<\/em> bouclera autant de fois qu&rsquo;il y a d&rsquo;interruptions re\u00e7ues, et d\u00e9cr\u00e9mentera le compteur. Voici un exemple d&rsquo;impl\u00e9mentation&nbsp;:<\/p>\n<pre>\/\/\/ \\file <strong>test-irq-04.c<\/strong>\n  [...]\n        <strong>#include &lt;linux\/atomic.h&gt;<\/strong>\n        #include &lt;linux\/delay.h&gt;\n        #include &lt;linux\/gpio.h&gt;\n        #include &lt;linux\/interrupt.h&gt;\n        #include &lt;linux\/module.h&gt;\n\n  [...]\n        <strong>static atomic_t irq_test_counter = ATOMIC_INIT(0);<\/strong>\n\n\nstatic irqreturn_t irq_test_handler(int irq, void * ident)\n{\n        printk(KERN_INFO \"%s: %s()\\n\", THIS_MODULE->name, __FUNCTION__);\n        <strong>atomic_inc(&irq_test_counter);<\/strong>\n        tasklet_schedule(&irq_test_tasklet);\n        return IRQ_HANDLED;\n}\n\n\nstatic void irq_test_tasklet_function(unsigned long unused)\n{\n        printk(KERN_INFO \"%s: %s()\\n\", THIS_MODULE->name, __FUNCTION__);\n        <strong>do<\/strong> {\n                printk(KERN_INFO \"%s:%s() loop\\n\", THIS_MODULE->name, __FUNCTION__);\n                udelay(1000);\n        } <strong>while (atomic_dec_return(&irq_test_counter) > 0);<\/strong>\n}\n  [...]<\/pre>\n<p style=\"text-align: justify;\">Heureuse surprise, d\u00e8s mon premier test, j&rsquo;ai eu trois interruptions rapproch\u00e9es et le r\u00e9sultat est concluant&nbsp;:<\/p>\n<pre>\n  [...]\n[171984.092518] test_irq_04: irq_test_handler()\n[171984.092569] test_irq_04: irq_test_handler()\n[171984.092581] test_irq_04: irq_test_handler()\n[171984.092595] test_irq_04: irq_test_tasklet_function()\n[171984.092606] test_irq_04:irq_test_tasklet_function() loop\n[171984.093617] test_irq_04:irq_test_tasklet_function() loop\n[171984.094627] test_irq_04:irq_test_tasklet_function() loop\n  [...]<\/pre>\n<h2>Tasklet et contexte d&rsquo;appel-syst\u00e8me<\/h2>\n<p style=\"text-align: justify;\">Nous avons vu que, programm\u00e9e depuis un contexte d&rsquo;interruption, une tasklet est ex\u00e9cut\u00e9e imm\u00e9diatement sans repasser par l&rsquo;ordonnanceur. Ceci est facile \u00e0 v\u00e9rifier, en modifiant l\u00e9g\u00e8rement le code de la <em>tasklet<\/em> ainsi&nbsp;:<\/p>\n<pre>\/\/\/ \\file <strong>test-irq-05.c<\/strong>\n\n  [..]\nstatic irqreturn_t irq_test_handler(int irq, void * ident)\n{\n        tasklet_schedule(&amp;irq_test_tasklet);\n        return IRQ_HANDLED;\n}\n\n\nstatic void irq_test_tasklet_function(unsigned long unused)\n{\n        <strong>printk<\/strong>(KERN_INFO \"%s: current pid=%d comm=%s\\n\", THIS_MODULE-&gt;name, <strong>current-&gt;pid, current-&gt;comm<\/strong>);\n}<\/pre>\n<p style=\"text-align: justify;\">Cette <em>tasklet<\/em> affiche le PID et le nom du processus <code>current<\/code> (celui actuellement ordonnanc\u00e9 sur le c&oelig;ur de CPU o\u00f9 elle s&rsquo;ex\u00e9cute). Nous voyons bien qu&rsquo;il n&rsquo;y a pas de t\u00e2che sp\u00e9cifique. Suivant les it\u00e9rations diff\u00e9rents processus o\u00f9 threads kernel sont actifs&nbsp;:<\/p>\n<pre>[198874.401602] test_irq_05: current pid=0 comm=swapper\/0\n  [...]\n[198874.402142] test_irq_05: current pid=463 comm=rs:main Q:Reg\n  [...]\n[198874.402669] test_irq_05: current pid=90 comm=jbd2\/mmcblk0p2-\n  [...]\n[198874.415554] test_irq_05: current pid=0 comm=swapper\/0\n  [...]\n[198874.421986] test_irq_05: current pid=3 comm=ksoftirqd\/0\n  [...]\n[198874.422530] test_irq_05: current pid=462 comm=in:imklog\n  [...]\n[198874.423017] test_irq_05: current pid=463 comm=rs:main Q:Reg\n  [...]\n[198874.423882] test_irq_05: current pid=0 comm=swapper\/0\n  [...]\n<\/pre>\n<p style=\"text-align: justify;\">Rien ne nous emp\u00eache n\u00e9anmoins de programmer une <em>tasklet<\/em> depuis un contexte d&rsquo;<strong>appel-syst\u00e8me<\/strong>. Voici par exemple un module qui impl\u00e9mente un mini-driver proposant un unique appel-syst\u00e8me <code>write()<\/code> qui ne fait qu&rsquo;appeler notre <em>tasklet<\/em>&nbsp;:<\/p>\n<pre>\/\/\/ \\file <strong>test-irq-06.c<\/strong>\n  [...]\n        #include &lt;linux\/delay.h&gt;\n        #include &lt;linux\/interrupt.h&gt;\n        #include &lt;linux\/miscdevice.h&gt;\n        #include &lt;linux\/module.h&gt;\n        #include &lt;linux\/sched.h&gt;\n\n        static void irq_test_tasklet_function(unsigned long);\n\n        static DECLARE_TASKLET(irq_test_tasklet, irq_test_tasklet_function, 0);\n\n\nstatic void irq_test_tasklet_function(unsigned long unused)\n{\n        printk(KERN_INFO \"%s: current pid=%d comm=%s\\n\", THIS_MODULE-&gt;name, current-&gt;pid, current-&gt;comm);\n}\n\n\nstatic ssize_t irq_test_write(struct file *filp, const char *buffer, size_t length, loff_t *offset)\n{\n        <strong>tasklet_schedule<\/strong>(&amp;irq_test_tasklet);\n        return length;\n}\n\n\nstatic struct file_operations irq_test_fops = {\n        .owner = THIS_MODULE,\n        .write = irq_test_write,\n};\n\nstatic struct miscdevice irq_test_misc = {\n        .minor = MISC_DYNAMIC_MINOR,\n        .name  = THIS_MODULE->name,\n        .fops  = &amp;irq_test_fops,\n};\n\n\nstatic int __init irq_test_init (void)\n{\n        return misc_register(&amp;irq_test_misc);\n}\n\n\nstatic void __exit irq_test_exit (void)\n{\n        misc_deregister(&amp;irq_test_misc);\n}\n  [...]<\/pre>\n<p style=\"text-align: justify;\">Pour d\u00e9clencher l&rsquo;appel-syst\u00e8me, il nous suffit de faire une \u00e9criture avec un <code>echo<\/code> redirig\u00e9 depuis la ligne de commande du shell.<\/p>\n<pre>$ <strong>sudo insmod test-irq-06.ko<\/strong>\n$ <strong>echo 1 &gt; \/dev\/test_irq_06<\/strong> \n-bash: \/dev\/test_irq_06: Permission denied\npi@raspberrypi:~\/article-2017-06-06$ <strong>sudo -s<\/strong>\nroot@raspberrypi:\/home\/pi\/article-2017-06-06# <strong>echo 1 &gt; \/dev\/test_irq_06<\/strong>\nroot@raspberrypi:\/home\/pi\/article-2017-06-06# <strong>echo 1 &gt; \/dev\/test_irq_06<\/strong>\nroot@raspberrypi:\/home\/pi\/article-2017-06-06# <strong>echo 1 &gt; \/dev\/test_irq_06<\/strong>\nroot@raspberrypi:\/home\/pi\/article-2017-06-06# <strong>dmesg<\/strong>\n  [...]\n[202648.772668] test_irq_06: current pid=15 comm=ksoftirqd\/1\n[202666.062771] test_irq_06: current pid=3 comm=ksoftirqd\/0\n[202668.352760] test_irq_06: current pid=3 comm=ksoftirqd\/0<\/pre>\n<p style=\"text-align: justify;\">Lors de la programmation d&rsquo;une <em>tasklet<\/em> depuis un contexte d&rsquo;appel syst\u00e8me, c&rsquo;est donc un thread du kernel qui l&rsquo;ex\u00e9cutera. Ces threads, nomm\u00e9s <strong>ksoftirqd<\/strong> sont parfaitement visibles dans la liste des t\u00e2ches du syst\u00e8me. Notons par ailleurs que ce n&rsquo;est pas toujours le m\u00eame thread, le noyau dispose d&rsquo;un ensemble de <em>ksoftirqd<\/em>, et en s\u00e9lectionne dynamiquement un disponible.<\/p>\n<pre># <strong>ps aux<\/strong>\nUSER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND\nroot         1  0.0  0.4  22780  3816 ?        Ss   Jun02   0:07 \/sbin\/init splash\nroot         2  0.0  0.0      0     0 ?        S    Jun02   0:00 [kthreadd]\n<strong>root         3  0.0  0.0      0     0 ?        S    Jun02   0:05 [ksoftirqd\/0]<\/strong>\nroot         5  0.0  0.0      0     0 ?        S<   Jun02   0:00 [kworker\/0:0H]\nroot         7  0.0  0.0      0     0 ?        S    Jun02   0:17 [rcu_preempt]\nroot         8  0.0  0.0      0     0 ?        S    Jun02   0:00 [rcu_sched]\nroot         9  0.0  0.0      0     0 ?        S    Jun02   0:00 [rcu_bh]\nroot        10  0.0  0.0      0     0 ?        S    Jun02   0:00 [migration\/0]\nroot        11  0.0  0.0      0     0 ?        S<   Jun02   0:00 [lru-add-drain]\nroot        12  0.0  0.0      0     0 ?        S    Jun02   0:00 [cpuhp\/0]\nroot        13  0.0  0.0      0     0 ?        S    Jun02   0:00 [cpuhp\/1]\nroot        14  0.0  0.0      0     0 ?        S    Jun02   0:00 [migration\/1]\n<strong>root        15  0.0  0.0      0     0 ?        S    Jun02   0:00 [ksoftirqd\/1]<\/strong>\nroot        17  0.0  0.0      0     0 ?        S<   Jun02   0:00 [kworker\/1:0H]\nroot        18  0.0  0.0      0     0 ?        S    Jun02   0:00 [cpuhp\/2]<\/pre>\n<h1>Conclusion<\/h1>\n<p style=\"text-align: justify;\">Nous avons vu le principe des <em>tasklets<\/em> pour r\u00e9aliser un traitement diff\u00e9r\u00e9 depuis un handler d'interruption. Il existe d'autres m\u00e9canismes que nous verrons dans le prochain article. L'int\u00e9r\u00eat de la <em>tasklet<\/em> est d'\u00eatre ex\u00e9cut\u00e9e imm\u00e9diatement, plus prioritairement que toutes les t\u00e2ches ordonnanc\u00e9es du syst\u00e8me. Ceci est toutefois diff\u00e9rent si elle est programm\u00e9e depuis un contexte d'appel syst\u00e8me, puisqu'alors c'est un thread du kernel qui l'ex\u00e9cute.<\/p>\n<p style=\"text-align: justify;\">Nous reviendrons sur cette ex\u00e9cution port\u00e9e par un thread dans le prochain article, car cela peut nous r\u00e9server des surprises dans un contexte d'application temps r\u00e9el...<\/p>","protected":false},"excerpt":{"rendered":"<p>Il existe plusieurs m&eacute;canismes propos&eacute;s par le noyau Linux pour programmer un traitement &agrave; r&eacute;aliser lorsqu&rsquo;un p&eacute;riph&eacute;rique externe d&eacute;clenche une interruption pour nous notifier de l&rsquo;occurrence d&rsquo;un &eacute;v&eacute;nement. Gestionnaire monolithique, tasklet, workqueue, threaded interrupt, chaque solution a des avantages et des inconv&eacute;nients, qu&rsquo;il est int&eacute;ressant de conna&icirc;tre pour optimiser l&rsquo;efficacit&eacute; de nos traitements.<\/p>","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[19,8,11,14],"tags":[],"class_list":["post-4979","post","type-post","status-publish","format-standard","hentry","category-kernel","category-linux-2","category-raspberry-pi","category-temps-reel"],"_links":{"self":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/4979","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=4979"}],"version-history":[{"count":69,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/4979\/revisions"}],"predecessor-version":[{"id":5073,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/4979\/revisions\/5073"}],"wp:attachment":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/media?parent=4979"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/categories?post=4979"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/tags?post=4979"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}