- 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 M → inicialización del kernel en modo S → inicio 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.