{"id":1248,"date":"2011-11-04T13:31:44","date_gmt":"2011-11-04T12:31:44","guid":{"rendered":"http:\/\/www.blaess.fr\/christophe\/?p=1248"},"modified":"2011-11-04T13:31:44","modified_gmt":"2011-11-04T12:31:44","slug":"experimentation-sur-la-preemptibilite-du-noyau-linux","status":"publish","type":"post","link":"https:\/\/www.blaess.fr\/christophe\/2011\/11\/04\/experimentation-sur-la-preemptibilite-du-noyau-linux\/","title":{"rendered":"Exp\u00e9rimentation sur la pr\u00e9emptibilit\u00e9 du noyau Linux"},"content":{"rendered":"<p style=\"text-align: justify;\">Cet article est extrait de la version pr\u00e9paratoire de mon livre \u00ab\u00a0<em>Solutions temps-r\u00e9el sous Linux<\/em>\u00a0\u00bb (parution envisag\u00e9e au d\u00e9but 2012).<\/p>\n<p style=\"text-align: justify;\">J&rsquo;ai eu envie de mettre en \u00e9vidence la diff\u00e9rence de comportement entre un noyau pr\u00e9emptible (avec l&rsquo;option <code>CONFIG_PREEMPT<\/code> activ\u00e9e durant sa compilation) et un noyau non-pr\u00e9emptible classique. Toutefois, cette mise en \u00e9vidence n&rsquo;est pas tr\u00e8s simple, car elle concerne pr\u00e9cis\u00e9ment des cas rares et difficiles \u00e0\u00a0 reproduire.<\/p>\n<p style=\"text-align: justify;\">Nous allons nous int\u00e9resser \u00e0 une interruption d\u00e9clench\u00e9e par le port de communication s\u00e9rie RS-232. Nous allons envoyer un caract\u00e8re sur une liaison s\u00e9rie \u00e0 destination d&rsquo;un syst\u00e8me Linux sur lequel fonctionnera un processus temps-r\u00e9el qui renverra le m\u00eame caract\u00e8re dans l&rsquo;autre sens sur la m\u00eame liaison s\u00e9rie.<\/p>\n<p>\n<!--more-->\n<\/p>\n<h1>Mise en \u0153uvre<\/h1>\n<p style=\"text-align: justify;\">L&rsquo;exp\u00e9rience met en \u0153uvre une carte \u00e0 processeur Arm (<em>Pandaboard<\/em>, \u00e0 gauche sur la photo ci-dessous) fonctionnant avec un noyau Linux 3.0. Ce choix s&rsquo;explique par le fait que la Pandaboard dispose d&rsquo;un v\u00e9ritable port s\u00e9rie RS-232 poss\u00e9dant sa propre interruption \u2013 et non d&rsquo;une \u00e9mulation bas\u00e9e sur un contr\u00f4leur USB comme la plupart des PC actuels. Nous essaierons de mesurer pr\u00e9cis\u00e9ment le temps de r\u00e9ponse de notre processus temps-r\u00e9el et voir s&rsquo;il varie sous l&rsquo;influence d&rsquo;un processus de moindre priorit\u00e9.<\/p>\n<p style=\"text-align: justify;\">Pour effectuer cette mesure, j&rsquo;utiliserai une carte de d\u00e9veloppement (<em>STK500<\/em>, \u00e0 droite sur la photo) pour micro-contr\u00f4leurs Atmel, plus particuli\u00e8rement ici un ATmega32 (ins\u00e9r\u00e9 dans le support de programmation rouge sur la partie gauche de la carte). Ce dernier fonctionne sans syst\u00e8me d&rsquo;exploitation et nous permet un contr\u00f4le direct et sans perturbation de la liaison s\u00e9rie. Le micro-contr\u00f4leur enverra le caract\u00e8re et mesurera le temps \u00e9coul\u00e9 jusqu&rsquo;\u00e0 la r\u00e9ception du caract\u00e8re renvoy\u00e9.<\/p>\n<p>&nbsp;<\/p>\n<p><a href=\"http:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2011\/11\/Pandaboard-STK500.gif\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-medium wp-image-1250\" title=\"Pandaboard et STK500\" src=\"http:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2011\/11\/Pandaboard-STK500-300x207.gif\" alt=\"Pandaboard et STK500\" width=\"300\" height=\"207\" \/><\/a><\/p>\n<p style=\"text-align: justify;\">Voici le programme qui est compil\u00e9 puis charg\u00e9 dans le micro-contr\u00f4leur (\u00e0 l&rsquo;aide des utilitaires <code>avr-gcc<\/code> et <code>avr-dude<\/code> qui sont librement disponible sous Linux). Mentionnons pour le lecteur non habitu\u00e9 \u00e0 la programmation de micro-contr\u00f4leur que les constantes <code>UDR<\/code>, <code>UCSRA<\/code>, <code>UCSRB<\/code>, <code>UCSRC<\/code>, <code>UBRRH<\/code> et <code>UBRRL<\/code> repr\u00e9sentent des acc\u00e8s directs aux registres du micro-contr\u00f4leur.<\/p>\n<pre><strong>exemple-mesure-atmega.c\u00a0:<\/strong>\n<em>\/\/ Declarer la frequence du micro-controleur (1MHz)<\/em>\n#define F_CPU 1000000\n\/\/ Inclusion d'entetes specifiques au micro-controleur\n#include &lt;avr\/io.h&gt;\n#include &lt;util\/delay.h&gt;\n\nvoid envoyer_octet (unsigned char octet)\n{\n    \/\/ Attendre buffer emission libre\n    while ((UCSRA &amp; (1&lt;&lt;UDRE)) == 0)\n        ;\n    \/\/ Ecriture sur le port Usart Data Register\n    UDR = octet;\n}\n\nint main(void)\n{\n    unsigned char uc;\n    unsigned long int nb_boucles;\n\n    \/\/ Parametrage du port de communication\n    \/\/ 12 -&gt; 9600 bits\/sec\n    UBRRH = (unsigned char) (0);\n    UBRRL = (unsigned char) (12);\n    \/\/ Clock x 2\n    UCSRA = (1 &lt;&lt; U2X);\n    \/\/ TX et RX actives\n    UCSRB = (1 &lt;&lt; RXEN) | (1 &lt;&lt; TXEN);\n    \/\/ 8 bits, 1 stop, pas de parite\n    UCSRC = (1 &lt;&lt; URSEL) | (1 &lt;&lt; UCSZ1) | (1 &lt;&lt; UCSZ0);\n\n    while (1) {\n        nb_boucles = 0;\n\n        envoyer_octet(0xFF);\n        \/\/ Attendre 1 octet recu\n        while ((UCSRA &amp; (1 &lt;&lt; RXC)) == 0)\n            nb_boucles ++;\n        \/\/ Lire l'octet\n        uc = UDR;\n\n        envoyer_octet((nb_boucles &gt;&gt; 24) &amp; 0xFF);\n        _delay_ms(2);\n        envoyer_octet((nb_boucles &gt;&gt; 16) &amp; 0xFF);\n        _delay_ms(2);\n        envoyer_octet((nb_boucles &gt;&gt; 8 ) &amp; 0xFF);\n        _delay_ms(2);\n        envoyer_octet((nb_boucles &gt;&gt; 0 ) &amp; 0xFF);\n        _delay_ms(500);\n    }\n    return 0;\n}<\/pre>\n<p style=\"text-align: justify;\">On peut voir que ce programme envoie un octet (<code>0xFF<\/code>) sur le port s\u00e9rie et boucle \u2013 en incr\u00e9mentant une variable \u2013 tant qu&rsquo;il n&rsquo;a pas re\u00e7u de r\u00e9ponse. La dur\u00e9e de la boucle importe peu. Chaque it\u00e9ration dure environ 10\u00a0micro-secondes, et on pourrait la calibrer pr\u00e9cis\u00e9ment, mais ceci ne pr\u00e9sente pas de v\u00e9ritable int\u00e9r\u00eat. Ce n&rsquo;est pas le nombre absolu de boucles effectu\u00e9es qui nous concerne, mais plut\u00f4t les variations de ce nombre au cours des essais successifs. Une fois la r\u00e9ponse obtenue, nous envoyons sur le m\u00eame port s\u00e9rie le nombre de boucles effectu\u00e9es. Le petit sommeil de 2\u00a0ms entre les octets sert \u00e0 garantir la r\u00e9ception par le correspondant car il n&rsquo;y a pas de contr\u00f4le de flux (CTS\/RTS, DTS\/DSR, etc.) sur la carte \u00e0 micro-contr\u00f4leur employ\u00e9e ici.<\/p>\n<p style=\"text-align: justify;\">Le programme qui fonctionne sur la Pandaboard sous Linux est le suivant\u00a0:<\/p>\n<pre><strong>exemple-reponse-irq-serie.c \u00a0:<\/strong>\n#include &lt;fcntl.h&gt;\n#include &lt;signal.h&gt;\n#include &lt;stdio.h&gt;\n#include &lt;stdlib.h&gt;\n#include &lt;termios.h&gt;\n#include &lt;unistd.h&gt;\n\nvolatile int quitter = 0;\n\nvoid handler_sigint(int unused)\n{\n    quitter = 1;\n}\n\nint main(int argc, char * argv[])\n{\n    int fd;\n    unsigned char\u00a0 octet;\n    int nb_cycles;\n    struct termios parametres;\n    struct termios original;\n\n    if (argc != 2) {\n        fprintf(stderr, \"usage: %s port_serien\",argv[0]);\n        exit(EXIT_FAILURE);\n    }\n\n    signal(SIGINT, handler_sigint);\n\n    if ((fd = open(argv[1], O_RDWR | | O_NONBLOCK))&lt;0){\n        perror(argv[1]);\n        exit(EXIT_FAILURE);\n    }\n\n    tcgetattr(fd, &amp; original);\n    tcgetattr(fd, &amp; parametres);\n    cfmakeraw(&amp; parametres);\n    cfsetispeed(&amp; parametres, B9600);\n    cfsetospeed(&amp; parametres, B9600);\n    parametres.c_iflag |= IGNPAR;\n    parametres.c_cflag ^= (CSIZE | PARENB | CSTOPB);\n    parametres.c_cflag |= CS8 | CLOCAL;\n    if (tcsetattr(fd, TCSANOW, &amp; parametres) != 0) {\n        perror(\"tcsetattr\");\n        exit(EXIT_FAILURE);\n    }\n    fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) ^ O_NONBLOCK);\n\n    while (! quitter) {\n        if (read(fd, &amp; octet, 1) &lt; 0) {\n            perror(argv[1]);\n            exit(EXIT_FAILURE);\n        }\n        if (write(fd, &amp; octet, 1) &lt; 0) {\n            perror(argv[1]);\n            exit(EXIT_FAILURE);\n        }\n        nb_cycles = 0;\n        read(fd, &amp; octet, 1);\n        nb_cycles |= octet;\n        nb_cycles &lt;&lt;= 8;\n        read(fd, &amp; octet, 1);\n        nb_cycles |= octet;\n        nb_cycles &lt;&lt;= 8;\n        read(fd, &amp; octet, 1);\n        nb_cycles |= octet;\n        nb_cycles &lt;&lt;= 8;\n        read(fd, &amp; octet, 1);\n        nb_cycles |= octet;\n        fprintf(stdout, \"%dn\", nb_cycles);\n    }\n    tcsetattr(fd, TCSANOW, &amp; original);\n    close(fd);\n    fprintf(stderr, \"Bye !n\");\n    return EXIT_SUCCESS;\n}<\/pre>\n<p style=\"text-align: justify;\">Le programme attend donc un caract\u00e8re, le renvoie en \u00e9cho, r\u00e9cup\u00e8re les quatre octets repr\u00e9sentant son temps de r\u00e9ponse et les affiche sur sa sortie standard. Voici un exemple d&rsquo;ex\u00e9cution\u00a0sur un noyau non-pr\u00e9emptible.<\/p>\n<pre>[Panda]# <strong>.\/exemple-reponse-irq-serie \/dev\/ttyO2<\/strong>\n290\n287\n289\n287\n[...]\n287\n287\n286\n286\n<strong>(<em>Contr\u00f4le-C<\/em>)<\/strong>\nBye !\n[Panda]#<\/pre>\n<h1>Appel-syst\u00e8me long<\/h1>\n<p style=\"text-align: justify;\">Pour perturber le fonctionnement du programme, j&rsquo;ai r\u00e9alis\u00e9 un petit script shell qui charge (avec <code>insmod<\/code>) et d\u00e9charge (avec <code>rmmod<\/code>) toutes les cinq secondes ce petit module du kernel\u00a0:<\/p>\n<pre><strong>module-delay.c \u00a0:<\/strong>\n#include &lt;linux\/module.h&gt;\n\nstatic int __init module_delay_init (void)\n{\n    unsigned long fin = jiffies + HZ\/2; \/\/ 500 ms\n    while(time_before(jiffies, fin))\n        ;\n    return 0;\n}\n\nstatic void __exit module_delay_exit (void)\n{}\n\nmodule_init(module_delay_init);\nmodule_exit(module_delay_exit);\nMODULE_LICENSE(\"GPL\");<\/pre>\n<p style=\"text-align: justify;\">Ce module effectue une boucle active de 500 millisecondes d\u00e8s son chargement dans le kernel. Ceci nous permet de disposer d&rsquo;un appel-syst\u00e8me suffisamment long pour perturber le temps de r\u00e9ponse \u00e0 une interruption sur un syst\u00e8me non-pr\u00e9emptible. Une remarque\u00a0: j&rsquo;ai \u00e9galement mont\u00e9 le thread kernel <code>kworker<\/code> qui est utilis\u00e9 pour g\u00e9rer une partie de l&rsquo;interruption s\u00e9rie \u00e0 la priorit\u00e9 <em>Fifo<\/em> 50.<\/p>\n<h1>R\u00e9sultats sur syst\u00e8me non-pr\u00e9emptible<\/h1>\n<p style=\"text-align: justify;\">[NB: Pour \u00e9tudier les r\u00e9sultats, apr\u00e8s les avoir redirig\u00e9s dans un fichier, j&rsquo;utilise un ensemble de petits outils d\u00e9velopp\u00e9s pr\u00e9c\u00e9dement dans le livre]<\/p>\n<pre>[Panda]# <strong>uname -a<\/strong>\nLinux (Pandaboard) 3.0.0-cpb #4 SMP Mon Oct 31 19:52:43 CET 2011 armv7l GNU\/Linux\n[Panda]# <strong>taskset -p 1\u00a0 $$<\/strong>\npid 547's current affinity mask: 3\npid 547's new affinity mask: 1\n[Panda]#\u00a0 <strong>chrt -f 40 . .\/exemple-reponse-irq-serie \/dev\/ttyO2 &gt; resultats-non-preempt.txt<\/strong>\n<strong><em>(Contr\u00f4le-C apr\u00e8s quelques minutes)<\/em><\/strong>\nBye!\n[Panda]#<\/pre>\n<p style=\"text-align: justify;\">Pendant le d\u00e9roulement du programme, mon script qui charge et d\u00e9charge le module perturbateur toutes les cinq secondes s&rsquo;ex\u00e9cute sur le m\u00eame processeur, avec une priorit\u00e9 temps-r\u00e9el 10.<\/p>\n<p style=\"text-align: justify;\">L&rsquo;analyse du r\u00e9sultat donne\u00a0:<\/p>\n<pre>$ <strong>..\/chapitre-04\/calculer-statistiques &lt; resultats-non-preempt.txt<\/strong>\nNb mesures = 1156\nMinimum = 283\nMaximum = 46181\nMoyenne = 4737\nEcart-type = 13397\n$ <strong>..\/chapitre-04\/calculer-histogramme 100 0 50000\u00a0 &lt; resultat-nonpreempt.txt &gt; histo-nonpreempt.txt<\/strong><\/pre>\n<p style=\"text-align: justify;\">[NB: L&rsquo;histogramme calcul\u00e9 ici est ensuite inject\u00e9 dans un petit script Gnuplot, pr\u00e9sent\u00e9 dans un chapitre pr\u00e9c\u00e9dent, pour produire les figures ci-dessous]<\/p>\n<p style=\"text-align: justify;\">Les r\u00e9sultats statistiques paraissent assez catastrophiques\u00a0: une variabilit\u00e9 entre 283 et 46181 it\u00e9rations de boucles, et un \u00e9cart-type largement plus grand que la moyenne des valeurs\u00a0! Graphiquement les r\u00e9sultats sont plus compr\u00e9hensibles, comme nous le voyons sur la figure suivante.<\/p>\n<p><a href=\"http:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2011\/11\/figure-6.8.gif\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-medium wp-image-1253\" title=\"R\u00e9ponse aux interruptions sur noyau non-pr\u00e9emptible\" src=\"http:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2011\/11\/figure-6.8-300x225.gif\" alt=\"R\u00e9ponse aux interruptions sur noyau non-pr\u00e9emptible\" width=\"300\" height=\"225\" \/><\/a><\/p>\n<p style=\"text-align: justify;\">Notre syst\u00e8me fait un grand \u00e9cart entre un temps de r\u00e9ponse faible (290 it\u00e9rations par boucles, environ 3 millisecondes) et une r\u00e9ponse tr\u00e8s retard\u00e9e par l&rsquo;appel-syst\u00e8me perturbateur (45000 it\u00e9rations par boucle, \u00e0 peu pr\u00e8s 500\u00a0ms).<\/p>\n<h1>R\u00e9sultat sur syst\u00e8me pr\u00e9emptible<\/h1>\n<p style=\"text-align: justify;\">Apr\u00e8s avoir red\u00e9marr\u00e9 la carte Pandaboard sur un noyau Linux compil\u00e9 avec l&rsquo;option CONFIG_PREEMPT, nous obtenons les r\u00e9sultats suivants.<\/p>\n<pre>[Panda] # <strong>uname -a <\/strong>\nLinux Pandaboard 3.0.0-rc7-cpb #1 SMP PREEMPT Thu Sep 29 14:49:25 CEST 2011 armv7l GNU\/Linux\n[Panda]# <strong>taskset -p 1 $$<\/strong>\npid 579's current affinity mask: 3\npid 579's new affinity mask: 1\n[Panda]#  <strong>chrt -f 40 . .\/exemple-reponse-irq-serie \/dev\/ttyO2 &gt; resultats-preempt.txt<\/strong>\n<strong><em>(Contr\u00f4le-C apr\u00e8s quelques minutes)<\/em><\/strong>\nBye!\n[Panda]#<\/pre>\n<p style=\"text-align: justify;\">Apr\u00e8s avoir rappatri\u00e9 le fichier sur le PC de d\u00e9veloppement, nous analysons les r\u00e9sultats.<\/p>\n<pre>$ <strong>..\/chapitre-04\/calculer-statistiques &lt; resultats-preempt.txt<\/strong>\nNb mesures = 1307\nMinimum = 284\nMaximum = 309\nMoyenne = 287\nEcart-type = 2\n$<\/pre>\n<p style=\"text-align: justify;\">Voil\u00e0 qui est beaucoup mieux\u00a0! Le r\u00e9sultat est visible sur la figure suivante avec la m\u00eame \u00e9chelle que pr\u00e9c\u00e9demment.<br \/>\n<a href=\"http:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2011\/11\/figure-6.9.gif\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-medium wp-image-1254\" title=\"R\u00e9ponse aux interruptions sur noyau pr\u00e9emptible (1)\" src=\"http:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2011\/11\/figure-6.9-300x225.gif\" alt=\"R\u00e9ponse aux interruptions sur noyau pr\u00e9emptible (1)\" width=\"300\" height=\"225\" \/><\/a><\/p>\n<p style=\"text-align: justify;\">Un zoom au d\u00e9but de l&rsquo;axe des abscisses est repr\u00e9sent\u00e9 sur la figure ci-dessous. Cette fois aucune pr\u00e9emption du processus de haute priorit\u00e9, la r\u00e9ponse \u00e0 l&rsquo;interruption est toujours comprise entre 284 et 309 boucles de la carte \u00e0 micro-contr\u00f4leur. La r\u00e9ponse n&rsquo;est pas r\u00e9ellement plus rapide, mais elle est plus fiable et pr\u00e9visible.<br \/>\n<a href=\"http:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2011\/11\/figure-6.10.gif\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-medium wp-image-1255\" title=\"R\u00e9ponse aux interruptions sur noyau pr\u00e9emptible (2)\" src=\"http:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2011\/11\/figure-6.10-300x225.gif\" alt=\"R\u00e9ponse aux interruptions sur noyau pr\u00e9emptible (2)\" width=\"300\" height=\"225\" \/><\/a><\/p>\n<h1>Conclusion<\/h1>\n<p style=\"text-align: justify;\">La pr\u00e9emptibilit\u00e9 optionnelle du noyau, disponible depuis sa version 2.6, est un \u00e9l\u00e9ment important pour am\u00e9liorer la fiabilit\u00e9 des syst\u00e8mes temps-r\u00e9el dans leur r\u00e9ponse aux \u00e9v\u00e9nements externes. Elle permet de garantir qu&rsquo;un processus de haute priorit\u00e9 en attente d&rsquo;un \u00e9v\u00e9nement externe (mat\u00e9rialis\u00e9 par cette interruption) sera r\u00e9veill\u00e9 avec rapidit\u00e9 et surtout fiabilit\u00e9, m\u00eame si une autre t\u00e2che &#8211; de priorit\u00e9 moindre &#8211; est en train d&rsquo;ex\u00e9cuter un appel-syst\u00e8me. Ceci \u00e9vite le probl\u00e8me classique de l&rsquo;<a title=\"\u00c9viter les inversions de priorit\u00e9 caus\u00e9es par des mutex\" href=\"http:\/\/www.blaess.fr\/christophe\/2011\/07\/29\/eviter-les-inversions-de-priorite-causees-par-des-mutex\/\" target=\"_blank\">inversion de priorit\u00e9<\/a>.<\/p>","protected":false},"excerpt":{"rendered":"<p>Cet article est extrait de la version pr&eacute;paratoire de mon livre &laquo;&nbsp;Solutions temps-r&eacute;el sous Linux&nbsp;&raquo; (parution envisag&eacute;e au d&eacute;but 2012). J&rsquo;ai eu envie de mettre en &eacute;vidence la diff&eacute;rence de comportement entre un noyau pr&eacute;emptible (avec l&rsquo;option CONFIG_PREEMPT activ&eacute;e durant sa compilation) et un noyau non-pr&eacute;emptible classique. Toutefois, cette mise en &eacute;vidence n&rsquo;est pas tr&egrave;s [&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,10,14],"tags":[],"class_list":["post-1248","post","type-post","status-publish","format-standard","hentry","category-embarque","category-linux-2","category-microprocesseur","category-temps-reel"],"_links":{"self":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/1248","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=1248"}],"version-history":[{"count":0,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/1248\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/media?parent=1248"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/categories?post=1248"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/tags?post=1248"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}