38 points par GN⁺ 2025-09-16 | 1 commentaires | Partager sur WhatsApp
  • Partage d’une expérience d’implémentation d’un noyau prototype de système d’exploitation à temps partagé sur l’architecture RISC-V
  • Explication, centrée sur la pratique, du concept et du fonctionnement d’un noyau time-sharing, implémenté en Zig plutôt qu’en C afin d’améliorer la reproductibilité
  • Adoption d’une approche unikernel qui regroupe le noyau et le code utilisateur dans un seul binaire, avec une architecture en couches s’appuyant sur OpenSBI pour la sortie console et le contrôle du timer
  • Les threads s’exécutent en mode utilisateur (U-mode), tandis que le noyau effectue les changements de contexte en mode superviseur (S-mode) via des interruptions de timer, et franchit la frontière au moyen des appels système
  • Le point clé est une technique qui remplace la stack frame empilée par le prologue/épilogue d’interruption afin de restaurer l’ensemble des registres et les CSR d’un autre thread, et ainsi basculer le flot d’exécution
  • En s’appuyant sur la machine virtuelle QEMU et une version récente de OpenSBI, l’article propose un environnement d’apprentissage reproductible par tous et relie conceptuellement le spectre de la virtualisation — threads, processus, conteneurs —, ce qui en fait une base utile à des fins pédagogiques et pratiques

Vue d’ensemble

  • Présentation du processus d’implémentation directe d’un noyau de système d’exploitation à temps partagé sur l’architecture RISC-V
  • Le lectorat visé est composé de débutants en logiciels système et en architecture des ordinateurs, d’étudiants, ainsi que d’ingénieurs intéressés par la compréhension des mécanismes bas niveau
  • Cette expérimentation utilise le langage Zig à la place du C afin d’améliorer la reproductibilité des manipulations, avec une installation simple
  • Le code final est publié dans le dépôt popovicu/zig-time-sharing-kernel, avec un léger décalage possible par rapport au texte de l’article
    • Il est recommandé de considérer la version du dépôt comme source de vérité unique plutôt que les extraits de code de l’article
    • Pour les manipulations, il est plus pratique d’aligner l’environnement sur les scripts de l’éditeur de liens et les options de build du dépôt

Recommended reading

  • L’article suppose des bases en architecture des ordinateurs comme les registres, l’adressage mémoire ou les interruptions
    • Comme ressources préalables, il recommande Bare metal on RISC-V, le processus de boot SBI et des exemples d’interruptions de timer
    • L’article sur une micro-distribution Linux peut aussi être utile, de façon optionnelle, pour comprendre la philosophie de séparation entre noyau et espace utilisateur

Unikernel

  • Adoption d’une configuration unikernel qui lie l’application et le noyau de l’OS dans un unique exécutable
    • Cela permet d’éviter la complexité du chargeur et de l’éditeur de liens à l’exécution, tout en simplifiant le chargement du code utilisateur en mémoire aux côtés du noyau
    • Pour un objectif pédagogique et de reproductibilité, cela offre l’avantage d’une distribution simplifiée et d’une cohérence d’environnement

Couche SBI

  • RISC-V utilise un modèle de privilèges en modes M/S/U, et cette expérimentation place OpenSBI en mode M tandis que le noyau fonctionne en mode S
    • La sortie console et le contrôle du périphérique timer sont délégués au SBI afin d’assurer la portabilité
    • En l’absence de SBI, un UART MMIO est utilisé en repli, mais l’usage d’une version récente d’OpenSBI est recommandé pour les manipulations

Goal for the kernel

  • Pour simplifier, seul le support de threads statiques est prévu, et les threads sont constitués de fonctions qui ne se terminent pas
    • Les threads s’exécutent en mode U et envoient des appels système au noyau en mode S
    • Une planification en temps partagé sur cœur unique est implémentée afin de permettre un basculement vers un autre thread à chaque tick du timer

Virtualization and what exactly is a thread

  • Le threading en temps partagé est une forme de virtualisation qui permet d’exécuter plusieurs tâches en parallèle sur un seul cœur sans modifier le modèle de programmation
    • Contrairement à l’ordonnancement coopératif, le basculement se produit via une interruption de timer sans yield explicite
    • Chaque thread possède son propre ensemble de registres intouchable et sa propre pile, tandis que le reste de la mémoire peut être partagé

The stack and memory virtualization

  • Un thread doit disposer de sa propre pile, indispensable au maintien du contexte d’exécution selon la convention d’appel, notamment pour les variables locales et la préservation de ra
    • Le spectre de la virtualisation s’étend de thread < processus < conteneur < VM, avec des niveaux d’isolation et de vue différents
    • Sous Linux, les conteneurs sont mis en œuvre comme une combinaison de mécanismes du noyau tels que chroot et cgroups

Virtualizing a thread

  • L’objectif minimal de virtualisation dans cette expérimentation est de conserver un modèle de programmation inchangé, de protéger les registres et certains CSR, et d’allouer une pile distincte à chaque thread
    • L’article souligne que sans protection de la vue sur les registres, aucun calcul significatif n’est possible
    • Des valeurs initiales comme a0 sont préchargées dans la pile afin de transmettre simplement les arguments au démarrage du thread

Interrupt context

  • Une interruption peut être comprise comme un modèle proche d’un appel de fonction, avec un prologue/épilogue qui sauvegarde et restaure les registres sur la pile
    • Le respect de la convention de sauvegarde est indispensable pour que des interruptions de timer asynchrones ne corrompent pas les registres
    • L’assembleur d’exemple sauvegarde et restaure non seulement x0–x31, mais aussi des CSR comme sstatus, sepc, scause, stval

Implémentation (vue d’ensemble)

Leveraging the interrupt stack convention

  • Le corps de la routine d’interruption se situe entre le prologue et l’épilogue, et si l’on remplace sp par une autre zone mémoire, on restaure alors l’ensemble des registres d’un autre contexte
    • Cela correspond précisément à un changement de contexte, idée centrale de cette implémentation du temps partagé
    • L’interruption de timer intervient périodiquement pour faire alterner le flot principal et le flot d’interruption

Kernel/user space separation

  • La frontière entre noyau en mode S et utilisateur en mode U est maintenue, et le traitement des interruptions comme des appels système est assuré par le trap handler en mode S
    • Le démarrage suit la séquence OpenSBI en mode Minitialisation du noyau en mode Slancement des threads en mode U
    • Des interruptions de timer périodiques rendent possibles l’ordonnancement et le changement de contexte

Implémentation (code)

Assembly startup

  • Dans startup.S, une séquence minimale initialise la BSS et le pointeur de pile initial avant de sauter vers le main de Zig
    • Le point d’entrée du noyau utilise la convention export afin d’assurer l’interopérabilité avec l’ABI C

Main kernel file and I/O drivers

  • Dans kernel.zig, le main commence par vérifier les fonctions console de OpenSBI, puis bascule sur UART MMIO en cas d’échec
    • sbi.debug_print effectue l’appel en configurant les registres a0/a1/a6/a7 conformément au protocole ECALL
    • Après configuration du timer, le code enregistre le gestionnaire d’interruptions en mode S et active les ticks

S-mode handler and the context switch

  • Le gestionnaire est écrit avec la convention naked de Zig afin de construire manuellement un prologue/épilogue complet incluant la sauvegarde des CSR
    • Dans son corps, il appelle handle_kernel(sp), puis remplace sp par la valeur retournée pour décider s’il faut effectuer un basculement
    • scause permet de distinguer un ECALL en mode U d’une interruption de timer et d’aiguiller le traitement

The user space threads

  • Le code utilisateur est inclus avec le noyau dans un binaire unique, et les threads d’exemple répètent affichage d’une chaîne → boucle de temporisation
    • syscall.debug_print place le numéro d’appel système 64 dans a7, ainsi que le buffer et sa longueur dans a0/a1, puis exécute ECALL
    • Lors de l’initialisation des threads, la pile est préchargée avec l’adresse de retour et les valeurs initiales des registres, de sorte que les arguments soient utilisables dès le premier retour

Running the kernel

  • La compilation se fait avec zig build, et l’exécution s’effectue dans QEMU en spécifiant la machine virt + nographic + OpenSBI fw_dynamic
    • Au démarrage, après la bannière OpenSBI, les sorties périodiques de chaque ID de thread apparaissent en alternance
    • Une build avec -Ddebug-logs=true affiche en détail la source des interruptions, la pile courante et les logs d’enfilement/défilement

Conclusion

  • Cette expérimentation modernise un noyau pédagogique avec la combinaison RISC-V + OpenSBI + Zig, en améliorant sa reproductibilité et sa lisibilité
    • Bien qu’elle conserve des simplifications comme une gestion d’erreurs minimale ou des piles surallouées, elle met l’accent sur l’essence du changement de contexte et la séparation des privilèges
    • La portabilité vers une machine réelle reste possible à condition d’ajuster les constantes de l’éditeur de liens et des pilotes et de disposer d’un SBI

Note supplémentaire : récapitulatif du spectre de la virtualisation

  • Threads : surtout virtualisation des registres et de la pile, avec forte possibilité de mémoire partagée
  • Process : isolation mémoire via la virtualisation de l’espace d’adressage, avec possibilité d’inclure plusieurs threads en interne
  • Container : unité d’isolation construite par combinaison d’une vue sur l’environnement comme les espaces de noms du système de fichiers et du réseau
  • VM : vise une virtualisation complète de l’ensemble du matériel

Résumé des points clés d’implémentation

  • Changement de contexte réalisé par remplacement de la pile d’interruption
  • Sauvegarde/restauration de l’état complet, CSR compris, dans le trap handler en mode S
  • Double chemin de sortie avec priorité au SBI, repli sur UART MMIO
  • Ordonnancement simple centré sur des threads statiques, un cœur unique et des tranches de temps
  • Frontière U/S clarifiée par des appels système basés sur ECALL

1 commentaires

 
GN⁺ 2025-09-16
Avis Hacker News
  • On peut découvrir un travail similaire sous forme de « paquet » avec "Operating System in 1000 Lines of Code" ; je l’avais suivi il y a quelque temps en Zig (en convertissant les extraits de code C en Zig au fur et à mesure) et c’était très amusant ; mon code et la VOD sont ici : https://github.com/kristoff-it/kristos/

    • Je me demande quel niveau il faut avoir en Zig pour faire cet exercice ; si quelqu’un a des conseils réalistes sur la façon de s’y essayer sans avoir jamais touché à Zig, en partant du principe qu’on a des bases en C++, je suis preneur
  • Il s’agit d’un envoi séparé par l’auteur de l’article https://news.ycombinator.com/item?id=45236479 ; l’auteur a lui-même recréé le Tiny OS Kernel, voulait plus précisément expérimenter sur RISC-V et dans l’environnement OpenSBI, et a utilisé Zig au lieu du C traditionnel ; je pense qu’on pourrait aussi facilement suivre le même parcours en C ou en Rust ; l’ensemble est un peu brut, mais c’est une expérience et une introduction pour faire ses premiers pas dans le développement de kernel OS et l’architecture des ordinateurs ; ça me semble être un projet amusant à tester le temps d’un week-end ; le walkthrough complet et le lien GitHub sont disponibles ci-dessus

  • Ce genre de projet est vraiment impressionnant ; Linux, au fond, n’est lui aussi qu’un kernel, mais ce travail a ouvert la voie à l’installation d’un Unix open source sur des milliards d’appareils ; je trouve ça vraiment génial

    • Ce qui est encore plus amusant, c’est qu’au tout début de la diffusion de Linux, Torvalds avait écrit dans un e-mail : « c’est juste un hobby, ce ne sera pas gros ni professionnel comme GNU » https://groups.google.com/g/comp.os.minix/c/dlNtH7RRrGA/m/SwRavCzVE7gJ

    • Je ne dirais pas que ce genre de projet est <i>énormément</i> impressionnant ; la manière de créer un kernel multitâche minimal est connue depuis des décennies ; faire un kernel qui boote et exécute quelques tâches simples demande surtout un certain niveau d’intelligence et de persévérance ; sur RISC-V c’est un peu plus compliqué que sur x86, mais les informations d’initialisation matérielle sont faciles à trouver (voir https://wiki.osdev.org/RISC-V_Meaty_Skeleton_with_QEMU_virt_board) ; dans ce cas aussi, l’auteur a lui-même expliqué qu’il s’agissait de « refaire l’exercice réalisé en cours de systèmes d’exploitation » ; à mon avis, toute personne titulaire d’un diplôme en génie logiciel pourrait y arriver ; il y aura bien sûr des bugs ou des parties incomplètes, mais le multiprocessus ou l’isolation des processus via MMU n’est plus quelque chose de difficile aujourd’hui

    • Autant que Linux, le fait que Stallman ait commencé en 1984 à écrire un compilateur C et des utilitaires Unix a aussi ouvert la voie à l’installation d’un Unix open source sur des milliards de machines

  • Zig est vraiment excellent pour le développement d’OS, et RISC-V aussi ; j’avais commencé le même exercice sur x86 mais je me suis vite épuisé à cause de toute la boilerplate legacy ; côté RISC-V, il n’y a presque rien de tout ça, donc c’est bien plus simple pour démarrer https://github.com/Fingel/aeros-v

    • Je pense que si on commence sur x86, il n’y a pas tant de boilerplate que ça à condition d’avoir un bon bootloader ; un chargeur multiboot laisse généralement le mode réel de côté, et comme la plupart des gens veulent le mode protégé, il suffit de configurer quelques tables puis de faire un saut ; si on veut désactiver l’ancien contrôleur d’interruptions, il y a un peu plus à faire, mais on a l’avantage de pouvoir booter sur un PC de bureau (avec quelques précautions pour l’interface console) ; mon OS hobby utilisait un boot BIOS et quelques fonctions VGA, et j’ai souffert de problèmes de compatibilité ; une console série est bien plus simple, mais les machines récentes n’ont souvent plus de port série

    • En pratique, c’est une remise au goût du jour de la sûreté d’Object Pascal ou de Modula-2, reconditionnée avec une syntaxe C ; C n’avait rien de particulièrement spécial en dehors du fait qu’il s’est largement diffusé grâce à la licence d’UNIX

    • J’aimerais bien essayer moi aussi, mais je me demande dans quel environnement vous exécutez le kernel RISC-V ; est-ce uniquement avec Qemu, ou bien avez-vous du matériel réel à recommander ?

  • Je trouve l’ISA RISC-V vraiment très accessible ; la documentation est excellente, il y a énormément d’exemples et beaucoup d’émulateurs ; même le machine code non compressé est facile à lire ; je suis en train d’écrire moi-même un livre pour ma fille tout en construisant un petit OS avec Forth et du time-sharing https://punkx.org/projekt0/book/part1/os.html ; je pense que je n’aurais même pas essayé si c’était du x86 ; j’apprends Forth et l’assembleur RISC-V en même temps au fil du processus, et c’est vraiment un plaisir ; si vous voulez créer un OS jouet à partir de zéro, c’est le moment idéal (aussi enthousiasmant que dans les années 1980) ; prenez une carte RISC-V bon marché (rp2350, etc.) et importez les sections pertinentes du manuel dans une IA comme Claude : ça aide énormément quand on bloque

    • Je viens de regarder le prix des cartes dans mon pays, et je suis surpris de voir à quel point c’est moins cher que je ne le pensais ; je suis à 90 % tenté d’en acheter une juste pour m’amuser
  • Ce genre d’essai est toujours amusant et intéressant ; j’ai aussi envie d’encourager les gens à tenter leur propre chiffrement ou d’autres choses difficiles ; le conseil « n’implémentez pas votre propre crypto » veut dire qu’il ne faut pas utiliser en production quelque chose qui n’a pas été validé en conditions réelles ; pour l’expérimentation ou la recherche, il n’y a pas de danger, donc allez-y sans retenue ; nous avons besoin de plus de systèmes d’exploitation et de plus de choix

  • (citation d’une décision de justice espagnole) Bon, bloquer http, passe encore, mais là c’est abusé...

    • J’ai entendu dire qu’en Espagne, Cloudflare (et probablement d’autres aussi) se fait bloquer par erreur à cause de problèmes liés à la diffusion de matchs de football ; je me demande comment les gens contournent ça, peut-être avec un VPN ? Si des IP importantes sont bloquées, ça doit aussi avoir un impact sur le travail

    • En voyant dans la décision qu’il est question de la Ligue espagnole de football professionnel et de Telefónica Audiovisual Digital, je me dis que ces gens sont des criminels

    • ...quoi ? Une organisation du football espagnol aurait le pouvoir de restreindre l’accès à Internet pour tout un pays ?

  • Je me demande comment se procurer du matériel RISC bon marché

    • AliExpress vend une carte Milk-V Duo S à 10 dollars, elle apparaît souvent dans mes recommandations récentes ; principales caractéristiques : SG2000 Master amélioré, 512 Mo de RAM, E/S plus étendues, Wi-Fi 6/BT5 sur certains modèles (sauf 512M-Basic/eMMC), port hôte USB 2.0, Ethernet 100 Mbps avec prise en charge PoE, double MIPI CSI, interrupteur de bascule de boot entre RISC-V et ARM, etc. https://aliexpress.com/w/wholesale-Milk%2525252dV-Duo-S.html

    • J’ai déjà plusieurs cartes, mais celle que j’ai trouvée intéressante au point de la financer est la VisionFive 2 Lite https://www.kickstarter.com/projects/starfive/visionfive-2-lite-unlock-risc-v-sbc-at-199/description ; je n’ai pas la VisionFive2 de première génération, mais elle a bonne réputation et l’écosystème semble se développer ; il reste encore des choses inachevées, mais j’espère qu’elle sera bientôt expédiée ; celle que j’utilise personnellement, c’est la PolarFire SoC Discovery Kit, une carte avec un RISC-V quadricœur et un FPGA ; c’est un peu cher (130 dollars) et ce n’est pas pour tout le monde, mais le plus amusant, c’est que la carte coûte moins cher que la puce elle-même https://www.microchip.com/en-us/development-tool/MPFS-DISCO-KIT ; la documentation et la toolchain de Microchip sont vieillottes et pas extraordinaires, mais une fois qu’on s’y habitue, exécuter du code bare-metal RISC-V devient vraiment facile ; les exemples Linux/bare-metal sont bien faits

    • Je proposerais déjà de tester sur émulateur sur une machine x86 ou Apple, sans matériel réel ; le développement y est plus rapide que sur une vraie carte, et avec quelque chose comme QEMU on peut s’y mettre tout de suite https://www.qemu.org/docs/master/system/target-riscv.html

    • Le Raspberry Pi Pico 2 prend aussi en charge RISC-V, donc c’est une bonne option https://www.raspberrypi.com/products/raspberry-pi-pico-2/

    • Merci à tous pour vos réponses ; en revanche, je ne vois aucune raison de remercier ceux qui ont downvoté la question