From 7695dbf3ceda3ab6114a460bbae238eae3d91dd8 Mon Sep 17 00:00:00 2001 From: sergio Date: Fri, 22 May 2026 18:29:23 +0000 Subject: [PATCH] =?UTF-8?q?feat(renaser):=20Fase=207b=20=E2=80=94=20boot?= =?UTF-8?q?=20siembra=20la=20imagen,=20muere=20el=20include=5Fbytes!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- renaser/.cargo/config.toml | 9 +- renaser/.gitignore | 10 +- renaser/CHANGELOG.md | 59 ++++++++ renaser/CLAUDE.md | 13 +- renaser/Cargo.lock | 14 +- renaser/DIARIO.md | 29 ++++ renaser/FASE7.md | 4 +- renaser/ROADMAP.md | 25 +++- renaser/boot/src/main.rs | 202 +++++++++++++++++++++++++--- renaser/kernel/src/almacen.rs | 129 ++++++------------ renaser/kernel/src/main.rs | 32 ++--- renaser/kernel/src/manifiesto.rs | 222 +++++-------------------------- renaser/kernel/src/wasm/mod.rs | 9 -- 13 files changed, 415 insertions(+), 342 deletions(-) diff --git a/renaser/.cargo/config.toml b/renaser/.cargo/config.toml index e7ab80c..d8a86ca 100644 --- a/renaser/.cargo/config.toml +++ b/renaser/.cargo/config.toml @@ -13,7 +13,12 @@ bindeps = true [alias] -# Compila unicamente el kernel, en aislamiento de arquitectura. -kernel = "build -p kernel --target x86_64-unknown-none" +# Compila unicamente el kernel, en aislamiento de arquitectura. Se invoca por +# `--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`). qemu = "run -p boot" diff --git a/renaser/.gitignore b/renaser/.gitignore index 39e8e2a..ba78996 100644 --- a/renaser/.gitignore +++ b/renaser/.gitignore @@ -2,15 +2,17 @@ # 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 /kernel/target +/formato/target /apps/*/target -# Bloqueo de dependencias del kernel: se genera al compilarlo en aislamiento. -# La verdad de versiones la fija el Cargo.lock raiz — el kernel es dependencia -# de artefacto de `boot` y se resuelve con el. +# Bloqueo de dependencias generado al compilar el kernel o el nucleo `formato` +# en aislamiento. La verdad de versiones la fija el Cargo.lock raiz — ambos son +# dependencias de `boot` y se resuelven con el. /kernel/Cargo.lock +/formato/Cargo.lock # Ajustes locales del entorno de desarrollo (personales, no compartidos). /.claude/settings.local.json diff --git a/renaser/CHANGELOG.md b/renaser/CHANGELOG.md index 8296d0f..a42b7c9 100644 --- a/renaser/CHANGELOG.md +++ b/renaser/CHANGELOG.md @@ -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 arranques sobrevivió al apagón. El userspace nace del grafo en ambos 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. diff --git a/renaser/CLAUDE.md b/renaser/CLAUDE.md index 2400deb..5f25f67 100644 --- a/renaser/CLAUDE.md +++ b/renaser/CLAUDE.md @@ -70,9 +70,11 @@ QEMU 11, OVMF en `/usr/share/edk2/x64/OVMF.4m.fd` (sin módulo KVM → TCG). ## Estado -Fases 1 a 5, la 6.0, la 6.1 completa (sustrato de almacenamiento: sonda PCI, -HAL/DMA y el grafo de objetos) y la 6.2 (E/S de disco asíncrona por -interrupción) completadas y verificadas en QEMU. Ver `ROADMAP.md`. +Fases 1 a 5, 6.0, 6.1 y 6.2 completas; y la Fase 7 —el userspace nace del +grafo de objetos— en sus sub-fases 7a (Manifiesto de Génesis, carga desde el +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 @@ -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). 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 -español. Verifica una fase en QEMU antes de darla por cerrada. +renaser vive ahora dentro del monorepo **brahman**; los commits van al remoto +de brahman (`gitea.gioser.net/sergio/brahman`). Mensajes de commit en español. +Verifica una fase en QEMU antes de darla por cerrada. diff --git a/renaser/Cargo.lock b/renaser/Cargo.lock index e1e1cd1..6dea5a4 100644 --- a/renaser/Cargo.lock +++ b/renaser/Cargo.lock @@ -96,6 +96,7 @@ name = "boot" version = "0.1.0" dependencies = [ "bootloader", + "formato", "kernel", ] @@ -328,6 +329,15 @@ dependencies = [ "ttf-parser", ] +[[package]] +name = "formato" +version = "0.1.0" +dependencies = [ + "blake3", + "postcard", + "serde", +] + [[package]] name = "funty" version = "2.0.0" @@ -446,16 +456,14 @@ dependencies = [ name = "kernel" version = "0.1.0" dependencies = [ - "blake3", "bootloader_api", "crossbeam-queue", "embedded-graphics", "fontdue", + "formato", "futures-util", "linked_list_allocator", "mirada-layout", - "postcard", - "serde", "spin", "virtio-drivers", "wasmi", diff --git a/renaser/DIARIO.md b/renaser/DIARIO.md index 4c853d8..3622f08 100644 --- a/renaser/DIARIO.md +++ b/renaser/DIARIO.md @@ -263,6 +263,35 @@ puerta que no quiso abrirse. 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. +## 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.* diff --git a/renaser/FASE7.md b/renaser/FASE7.md index 1379c0d..dc6783d 100644 --- a/renaser/FASE7.md +++ b/renaser/FASE7.md @@ -36,7 +36,7 @@ ataca **incremental**, como las Fases 6.1a/b/c. ## 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` / `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 (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 disco con los objetos de bytecode + el manifiesto. diff --git a/renaser/ROADMAP.md b/renaser/ROADMAP.md index c00988c..4270df8 100644 --- a/renaser/ROADMAP.md +++ b/renaser/ROADMAP.md @@ -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 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 -objetos; más capacidades del host (temporización, audio). +## Fase 7 — el userspace nace del Grafo de Objetos + +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 diff --git a/renaser/boot/src/main.rs b/renaser/boot/src/main.rs index 100a7c6..ca08214 100644 --- a/renaser/boot/src/main.rs +++ b/renaser/boot/src/main.rs @@ -3,21 +3,40 @@ // ----------------------------------------------------------------------------- // 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 -// 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: // // 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. -// 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: // 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::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`. /// /// 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. 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; 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> { // --- 1. Localizar el artefacto del kernel. --- let kernel = Path::new(KERNEL_ELF); @@ -67,7 +87,7 @@ fn orquestar() -> Result<(), String> { .create_disk_image(&imagen) .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()?; // --- 4. Lanzar QEMU sobre esa imagen. --- @@ -75,35 +95,185 @@ fn orquestar() -> Result<(), String> { 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 -/// 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. +// ============================================================================= +// Fase 7b — la siembra del grafo: el userspace nace de la imagen de disco +// ============================================================================= + +/// 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> { let disco = Path::new(NOMBRE_DISCO); 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(()); } if let Some(directorio) = disco.parent() { std::fs::create_dir_all(directorio) .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. - let fichero = std::fs::File::create(disco) + + // Sembrar el grafo: el bytecode del userspace y el Manifiesto de Genesis. + 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()))?; + fichero + .write_all(&imagen) + .map_err(|e| format!("no se pudo escribir el grafo sembrado: {e}"))?; fichero .set_len(TAM_DISCO) .map_err(|e| format!("no se pudo dimensionar el disco de objetos: {e}"))?; println!( - "[renaser/boot] disco de objetos forjado :: {} ({} MiB, virgen)", - disco.display(), - TAM_DISCO / (1024 * 1024) + "[renaser/boot] disco de objetos sembrado :: {} ({objetos} objetos, manifiesto anclado)", + disco.display() ); 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, 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, cursor: &mut u64, payload: &[u8]) -> Result { + 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, 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 = 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 = Vec::new(); + let mut apps: Vec = 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, /// dentro de `target/`. Una ubicacion predecible y siempre escribible. fn ruta_imagen(kernel: &Path) -> PathBuf { diff --git a/renaser/kernel/src/almacen.rs b/renaser/kernel/src/almacen.rs index 95bd5de..a2ce6b2 100644 --- a/renaser/kernel/src/almacen.rs +++ b/renaser/kernel/src/almacen.rs @@ -16,63 +16,27 @@ // 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 // 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::vec; use alloc::vec::Vec; -use serde::{Deserialize, Serialize}; 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 -/// renaser de uno virgen o ajeno. -const MAGIA: [u8; 8] = *b"RENASGRF"; - -/// 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, - /// Los hashes de los objetos hijos: las aristas salientes del DAG. - pub hijos: Vec, -} - -/// 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, - /// 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, -} +// El identificador y el objeto del grafo los define `formato`; se reexportan +// para que el resto del kernel siga nombrandolos `almacen::Hash` y +// `almacen::Objeto`, sin enterarse de donde viven realmente. +pub use formato::{Hash, Objeto}; /// 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. @@ -104,12 +68,6 @@ pub struct Resumen { 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 /// disco ya es de renaser, reconstruye el indice recorriendo el log; si es /// virgen o ajeno, lo formatea. Toda falla se devuelve como `Err`. @@ -120,13 +78,13 @@ pub fn init() -> Result { } // 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)?; let (cursor, raiz, manifiesto, indice, formateado) = - match postcard::take_from_bytes::(§or0) { + match formato::SuperBloque::deserializar(§or0) { // 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)?; (sb.cursor, sb.raiz, sb.manifiesto, indice, false) } @@ -166,13 +124,11 @@ fn reconstruir_indice(cursor: u64) -> Result, &'static str> let mut indice = BTreeMap::new(); let mut sector: u64 = 1; while sector < cursor { - let payload = leer_registro(sector)?; - match payload { + match leer_registro(sector)? { // Un payload valido: hashearlo e indexarlo. Some(payload) => { - let n = sectores_registro(payload.len()); - let hash = *blake3::hash(&payload).as_bytes(); - indice.insert(hash, sector); + let n = formato::sectores_registro(payload.len()); + indice.insert(formato::hash(&payload), sector); sector += n; } // Cabecera a cero o longitud imposible: fin (o corrupcion) del log. @@ -186,19 +142,18 @@ fn reconstruir_indice(cursor: u64) -> Result, &'static str> /// (sin la cabecera de longitud ni el relleno). `None` si la cabecera dice /// longitud cero —fin del log— o una longitud imposible —corrupcion—. fn leer_registro(sector: u64) -> Result>, &'static str> { - let mut cabecera = [0u8; TAM_SECTOR]; + let mut cabecera = [0u8; formato::TAM_SECTOR]; disco::leer_sectores(sector, &mut cabecera)?; - let longitud = - u32::from_le_bytes([cabecera[0], cabecera[1], cabecera[2], cabecera[3]]) as usize; - if longitud == 0 || longitud > MAX_OBJETO { - return Ok(None); - } - let n = sectores_registro(longitud) as usize; + let longitud = match formato::longitud_registro(&cabecera) { + Some(longitud) => longitud, + None => return Ok(None), + }; + let n = formato::sectores_registro(longitud) as usize; // Si el registro cabe en el sector ya leido, evitar una segunda lectura. let payload = if n == 1 { cabecera[4..4 + longitud].to_vec() } else { - let mut buf = vec![0u8; n * TAM_SECTOR]; + let mut buf = vec![0u8; n * formato::TAM_SECTOR]; disco::leer_sectores(sector, &mut buf)?; buf[4..4 + longitud].to_vec() }; @@ -207,18 +162,18 @@ fn leer_registro(sector: u64) -> Result>, &'static str> { /// Graba el superbloque —el ancla del grafo— en el sector 0. fn persistir(almacen: &Almacen) -> Result<(), &'static str> { - let sb = SuperBloque { - magia: MAGIA, - version: VERSION, + let sb = formato::SuperBloque { + magia: formato::MAGIA, + version: formato::VERSION_SUPERBLOQUE, cursor: almacen.cursor, raiz: almacen.raiz, manifiesto: almacen.manifiesto, }; - let bytes = postcard::to_allocvec(&sb).map_err(|_| "no se pudo serializar el superbloque")?; - if bytes.len() > TAM_SECTOR { + let bytes = sb.serializar()?; + if bytes.len() > formato::TAM_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); disco::escribir_sectores(0, §or0) } @@ -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. pub fn almacenar(datos: Vec, hijos: Vec) -> Result { let objeto = Objeto { datos, hijos }; - let bytes = - postcard::to_allocvec(&objeto).map_err(|_| "no se pudo serializar el objeto")?; - if bytes.is_empty() || bytes.len() > MAX_OBJETO { + let bytes = objeto.serializar()?; + if bytes.is_empty() || bytes.len() > formato::MAX_OBJETO { return Err("el objeto tiene un tamaño invalido"); } // 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 mut almacen = mutex.lock(); @@ -245,16 +199,14 @@ pub fn almacenar(datos: Vec, hijos: Vec) -> Result } // 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 { return Err("el grafo de objetos esta lleno"); } let sector = almacen.cursor; - // Componer el registro: [longitud][payload][relleno a cero] y grabarlo. - let mut registro = vec![0u8; n as usize * TAM_SECTOR]; - registro[0..4].copy_from_slice(&(bytes.len() as u32).to_le_bytes()); - registro[4..4 + bytes.len()].copy_from_slice(&bytes); + // Componer el registro —[longitud][payload][relleno]— y grabarlo. + let registro = formato::componer_registro(&bytes); disco::escribir_sectores(sector, ®istro)?; // El objeto ya esta en disco: avanzar el cursor, indexarlo y RE-anclar el @@ -279,12 +231,10 @@ pub fn recuperar(hash: &Hash) -> Result, &'static str> { let payload = leer_registro(sector)?.ok_or("registro de objeto corrupto")?; // Verificacion de integridad: el contenido leido DEBE rehashear al hash // 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"); } - let (objeto, _) = postcard::take_from_bytes::(&payload) - .map_err(|_| "no se pudo deserializar el objeto")?; - Ok(Some(objeto)) + Ok(Some(Objeto::deserializar(&payload)?)) } /// El hash del objeto raiz del grafo, si lo hay. @@ -309,6 +259,7 @@ pub fn manifiesto() -> Option { /// Ancla un objeto como el Manifiesto de Genesis y graba el cambio en el /// 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> { let mutex = ALMACEN.get().ok_or("almacen no inicializado")?; let mut almacen = mutex.lock(); diff --git a/renaser/kernel/src/main.rs b/renaser/kernel/src/main.rs index 04de05d..d21103a 100644 --- a/renaser/kernel/src/main.rs +++ b/renaser/kernel/src/main.rs @@ -134,7 +134,7 @@ async fn tarea_sonda_disco() { /// 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. 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 // 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. @@ -163,24 +163,20 @@ fn reportar(linea: &str) { } /// 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 -/// cargar. Por cada `EntradaApp`, enciende su aplicacion. Toda falla se -/// reporta a la consola y NO detiene el arranque: el kernel se levanta con -/// las apps que pueda — o con ninguna, si el grafo no tiene userspace. +/// Genesis que `boot` sembro en la imagen de disco y, por cada `EntradaApp`, +/// enciende su aplicacion. Toda falla se reporta a la consola y NO detiene el +/// arranque: el kernel se levanta con las apps que pueda — o con ninguna, si +/// el grafo no tiene userspace. fn cargar_userspace(ejecutor: &mut Executor) { let manifiesto = match manifiesto::cargar() { Ok(Some(m)) => Some(m), - // Disco sin manifiesto: sembrar la genesis y volver a cargarlo. - Ok(None) => match manifiesto::sembrar_genesis() { - Ok(_) => { - reportar("manifiesto :: genesis sembrada en disco virgen"); - manifiesto::cargar().ok().flatten() - } - Err(motivo) => { - reportar(&format!("manifiesto :: siembra fallida -- {motivo}")); - None - } - }, + // Disco sin manifiesto anclado: `boot` no lo sembro. El kernel se + // levanta sin userspace —pero se levanta—; en la practica, ninguna + // imagen forjada por `boot` llega aqui sin su Manifiesto de Genesis. + Ok(None) => { + reportar("manifiesto :: el disco no trae uno -- el kernel se levanta solo"); + None + } Err(motivo) => { reportar(&format!("manifiesto :: carga fallida -- {motivo}")); 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 // 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 // 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. // // Las interrupciones se habilitan AHORA: el temporizador marcara el diff --git a/renaser/kernel/src/manifiesto.rs b/renaser/kernel/src/manifiesto.rs index 20c3714..60586ed 100644 --- a/renaser/kernel/src/manifiesto.rs +++ b/renaser/kernel/src/manifiesto.rs @@ -3,216 +3,54 @@ // ----------------------------------------------------------------------------- // 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 -// destierra: las aplicaciones pasan a ser OBJETOS DEL GRAFO, y lo que arranca -// —con que cuota, en que region— lo dicta este Manifiesto de Genesis, que -// tambien habita el grafo. El superbloque guarda su hash en un ancla propia. +// desterro: las aplicaciones son OBJETOS DEL GRAFO, y lo que arranca —con que +// cuota, en que region— lo dicta este Manifiesto de Genesis, que tambien +// habita el grafo. El superbloque guarda su hash en un ancla propia. // -// El kernel, al despertar: lee el ancla del superbloque, recupera el objeto -// del manifiesto, lo deserializa, y por cada `EntradaApp` recupera el objeto -// de bytecode —verificado por su hash— y lo inyecta en `wasmi`. +// ESTADO: Fase 7b. El kernel ya NO empotra una sola app. La siembra de la +// imagen —grabar los objetos de bytecode y el manifiesto en un disco virgen— +// 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 -// de la genesis, implementados. La siembra es TRANSITORIA — el bytecode aun -// viaja empotrado (`include_bytes!`, abajo); la Fase 7b lo movera al -// constructor de imagen `boot` y el kernel dejara de empotrar una sola app. -// Ver `FASE7.md` para el plan completo. +// Los tipos `Manifiesto` / `EntradaApp` y su (de)serializacion viven en la +// crate `formato`, el nucleo `no_std` compartido con `boot`. Aqui solo queda +// lo que es del kernel: recuperar el manifiesto del grafo y traducir las +// regiones de su formato en disco (`u32`) a la `RegionPantalla` del kernel. // ============================================================================= -use alloc::string::String; -use alloc::vec::Vec; - -use serde::{Deserialize, Serialize}; - -use crate::almacen::Hash; +use crate::almacen; use crate::grafico::RegionPantalla; -/// Version del formato del manifiesto serializado. Independiente de la -/// version del superbloque (`almacen::VERSION`): el manifiesto es un objeto -/// del grafo, no una estructura de disco. -pub const VERSION_MANIFIESTO: u32 = 1; +// Los tipos del manifiesto los define `formato`; se reexportan para que el +// resto del kernel los nombre `manifiesto::EntradaApp` / `manifiesto::Manifiesto`. +pub use formato::{EntradaApp, Manifiesto}; -/// El Manifiesto de Genesis: la lista de aplicaciones que el kernel instancia -/// al arrancar. Vive como un objeto del grafo de objetos; el superbloque -/// guarda su hash en el campo `manifiesto`. -#[derive(Serialize, Deserialize, Clone)] -pub struct Manifiesto { - /// Version del formato — debe ser [`VERSION_MANIFIESTO`]. - pub version: u32, - /// Las aplicaciones del userspace, en orden de arranque. - pub apps: Vec, -} - -/// 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, -} - -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 { - x: self.region_x as usize, - y: self.region_y as usize, - ancho: self.region_ancho as usize, - alto: self.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, &'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 { - let (manifiesto, _) = postcard::take_from_bytes::(bytes) - .map_err(|_| "manifiesto :: deserializacion fallida")?; - if manifiesto.version != VERSION_MANIFIESTO { - return Err("manifiesto :: version de formato desconocida"); - } - Ok(manifiesto) +/// Traduce la sub-region de una `EntradaApp` —campos `u32` de ancho fijo, el +/// formato en disco— a la `RegionPantalla` que el kernel entiende (`usize`, +/// ancho de plataforma). El puente entre lo que el disco guarda y lo que el +/// compositor dibuja. +pub fn region(entrada: &EntradaApp) -> RegionPantalla { + RegionPantalla { + x: entrada.region_x as usize, + y: entrada.region_y as usize, + ancho: entrada.region_ancho as usize, + alto: entrada.region_alto as usize, } } /// 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 -/// manifiesto anclado — el caller debe entonces sembrar la genesis. +/// recupera el objeto y lo deserializa. `Ok(None)` si el disco no tiene +/// manifiesto anclado —un disco que `boot` no sembro—; el kernel se levanta +/// entonces sin userspace, pero se levanta. pub fn cargar() -> Result, &'static str> { - let hash = match crate::almacen::manifiesto() { + let hash = match almacen::manifiesto() { Some(hash) => hash, None => return Ok(None), }; // `recuperar` recomputa el hash del objeto y verifica su integridad: un // 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")?; let manifiesto = Manifiesto::deserializar(&objeto.datos)?; 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 { - let mut apps: Vec = Vec::new(); - let mut hijos: Vec = 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) -} diff --git a/renaser/kernel/src/wasm/mod.rs b/renaser/kernel/src/wasm/mod.rs index 318762a..75e4ad1 100644 --- a/renaser/kernel/src/wasm/mod.rs +++ b/renaser/kernel/src/wasm/mod.rs @@ -32,15 +32,6 @@ const FUEL_ARRANQUE: u64 = 20_000_000; /// milisegundos y es desalojada. Este numero ES el techo temporal del userspace. 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. #[derive(Clone, Copy)] pub enum FallaApp {