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>
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<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,
/// 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};
-
-/// 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;
+use crate::drivers::disco;
-/// 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 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<Resumen, &'static str> {
}
// 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::<SuperBloque>(§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<BTreeMap<Hash, u64>, &'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<BTreeMap<Hash, u64>, &'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<Option<Vec<u8>>, &'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<Option<Vec<u8>>, &'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<u8>, hijos: Vec<Hash>) -> Result<Hash, &'static str> {
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<u8>, hijos: Vec<Hash>) -> Result<Hash, &'static str>
}
// 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<Option<Objeto>, &'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::<Objeto>(&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<Hash> {
/// 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;
-
-/// 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<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 {
- 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<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)
+// 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};
+
+/// 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<Option<Manifiesto>, &'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<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)
-}
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 {