From bce4abd8cc7cf59daef42c8901d705ed8bb16a92 Mon Sep 17 00:00:00 2001 From: sergio Date: Fri, 22 May 2026 15:11:47 +0000 Subject: [PATCH] =?UTF-8?q?feat(renaser):=20Fase=207a=20=E2=80=94=20el=20u?= =?UTF-8?q?serspace=20nace=20del=20Grafo=20de=20Objetos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit El kernel deja de empotrar las apps. Las cinco aplicaciones ya no llegan por include_bytes! en main.rs: nacen del grafo, gobernadas por un Manifiesto de Génesis que también vive en el grafo. - almacen: el SuperBloque gana el ancla `manifiesto: Option` (gemela de `raiz`, del lado del kernel) + accesores. VERSION 1→2 — un disco v1 se reformatea. - manifiesto.rs: implementados `cargar` (lee el manifiesto del grafo) y `sembrar_genesis` (puebla un disco virgen con las 5 apps de génesis). El bytecode viaja empotrado AÚN, sólo como semilla transitoria (la Fase 7b lo mueve al constructor de imagen `boot`). - kernel_main: `cargar_userspace` reemplaza las 5 `encender_app` escritas a mano; `encender_app` recupera el bytecode del grafo — `recuperar` verifica el hash, un módulo corrupto se niega y el arranque sigue. - wasm: el techo de memoria pasa a ser por-app (del manifiesto). Compila limpio. Verificación en QEMU pendiente (la corre el operador): la pantalla debe verse idéntica a la Fase 6.2 + la línea «manifiesto». Co-Authored-By: Claude Opus 4.7 --- renaser/CHANGELOG.md | 39 ++++++++ renaser/DIARIO.md | 24 +++++ renaser/kernel/src/almacen.rs | 39 ++++++-- renaser/kernel/src/main.rs | 147 ++++++++++++++++--------------- renaser/kernel/src/manifiesto.rs | 136 +++++++++++++++++++++++----- renaser/kernel/src/wasm/mod.rs | 17 +++- 6 files changed, 302 insertions(+), 100 deletions(-) diff --git a/renaser/CHANGELOG.md b/renaser/CHANGELOG.md index 0de6bcc..076f164 100644 --- a/renaser/CHANGELOG.md +++ b/renaser/CHANGELOG.md @@ -480,3 +480,42 @@ con qué cuota y en qué región. (`#![allow(dead_code)]` temporal, hasta que la 7a le dé un llamador). El kernel compila y se comporta idéntico a la Fase 6.2 — nada observable que verificar en QEMU en esta apertura. + +## Fase 7a — Apps que nacen del Grafo: el Manifiesto — 2026-05-22 + +El kernel deja de empotrar el userspace. Las cinco aplicaciones ya no llegan +por `include_bytes!` en `main.rs`: nacen del grafo de objetos, gobernadas por +un Manifiesto de Génesis que también vive en el grafo. + +### Añadido +- `kernel/src/manifiesto.rs` — el Manifiesto de Génesis implementado: tipos + `Manifiesto` / `EntradaApp`, (de)serialización `postcard`, `cargar` (lee el + manifiesto del grafo vía el ancla del superbloque) y `sembrar_genesis` + (puebla un disco virgen con las cinco apps de génesis). +- `almacen`: el `SuperBloque` gana el ancla `manifiesto: Option` — + gemela de `raiz`, pero del lado del kernel. Accesores `manifiesto()` y + `fijar_manifiesto()`. + +### Cambiado +- `almacen::VERSION` 1 → 2: el superbloque cambia de forma. Un disco v1 se + reformatea al arrancar, como cualquier disco ajeno — la cuenta de la + cronista reinicia una vez. +- `kernel_main`: las cinco llamadas `encender_app` escritas a mano se + sustituyen por `cargar_userspace`, que carga el manifiesto —o lo siembra si + el disco está virgen— e itera sus `EntradaApp`. +- `encender_app` recibe una `EntradaApp` y recupera el bytecode del grafo. + `recuperar` ya recomputa y verifica el hash: un bytecode corrupto se niega, + se pinta la baliza de desalojo en su región y el arranque continúa. +- `wasm::AplicacionWasm::cargar` recibe el techo de memoria por-app (del + manifiesto) en vez de la constante global `TECHO_MEMORIA`. + +### Notas +- La siembra es TRANSITORIA: el bytecode aún viaja empotrado en + `manifiesto.rs` (`include_bytes!`) como semilla. La Fase 7b lo moverá al + constructor de imagen `boot` y el `include_bytes!` morirá del todo. + +### Pendiente de verificación +- QEMU (la corre el operador): la pantalla debe verse idéntica a la Fase 6.2 + —cinco apps en sus regiones—, más una línea de consola «manifiesto :: …». + El primer arranque reformatea el disco v1→v2 y siembra la génesis; los + siguientes cargan el manifiesto ya grabado en el grafo. diff --git a/renaser/DIARIO.md b/renaser/DIARIO.md index e183f79..4c853d8 100644 --- a/renaser/DIARIO.md +++ b/renaser/DIARIO.md @@ -239,6 +239,30 @@ rincón de la pantalla viven. Por ahora el cuaderno está en blanco y sus páginas son sólo molde; pero el molde ya tiene la forma exacta de lo que vendrá. La casa no cambió aún — sólo sabe, por fin, hacia dónde va a crecer. +## Las casas dejan de venir en la maleta — el userspace nace del disco + +Durante seis fases, renaser cargó a sus inquilinos consigo. Las aplicaciones +viajaban cosidas dentro del propio kernel, como muebles dentro de la maleta de +quien se muda: prácticas de llevar, pero imposibles de cambiar sin rehacer la +maleta entera. + +Hoy eso terminó. El kernel abrió el cuaderno que ayer era sólo molde —el +Manifiesto— y escribió en él quiénes viven en la casa, en qué habitación y +cuánto sitio se les presta. Luego dejó a los inquilinos donde siempre +debieron estar: en el disco, en el tejido de objetos que perdura. Cuando +renaser despierta, ya no desempaca nada; abre el cuaderno, va al disco, y va +trayendo a cada uno a su cuarto. + +Si el disco está en blanco —una casa recién construida—, el kernel siembra él +mismo la primera versión: escribe a los inquilinos y su cuaderno. Y si alguna +vez encontrara a un inquilino corrompido, con la cara que no le corresponde, +sencillamente no le abre la puerta: enciende su señal de alarma en esa +habitación y sigue acomodando a los demás. La casa nunca se cae por una +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 diario continúa. La próxima página la escribirá la próxima jornada.* diff --git a/renaser/kernel/src/almacen.rs b/renaser/kernel/src/almacen.rs index 025ff00..95bd5de 100644 --- a/renaser/kernel/src/almacen.rs +++ b/renaser/kernel/src/almacen.rs @@ -33,7 +33,9 @@ use crate::drivers::disco::{self, TAM_SECTOR}; const MAGIA: [u8; 8] = *b"RENASGRF"; /// Version del formato en disco. Un disco con otra version se reformatea. -const VERSION: u32 = 1; +/// 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. @@ -56,7 +58,7 @@ pub struct Objeto { } /// El superbloque: el sector 0 del disco. Ancla el grafo entero — dice por -/// donde continua el log y cual es el objeto raiz. +/// donde continua el log, cual es el objeto raiz y cual el manifiesto. #[derive(Serialize, Deserialize)] struct SuperBloque { /// Firma magica: debe ser [`MAGIA`]. @@ -67,15 +69,20 @@ struct SuperBloque { 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 estado vivo del almacen: el cursor del log, la raiz y el indice en -/// memoria que traduce cada hash al sector donde habita su registro. +/// 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. struct Almacen { /// Proximo sector libre del log. cursor: u64, /// El objeto raiz del DAG. raiz: Option, + /// El objeto del Manifiesto de Genesis (Fase 7). + manifiesto: Option, /// Indice hash -> sector del registro. Se reconstruye al arrancar. indice: BTreeMap, /// Capacidad del disco, en sectores. @@ -116,16 +123,16 @@ pub fn init() -> Result { let mut sector0 = [0u8; TAM_SECTOR]; disco::leer_sectores(0, &mut sector0)?; - let (cursor, raiz, indice, formateado) = + let (cursor, raiz, manifiesto, indice, formateado) = match postcard::take_from_bytes::(§or0) { // Disco de renaser, con la version corriente: adoptar su grafo. Ok((sb, _)) if sb.magia == MAGIA && sb.version == VERSION => { let indice = reconstruir_indice(sb.cursor)?; - (sb.cursor, sb.raiz, indice, false) + (sb.cursor, sb.raiz, sb.manifiesto, indice, false) } // Disco virgen, ajeno o de otra version: empezar de cero. El log // arranca en el sector 1, justo despues del superbloque. - _ => (1, None, BTreeMap::new(), true), + _ => (1, None, None, BTreeMap::new(), true), }; let objetos = indice.len(); @@ -133,6 +140,7 @@ pub fn init() -> Result { let almacen = Almacen { cursor, raiz, + manifiesto, indice, capacidad, }; @@ -204,6 +212,7 @@ fn persistir(almacen: &Almacen) -> Result<(), &'static str> { version: VERSION, 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 { @@ -290,3 +299,19 @@ pub fn fijar_raiz(hash: Hash) -> Result<(), &'static str> { almacen.raiz = Some(hash); persistir(&almacen) } + +/// El hash del objeto del Manifiesto de Genesis, si el disco tiene uno +/// anclado. Gemelo de [`raiz`], pero del lado del kernel: lo lee la Fase 7 +/// para descubrir que apps poblar al arrancar. +pub fn manifiesto() -> Option { + ALMACEN.get().and_then(|mutex| mutex.lock().manifiesto) +} + +/// Ancla un objeto como el Manifiesto de Genesis y graba el cambio en el +/// superbloque. Gemelo de [`fijar_raiz`]. +pub fn fijar_manifiesto(hash: Hash) -> Result<(), &'static str> { + let mutex = ALMACEN.get().ok_or("almacen no inicializado")?; + let mut almacen = mutex.lock(); + almacen.manifiesto = Some(hash); + persistir(&almacen) +} diff --git a/renaser/kernel/src/main.rs b/renaser/kernel/src/main.rs index 2639674..04de05d 100644 --- a/renaser/kernel/src/main.rs +++ b/renaser/kernel/src/main.rs @@ -66,33 +66,9 @@ use async_system::executor::Executor; use baliza::BALIZA_PANICO; use consola::{Consola, CONSOLA}; use grafico::{ - codificar, reclamar_memoria_lienzo, Color, Lienzo, Pantalla, RegionPantalla, ALTO_MAX, - ANCHO_MAX, + codificar, reclamar_memoria_lienzo, Color, Lienzo, Pantalla, ALTO_MAX, ANCHO_MAX, }; -/// El modulo WASM del userspace, empotrado en el binario del kernel para esta -/// fase de pruebas. Es un `.wasm` puro, compilado aparte para `wasm32`. La -/// Fase 5 lo instancia DOS veces —el mismo bytecode, dos regiones distintas— -/// para demostrar la multitarea cooperativa sobre el espacio unico. -static APP_WASM: &[u8] = include_bytes!("../assets/app.wasm"); - -/// La aplicacion DISCOLA: un modulo WASM cuyo `tick` cae en un bucle cerrado y -/// jamas retorna. Existe para una sola cosa — demostrar que el guardarrail de -/// combustible la fulmina sin despeinar al kernel ni a sus vecinas. -static DISCOLA_WASM: &[u8] = include_bytes!("../assets/discola.wasm"); - -/// La aplicacion GLOTONA: un modulo WASM que reclama memoria lineal sin freno. -/// Demuestra el guardarrail ESPACIAL — el techo de memoria la desaloja con la -/// baliza amarilla, gemela de la purpura del desalojo por combustible. -static GLOTONA_WASM: &[u8] = include_bytes!("../assets/glotona.wasm"); - -/// La aplicacion CRONISTA: la primera ciudadana del userspace que escribe en el -/// almacenamiento PERSISTENTE. En cada arranque graba un objeto en el grafo -/// —enlazado al del arranque anterior—, lo corona como raiz y pinta una celda -/// por cada arranque registrado. El disco recuerda; la cuenta sobrevive a los -/// reinicios. Demuestra las capacidades `sys_object_*` de la Fase 6.1c. -static CRONISTA_WASM: &[u8] = include_bytes!("../assets/cronista.wasm"); - /// Configuracion que el cargador `bootloader` aplicara antes de cedernos la CPU. static CONFIG_ARRANQUE: BootloaderConfig = { let mut config = BootloaderConfig::new_default(); @@ -152,16 +128,80 @@ async fn tarea_sonda_disco() { consola.presentar(); } -/// Da vida a una aplicacion del userspace: la carga en su region y, si lo -/// logra, la despacha como tarea cooperativa del reactor. Una carga fallida se -/// salda pintando su region con la baliza de desalojo — el kernel no se inmuta. -fn encender_app(ejecutor: &mut Executor, bytecode: &'static [u8], region: RegionPantalla) { - match wasm::AplicacionWasm::cargar(bytecode, region) { +/// Da vida a una aplicacion del userspace a partir de su `EntradaApp` del +/// manifiesto: recupera su bytecode del grafo, la carga en su region y la +/// despacha como tarea cooperativa del reactor. Si el bytecode falta, esta +/// 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(); + // 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. + let bytecode = match almacen::recuperar(&entrada.bytecode) { + Ok(Some(objeto)) => objeto.datos, + _ => { + consola::pintar_desalojo(region, Color::DESALOJO); + return; + } + }; + match wasm::AplicacionWasm::cargar(&bytecode, region, entrada.techo_memoria as usize) { Ok(app) => ejecutor.spawn(tarea_aplicacion(app)), Err(_) => consola::pintar_desalojo(region, Color::DESALOJO), } } +/// Escribe una linea en la consola global y la presenta. Atajo para los +/// informes de arranque; no hace nada si la consola aun no existe. +fn reportar(linea: &str) { + if let Some(consola) = CONSOLA.get() { + let mut consola = consola.lock(); + consola.escribir(linea); + consola.escribir("\n"); + consola.presentar(); + } +} + +/// 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. +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 + } + }, + Err(motivo) => { + reportar(&format!("manifiesto :: carga fallida -- {motivo}")); + None + } + }; + + match &manifiesto { + Some(m) => reportar(&format!( + "manifiesto :: {} apps nacidas del grafo", + m.apps.len(), + )), + None => reportar("manifiesto :: sin userspace -- el kernel se levanta solo"), + } + + if let Some(m) = manifiesto { + for entrada in &m.apps { + encender_app(ejecutor, entrada); + } + } +} + /// Localiza la mayor region de RAM libre que el cargador reporto — la cantera /// de la que el DMA del disco tomara sus marcos fisicos. fn mayor_region_usable(regiones: &MemoryRegions) -> Option<(u64, u64)> { @@ -291,50 +331,19 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! { } } - // --- 7. FASE 5/6 :: levantar el reactor y poblar el userspace con CINCO - // aplicaciones WASM concurrentes, cada una en su propia region: - // - // * App 1 — instancia de hello_wasm, a la izquierda, gobernada - // por el teclado. - // * App 2 — segunda instancia del MISMO bytecode, a la derecha: - // un unico modulo en la RAM unificada, dos vidas aisladas. - // * App discola — su `tick` es un bucle cerrado: el escudo de - // COMBUSTIBLE la desaloja (baliza purpura) en su 1er fotograma. - // * App glotona — reclama memoria sin freno: el escudo ESPACIAL - // la desaloja (baliza amarilla) en su 1er fotograma. - // * App cronista — escribe en el GRAFO DE OBJETOS persistente: - // cada arranque deja un objeto enlazado al anterior y pinta una - // celda por arranque. El disco recuerda entre reinicios. + // --- 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 + // 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 + // binario del kernel. // // Las interrupciones se habilitan AHORA: el temporizador marcara el // compas de los fotogramas y la IRQ del teclado difundira cada // scancode a los canales que las apps consultan. --- let mut ejecutor = Executor::nuevo(); - encender_app( - &mut ejecutor, - APP_WASM, - RegionPantalla { x: 100, y: 120, ancho: 480, alto: 560 }, - ); - encender_app( - &mut ejecutor, - APP_WASM, - RegionPantalla { x: 700, y: 120, ancho: 480, alto: 560 }, - ); - encender_app( - &mut ejecutor, - DISCOLA_WASM, - RegionPantalla { x: 60, y: 700, ancho: 360, alto: 80 }, - ); - encender_app( - &mut ejecutor, - GLOTONA_WASM, - RegionPantalla { x: 460, y: 700, ancho: 360, alto: 80 }, - ); - encender_app( - &mut ejecutor, - CRONISTA_WASM, - RegionPantalla { x: 860, y: 700, ancho: 360, alto: 80 }, - ); + cargar_userspace(&mut ejecutor); // FASE 6.2 :: una tarea mas del reactor — no una app WASM— que sondea el // disco de forma ASINCRONA: la demostracion de que la IRQ del disco // conduce la E/S sin detener a las aplicaciones visuales. diff --git a/renaser/kernel/src/manifiesto.rs b/renaser/kernel/src/manifiesto.rs index d3b7467..20c3714 100644 --- a/renaser/kernel/src/manifiesto.rs +++ b/renaser/kernel/src/manifiesto.rs @@ -11,16 +11,13 @@ // del manifiesto, lo deserializa, y por cada `EntradaApp` recupera el objeto // de bytecode —verificado por su hash— y lo inyecta en `wasmi`. // -// ESTADO: andamiaje de la Fase 7a. Los tipos y la (de)serializacion estan -// completos; `cargar` y `sembrar_genesis` son esbozos — se implementan al -// abordar la 7a, cuando el superbloque gane su campo `manifiesto`. Ver -// `FASE7.md` para el plan completo. +// 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. // ============================================================================= -// Fase 7a en construccion: el modulo aun no se cablea a `kernel_main`. El -// `allow` cae en cuanto `cargar`/`sembrar_genesis` tengan llamador real. -#![allow(dead_code)] - use alloc::string::String; use alloc::vec::Vec; @@ -104,21 +101,118 @@ impl Manifiesto { /// 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. -/// -/// ANDAMIAJE (Fase 7a-4): depende de `almacen::manifiesto()` —el nuevo ancla -/// del superbloque— todavia por implementar (tarea 7a-2). pub fn cargar() -> Result, &'static str> { - todo!("Fase 7a-4: leer almacen::manifiesto(), recuperar el objeto y deserializar") + let hash = match crate::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)? + .ok_or("manifiesto :: el objeto anclado no existe en el grafo")?; + let manifiesto = Manifiesto::deserializar(&objeto.datos)?; + Ok(Some(manifiesto)) } -/// Siembra el grafo en un disco sin manifiesto: graba el bytecode de las -/// aplicaciones de genesis, compone un `Manifiesto` por defecto con sus -/// regiones y cuotas, lo graba y lo ancla en el superbloque. Devuelve el -/// hash del manifiesto recien anclado. -/// -/// ANDAMIAJE (Fase 7a-3): la semilla TRANSITORIA — en la 7a el bytecode aun -/// llega vacia `include_bytes!`; la 7b mueve la siembra al constructor de -/// imagen `boot` y elimina el empotrado del kernel. +// ============================================================================= +// 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 { - todo!("Fase 7a-3: grabar los bytecodes de genesis + el manifiesto por defecto, y anclarlo") + 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 5710331..318762a 100644 --- a/renaser/kernel/src/wasm/mod.rs +++ b/renaser/kernel/src/wasm/mod.rs @@ -35,7 +35,11 @@ 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. -const TECHO_MEMORIA: usize = 4 * 1024 * 1024; +/// +/// 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)] @@ -88,7 +92,14 @@ impl AplicacionWasm { /// El nuevo ABI del userspace exige dos exportaciones: `init` —invocada una /// sola vez, aqui— y `tick` —un fotograma de trabajo, invocada despues por /// el reactor en cada pulso del reloj. - pub fn cargar(bytecode: &[u8], region: RegionPantalla) -> Result { + /// + /// `techo_memoria` es la cuota de memoria lineal de ESTA app, en bytes — + /// desde la Fase 7 la dicta su `EntradaApp` del manifiesto. + pub fn cargar( + bytecode: &[u8], + region: RegionPantalla, + techo_memoria: usize, + ) -> Result { // 1. El motor, con metricas de combustible y compilacion ANTICIPADA: la // traduccion del modulo ocurre ahora, de modo que el `fuel` mida // despues solo EJECUCION, jamas compilacion diferida. @@ -106,7 +117,7 @@ impl AplicacionWasm { // ya con la app cargada: una carga fallida no deja canales huerfanos. let canal = crate::async_system::teclado::crear_canal(); let limites = StoreLimitsBuilder::new() - .memory_size(TECHO_MEMORIA) + .memory_size(techo_memoria) // Una expansion denegada se convierte en TRAMPA, no en un -1 que la // app pudiera ignorar: asi el kernel la captura y la desaloja. .trap_on_grow_failure(true)