38 points par GN⁺ 2025-09-16 | Aucun commentaire pour le moment. | 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

Aucun commentaire pour le moment.

Aucun commentaire pour le moment.