Introduction
Zephyr est un projet open source visant à créer un OS indépendant ouvert conçu pour l'IoT. Il évolue donc dans le même monde que Contiki, MynewtOS, RIOT OS et nombreuses autres solutions. Le marché de l'IoT étant en plein essor, les développeurs d'applications ont besoin d'un système sur lequel baser leurs solutions innovantes. Ces systèmes doivent répondre aux contraintes suivantes :
- Être léger, de façon à tourner sur des plateformes matérielles limitées, dont le coût doit être le plus bas possible ;
- Consommer peu, ces devices pouvant rester plusieurs années sans intervention humaine, fonctionnant sur batterie ;
- Fournir une connectivité performante ;
- Répondre aux problématiques de sécurité soulevées par l'explosion du marché "connecté".
Zephyr est une solution nouvelle à tous ces problèmes, apparue en 2015 au sein de Wind River sous le nom de Rocket OS. Depuis un peu plus d'un an, le projet se nomme Zephyr et est passé sous la gouvernance de la Linux Foundation.
Dans cet article, je vous propose de faire un tour de son fonctionnement et de ses particularités. Nous étudierons d'abord le design de cette solution et ce qu'il implique face à ses concurrents. Nous irons, ensuite, mettre la main sur les sources et verrons comment un développeur peut utiliser Zephyr pour son propre projet, ou y contribuer, en prenant l'exemple d'un travail en cours chez Smile-ECS.
Buts du projet
Zephyr centre son développement sur les points suivants :
- La légèreté (empreinte mémoire la plus faible possible) ;
- La sécurité ;
- Un travail collaboratif et ouvert ;
- Une modularité élevée, face à la multitude de solutions rigides et différentes déjà présentes sur le marché.
Zephyr arrive en effet assez tard sur le marché des "RTOS" axés IoT; l'un des constats affichés lors de sa création était la fragmentation des solutions sur le marché. Toutes ont des empreintes mémoire minimes, mais diffèrentes en connectivité, en device management, voire même en raccordement au cloud (Mbed et Contiki offrant des environnements de déploiement sur lesquels se baser).
Zephyr vise en priorité la couche système pour les devices terminaux de l'IoT : capteurs, wearables et autres petits objets connectés.
Un système sécurisé et ouvert : les arguments posés sont l'ouverture et l'organisation du projet, qui doivent diminuer au maximum la difficulté pour quiconque d'y porter une nouvelle plateforme et de profiter de la robustesse de l'OS sans avoir à se poser de questions.
Communauté
Une des différences notables entre Zephyr et d'autres OS créés pour l'IoT est la manière dont le projet est géré: deux comités de pilotage, administratif et technique, supervisent et guident son avancée. L'administratif gère les perspectives commerciales, le marketing et la visibilité sur le marché. Le côté technique, consiste en une hiérarchie de mainteneurs sur le même modèle que Linux, avec au sommet des lead architects.
Le TSC (Technical Steering Comitee) fait le pont entre ces deux pôles, et produit la roadmap concrète du projet.
Actuellement, il faut être employé(e) d'une des entreprises soutenant le projet pour faire partie d'un comité de pilotage, moyennant participation financière. Les sociétés ayant co-fondé le projet (Intel, NXP, et Linaro) ont été rejointes plus tard par d'autres industriels, comme Nordic Semiconductors dernièrement.
La communauté est très active et utilise principalement des mailings lists pour communiquer. Elle s'appuie sur Jira pour détailler sa roadmap et affecter des membres aux différentes tâches à accomplir. On y soumet son travail via des patches (avec Gerrit) aux mainteneurs des sous-systèmes de l'OS, comme sous Linux. Bien que le but affiché ne soit pas de copier Linux dans une nouvelle version plus légère "from scratch", plusieurs mécanismes en sont inspirés de part l'efficacité de leur modèle, comme nous continuerons à le voir dans cet article.
Particularités
Voyons tout de suite les différences "haut niveau" entre Zephyr et un projet feature-rich comme Contiki qui existe depuis une dizaine d'années.
Zephyr | Contiki | |
---|---|---|
Licence | Apache-2.0 | BSD-3 |
Gérance | Hiérarchique: Linux Foundation & comités de pilotage + contributions individuelles |
Horizontale: contributions individuelles |
Largeur mot-mém. | 32 bit | 8 / 16 / 32 bit |
Architectures | ARC, ARM, NIOS-2, RISCV-32, x86, Xtensa | 6502, 8051, ARM, AVR, MSP430, PIC32, RL78, x86 |
Taille (stack IP comprise) | ~ 30Ko ROM | ~ 30Ko ROM |
Multi-thread | Oui | Oui |
Dynamique | Non: format binaire, ressources allouées statiquement, pas de runtime | Oui: format ELF + modules chargeables dynamiquement, ressources allouées stat. ou dyn., runtime |
Stacks réseau | IPv4/6 (native: en cours), BLE | IPV4/6 (native: uIP + RIME), BLE |
Protocoles réseau+ | COAP, MQTT, HTTP, DHCP, RPL, 6LoWPAN | COAP, HTTP, DHCP, RPL, 6LoWPAN, Telnet, FTP |
On constate que Zephyr propose déjà un certain nombre de fonctionnalités et une empreinte mémoire similaire à l'un des ténors du marché, malgré son jeune âge. On notera l'utilisation d'une licence permissive
souvent jugée comme la plus facile à manier aujourd'hui, Apache-2.0.
Choix de conception
Zephyr est un unikernel, aussi appelé "library-based OS": le code de l'application et celui du kernel sont unis dans un même binaire à la compilation. Un seul espace d'adressage existe ; il n'y a donc pas de notion de niveau de privilège. Les fonctionnalités, elles, sont choisies avant la compilation, dans le but de n'utiliser que le strict nécessaire. Ce choix a plusieurs conséquences :
- Pas de perte de performances due aux changements de contexte ;
- Sécurité améliorée du point de vue des allocations mémoire 100% dynamiques qui deviennent impossibles ;
- Diminution de l'empreinte mémoire ;
- Impossible de ne changer qu'un module sur un device distant devant être mis à jour: il faut re-flasher un nouveau binaire.
Fonctionnalités
Connectivité :
- Une stack IPv6 d'abord copiée sur celle de Contiki (uIP), puis une stack natif (en cours)
- Une stack IPv4 natif
- Une stack BLE natif, actuellement 4.2, en cours d'adaptation pour la version 5
Par rapport à ses concurrents, Zephyr reste un peu en retard et surtout manque de possibilités au niveau du device management. Contiki offre un bon contre-exemple, avec une gestion de son parc de devices facilitée par une plateforme dédiée, Thingsquare. Les comités de pilotage sont cependant conscients de l'importance de cette fonctionnalité ; espérons bientôt voir l'apparition de nouveautés de ce côté.
Sécurit é:
- Bibliothèque cryptographique basée sur TinyCrypt 2
- Protection de pile (canari)
- Binaire statique
- TLS/DTLS
On remarque que pour l'instant, Zephyr offre surtout des possibilités de sécurisation au niveau device. L'ajout d'un support pour TLS est récemment venu renforcer la sécurité au niveau communication. Le TSC a également exprimé le souhait de permettre d'intégrer facilement au kernel des solutions arch-specific telles que Secure Boot dans le futur.
API kernel :
- Threads coopératifs ou préemptibles (round-robin à priorités dynamiques)
- Interruptions et workqueues, ces dernières permettant d'imiter la partie bottom-half des drivers Linux
- Clocks et timers
- Memory pools allouées statiquement, API "malloc"
- Sémaphores & mutexes, alertes (similaires aux signaux dans Linux)
- Message queues, mailboxes, structures de données classiques (LIFO, FIFO, etc.), pipes
- Choix entre API polling (synchrone) ou asynchronous (avec interruptions)
Pas de surprises ici, on trouve les services classiques exposés par un OS. On notera la similarité de certains services avec ceux de Linux.
Sous-systèmes joints au kernel :
- Gestion de l'énergie (kernel "tick-less", API spécifique)
- Stack Bluetooth
- Stack USB
- Stack IP
- Shell minimaliste
- Framework de test
- 2 "lib C" au choix: une basée sur la newlibC et une vraiment minimaliste (minimal)
- API générique d'utilisation de capteurs
Les sous-systèmes sont développés en parallèle; ils sont critiques au succès d'un OS "IoT" et se posent par-dessus le noyau de base, qui pèse moins de 10 Ko en lui-même. La stack IP rajoute typiquement 20 Ko à la taille du système compilé avec l'application.
Utilisation concrète
Clonons le dépôt Git du projet :
git clone https://gerrit.zephyrproject.org/r/zephyr && cd zephyr
L'arborescence des sources évoque celle du kernel Linux:
- Le cœur du système se trouve dans kernel/
- Les parties spécifiques à chaque architecture matérielle dans arch/,
- Les drivers divers (UART, GPIO...) dans drivers/,
- Les sous-systèmes évoqués précédemment ont leur place dans subsys/,
- Le code venant de l'extérieur (bibliothèques HAL notamment) dans ext/,
- La documentation générée dans doc/
Impossible cependant de construire une application ou de contribuer sans les bons outils ! Le SDK Zephyr regroupe toutes les chaînes de compilation croisée nécessaires. Elles ont été générées avec Yocto par des membres de la communauté. On y trouve aussi OpenOCD, le support pour GDB et de quoi faire tourner l'OS sous QEMU. Pour installer le SDK et mettre en place un environnement de développement correct, suivez les instructions du site officiel: https://nexus.zephyrproject.org/content/sites/site/org.zephyrproject.zephyr/dev/getting_started/installation_linux.html
Vous pouvez maintenant compiler les sources, tester sous QEMU... Pour cela, commencez par sourcer le fichier zephyr-env.sh rangé à la racine des sources:
. zephyr-env.sh
Que vous souhaitiez utiliser Zephyr comme OS pour votre projet ou que vous vouliez contribuer (en portant une nouvelle plateforme matérielle par exemple), la documentation vous sera utile, et garantie à jour par rapport à celle du site Web qui peut être un peu en retard. Il va d'abord falloir installer le générateur de documentation Sphinx: http://www.sphinx-doc.org/en/stable/index.html
Puis pour générer la doc, puis l'ouvrir, depuis la racine:
make htmldocs
votre_navigateur_favori doc/_build/html/index.html
Enfin, quand il s'agit de produire un binaire, Zephyr imite complètement Linux sur ces outils:
- Le système de build est Kbuild
- Le système de configuration est Kconfig
C'est un des points forts de Zephyr : quelqu'un qui est déjà habitué à manipuler le kernel Linux retrouve rapidement ses marques. Un avantage retrouvé aussi lorsqu'il s'agit d'écrire un nouveau driver ou de porter sur une nouvelle plateforme. Pour un retour d'expérience récent (parmi les premiers!) sur l'utilisation de Zephyr, je vous recommande la conférence de Fabien Parent, de BayLibre, que vous pouvez trouver facilement sur YouTube: https://www.youtube.com/watch?v=XUJK2htXxKw
Côté applicatif : comment builder ?
La documentation répertorie les boards supportées actuellement par Zephyr. Si vous n'y trouvez pas la vôtre et que vous y tenez absolument, je vous invite à lire la sous-section suivante, qui explique comment contribuer au projet.
La section "Getting Started" de la documentation est très complète et permet de se mettre en jambes rapidement. Dans cet article, nous allons prendre l'exemple "Hello World" disponible dans les sources pour étudier comment compiler une application. N'oubliez pas de sourcer zephyr-env.sh, si ce n'est déjà fait.
D'abord, aller aux sources du mini-projet :
cd samples/hello_world/
Un simple ls nous permet de distinguer :
- Un Makefile
- Des fichiers de configuration *.conf
- Un fichier de configuration testcase.ini
- Des sources, dans src/
Les deux variables qui vont nous intéresser pour compiler sont BOARD et CONF_FILE. La première, vous vous en doutez, permet de sélectionner une board parmi toutes celles que vous trouverez à la racine du projet, dans boards/ (il suffit d'y copier le nom d'un sous-dossier correspondant à la board voulue). La seconde indique le fichier de configuration haut niveau (par défaut, "prj.conf") à utiliser. Par exemple, compilons "Hello World" pour QEMU :
make BOARD=qemu_x86
Notez que nous n'avons pas précisé CONF_FILE et ici prj.conf est vide. Zephyr a donc utilisé la configuration par défaut pour cette board, sans rien y changer. Les fichiers résultants se trouvent dans output/qemu_x86/,
notamment le binaire zephyr.bin qui, sur un vrai device, aurait été déployable. Il est d'ailleurs très petit :
ls -lh outdir/qemu_x86/zephyr.bin
-rwxr-xr-x 1 geleg smileusers 8,9K avril 11 11:48 outdir/qemu_x86/zephyr.bin
On peut ensuite tester simplement le software généré :
make BOARD=qemu_x86 run
Using /home/geleg/zephyr as source for kernel
GEN ./Makefile
CHK include/generated/version.h
CHK misc/generated/configs.c
CHK include/generated/generated_dts_board.h
CHK include/generated/offsets.h
To exit from QEMU enter: 'CTRL+a, x'
[QEMU] CPU: qemu32
qemu-system-i386: warning: Unknown firmware file in legacy mode: genroms/multiboot.bin
***** BOOTING ZEPHYR OS v1.7.99 - BUILD: Apr 11 2017 09:48:32 *****
Hello World! x86
Si vous souhaitez modifier les options de compilation du système, plusieurs choix s'offrent à vous. Ils font tous la même chose (modifier le fichier .config) mais n'ont pas la même priorité au moment de compiler :
- La configuration de base se trouve dans le fichier .config. C'est aussi là que sera inscrite la config finale. En donnant une valeur à BOARD, on demande d'utiliser la config par défaut pour cette board, écrite par la personne qui en a fait le portage.
- Vous pouvez modifier à la main .config, ou utiliser menuconfig (interface graphique avec ncurses, comme pour Linux). Ceci permet d'éditer les valeurs du .config.
- Vous pouvez écrire un fichier .conf et le pointer dans le Makefile, ou utiliser CONF_FILE. Il édite aussi les valeurs du .config.
L'ordre d'utilisation de menuconfig, .conf, etc. détermine qui aura le dernier mot sur les options de configuration en conflit. Il faut juste voir le .conf comme une extension au .config de base avant de passer la main à d'éventuelles modifications manuelles.
Pour plus d'infos, je vous invite à lire la section "Application Development Primer" dans la doc.
Côté contributeur : comment modifier ?
Un travail en cours chez Smile-ECS : le portage de Zephyr sur la populaire board "SensorTag" de Texas Instruments.
Il s'agit d'un device embarquant pléthore de capteurs MEMS capable de communiquer en Bluethooth (low energy), mais aussi 802.15.4 dont 6LoWPAN et ZigBee sont des implémentations possibles.
Il y a deux facettes à ce travail de portage : celle du SoC (System on Chip) et celle de la board complète. Ceci va nous permettre de faire un tour des sources de l'OS pour y voir plus clair.
Comme dit plus haut, le code générique de l'OS se situe dans kernel/. Ce répertoire contient à la fois les mécanismes internes (e.g. la gestion des threads) et l'implémentation de l'API externe, du moins sur la partie générique.
Tout ce qui va dépendre de l'architecture et plus particulièrement du SoC va se trouver dans arch/. Ainsi, pour porter le SoC CC2650 de Texas Instruments utilisé dans le SensorTag, qui est un cœur ARM Cortex-M3. Il a fallu descendre dans arch/arm/soc/ et créer un sous-répertoire dédié. Chaque SoC utilise son propre code, mais parfois les constructeurs de hardware groupent par familles leurs produits, qui dérivent d'une même variation.
Dans Zephyr, on gère ça par un répertoire intermédiaire. Dans mon cas, après discussion avec le mainteneur d'un SoC voisin, le CC3200, j'ai décidé d'utiliser le sous-répertoire "ti_simplelink" qui existait déjà. Tout code mis en commun (e.g. le Cortex-M3 très répandu) se trouve dans arch/arm/core.
Le code du SoC est utilisé par deux portions de Zephyr : le code propre à la board et les drivers.
Au moment de l'écriture de ces lignes, Zephyr est en train de passer à l'utilisation des Device Trees, devenus courants chez les utilisateurs ARM de Linux. Le code de boards/ (multiplexage des pins de la board) est donc destiné à se déplacer vers des fichiers .dts dans dts/ (visible depuis la racine). Cet article n'est pas le lieu pour une introduction à cet outil, mais la documentation de Linux est la meilleure référence à ce jour :
http://www.kernel.org/doc/Documentation/devicetree/
Rapidement : il s'agit d'un langage descriptif du hardware permettant d'écrire des drivers génériques et de dissocier le kernel des spécificités de la plateforme.
Les drivers implémentent les interfaces publiques exposées dans include/: uart.h, gpio.h, etc. Le modèle Linux est utilisé, avec une struct de pointeurs sur fonctions pour l'API bas niveau,
qu'un driver haut-niveau du kernel appellera via des wrappers. En revanche, on n'y trouve pas de notion de driver en mode "caractère" ou "bloc"; Zephyr n'a pas de système de fichier et encore moins de devfs.
Si vous souhaitez utiliser du code tiers déjà fourni pour implémenter un driver, il faudra le placer dans /ext/hal/. La politique de Zephyr sur les HAL semble encore floue. N'hésitez donc pas à signaler votre intention sur la mailing list au préalable.
Pour résumer, un contributeur souhaitant (peut-être par besoin) utiliser Zephyr sur sa plateforme passera le plus clair de son temps dans les répertoires arch/arm/soc et drivers/. Actuellement, la politique du projet pour soumettre un patch de portage demande au minimum :
- UART
- GPIO
- Générateur de nombres aléatoires, si présent en hardware
- Contrôleur d'interruptions
Les devices ARM doivent désormais y ajouter le support du Device Tree, bien qu'il s'agisse encore pour l'instant d'une option de compilation et que peu de choses changent avec son utilisation.
Au niveau des tests, plusieurs applications existent dans samples/ et tests/ pour vérifier le comportement d'un driver ou évaluer la connectivité. Grâce aux fichiers de configuration, ils sont facilement modifiables.
Il est très facile de les sortir hors de l'arborescence du projet puisque sourcer le script zephyr-env.sh suffit pour configurer et builder.
Perspectives d'évolution
Pour conclure, voyons brièvement les buts de Zephyr dans un futur proche. Zephyr tend actuellement vers la version 1.8, pour fin mai 2017.
De nombreuses boards s'ajoutent progressivement à la liste des plateformes supportées par Zephyr. Les architectures micro-contrôleurs 8-bit ou proches, qu'un concurrent comme Contiki sait gérer, lui manquent encore. Récemment, Zephyr a ajouté du support pour les Cortex-M0/M0+, ce qui laisse entrevoir une percée dans cette gamme de machines.
La stack IP réécrite nativement doit se compléter, ainsi que du travail sur l'efficacité du 6LoWPAN.
Le projet offre déjà une excellente documentation et un SDK complet, mais les utilisateurs sous Windows sont actuellement obligés d'utiliser une couche de compatibilité Linux; des réflexions sont en cours pour leur faciliter la vie avec des outils plus cross-platformes.
Liens externes
Vous pouvez trouver diverses informations sur le projet sur son site Web :
https://www.zephyrproject.org/
Enfin, inscrivez-vous à une mailing list si vous souhaitez vous tenir au courant des progrès :
https://lists.zephyrproject.org/mailman/listinfo
Peut-être ne vouliez-vous pas faire l’équivalent d'un tutoriel cependant je pense qu'il vous manque quelques petites choses avant de pouvoir faire un make helloworld (en tout cas cela a été le cas pour moi) :
1) installer le sdk : https://nexus.zephyrproject.org/content/sites/site/org.zephyrproject.ze…
2) faire les exports nécessaires :
$ export ZEPHYR_GCC_VARIANT=zephyr
$ export ZEPHYR_SDK_INSTALL_DIR=/opt/zephyr-sdk #emplacement du 1
3) configurer l'environnement
$ source zephyr-env.sh
4) make
Je n'ai pas réussi à générer la documentation via la commande zephyr-setup-envmake htmldocs (cmd introuvable).
Dernière petite question, votre travail sur le CC2650 a-t-il déjà été commit ou pas encore, je ne trouve que le cc32xx dans ti_simplelink.
Bonne journée