feat(renaser): Fase 7b — boot siembra la imagen, muere el include_bytes!

El kernel deja de empotrar el userspace por completo. Ya no carga ni un
solo .wasm: es boot quien siembra el disco con el grafo poblado.

- kernel/almacen.rs y manifiesto.rs migran al nucleo compartido `formato`
  (tipos, postcard, BLAKE3, trazado de registros). El kernel pierde los
  include_bytes!, genesis() y sembrar_genesis().
- boot::sembrar_grafo siembra un disco virgen con el bytecode de las apps
  (deduplicado) y el Manifiesto de Genesis anclado en el superbloque.
- cargar_userspace sin rama de siembra; wasm/mod.rs sin TECHO_MEMORIA.
- alias `cargo kernel` -> --manifest-path (esquiva un ICE de cargo con
  formato compartido entre el kernel y boot via artifact-dep).

Verificado en QEMU (screendump): disco virgen -> boot siembra 5 objetos,
el kernel monta su grafo; segundo arranque -> boot respeta el disco, la
cronista persiste. formato: 5/5 pruebas.

Nota: el crate `formato` y los 3 Cargo.toml entraron antes en 43e6b32 por
un `git add -A` de un trabajo concurrente; este commit cierra el resto.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-22 18:29:23 +00:00
parent 43e6b32e15
commit 7695dbf3ce
13 changed files with 415 additions and 342 deletions
+7 -2
View File
@@ -13,7 +13,12 @@
bindeps = true bindeps = true
[alias] [alias]
# Compila unicamente el kernel, en aislamiento de arquitectura. # Compila unicamente el kernel, en aislamiento de arquitectura. Se invoca por
kernel = "build -p kernel --target x86_64-unknown-none" # `--manifest-path` y NO por `-p kernel`: desde la Fase 7b el kernel y `boot`
# comparten la crate `formato`, y pedir `-p kernel` dentro del workspace —con
# el kernel a la vez como dependencia de artefacto— hace caer al resolvedor de
# features de cargo. Apuntar al manifiesto del kernel lo compila como raiz, sin
# rozar el grafo de artifact-deps.
kernel = "build --manifest-path kernel/Cargo.toml --target x86_64-unknown-none"
# Construye la imagen UEFI y abre QEMU (equivale a `cargo run -p boot`). # Construye la imagen UEFI y abre QEMU (equivale a `cargo run -p boot`).
qemu = "run -p boot" qemu = "run -p boot"
+6 -4
View File
@@ -2,15 +2,17 @@
# renaser :: archivos que NO se versionan # renaser :: archivos que NO se versionan
# ============================================================================= # =============================================================================
# Artefactos de compilacion de Rust — kernel, boot y apps WASM. # Artefactos de compilacion de Rust — kernel, boot, el nucleo `formato` y apps.
/target /target
/kernel/target /kernel/target
/formato/target
/apps/*/target /apps/*/target
# Bloqueo de dependencias del kernel: se genera al compilarlo en aislamiento. # Bloqueo de dependencias generado al compilar el kernel o el nucleo `formato`
# La verdad de versiones la fija el Cargo.lock raiz — el kernel es dependencia # en aislamiento. La verdad de versiones la fija el Cargo.lock raiz — ambos son
# de artefacto de `boot` y se resuelve con el. # dependencias de `boot` y se resuelven con el.
/kernel/Cargo.lock /kernel/Cargo.lock
/formato/Cargo.lock
# Ajustes locales del entorno de desarrollo (personales, no compartidos). # Ajustes locales del entorno de desarrollo (personales, no compartidos).
/.claude/settings.local.json /.claude/settings.local.json
+59
View File
@@ -528,3 +528,62 @@ un Manifiesto de Génesis que también vive en el grafo.
nacidas del grafo», y la cronista pinta su segunda celda — la cuenta de nacidas del grafo», y la cronista pinta su segunda celda — la cuenta de
arranques sobrevivió al apagón. El userspace nace del grafo en ambos arranques sobrevivió al apagón. El userspace nace del grafo en ambos
casos. casos.
## Fase 7b — La imagen sembrada por `boot`; muere el `include_bytes!` — 2026-05-22
El kernel deja de empotrar el userspace POR COMPLETO. Hasta la 7a, el bytecode
de las cinco apps de génesis aún viajaba dentro del binario del kernel
(`include_bytes!`), como semilla transitoria. La 7b lo mata: es el constructor
de imagen `boot` —en el anfitrión— quien siembra el disco con el grafo ya
poblado. El binario del kernel ya no carga ni un solo `.wasm`.
### Añadido
- **`formato`** — nueva crate: un núcleo `#![no_std]` COMPARTIDO que define el
formato del grafo de objetos en disco. Tipos (`Objeto`, `SuperBloque`,
`Manifiesto`, `EntradaApp`), su (de)serialización `postcard`, la función hash
BLAKE3 (escalar pura) y el trazado de un registro del log
(`componer_registro` / `longitud_registro`). Lo enlazan el kernel bare-metal
Y el anfitrión `boot`: una sola verdad del formato, imposible de divergir
entre los dos lados. 5 pruebas de ida y vuelta. Excluida del workspace, como
el kernel.
- `boot`: `sembrar_grafo` — siembra un disco virgen con el grafo ya poblado.
Graba el bytecode de cada app de génesis como un objeto (deduplicado:
`app.wasm`, usado dos veces, se guarda una sola), compone el Manifiesto de
Génesis con sus regiones y cuotas, lo graba con las aristas hacia los objetos
de bytecode, y forja el superbloque que lo ancla. El `.wasm` se lee de
`kernel/assets/` en tiempo de ejecución.
### Cambiado
- `kernel/manifiesto.rs`: pierde los cuatro `include_bytes!`, el descriptor
`AppGenesis`, `genesis()` y `sembrar_genesis()`. Se reduce a `cargar()` —leer
el manifiesto del grafo— y `region()` —traducir la región en disco a la del
kernel—. Los tipos `Manifiesto` / `EntradaApp` migran a `formato`.
- `kernel/almacen.rs`: los tipos `Objeto` / `SuperBloque`, las constantes del
formato, el hash y el trazado de registros migran a `formato`. El módulo
queda como el almacén VIVO: cursor, índice y E/S contra virtio-blk.
- `kernel_main` / `cargar_userspace`: sin la rama de siembra. Un disco sin
manifiesto anclado ya no se siembra desde el kernel —`boot` lo hace siempre
al forjar la imagen—; el kernel se levanta sin userspace y lo reporta.
- `kernel/Cargo.toml`: deja de declarar `serde` / `postcard` / `blake3` por su
cuenta; los hereda —mismas features, BLAKE3 escalar puro— a través de
`formato`.
- `wasm/mod.rs`: se retira la constante `TECHO_MEMORIA`, ya sin uso (el techo
por-app lo dicta el manifiesto; el valor de génesis vive ahora en `boot`).
- `.cargo/config.toml`: el alias `cargo kernel` pasa de `-p kernel` a
`--manifest-path kernel/Cargo.toml`. Con `formato` compartido entre el kernel
y `boot`, pedir `-p kernel` dentro del workspace —con el kernel a la vez como
dependencia de artefacto— hace caer al resolvedor de features de cargo;
apuntar al manifiesto del kernel lo compila como raíz y esquiva el grafo.
### Verificado
- QEMU (captura headless por el monitor, `screendump`), los dos caminos:
- **Disco virgen** — `boot` imprime «disco de objetos sembrado :: 5 objetos,
manifiesto anclado»; el kernel monta el grafo de `boot` —«grafo montado ::
5 objetos :: raíz ausente», SIN «disco formateado» ni «génesis sembrada»—
e instancia las cinco apps. Pantalla idéntica a la Fase 6.2.
- **Segundo arranque** — `boot` respeta el disco existente («el grafo
perdura»); el kernel carga «6 objetos :: raíz presente» y la cronista pinta
su segunda celda: la persistencia sobrevive a través de un disco sembrado
por `boot`.
- `cargo build -p boot` y `cargo kernel` compilan limpios; `cargo test` de
`formato` — 5/5 en verde.
+8 -5
View File
@@ -70,9 +70,11 @@ QEMU 11, OVMF en `/usr/share/edk2/x64/OVMF.4m.fd` (sin módulo KVM → TCG).
## Estado ## Estado
Fases 1 a 5, la 6.0, la 6.1 completa (sustrato de almacenamiento: sonda PCI, Fases 1 a 5, 6.0, 6.1 y 6.2 completas; y la Fase 7 —el userspace nace del
HAL/DMA y el grafo de objetos) y la 6.2 (E/S de disco asíncrona por grafo de objetos— en sus sub-fases 7a (Manifiesto de Génesis, carga desde el
interrupción) completadas y verificadas en QEMU. Ver `ROADMAP.md`. grafo) y 7b (`boot` siembra la imagen, muere el `include_bytes!` del kernel).
Todo verificado en QEMU. Pendiente: 7c (persistencia inter-sesión). Ver
`ROADMAP.md`.
## Flujo de trabajo ## Flujo de trabajo
@@ -84,5 +86,6 @@ En **cada iteración** de trabajo, sin excepción:
3. Verificar en QEMU si el cambio es observable (con captura de pantalla). 3. Verificar en QEMU si el cambio es observable (con captura de pantalla).
4. `git commit` (mensaje en español, descriptivo) y `git push origin main`. 4. `git commit` (mensaje en español, descriptivo) y `git push origin main`.
El remoto `origin` es `gitea.gioser.net/sergio/renaser`. Mensajes de commit en renaser vive ahora dentro del monorepo **brahman**; los commits van al remoto
español. Verifica una fase en QEMU antes de darla por cerrada. de brahman (`gitea.gioser.net/sergio/brahman`). Mensajes de commit en español.
Verifica una fase en QEMU antes de darla por cerrada.
+11 -3
View File
@@ -96,6 +96,7 @@ name = "boot"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bootloader", "bootloader",
"formato",
"kernel", "kernel",
] ]
@@ -328,6 +329,15 @@ dependencies = [
"ttf-parser", "ttf-parser",
] ]
[[package]]
name = "formato"
version = "0.1.0"
dependencies = [
"blake3",
"postcard",
"serde",
]
[[package]] [[package]]
name = "funty" name = "funty"
version = "2.0.0" version = "2.0.0"
@@ -446,16 +456,14 @@ dependencies = [
name = "kernel" name = "kernel"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"blake3",
"bootloader_api", "bootloader_api",
"crossbeam-queue", "crossbeam-queue",
"embedded-graphics", "embedded-graphics",
"fontdue", "fontdue",
"formato",
"futures-util", "futures-util",
"linked_list_allocator", "linked_list_allocator",
"mirada-layout", "mirada-layout",
"postcard",
"serde",
"spin", "spin",
"virtio-drivers", "virtio-drivers",
"wasmi", "wasmi",
+29
View File
@@ -263,6 +263,35 @@ puerta que no quiso abrirse.
A los ojos casi no cambió nada —las mismas cinco ventanas, encendiéndose—. A los ojos casi no cambió nada —las mismas cinco ventanas, encendiéndose—.
Pero por dentro la mudanza fue total: las casas ya no vienen en la maleta. Pero por dentro la mudanza fue total: las casas ya no vienen en la maleta.
## El último mueble sale de la maleta
La jornada pasada presumió de una mudanza completa, y mintió un poco. Era
cierto que el kernel ya abría su cuaderno y traía a los inquilinos desde el
disco; pero guardaba, doblada en un bolsillo, una copia de todos ellos. Por si
acaso: si alguna vez despertaba en una casa vacía —un disco recién estrenado—,
sacaría esa copia y amueblaría él mismo la casa desde cero.
Hoy ese bolsillo se vació. El kernel ya no lleva encima a nadie: ni una copia,
ni una semilla, ni un recuerdo. Viaja, por fin, ligero de verdad.
¿Y quién amuebla entonces la casa nueva? Quien la construye. Antes, el albañil
—el que funde los planos y levanta los muros— entregaba las llaves de una casa
desnuda. Ahora, antes de entregarlas, entra, sienta a cada inquilino en su
habitación y deja sobre la mesa el cuaderno que dice quién vive dónde y cuánto
sitio se le presta. El kernel, al llegar, ya no encuentra una casa vacía que
llenar: encuentra un hogar tibio, y sólo tiene que abrir las puertas.
Para que esto fuera posible hubo que hacer algo callado pero esencial: que el
albañil y el inquilino hablaran el mismo idioma. Se redactó un pequeño
diccionario común —cómo se nombra una habitación, cómo se describe a un
inquilino, cómo se anota una página del cuaderno— y se entregó una copia a
cada uno. Así, lo que el albañil escribe es, letra por letra, lo que el kernel
lee: ninguna palabra se pierde en la traducción, porque ya no hay traducción.
Hay una sola lengua.
A los ojos, otra vez, casi nada cambió: las mismas cinco ventanas. Pero la
maleta del kernel, esta vez sí, está del todo vacía.
--- ---
*El diario continúa. La próxima página la escribirá la próxima jornada.* *El diario continúa. La próxima página la escribirá la próxima jornada.*
+2 -2
View File
@@ -36,7 +36,7 @@ ataca **incremental**, como las Fases 6.1a/b/c.
## Sub-fases ## Sub-fases
### 7a — El Manifiesto y la carga desde el grafo (semilla por el kernel) ### 7a — El Manifiesto y la carga desde el grafo (semilla por el kernel) — ✅ HECHA
1. **`manifiesto.rs`** *(andamiaje ya creado)* — tipos `Manifiesto` / 1. **`manifiesto.rs`** *(andamiaje ya creado)* — tipos `Manifiesto` /
`EntradaApp`, (de)serialización postcard. Hoy es un módulo `no_std` del `EntradaApp`, (de)serialización postcard. Hoy es un módulo `no_std` del
@@ -58,7 +58,7 @@ ataca **incremental**, como las Fases 6.1a/b/c.
7. **Verificar en QEMU** — la pantalla debe verse idéntica a la Fase 6.2 7. **Verificar en QEMU** — la pantalla debe verse idéntica a la Fase 6.2
(cinco apps en sus regiones), pero ahora **nacidas del grafo**. (cinco apps en sus regiones), pero ahora **nacidas del grafo**.
### 7b — La imagen sembrada por `boot`; muere `include_bytes!` ### 7b — La imagen sembrada por `boot`; muere `include_bytes!` — ✅ HECHA
- `boot` (anfitrión) aprende el formato del grafo y pre-puebla la imagen de - `boot` (anfitrión) aprende el formato del grafo y pre-puebla la imagen de
disco con los objetos de bytecode + el manifiesto. disco con los objetos de bytecode + el manifiesto.
+23 -2
View File
@@ -97,8 +97,29 @@ gobierna, no por el IOAPIC — basta leer la línea que el firmware ya asignó.
Verificado en QEMU: el disco se enruta a la IRQ 11; una tarea-sonda del reactor Verificado en QEMU: el disco se enruta a la IRQ 11; una tarea-sonda del reactor
lee un bloque de forma asíncrona mientras las apps siguen pintando. lee un bloque de forma asíncrona mientras las apps siguen pintando.
Líneas abiertas posteriores: carga/descarga dinámica de apps desde el grafo de ## Fase 7 — el userspace nace del Grafo de Objetos
objetos; más capacidades del host (temporización, audio).
Hasta la Fase 6, el userspace venía **empotrado en el binario del kernel**:
cuatro `include_bytes!` de `.wasm` y regiones escritas a mano. La Fase 7 lo
destierra — las aplicaciones pasan a ser objetos del grafo, gobernadas por un
**Manifiesto de Génesis** que también vive en el grafo. Plan completo en
`FASE7.md`.
- **7a — el Manifiesto (completada).** `manifiesto.rs`: tipos `Manifiesto` /
`EntradaApp` y carga desde el grafo. El superbloque gana el ancla
`manifiesto` (VERSION 1→2). `kernel_main` lee el manifiesto e instancia cada
app recuperando su bytecode del grafo, verificado por su hash.
- **7b — la imagen sembrada por `boot` (completada).** Nace la crate
`formato`, un núcleo `no_std` con el formato del grafo en disco, COMPARTIDO
por el kernel y el constructor de imagen `boot`. `boot` siembra el disco
virgen con el grafo ya poblado —bytecode y manifiesto—; el kernel pierde
todo `include_bytes!` del userspace. Su binario ya no carga ni un `.wasm`.
- **7c — persistencia inter-sesión (pendiente).** Una app guarda su estado
mutado como un objeto nuevo del grafo; el campo `EntradaApp.estado` lo
ancla. Al despertar, la app retoma donde quedó.
Líneas abiertas posteriores: más capacidades del host (temporización, audio);
la Fase 8 — el compositor sobre `mirada-layout`.
## Principios que persisten entre fases ## Principios que persisten entre fases
+186 -16
View File
@@ -3,21 +3,40 @@
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Un kernel bare-metal no nace solo: alguien debe fusionarlo con un cargador, // Un kernel bare-metal no nace solo: alguien debe fusionarlo con un cargador,
// sellarlo en una imagen de disco arrancable y entregarlo al hardware. Esa es // sellarlo en una imagen de disco arrancable y entregarlo al hardware. Esa es
// la unica mision de este orquestador de ANFITRION. // la mision de este orquestador de ANFITRION.
//
// Desde la Fase 7b hace algo mas: SIEMBRA el grafo. El kernel ya no empotra
// el userspace —ni un solo `include_bytes!` de un `.wasm`—; en su lugar, este
// constructor pre-puebla el disco de objetos con el bytecode de las apps de
// genesis y el Manifiesto de Genesis que dicta cuales arrancan, en que region
// y con que cuota. Para ello habla el MISMO formato del grafo que el kernel,
// a traves de la crate compartida `formato`.
// //
// El flujo es deliberadamente lineal y sin ambiguedad: // El flujo es deliberadamente lineal y sin ambiguedad:
// //
// 1. Localizar el ELF nativo del kernel (lo inyecta la dep. de artefacto). // 1. Localizar el ELF nativo del kernel (lo inyecta la dep. de artefacto).
// 2. Fusionarlo con el cargador UEFI en una imagen de disco GPT. // 2. Fusionarlo con el cargador UEFI en una imagen de disco GPT.
// 3. Lanzar QEMU con esa imagen y el firmware OVMF. // 3. Sembrar el disco de objetos: el grafo poblado con el bytecode del
// userspace y el Manifiesto de Genesis (Fase 7b).
// 4. Lanzar QEMU con la imagen, el disco de objetos y el firmware OVMF.
// //
// Cada paso que pueda fallar lo hace en voz alta, con un mensaje accionable: // Cada paso que pueda fallar lo hace en voz alta, con un mensaje accionable:
// preferimos un error claro a un arranque silencioso hacia la nada. // preferimos un error claro a un arranque silencioso hacia la nada.
// ============================================================================= // =============================================================================
use std::collections::BTreeMap;
use std::io::Write;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::Command; use std::process::Command;
// El formato del grafo de objetos en disco — el MISMO nucleo `no_std` que
// enlaza el kernel. Gracias a el, lo que `boot` siembra y lo que el kernel lee
// es, byte a byte, el mismo idioma.
use formato::{
EntradaApp, Hash, Manifiesto, Objeto, SuperBloque, MAGIA, MAX_OBJETO, TAM_SECTOR,
VERSION_MANIFIESTO, VERSION_SUPERBLOQUE,
};
/// Ruta del ELF del kernel, ya compilado para `x86_64-unknown-none`. /// Ruta del ELF del kernel, ya compilado para `x86_64-unknown-none`.
/// ///
/// La dependencia de artefacto define esta variable de entorno en tiempo de /// La dependencia de artefacto define esta variable de entorno en tiempo de
@@ -35,7 +54,8 @@ const NOMBRE_IMAGEN: &str = "renaser-uefi.img";
/// directorio de trabajo —la raiz del repo—, comun a `boot` y a QEMU. /// directorio de trabajo —la raiz del repo—, comun a `boot` y a QEMU.
const NOMBRE_DISCO: &str = "target/disk.img"; const NOMBRE_DISCO: &str = "target/disk.img";
/// Tamaño del disco de objetos: 32 MiB. Se crea como fichero disperso. /// Tamaño del disco de objetos: 32 MiB. La imagen sembrada ocupa solo unos
/// pocos KiB; el resto queda a cero —espacio libre para que el grafo crezca—.
const TAM_DISCO: u64 = 32 * 1024 * 1024; const TAM_DISCO: u64 = 32 * 1024 * 1024;
fn main() { fn main() {
@@ -47,7 +67,7 @@ fn main() {
} }
} }
/// Ejecuta, en orden, las tres operaciones de la Fase 1.5. /// Ejecuta, en orden, las operaciones de la Fase 1.5.
fn orquestar() -> Result<(), String> { fn orquestar() -> Result<(), String> {
// --- 1. Localizar el artefacto del kernel. --- // --- 1. Localizar el artefacto del kernel. ---
let kernel = Path::new(KERNEL_ELF); let kernel = Path::new(KERNEL_ELF);
@@ -67,7 +87,7 @@ fn orquestar() -> Result<(), String> {
.create_disk_image(&imagen) .create_disk_image(&imagen)
.map_err(|e| format!("la crate `bootloader` no pudo crear la imagen UEFI: {e:?}"))?; .map_err(|e| format!("la crate `bootloader` no pudo crear la imagen UEFI: {e:?}"))?;
// --- 3. Garantizar el disco de objetos del grafo persistente. --- // --- 3. Garantizar —y, si es virgen, SEMBRAR— el disco de objetos. ---
preparar_disco_objetos()?; preparar_disco_objetos()?;
// --- 4. Lanzar QEMU sobre esa imagen. --- // --- 4. Lanzar QEMU sobre esa imagen. ---
@@ -75,35 +95,185 @@ fn orquestar() -> Result<(), String> {
lanzar_qemu(&imagen, &ovmf) lanzar_qemu(&imagen, &ovmf)
} }
/// Garantiza la existencia del disco de objetos del grafo persistente. Si no // =============================================================================
/// existe, lo forja como un fichero disperso de 32 MiB, ENTERAMENTE A CERO: el // Fase 7b — la siembra del grafo: el userspace nace de la imagen de disco
/// kernel, al no hallar la firma de su superbloque, lo formateara como un grafo // =============================================================================
/// virgen. Si ya existe, lo respeta — el grafo perdura entre arranques.
/// Una app de genesis: su nombre legible, el `.wasm` que la encarna y la
/// ventana del framebuffer que habitara — `(x, y, ancho, alto)` en pixeles.
struct AppGenesis {
nombre: &'static str,
archivo: &'static str,
region: (u32, u32, u32, u32),
}
/// El userspace de genesis — las cinco aplicaciones que pueblan un disco recien
/// forjado, con las regiones de la Fase 6.2. `app.wasm` aparece dos veces —dos
/// instancias del mismo bytecode—; el grafo, direccionado por contenido, guarda
/// su objeto una sola vez.
const GENESIS: [AppGenesis; 5] = [
AppGenesis { nombre: "hola-izq", archivo: "app.wasm", region: (100, 120, 480, 560) },
AppGenesis { nombre: "hola-der", archivo: "app.wasm", region: (700, 120, 480, 560) },
AppGenesis { nombre: "discola", archivo: "discola.wasm", region: (60, 700, 360, 80) },
AppGenesis { nombre: "glotona", archivo: "glotona.wasm", region: (460, 700, 360, 80) },
AppGenesis { nombre: "cronista", archivo: "cronista.wasm", region: (860, 700, 360, 80) },
];
/// Techo de memoria lineal de cada app de genesis: 4 MiB. Un modulo que intente
/// crecer su memoria mas alla es desalojado por el kernel.
const TECHO_GENESIS: u32 = 4 * 1024 * 1024;
/// Garantiza la existencia del disco de objetos del grafo persistente. Si ya
/// existe, lo RESPETA — el grafo perdura entre arranques (la cuenta de la
/// cronista, el estado del userspace). Si no existe, lo forja Y LO SIEMBRA:
/// graba el grafo ya poblado con el bytecode de las apps y su Manifiesto de
/// Genesis. El kernel jamas vuelve a empotrar una sola app.
fn preparar_disco_objetos() -> Result<(), String> { fn preparar_disco_objetos() -> Result<(), String> {
let disco = Path::new(NOMBRE_DISCO); let disco = Path::new(NOMBRE_DISCO);
if disco.is_file() { if disco.is_file() {
println!("[renaser/boot] disco de objetos presente :: {}", disco.display()); println!(
"[renaser/boot] disco de objetos presente :: {} — el grafo perdura",
disco.display()
);
return Ok(()); return Ok(());
} }
if let Some(directorio) = disco.parent() { if let Some(directorio) = disco.parent() {
std::fs::create_dir_all(directorio) std::fs::create_dir_all(directorio)
.map_err(|e| format!("no se pudo crear el directorio del disco de objetos: {e}"))?; .map_err(|e| format!("no se pudo crear el directorio del disco de objetos: {e}"))?;
} }
// Forjar el disco: un fichero disperso, a cero, de 32 MiB. El kernel
// escribira su superbloque la primera vez que lo monte. // Sembrar el grafo: el bytecode del userspace y el Manifiesto de Genesis.
let fichero = std::fs::File::create(disco) let (imagen, objetos) = sembrar_grafo()?;
if imagen.len() as u64 > TAM_DISCO {
return Err(format!(
"el grafo sembrado ({} bytes) no cabe en el disco de objetos ({TAM_DISCO} bytes)",
imagen.len()
));
}
// Escribir la imagen sembrada y extender el fichero a 32 MiB: el log queda
// al principio; el resto, a cero —`set_len` lo deja disperso—.
let mut fichero = std::fs::File::create(disco)
.map_err(|e| format!("no se pudo crear el disco de objetos «{}»: {e}", disco.display()))?; .map_err(|e| format!("no se pudo crear el disco de objetos «{}»: {e}", disco.display()))?;
fichero
.write_all(&imagen)
.map_err(|e| format!("no se pudo escribir el grafo sembrado: {e}"))?;
fichero fichero
.set_len(TAM_DISCO) .set_len(TAM_DISCO)
.map_err(|e| format!("no se pudo dimensionar el disco de objetos: {e}"))?; .map_err(|e| format!("no se pudo dimensionar el disco de objetos: {e}"))?;
println!( println!(
"[renaser/boot] disco de objetos forjado :: {} ({} MiB, virgen)", "[renaser/boot] disco de objetos sembrado :: {} ({objetos} objetos, manifiesto anclado)",
disco.display(), disco.display()
TAM_DISCO / (1024 * 1024)
); );
Ok(()) Ok(())
} }
/// Lee el bytecode `.wasm` de una app de genesis desde `kernel/assets/`. La
/// ruta se ancla al directorio de ESTE crate —no al de trabajo—: el
/// constructor funciona se invoque desde donde se invoque.
fn leer_wasm(archivo: &str) -> Result<Vec<u8>, String> {
let ruta = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../kernel/assets")
.join(archivo);
std::fs::read(&ruta)
.map_err(|e| format!("no se pudo leer el bytecode «{}»: {e}", ruta.display()))
}
/// Anexa un objeto al log: compone su registro `[longitud][payload][relleno]`,
/// lo añade a la imagen y avanza el cursor. Devuelve el hash del objeto — su
/// identidad en el grafo direccionado por contenido.
fn anexar_objeto(log: &mut Vec<u8>, cursor: &mut u64, payload: &[u8]) -> Result<Hash, String> {
if payload.is_empty() || payload.len() > MAX_OBJETO {
return Err(format!(
"un objeto del grafo tiene un tamaño invalido: {} bytes",
payload.len()
));
}
let hash = formato::hash(payload);
log.extend_from_slice(&formato::componer_registro(payload));
*cursor += formato::sectores_registro(payload.len());
Ok(hash)
}
/// Siembra el grafo de objetos de un disco virgen: graba el bytecode de cada
/// app de genesis como un objeto del grafo, compone el Manifiesto de Genesis
/// —con sus regiones y cuotas—, lo graba con las aristas hacia los objetos de
/// bytecode, y forja el superbloque que lo ancla. Devuelve la imagen del disco
/// (superbloque en el sector 0 + el log de registros) y el numero de objetos
/// sembrados. Habla, byte a byte, el formato que el kernel leera al montar.
fn sembrar_grafo() -> Result<(Vec<u8>, usize), String> {
// El log de registros: del sector 1 en adelante. El sector 0 es el
// superbloque, que aun no podemos escribir —no conocemos el cursor final—.
let mut log: Vec<u8> = Vec::new();
let mut cursor: u64 = 1;
// --- 1. Los objetos de bytecode, DEDUPLICADOS por archivo. Dos apps que
// comparten el mismo `.wasm` comparten un unico objeto del grafo. ---
let mut hash_de: BTreeMap<&str, Hash> = BTreeMap::new();
let mut hijos_manifiesto: Vec<Hash> = Vec::new();
let mut apps: Vec<EntradaApp> = Vec::new();
for app in &GENESIS {
let bytecode = match hash_de.get(app.archivo) {
// Ya grabado: el grafo no guarda dos veces el mismo contenido.
Some(&hash) => hash,
None => {
let datos = leer_wasm(app.archivo)?;
let objeto = Objeto { datos, hijos: Vec::new() };
let payload = objeto.serializar().map_err(|e| e.to_string())?;
let hash = anexar_objeto(&mut log, &mut cursor, &payload)?;
hash_de.insert(app.archivo, hash);
hijos_manifiesto.push(hash);
hash
}
};
let (x, y, ancho, alto) = app.region;
apps.push(EntradaApp {
nombre: app.nombre.to_string(),
bytecode,
region_x: x,
region_y: y,
region_ancho: ancho,
region_alto: alto,
techo_memoria: TECHO_GENESIS,
estado: None,
});
}
// --- 2. El objeto del Manifiesto de Genesis. Sus `hijos` son los objetos
// de bytecode: el grafo lo lee como el nodo padre del userspace. ---
let manifiesto = Manifiesto { version: VERSION_MANIFIESTO, apps };
let man_datos = manifiesto.serializar().map_err(|e| e.to_string())?;
let man_objeto = Objeto { datos: man_datos, hijos: hijos_manifiesto };
let man_payload = man_objeto.serializar().map_err(|e| e.to_string())?;
let hash_manifiesto = anexar_objeto(&mut log, &mut cursor, &man_payload)?;
// El grafo sembrado: un objeto por cada `.wasm` unico, mas el manifiesto.
let objetos = hash_de.len() + 1;
// --- 3. El superbloque: el ancla del grafo, en el sector 0. `raiz` queda
// vacia —el userspace la fija; `manifiesto` apunta a la genesis. ---
let superbloque = SuperBloque {
magia: MAGIA,
version: VERSION_SUPERBLOQUE,
cursor,
raiz: None,
manifiesto: Some(hash_manifiesto),
};
let sb_bytes = superbloque.serializar().map_err(|e| e.to_string())?;
if sb_bytes.len() > TAM_SECTOR {
return Err("el superbloque sembrado no cabe en un sector".to_string());
}
// La imagen: el superbloque en el sector 0 (relleno a cero) y, tras el, el
// log de registros que acabamos de componer.
let mut imagen = vec![0u8; TAM_SECTOR];
imagen[..sb_bytes.len()].copy_from_slice(&sb_bytes);
imagen.extend_from_slice(&log);
Ok((imagen, objetos))
}
/// Calcula la ruta de la imagen: junto al propio ELF del kernel, es decir, /// Calcula la ruta de la imagen: junto al propio ELF del kernel, es decir,
/// dentro de `target/`. Una ubicacion predecible y siempre escribible. /// dentro de `target/`. Una ubicacion predecible y siempre escribible.
fn ruta_imagen(kernel: &Path) -> PathBuf { fn ruta_imagen(kernel: &Path) -> PathBuf {
+40 -89
View File
@@ -16,63 +16,27 @@
// El disco se organiza como un LOG: el sector 0 es el superbloque —el ancla // El disco se organiza como un LOG: el sector 0 es el superbloque —el ancla
// del grafo—, y tras el se anexan los registros de objetos, uno tras otro. Un // del grafo—, y tras el se anexan los registros de objetos, uno tras otro. Un
// indice en memoria (hash -> sector) se reconstruye al arrancar recorriendo el // indice en memoria (hash -> sector) se reconstruye al arrancar recorriendo el
// log. La serializacion la hace `postcard`: binaria, compacta, determinista. // log.
//
// El FORMATO en disco —los tipos `Objeto`/`SuperBloque`, su (de)serializacion
// `postcard`, el hash BLAKE3 y el trazado de cada registro— ya no vive aqui:
// habita la crate `formato` (Fase 7b), un nucleo `no_std` COMPARTIDO con el
// constructor de imagen `boot`. Este modulo es solo el almacen VIVO: el
// cursor, el indice y la E/S contra el disco virtio-blk.
// ============================================================================= // =============================================================================
use alloc::collections::BTreeMap; use alloc::collections::BTreeMap;
use alloc::vec; use alloc::vec;
use alloc::vec::Vec; use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
use spin::{Mutex, Once}; use spin::{Mutex, Once};
use crate::drivers::disco::{self, TAM_SECTOR}; use crate::drivers::disco;
/// Firma magica del superbloque — «RENASer GRaFo». Distingue un disco de // El identificador y el objeto del grafo los define `formato`; se reexportan
/// renaser de uno virgen o ajeno. // para que el resto del kernel siga nombrandolos `almacen::Hash` y
const MAGIA: [u8; 8] = *b"RENASGRF"; // `almacen::Objeto`, sin enterarse de donde viven realmente.
pub use formato::{Hash, Objeto};
/// Version del formato en disco. Un disco con otra version se reformatea.
/// v2 (Fase 7) — el superbloque gana el ancla `manifiesto`; un disco v1 se
/// reformatea al arrancar, como cualquier disco ajeno.
const VERSION: u32 = 2;
/// Techo del tamaño de un objeto serializado: 1 MiB. Acota los buferes de E/S
/// y permite descartar un registro corrupto sin intentar leer un disparate.
const MAX_OBJETO: usize = 1024 * 1024;
/// El identificador de un objeto: el hash BLAKE3 de su forma serializada. En un
/// almacen direccionado por contenido, la identidad ES el contenido.
pub type Hash = [u8; 32];
/// Un objeto del grafo: una carga util opaca y las aristas que lo enlazan con
/// otros objetos. Los `hijos` hacen del almacen un DAG —no un arbol, no una
/// lista—: un objeto puede ser hijo de muchos, y el direccionamiento por
/// contenido garantiza que cada contenido distinto se guarda una sola vez.
#[derive(Serialize, Deserialize, Clone)]
pub struct Objeto {
/// La carga util del objeto: bytes crudos, que el kernel no interpreta.
pub datos: Vec<u8>,
/// Los hashes de los objetos hijos: las aristas salientes del DAG.
pub hijos: Vec<Hash>,
}
/// El superbloque: el sector 0 del disco. Ancla el grafo entero — dice por
/// donde continua el log, cual es el objeto raiz y cual el manifiesto.
#[derive(Serialize, Deserialize)]
struct SuperBloque {
/// Firma magica: debe ser [`MAGIA`].
magia: [u8; 8],
/// Version del formato: debe ser [`VERSION`].
version: u32,
/// Proximo sector libre del log — donde se anexara el siguiente objeto.
cursor: u64,
/// El objeto raiz del DAG: el punto de entrada que el userspace fija y lee.
raiz: Option<Hash>,
/// El Manifiesto de Genesis (Fase 7): el objeto que dicta que apps nacen
/// del grafo al arrancar. Ancla del kernel, gemela de `raiz` (del userspace).
manifiesto: Option<Hash>,
}
/// El estado vivo del almacen: el cursor del log, la raiz, el manifiesto y el /// El estado vivo del almacen: el cursor del log, la raiz, el manifiesto y el
/// indice en memoria que traduce cada hash al sector donde habita su registro. /// indice en memoria que traduce cada hash al sector donde habita su registro.
@@ -104,12 +68,6 @@ pub struct Resumen {
pub formateado: bool, pub formateado: bool,
} }
/// Numero de sectores que ocupa un registro cuyo payload mide `longitud` bytes.
/// Cada registro en disco es: `[longitud: u32 LE][payload postcard][relleno 0]`.
fn sectores_registro(longitud: usize) -> u64 {
(4 + longitud).div_ceil(TAM_SECTOR) as u64
}
/// Funda el almacen de objetos: monta el disco, lee el superbloque y, si el /// Funda el almacen de objetos: monta el disco, lee el superbloque y, si el
/// disco ya es de renaser, reconstruye el indice recorriendo el log; si es /// disco ya es de renaser, reconstruye el indice recorriendo el log; si es
/// virgen o ajeno, lo formatea. Toda falla se devuelve como `Err`. /// virgen o ajeno, lo formatea. Toda falla se devuelve como `Err`.
@@ -120,13 +78,13 @@ pub fn init() -> Result<Resumen, &'static str> {
} }
// Leer el sector 0 e intentar interpretarlo como superbloque de renaser. // Leer el sector 0 e intentar interpretarlo como superbloque de renaser.
let mut sector0 = [0u8; TAM_SECTOR]; let mut sector0 = [0u8; formato::TAM_SECTOR];
disco::leer_sectores(0, &mut sector0)?; disco::leer_sectores(0, &mut sector0)?;
let (cursor, raiz, manifiesto, indice, formateado) = let (cursor, raiz, manifiesto, indice, formateado) =
match postcard::take_from_bytes::<SuperBloque>(&sector0) { match formato::SuperBloque::deserializar(&sector0) {
// Disco de renaser, con la version corriente: adoptar su grafo. // Disco de renaser, con la version corriente: adoptar su grafo.
Ok((sb, _)) if sb.magia == MAGIA && sb.version == VERSION => { Ok(sb) if sb.magia == formato::MAGIA && sb.version == formato::VERSION_SUPERBLOQUE => {
let indice = reconstruir_indice(sb.cursor)?; let indice = reconstruir_indice(sb.cursor)?;
(sb.cursor, sb.raiz, sb.manifiesto, indice, false) (sb.cursor, sb.raiz, sb.manifiesto, indice, false)
} }
@@ -166,13 +124,11 @@ fn reconstruir_indice(cursor: u64) -> Result<BTreeMap<Hash, u64>, &'static str>
let mut indice = BTreeMap::new(); let mut indice = BTreeMap::new();
let mut sector: u64 = 1; let mut sector: u64 = 1;
while sector < cursor { while sector < cursor {
let payload = leer_registro(sector)?; match leer_registro(sector)? {
match payload {
// Un payload valido: hashearlo e indexarlo. // Un payload valido: hashearlo e indexarlo.
Some(payload) => { Some(payload) => {
let n = sectores_registro(payload.len()); let n = formato::sectores_registro(payload.len());
let hash = *blake3::hash(&payload).as_bytes(); indice.insert(formato::hash(&payload), sector);
indice.insert(hash, sector);
sector += n; sector += n;
} }
// Cabecera a cero o longitud imposible: fin (o corrupcion) del log. // Cabecera a cero o longitud imposible: fin (o corrupcion) del log.
@@ -186,19 +142,18 @@ fn reconstruir_indice(cursor: u64) -> Result<BTreeMap<Hash, u64>, &'static str>
/// (sin la cabecera de longitud ni el relleno). `None` si la cabecera dice /// (sin la cabecera de longitud ni el relleno). `None` si la cabecera dice
/// longitud cero —fin del log— o una longitud imposible —corrupcion—. /// longitud cero —fin del log— o una longitud imposible —corrupcion—.
fn leer_registro(sector: u64) -> Result<Option<Vec<u8>>, &'static str> { fn leer_registro(sector: u64) -> Result<Option<Vec<u8>>, &'static str> {
let mut cabecera = [0u8; TAM_SECTOR]; let mut cabecera = [0u8; formato::TAM_SECTOR];
disco::leer_sectores(sector, &mut cabecera)?; disco::leer_sectores(sector, &mut cabecera)?;
let longitud = let longitud = match formato::longitud_registro(&cabecera) {
u32::from_le_bytes([cabecera[0], cabecera[1], cabecera[2], cabecera[3]]) as usize; Some(longitud) => longitud,
if longitud == 0 || longitud > MAX_OBJETO { None => return Ok(None),
return Ok(None); };
} let n = formato::sectores_registro(longitud) as usize;
let n = sectores_registro(longitud) as usize;
// Si el registro cabe en el sector ya leido, evitar una segunda lectura. // Si el registro cabe en el sector ya leido, evitar una segunda lectura.
let payload = if n == 1 { let payload = if n == 1 {
cabecera[4..4 + longitud].to_vec() cabecera[4..4 + longitud].to_vec()
} else { } else {
let mut buf = vec![0u8; n * TAM_SECTOR]; let mut buf = vec![0u8; n * formato::TAM_SECTOR];
disco::leer_sectores(sector, &mut buf)?; disco::leer_sectores(sector, &mut buf)?;
buf[4..4 + longitud].to_vec() buf[4..4 + longitud].to_vec()
}; };
@@ -207,18 +162,18 @@ fn leer_registro(sector: u64) -> Result<Option<Vec<u8>>, &'static str> {
/// Graba el superbloque —el ancla del grafo— en el sector 0. /// Graba el superbloque —el ancla del grafo— en el sector 0.
fn persistir(almacen: &Almacen) -> Result<(), &'static str> { fn persistir(almacen: &Almacen) -> Result<(), &'static str> {
let sb = SuperBloque { let sb = formato::SuperBloque {
magia: MAGIA, magia: formato::MAGIA,
version: VERSION, version: formato::VERSION_SUPERBLOQUE,
cursor: almacen.cursor, cursor: almacen.cursor,
raiz: almacen.raiz, raiz: almacen.raiz,
manifiesto: almacen.manifiesto, manifiesto: almacen.manifiesto,
}; };
let bytes = postcard::to_allocvec(&sb).map_err(|_| "no se pudo serializar el superbloque")?; let bytes = sb.serializar()?;
if bytes.len() > TAM_SECTOR { if bytes.len() > formato::TAM_SECTOR {
return Err("el superbloque no cabe en un sector"); return Err("el superbloque no cabe en un sector");
} }
let mut sector0 = [0u8; TAM_SECTOR]; let mut sector0 = [0u8; formato::TAM_SECTOR];
sector0[..bytes.len()].copy_from_slice(&bytes); sector0[..bytes.len()].copy_from_slice(&bytes);
disco::escribir_sectores(0, &sector0) disco::escribir_sectores(0, &sector0)
} }
@@ -228,13 +183,12 @@ fn persistir(almacen: &Almacen) -> Result<(), &'static str> {
/// se devuelve el hash que ya tenia. El grafo nunca guarda dos veces lo mismo. /// se devuelve el hash que ya tenia. El grafo nunca guarda dos veces lo mismo.
pub fn almacenar(datos: Vec<u8>, hijos: Vec<Hash>) -> Result<Hash, &'static str> { pub fn almacenar(datos: Vec<u8>, hijos: Vec<Hash>) -> Result<Hash, &'static str> {
let objeto = Objeto { datos, hijos }; let objeto = Objeto { datos, hijos };
let bytes = let bytes = objeto.serializar()?;
postcard::to_allocvec(&objeto).map_err(|_| "no se pudo serializar el objeto")?; if bytes.is_empty() || bytes.len() > formato::MAX_OBJETO {
if bytes.is_empty() || bytes.len() > MAX_OBJETO {
return Err("el objeto tiene un tamaño invalido"); return Err("el objeto tiene un tamaño invalido");
} }
// La identidad del objeto: el hash de su forma serializada. // La identidad del objeto: el hash de su forma serializada.
let hash = *blake3::hash(&bytes).as_bytes(); let hash = formato::hash(&bytes);
let mutex = ALMACEN.get().ok_or("almacen no inicializado")?; let mutex = ALMACEN.get().ok_or("almacen no inicializado")?;
let mut almacen = mutex.lock(); let mut almacen = mutex.lock();
@@ -245,16 +199,14 @@ pub fn almacenar(datos: Vec<u8>, hijos: Vec<Hash>) -> Result<Hash, &'static str>
} }
// Reservar los sectores del registro al final del log. // Reservar los sectores del registro al final del log.
let n = sectores_registro(bytes.len()); let n = formato::sectores_registro(bytes.len());
if almacen.cursor + n > almacen.capacidad { if almacen.cursor + n > almacen.capacidad {
return Err("el grafo de objetos esta lleno"); return Err("el grafo de objetos esta lleno");
} }
let sector = almacen.cursor; let sector = almacen.cursor;
// Componer el registro: [longitud][payload][relleno a cero] y grabarlo. // Componer el registro [longitud][payload][relleno] y grabarlo.
let mut registro = vec![0u8; n as usize * TAM_SECTOR]; let registro = formato::componer_registro(&bytes);
registro[0..4].copy_from_slice(&(bytes.len() as u32).to_le_bytes());
registro[4..4 + bytes.len()].copy_from_slice(&bytes);
disco::escribir_sectores(sector, &registro)?; disco::escribir_sectores(sector, &registro)?;
// El objeto ya esta en disco: avanzar el cursor, indexarlo y RE-anclar el // El objeto ya esta en disco: avanzar el cursor, indexarlo y RE-anclar el
@@ -279,12 +231,10 @@ pub fn recuperar(hash: &Hash) -> Result<Option<Objeto>, &'static str> {
let payload = leer_registro(sector)?.ok_or("registro de objeto corrupto")?; let payload = leer_registro(sector)?.ok_or("registro de objeto corrupto")?;
// Verificacion de integridad: el contenido leido DEBE rehashear al hash // Verificacion de integridad: el contenido leido DEBE rehashear al hash
// pedido. Si no, el disco ha mentido — y se delata. // pedido. Si no, el disco ha mentido — y se delata.
if *blake3::hash(&payload).as_bytes() != *hash { if formato::hash(&payload) != *hash {
return Err("el objeto no supero la verificacion de integridad"); return Err("el objeto no supero la verificacion de integridad");
} }
let (objeto, _) = postcard::take_from_bytes::<Objeto>(&payload) Ok(Some(Objeto::deserializar(&payload)?))
.map_err(|_| "no se pudo deserializar el objeto")?;
Ok(Some(objeto))
} }
/// El hash del objeto raiz del grafo, si lo hay. /// El hash del objeto raiz del grafo, si lo hay.
@@ -309,6 +259,7 @@ pub fn manifiesto() -> Option<Hash> {
/// Ancla un objeto como el Manifiesto de Genesis y graba el cambio en el /// Ancla un objeto como el Manifiesto de Genesis y graba el cambio en el
/// superbloque. Gemelo de [`fijar_raiz`]. /// superbloque. Gemelo de [`fijar_raiz`].
#[allow(dead_code)] // Lo usara la Fase 7c (persistencia inter-sesion).
pub fn fijar_manifiesto(hash: Hash) -> Result<(), &'static str> { pub fn fijar_manifiesto(hash: Hash) -> Result<(), &'static str> {
let mutex = ALMACEN.get().ok_or("almacen no inicializado")?; let mutex = ALMACEN.get().ok_or("almacen no inicializado")?;
let mut almacen = mutex.lock(); let mut almacen = mutex.lock();
+12 -16
View File
@@ -134,7 +134,7 @@ async fn tarea_sonda_disco() {
/// corrupto, o la carga fracasa, se salda pintando la region de la app con /// corrupto, o la carga fracasa, se salda pintando la region de la app con
/// la baliza de desalojo — el kernel no se inmuta y sigue con las demas. /// la baliza de desalojo — el kernel no se inmuta y sigue con las demas.
fn encender_app(ejecutor: &mut Executor, entrada: &manifiesto::EntradaApp) { fn encender_app(ejecutor: &mut Executor, entrada: &manifiesto::EntradaApp) {
let region = entrada.region(); let region = manifiesto::region(entrada);
// Recuperar el bytecode del grafo. `recuperar` recomputa el hash del // Recuperar el bytecode del grafo. `recuperar` recomputa el hash del
// objeto y verifica su integridad: un bytecode corrupto se delata aqui // objeto y verifica su integridad: un bytecode corrupto se delata aqui
// —y la app se niega, no se instancia un modulo en el que no se confia. // —y la app se niega, no se instancia un modulo en el que no se confia.
@@ -163,24 +163,20 @@ fn reportar(linea: &str) {
} }
/// FASE 7 :: puebla el userspace DESDE EL GRAFO. Carga el Manifiesto de /// FASE 7 :: puebla el userspace DESDE EL GRAFO. Carga el Manifiesto de
/// Genesis; si el disco no tiene uno —disco virgen—, lo siembra y lo vuelve a /// Genesis que `boot` sembro en la imagen de disco y, por cada `EntradaApp`,
/// cargar. Por cada `EntradaApp`, enciende su aplicacion. Toda falla se /// enciende su aplicacion. Toda falla se reporta a la consola y NO detiene el
/// reporta a la consola y NO detiene el arranque: el kernel se levanta con /// arranque: el kernel se levanta con las apps que pueda — o con ninguna, si
/// las apps que pueda — o con ninguna, si el grafo no tiene userspace. /// el grafo no tiene userspace.
fn cargar_userspace(ejecutor: &mut Executor) { fn cargar_userspace(ejecutor: &mut Executor) {
let manifiesto = match manifiesto::cargar() { let manifiesto = match manifiesto::cargar() {
Ok(Some(m)) => Some(m), Ok(Some(m)) => Some(m),
// Disco sin manifiesto: sembrar la genesis y volver a cargarlo. // Disco sin manifiesto anclado: `boot` no lo sembro. El kernel se
Ok(None) => match manifiesto::sembrar_genesis() { // levanta sin userspace —pero se levanta—; en la practica, ninguna
Ok(_) => { // imagen forjada por `boot` llega aqui sin su Manifiesto de Genesis.
reportar("manifiesto :: genesis sembrada en disco virgen"); Ok(None) => {
manifiesto::cargar().ok().flatten() reportar("manifiesto :: el disco no trae uno -- el kernel se levanta solo");
}
Err(motivo) => {
reportar(&format!("manifiesto :: siembra fallida -- {motivo}"));
None None
} }
},
Err(motivo) => { Err(motivo) => {
reportar(&format!("manifiesto :: carga fallida -- {motivo}")); reportar(&format!("manifiesto :: carga fallida -- {motivo}"));
None None
@@ -333,10 +329,10 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
// --- 7. FASE 7 :: levantar el reactor y poblar el userspace DESDE EL // --- 7. FASE 7 :: levantar el reactor y poblar el userspace DESDE EL
// GRAFO. El kernel ya no empotra los modulos WASM: lee el // GRAFO. El kernel ya no empotra los modulos WASM: lee el
// Manifiesto de Genesis —si el disco esta virgen, lo siembra— e // Manifiesto de Genesis que `boot` sembro en la imagen de disco e
// instancia cada `EntradaApp` recuperando su bytecode del grafo de // instancia cada `EntradaApp` recuperando su bytecode del grafo de
// objetos. Las cinco apps de genesis (dos instancias de hello, la // objetos. Las cinco apps de genesis (dos instancias de hello, la
// discola, la glotona y la cronista) nacen ahora del disco, no del // discola, la glotona y la cronista) nacen del disco, no del
// binario del kernel. // binario del kernel.
// //
// Las interrupciones se habilitan AHORA: el temporizador marcara el // Las interrupciones se habilitan AHORA: el temporizador marcara el
+29 -191
View File
@@ -3,216 +3,54 @@
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Hasta la Fase 6, el userspace venia EMPOTRADO en el binario del kernel: // Hasta la Fase 6, el userspace venia EMPOTRADO en el binario del kernel:
// `include_bytes!` de cada `.wasm` y regiones escritas a mano. La Fase 7 lo // `include_bytes!` de cada `.wasm` y regiones escritas a mano. La Fase 7 lo
// destierra: las aplicaciones pasan a ser OBJETOS DEL GRAFO, y lo que arranca // desterro: las aplicaciones son OBJETOS DEL GRAFO, y lo que arranca —con que
// —con que cuota, en que region— lo dicta este Manifiesto de Genesis, que // cuota, en que region— lo dicta este Manifiesto de Genesis, que tambien
// tambien habita el grafo. El superbloque guarda su hash en un ancla propia. // habita el grafo. El superbloque guarda su hash en un ancla propia.
// //
// El kernel, al despertar: lee el ancla del superbloque, recupera el objeto // ESTADO: Fase 7b. El kernel ya NO empotra una sola app. La siembra de la
// del manifiesto, lo deserializa, y por cada `EntradaApp` recupera el objeto // imagen —grabar los objetos de bytecode y el manifiesto en un disco virgen—
// de bytecode —verificado por su hash— y lo inyecta en `wasmi`. // la hace por completo el constructor de imagen `boot`, en el anfitrion. Este
// modulo se reduce a su esencia: LEER el manifiesto del grafo al arrancar.
// //
// ESTADO: Fase 7a. Tipos, (de)serializacion, carga desde el grafo y siembra // Los tipos `Manifiesto` / `EntradaApp` y su (de)serializacion viven en la
// de la genesis, implementados. La siembra es TRANSITORIA — el bytecode aun // crate `formato`, el nucleo `no_std` compartido con `boot`. Aqui solo queda
// viaja empotrado (`include_bytes!`, abajo); la Fase 7b lo movera al // lo que es del kernel: recuperar el manifiesto del grafo y traducir las
// constructor de imagen `boot` y el kernel dejara de empotrar una sola app. // regiones de su formato en disco (`u32`) a la `RegionPantalla` del kernel.
// Ver `FASE7.md` para el plan completo.
// ============================================================================= // =============================================================================
use alloc::string::String; use crate::almacen;
use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
use crate::almacen::Hash;
use crate::grafico::RegionPantalla; use crate::grafico::RegionPantalla;
/// Version del formato del manifiesto serializado. Independiente de la // Los tipos del manifiesto los define `formato`; se reexportan para que el
/// version del superbloque (`almacen::VERSION`): el manifiesto es un objeto // resto del kernel los nombre `manifiesto::EntradaApp` / `manifiesto::Manifiesto`.
/// del grafo, no una estructura de disco. pub use formato::{EntradaApp, Manifiesto};
pub const VERSION_MANIFIESTO: u32 = 1;
/// El Manifiesto de Genesis: la lista de aplicaciones que el kernel instancia /// Traduce la sub-region de una `EntradaApp` —campos `u32` de ancho fijo, el
/// al arrancar. Vive como un objeto del grafo de objetos; el superbloque /// formato en disco— a la `RegionPantalla` que el kernel entiende (`usize`,
/// guarda su hash en el campo `manifiesto`. /// ancho de plataforma). El puente entre lo que el disco guarda y lo que el
#[derive(Serialize, Deserialize, Clone)] /// compositor dibuja.
pub struct Manifiesto { pub fn region(entrada: &EntradaApp) -> RegionPantalla {
/// Version del formato — debe ser [`VERSION_MANIFIESTO`].
pub version: u32,
/// Las aplicaciones del userspace, en orden de arranque.
pub apps: Vec<EntradaApp>,
}
/// Una entrada del manifiesto: una aplicacion del userspace y todo lo que el
/// kernel necesita para darle vida — su bytecode, su ventana, su cuota de
/// memoria y, si lo tuviera, su ultimo estado persistido.
#[derive(Serialize, Deserialize, Clone)]
pub struct EntradaApp {
/// Nombre legible — para los rotulos de la consola y la baliza.
pub nombre: String,
/// Hash del objeto del grafo que contiene el bytecode WASM de la app.
pub bytecode: Hash,
/// Sub-region del framebuffer asignada a la app. Campos de ancho fijo
/// `u32` A PROPOSITO: esto es un formato EN DISCO. `RegionPantalla` usa
/// `usize` (ancho dependiente de plataforma) y no sirve para serializar.
pub region_x: u32,
pub region_y: u32,
pub region_ancho: u32,
pub region_alto: u32,
/// Techo de memoria lineal de la app, en bytes. Sustituye a la constante
/// global `wasm::TECHO_MEMORIA` — cada app lleva su cuota.
pub techo_memoria: u32,
/// Hash del ultimo estado persistido de la app (Fase 7c). `None` hasta
/// que la app guarde estado por primera vez.
pub estado: Option<Hash>,
}
impl EntradaApp {
/// Construye la `RegionPantalla` que el kernel entiende a partir de los
/// campos de ancho fijo del manifiesto.
pub fn region(&self) -> RegionPantalla {
RegionPantalla { RegionPantalla {
x: self.region_x as usize, x: entrada.region_x as usize,
y: self.region_y as usize, y: entrada.region_y as usize,
ancho: self.region_ancho as usize, ancho: entrada.region_ancho as usize,
alto: self.region_alto as usize, alto: entrada.region_alto as usize,
}
}
}
impl Manifiesto {
/// Serializa el manifiesto a su forma binaria `postcard` — la carga util
/// del objeto del grafo que lo aloja.
pub fn serializar(&self) -> Result<Vec<u8>, &'static str> {
postcard::to_allocvec(self).map_err(|_| "manifiesto :: serializacion fallida")
}
/// Reconstruye un manifiesto desde la carga util de su objeto. Rechaza
/// un formato de version desconocida en lugar de malinterpretarlo.
pub fn deserializar(bytes: &[u8]) -> Result<Manifiesto, &'static str> {
let (manifiesto, _) = postcard::take_from_bytes::<Manifiesto>(bytes)
.map_err(|_| "manifiesto :: deserializacion fallida")?;
if manifiesto.version != VERSION_MANIFIESTO {
return Err("manifiesto :: version de formato desconocida");
}
Ok(manifiesto)
} }
} }
/// Lee el manifiesto del grafo: toma su hash del ancla del superbloque, /// Lee el manifiesto del grafo: toma su hash del ancla del superbloque,
/// recupera el objeto y lo deserializa. `Ok(None)` si el disco aun no tiene /// recupera el objeto y lo deserializa. `Ok(None)` si el disco no tiene
/// manifiesto anclado — el caller debe entonces sembrar la genesis. /// manifiesto anclado —un disco que `boot` no sembro—; el kernel se levanta
/// entonces sin userspace, pero se levanta.
pub fn cargar() -> Result<Option<Manifiesto>, &'static str> { pub fn cargar() -> Result<Option<Manifiesto>, &'static str> {
let hash = match crate::almacen::manifiesto() { let hash = match almacen::manifiesto() {
Some(hash) => hash, Some(hash) => hash,
None => return Ok(None), None => return Ok(None),
}; };
// `recuperar` recomputa el hash del objeto y verifica su integridad: un // `recuperar` recomputa el hash del objeto y verifica su integridad: un
// manifiesto corrupto se delata aqui. // manifiesto corrupto se delata aqui.
let objeto = crate::almacen::recuperar(&hash)? let objeto = almacen::recuperar(&hash)?
.ok_or("manifiesto :: el objeto anclado no existe en el grafo")?; .ok_or("manifiesto :: el objeto anclado no existe en el grafo")?;
let manifiesto = Manifiesto::deserializar(&objeto.datos)?; let manifiesto = Manifiesto::deserializar(&objeto.datos)?;
Ok(Some(manifiesto)) Ok(Some(manifiesto))
} }
// =============================================================================
// La genesis — la semilla transitoria de la Fase 7a
// -----------------------------------------------------------------------------
// El bytecode de las apps de genesis viaja, POR AHORA, empotrado en el kernel.
// Es el unico `include_bytes!` que sobrevive a la Fase 7a — y solo como
// semilla: en un disco virgen, `sembrar_genesis` lo graba en el grafo una vez.
// La Fase 7b lo movera al constructor de imagen `boot` y este bloque morira.
// =============================================================================
static APP_WASM: &[u8] = include_bytes!("../assets/app.wasm");
static DISCOLA_WASM: &[u8] = include_bytes!("../assets/discola.wasm");
static GLOTONA_WASM: &[u8] = include_bytes!("../assets/glotona.wasm");
static CRONISTA_WASM: &[u8] = include_bytes!("../assets/cronista.wasm");
/// Descriptor de una app de genesis: lo que el kernel sabe de ella ANTES de
/// que exista en el grafo. `region` es `(x, y, ancho, alto)` en pixeles.
struct AppGenesis {
nombre: &'static str,
bytecode: &'static [u8],
region: (u32, u32, u32, u32),
techo_memoria: u32,
}
/// El userspace de genesis: las cinco aplicaciones que pueblan un disco
/// virgen, con las regiones de la Fase 6.2. `app.wasm` aparece dos veces
/// —dos instancias del mismo bytecode—; el grafo, direccionado por contenido,
/// lo guarda una sola vez.
fn genesis() -> [AppGenesis; 5] {
let techo = crate::wasm::TECHO_MEMORIA as u32;
[
AppGenesis {
nombre: "hola-izq",
bytecode: APP_WASM,
region: (100, 120, 480, 560),
techo_memoria: techo,
},
AppGenesis {
nombre: "hola-der",
bytecode: APP_WASM,
region: (700, 120, 480, 560),
techo_memoria: techo,
},
AppGenesis {
nombre: "discola",
bytecode: DISCOLA_WASM,
region: (60, 700, 360, 80),
techo_memoria: techo,
},
AppGenesis {
nombre: "glotona",
bytecode: GLOTONA_WASM,
region: (460, 700, 360, 80),
techo_memoria: techo,
},
AppGenesis {
nombre: "cronista",
bytecode: CRONISTA_WASM,
region: (860, 700, 360, 80),
techo_memoria: techo,
},
]
}
/// Siembra el grafo en un disco sin manifiesto: graba el bytecode de cada app
/// de genesis como un objeto, compone un `Manifiesto` con sus regiones y
/// cuotas, lo graba —con las aristas hacia los objetos de bytecode— y lo
/// ancla en el superbloque. Devuelve el hash del manifiesto recien anclado.
pub fn sembrar_genesis() -> Result<Hash, &'static str> {
let mut apps: Vec<EntradaApp> = Vec::new();
let mut hijos: Vec<Hash> = Vec::new();
for app in genesis() {
// Grabar el bytecode como objeto del grafo. Idempotente: dos
// instancias de la misma app comparten un unico objeto.
let bytecode = crate::almacen::almacenar(app.bytecode.to_vec(), Vec::new())?;
if !hijos.contains(&bytecode) {
hijos.push(bytecode);
}
let (x, y, ancho, alto) = app.region;
apps.push(EntradaApp {
nombre: String::from(app.nombre),
bytecode,
region_x: x,
region_y: y,
region_ancho: ancho,
region_alto: alto,
techo_memoria: app.techo_memoria,
estado: None,
});
}
// El objeto del manifiesto: sus `hijos` son los objetos de bytecode, de
// modo que el grafo lo lea como el nodo padre del userspace.
let manifiesto = Manifiesto {
version: VERSION_MANIFIESTO,
apps,
};
let bytes = manifiesto.serializar()?;
let hash = crate::almacen::almacenar(bytes, hijos)?;
crate::almacen::fijar_manifiesto(hash)?;
Ok(hash)
}
-9
View File
@@ -32,15 +32,6 @@ const FUEL_ARRANQUE: u64 = 20_000_000;
/// milisegundos y es desalojada. Este numero ES el techo temporal del userspace. /// milisegundos y es desalojada. Este numero ES el techo temporal del userspace.
const FUEL_FOTOGRAMA: u64 = 2_000_000; const FUEL_FOTOGRAMA: u64 = 2_000_000;
/// Techo de memoria lineal por aplicacion: 4 MiB. Un modulo que intente crecer
/// su memoria mas alla es desalojado — el aislamiento ESPACIAL del userspace,
/// gemelo del techo TEMPORAL que impone el combustible.
///
/// Desde la Fase 7 el techo es POR-APP: cada `EntradaApp` del manifiesto
/// lleva el suyo. Esta constante es el valor por DEFECTO — el que usan las
/// apps de genesis (ver `manifiesto::genesis`).
pub(crate) const TECHO_MEMORIA: usize = 4 * 1024 * 1024;
/// Por que el kernel da por terminada —desaloja— una aplicacion WASM. /// Por que el kernel da por terminada —desaloja— una aplicacion WASM.
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum FallaApp { pub enum FallaApp {