38 puntos por GN⁺ 2025-09-16 | Aún no hay comentarios. | Compartir por WhatsApp
  • Comparte la experiencia de implementar un kernel prototipo de sistema operativo con time-sharing en la arquitectura RISC-V
  • Explica de forma práctica el concepto y el funcionamiento de un kernel de time-sharing, e incrementa la reproducibilidad al implementarlo en Zig en lugar de C
  • Adopta un enfoque de unikernel que agrupa el kernel y el código de usuario en un solo binario, y utiliza una capa basada en OpenSBI para la salida de consola y el control del temporizador
  • Los hilos se ejecutan en modo usuario (U-mode), mientras que el kernel realiza el context switch en modo supervisor (S-mode) mediante interrupciones de temporizador, cruzando el límite mediante llamadas al sistema
  • La idea central es una técnica que cambia el stack frame acumulado por el prólogo/epílogo de la interrupción para restaurar el conjunto de registros y los CSR de otro hilo, cambiando así el flujo de ejecución
  • Ofrece un entorno de aprendizaje reproducible para cualquiera, basado en la máquina virtual QEMU y la versión más reciente de OpenSBI, y tiene valor como material base para educación y práctica al conectar conceptualmente el espectro de virtualización de hilos, procesos y contenedores

Descripción general

  • Presenta el proceso de implementar directamente un kernel de sistema operativo con time-sharing sobre la arquitectura RISC-V
  • Sus principales lectores son principiantes en software de sistemas y arquitectura de computadoras, estudiantes e ingenieros interesados en comprender principios de funcionamiento de bajo nivel
  • Este experimento usa el lenguaje Zig en lugar del lenguaje C tradicional para aumentar la reproducibilidad de la práctica, además de que su instalación es sencilla
  • El código final está publicado en el repositorio popovicu/zig-time-sharing-kernel, aunque puede haber una ligera diferencia de sincronización con el contenido del artículo
    • Se recomienda considerar la versión del repositorio como la única fuente de verdad por encima de los fragmentos de código del texto
    • Para la práctica, conviene basar el entorno en el linker script y las opciones de compilación del repositorio

Lectura recomendada

  • El artículo asume conocimientos básicos de arquitectura de computadoras, como registros, direccionamiento de memoria e interrupciones
    • Como material previo, recomienda Bare metal on RISC-V, proceso de arranque de SBI y ejemplos de interrupciones de temporizador
    • El texto sobre microdistribución de Linux también puede ser útil de forma opcional para entender la filosofía de separación entre kernel y espacio de usuario

Unikernel

  • Adopta una configuración de unikernel que enlaza la aplicación y el kernel del SO en un único ejecutable
    • Evita la complejidad del loader y del linker del runtime, y simplifica la carga del código de usuario en memoria junto con el kernel
    • Para fines educativos y de reproducibilidad, ofrece ventajas en simplicidad de distribución y consistencia del entorno

Capa SBI

  • RISC-V usa un modelo de privilegios con modos M/S/U, y en este experimento OpenSBI se ubica en modo M mientras el kernel opera en modo S
    • Delega en SBI la salida de consola y el control del dispositivo temporizador para asegurar la portabilidad
    • Si SBI no está disponible, usa UART MMIO como fallback, aunque para la práctica se recomienda la versión más reciente de OpenSBI

Objetivo del kernel

  • Para simplificar, solo soporta hilos estáticos, y los hilos están compuestos por funciones que no terminan
    • Los hilos se ejecutan en modo U y envían llamadas al sistema al kernel en modo S
    • Implementa scheduling por time-sharing sobre un solo núcleo para que en cada tick del temporizador pueda cambiar a otro hilo

Virtualización y qué es exactamente un hilo

  • El threading con time-sharing es una forma de virtualización que permite ejecutar varias tareas en paralelo sobre un solo núcleo sin cambiar el modelo de programación
    • A diferencia del scheduling cooperativo, el cambio ocurre mediante interrupciones de temporizador sin necesidad de un yield explícito
    • Cada hilo tiene su propio conjunto de registros y stack inaccesibles para los demás, mientras que el resto de la memoria puede compartirse

El stack y la virtualización de memoria

  • Los hilos deben tener un stack separado, un elemento esencial para mantener el contexto de ejecución según la convención de llamadas, incluidas las variables locales y la preservación de ra
    • El espectro de virtualización sigue hilo < proceso < contenedor < VM, y el nivel de aislamiento y la vista cambian en cada caso
    • En Linux, los contenedores se implementan combinando mecanismos del kernel como chroot y cgroups

Virtualizar un hilo

  • El objetivo mínimo de virtualización en este experimento es mantener intacto el modelo de programación, proteger registros y algunos CSR, y asignar un stack individual
    • Se enfatiza que sin una vista protegida de los registros, el cómputo deja de ser significativo
    • Al sembrar valores iniciales como a0 en el stack, el paso de argumentos al inicio del hilo se resuelve de forma sencilla

Contexto de interrupción

  • Las interrupciones pueden entenderse como un modelo similar a una llamada a función en el que el prólogo/epílogo guarda y restaura registros en el stack
    • Para que una interrupción asíncrona de temporizador no corrompa los registros, es indispensable respetar la convención de preservación
    • El ensamblador de ejemplo guarda y restaura no solo x0–x31, sino también CSR como sstatus, sepc, scause y stval

Implementación (alto nivel)

Aprovechar la convención del stack de interrupción

  • El cuerpo de la rutina de interrupción se ubica entre el prólogo y el epílogo; si se reemplaza sp por otra región de memoria, se restaurará el conjunto de registros de otro contexto
    • Eso equivale a un context switch y es la idea central para implementar el time-sharing en este experimento
    • La interrupción de temporizador interviene periódicamente para ejecutar de forma alternada el flujo principal y el flujo de interrupción

Separación entre kernel y espacio de usuario

  • Se mantiene el límite entre kernel en modo S y usuario en modo U, y el manejo de interrupciones y llamadas al sistema se realiza en el trap handler de modo S
    • El arranque sigue la secuencia OpenSBI en modo Minicialización del kernel en modo Sinicio de hilos en modo U
    • Las interrupciones periódicas del temporizador hacen posible el scheduling y el cambio de contexto

Implementación (código)

Inicio en ensamblador

  • En startup.S se compone una secuencia mínima que inicializa BSS, configura el stack pointer inicial y luego salta al main de Zig
    • El punto de entrada del kernel usa la convención export para enlazarse con el ABI de C

Archivo principal del kernel y drivers de I/O

  • El main de kernel.zig primero comprueba las funciones de consola de OpenSBI, y si fallan, usa UART MMIO como fallback
    • sbi.debug_print se invoca configurando los registros a0/a1/a6/a7 de acuerdo con el protocolo ECALL
    • Después de configurar el temporizador, registra el handler de interrupciones de modo S y activa los ticks

Handler de modo S y el context switch

  • El handler está escrito con la convención naked de Zig para construir manualmente un prólogo/epílogo completo que incluye la preservación de CSR
    • En el cuerpo llama a handle_kernel(sp) y decide si debe hacer el cambio sustituyendo por el sp devuelto
    • Usa scause para distinguir entre un ECALL de modo U y una interrupción de temporizador, y bifurca el procesamiento según el caso

Los hilos en espacio de usuario

  • El código de usuario se incluye en el mismo binario junto con el kernel, y el hilo de ejemplo repite impresión de cadenas → bucle de espera
    • syscall.debug_print coloca el número de llamada al sistema 64 en a7 y el buffer/longitud en a0/a1, y luego ejecuta ECALL
    • Durante la inicialización del hilo, se siembran en el stack la dirección de retorno y los valores iniciales de registros para que los argumentos puedan usarse inmediatamente en el primer retorno

Ejecutar el kernel

  • La compilación se hace con zig build, y la ejecución en QEMU especificando máquina virt + nographic + OpenSBI fw_dynamic
    • Al arrancar, después del banner de OpenSBI, aparecen de forma alternada salidas periódicas según el ID de cada hilo
    • Si se compila con -Ddebug-logs=true, se muestran en detalle el origen de la interrupción, el stack actual y los logs de encolado y desencolado

Conclusión

  • Este experimento moderniza un kernel educativo con la combinación RISC-V + OpenSBI + Zig, mejorando la reproducibilidad y la legibilidad
    • Aunque hay simplificaciones, como manejo mínimo de errores y stacks sobredimensionados, el enfoque está puesto en aprender la esencia del context switch y la separación de privilegios
    • La portabilidad a hardware real es posible siempre que se ajusten el linker, las constantes de drivers y se garantice la disponibilidad de SBI

Nota adicional: resumen del espectro de virtualización

  • Threads: centrados en la virtualización de registros y stack, con alta posibilidad de memoria compartida
  • Process: virtualización del espacio de direcciones para aislamiento de memoria, con posibilidad de contener varios hilos internamente
  • Container: unidad de aislamiento construida combinando visibilidad del entorno como filesystem namespaces y network namespaces
  • VM: orientada a la virtualización completa del hardware

Resumen de puntos clave de implementación

  • El context switch se logra mediante reemplazo del stack de interrupción
  • En el trap handler de modo S se preserva y restaura el estado completo, incluidos los CSR
  • Ruta de salida redundante con SBI primero y UART MMIO como fallback
  • Scheduling simple centrado en hilos estáticos, un solo núcleo y time slice
  • Las llamadas al sistema basadas en ECALL clarifican el límite entre U y S

Aún no hay comentarios.

Aún no hay comentarios.