From 900cd19e496f1e45eab2823e909ace7625f78f3a Mon Sep 17 00:00:00 2001 From: sergio Date: Fri, 22 May 2026 18:43:58 +0000 Subject: [PATCH] =?UTF-8?q?feat(renaser):=20Fase=207c=20=E2=80=94=20persis?= =?UTF-8?q?tencia=20inter-sesi=C3=B3n=20por-app?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cada app tiene ahora su propia ranura de estado en el Manifiesto de Génesis (EntradaApp.estado): guarda y recobra lo suyo, sobrevive al reinicio, y no pisa a ninguna otra app. - apps/memoriosa: app WASM interactiva nueva. Cuenta las teclas pulsadas y persiste el recuento; al reiniciar despierta con su cuenta intacta. Reemplaza la 2a instancia de hola en la genesis. - kernel: capacidades sys_estado_cargar / sys_estado_guardar. El kernel custodia un manifiesto VIVO (Mutex); fijar_estado lo muta, lo re-graba en el grafo y lo re-ancla. ContextoCapacidades.indice_app da a cada app su identidad — su ranura, jamas la de otra. - cargar_userspace instala el manifiesto vivo antes de instanciar las apps: el init de una app ya consulta su estado al despertar. Verificado en QEMU (screendump + sendkey): disco virgen -> memoriosa con 0 celdas, testigo verde; 5 pulsaciones -> 5 celdas; reinicio -> 5 celdas intactas, testigo ambar (init releyo el estado del grafo). Cierra la Fase 7 — el userspace nace del grafo, completa. Co-Authored-By: Claude Opus 4.7 --- renaser/CHANGELOG.md | 43 ++++++ renaser/CLAUDE.md | 16 ++- renaser/DIARIO.md | 26 ++++ renaser/FASE7.md | 2 +- renaser/ROADMAP.md | 10 +- renaser/apps/memoriosa/Cargo.lock | 7 + renaser/apps/memoriosa/Cargo.toml | 30 +++++ renaser/apps/memoriosa/src/lib.rs | 190 +++++++++++++++++++++++++++ renaser/boot/src/main.rs | 10 +- renaser/kernel/assets/memoriosa.wasm | Bin 0 -> 1430 bytes renaser/kernel/src/almacen.rs | 4 +- renaser/kernel/src/main.rs | 24 ++-- renaser/kernel/src/manifiesto.rs | 67 +++++++++- renaser/kernel/src/wasm/env.rs | 92 ++++++++++++- renaser/kernel/src/wasm/mod.rs | 6 +- 15 files changed, 492 insertions(+), 35 deletions(-) create mode 100644 renaser/apps/memoriosa/Cargo.lock create mode 100644 renaser/apps/memoriosa/Cargo.toml create mode 100644 renaser/apps/memoriosa/src/lib.rs create mode 100755 renaser/kernel/assets/memoriosa.wasm diff --git a/renaser/CHANGELOG.md b/renaser/CHANGELOG.md index a42b7c9..ed24566 100644 --- a/renaser/CHANGELOG.md +++ b/renaser/CHANGELOG.md @@ -587,3 +587,46 @@ poblado. El binario del kernel ya no carga ni un solo `.wasm`. por `boot`. - `cargo build -p boot` y `cargo kernel` compilan limpios; `cargo test` de `formato` — 5/5 en verde. + +## Fase 7c — Persistencia inter-sesión: cada app recuerda lo suyo — 2026-05-22 + +La cronista dejaba huella, pero en la RAÍZ del grafo — un ancla única que sólo +una app puede usar. La 7c estrena la persistencia POR-APP: cada `EntradaApp` +del Manifiesto de Génesis tiene su propia ranura `estado`, y una app guarda y +recobra lo suyo sin pisar a nadie. El estado de cada aplicación sobrevive, por +separado, a un reinicio. + +### Añadido +- **`apps/memoriosa`** — nueva app WASM interactiva. Cuenta las teclas pulsadas + en toda su historia y persiste el recuento; al reiniciar, despierta con su + cuenta intacta. Una celda violeta por pulsación; el testigo de la esquina es + verde si nació limpia, ámbar si despertó con memoria de una vida anterior. +- `kernel` — dos capacidades del host nuevas: + - `sys_estado_cargar(salida, capacidad)` — copia el estado persistido de la + app que llama en su memoria lineal; 0 si aún no tiene estado previo. + - `sys_estado_guardar(datos, datos_len)` — graba `datos` como objeto del + grafo y ancla su hash en la `EntradaApp` de la app, re-grabando y + re-anclando el manifiesto. +- `manifiesto.rs` — el manifiesto VIVO: `VIVO`, un `Mutex` que el + kernel custodia. `instalar` (en el arranque), `estado_de` (lee la ranura de + una app) y `fijar_estado` (la muta, re-graba el manifiesto y lo re-ancla). +- `ContextoCapacidades` gana `indice_app`: la identidad de cada app en el + manifiesto, con la que las capacidades de estado hallan SU ranura — jamás la + de otra. + +### Cambiado +- `AplicacionWasm::cargar` y `encender_app` reciben el `indice_app` de la app. +- `cargar_userspace` instala el manifiesto VIVO ANTES de instanciar las apps: + el `init` de una app ya consulta su estado persistido al despertar. +- `almacen::fijar_manifiesto` deja de ser `dead_code` — lo invoca `fijar_estado`. +- La génesis (`boot`) sustituye la segunda instancia de `hola` por `memoriosa` + (región 700,120 de 360×80). + +### Verificado +- QEMU (captura headless + `sendkey` por el monitor): + - **Disco virgen** — memoriosa arranca con 0 celdas y el testigo verde. + - **Cinco pulsaciones** (`sendkey`) — memoriosa pinta 5 celdas violetas y + persiste el recuento; `sys_estado_guardar` re-ancla el manifiesto. + - **Reinicio** — `boot` respeta el disco; memoriosa despierta con las 5 + celdas intactas y el testigo en ámbar: su `init` releyó el estado del + grafo. El estado por-app sobrevivió al apagón. diff --git a/renaser/CLAUDE.md b/renaser/CLAUDE.md index 5f25f67..b84c152 100644 --- a/renaser/CLAUDE.md +++ b/renaser/CLAUDE.md @@ -23,12 +23,15 @@ Verificación headless (sin ventana): añadir `-- -display none -monitor unix:/tmp/q.sock,server,nowait` y capturar con `screendump` por el socket del monitor. -Reconstruir la app WASM del userspace tras tocar `apps/hello_wasm`: +Reconstruir una app WASM del userspace tras tocarla. Los `.wasm` viven en +`kernel/assets/` —de ahí los lee `boot` al sembrar la imagen (Fase 7b)—; el +modulo `hello_wasm` se copia como `app.wasm`, el resto conserva su nombre: ```sh -cd apps/hello_wasm +cd apps/ # hello_wasm, discola, glotona, cronista, memoriosa cargo build --target wasm32-unknown-unknown --release -cp target/wasm32-unknown-unknown/release/hello_wasm.wasm ../../kernel/assets/app.wasm +cp target/wasm32-unknown-unknown/release/.wasm ../../kernel/assets/.wasm +# (hello_wasm es la excepcion: su destino es kernel/assets/app.wasm) ``` ## Estructura del espacio de trabajo @@ -70,10 +73,9 @@ QEMU 11, OVMF en `/usr/share/edk2/x64/OVMF.4m.fd` (sin módulo KVM → TCG). ## Estado -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 +Fases 1 a 5, 6.0, 6.1, 6.2 y la Fase 7 COMPLETA —el userspace nace del grafo +de objetos: Manifiesto de Génesis (7a), imagen sembrada por `boot` (7b) y +persistencia inter-sesión por-app (7c)—. Todo verificado en QEMU. Ver `ROADMAP.md`. ## Flujo de trabajo diff --git a/renaser/DIARIO.md b/renaser/DIARIO.md index 3622f08..0365bf9 100644 --- a/renaser/DIARIO.md +++ b/renaser/DIARIO.md @@ -292,6 +292,32 @@ 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. +## Cada quien, su cajón de recuerdos + +Había una inquilina —la cronista— que sabía dejar huella. Cada vez que la casa +despertaba, ella anotaba el número del despertar y lo guardaba donde nada se +pierde. Pero lo guardaba en un único cajón, el cajón de la casa: si otro +inquilino hubiera querido recordar algo suyo, habría tenido que borrar lo de +ella. Memoria había, sí, pero una sola, y a empujones. + +Hoy eso cambió. El cuaderno de la casa —el que dice quién vive dónde— ganó, en +cada página, un margen en blanco: un cajoncito propio para cada inquilino, +donde guardar lo que quiera sin tocar lo ajeno. Mil inquilinos, mil cajones; +ninguno pisa al otro. + +La primera en estrenarlo es una recién llegada que hace honor a su nombre: +*memoriosa*. Su oficio es sencillo y entrañable — cuenta las teclas que se +pulsan. Cada vez que alguien teclea, ella suma uno y lo apunta en su cajón. Y +cuando la casa se apaga del todo y vuelve a encenderse, memoriosa no empieza de +cero: abre su cajón, lee la cuenta donde la dejó, y continúa. En su rincón +pinta una marca por cada tecla de toda su vida; y una lucecita —ámbar— confiesa +que ha despertado recordando. + +No es poca cosa. Una máquina que se apaga suele olvidarlo todo; renaser, no. +Sus inquilinos cierran los ojos en el apagón y los abren, al volver, justo +donde los cerraron. La casa ya no sólo perdura: recuerda, uno por uno, a los +suyos. + --- *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 dc6783d..882ff87 100644 --- a/renaser/FASE7.md +++ b/renaser/FASE7.md @@ -65,7 +65,7 @@ ataca **incremental**, como las Fases 6.1a/b/c. - El kernel pierde los `include_bytes!` del userspace; `sembrar_genesis` se retira o queda como camino vacío de respaldo. -### 7c — Persistencia inter-sesión +### 7c — Persistencia inter-sesión — ✅ HECHA - Una app (cronista evolucionada, o una nueva interactiva) guarda su estado mutado como un objeto nuevo del grafo al recibir teclado. diff --git a/renaser/ROADMAP.md b/renaser/ROADMAP.md index 4270df8..07f3248 100644 --- a/renaser/ROADMAP.md +++ b/renaser/ROADMAP.md @@ -97,7 +97,7 @@ 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. -## Fase 7 — el userspace nace del Grafo de Objetos +## Fase 7 — el userspace nace del Grafo de Objetos (completada) 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 @@ -114,9 +114,11 @@ destierra — las aplicaciones pasan a ser objetos del grafo, gobernadas por un 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ó. +- **7c — persistencia inter-sesión (completada).** La app `memoriosa` graba + su estado como un objeto del grafo; el kernel lo ancla en la ranura + `EntradaApp.estado` y re-graba el manifiesto. Al despertar, `init` lo relee + y la app retoma donde quedó. Capacidades `sys_estado_cargar` / + `sys_estado_guardar`; el kernel custodia un manifiesto VIVO y mutable. Líneas abiertas posteriores: más capacidades del host (temporización, audio); la Fase 8 — el compositor sobre `mirada-layout`. diff --git a/renaser/apps/memoriosa/Cargo.lock b/renaser/apps/memoriosa/Cargo.lock new file mode 100644 index 0000000..9b64ae2 --- /dev/null +++ b/renaser/apps/memoriosa/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "memoriosa" +version = "0.1.0" diff --git a/renaser/apps/memoriosa/Cargo.toml b/renaser/apps/memoriosa/Cargo.toml new file mode 100644 index 0000000..7a35156 --- /dev/null +++ b/renaser/apps/memoriosa/Cargo.toml @@ -0,0 +1,30 @@ +# ============================================================================= +# renaser :: apps/memoriosa — Fase 7c :: la app que recuerda entre vidas +# ----------------------------------------------------------------------------- +# Un modulo WebAssembly interactivo. Cuenta las teclas que se pulsan y graba +# ese recuento como SU estado persistido —anclado en su `EntradaApp` del +# Manifiesto de Genesis—. Al reiniciar la maquina, despierta justo donde se +# quedo: la cuenta no vive en la RAM, vive en el grafo. +# Tiene su propio `[workspace]`: queda fuera del espacio de trabajo del kernel. +# ============================================================================= + +[package] +name = "memoriosa" +version = "0.1.0" +edition = "2021" +description = "renaser :: app WASM memoriosa — persistencia inter-sesion por-app" + +[workspace] + +# `cdylib` produce un modulo `.wasm` que exporta funciones — el formato que +# wasmi instancia. +[lib] +crate-type = ["cdylib"] + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" +opt-level = "s" +lto = true diff --git a/renaser/apps/memoriosa/src/lib.rs b/renaser/apps/memoriosa/src/lib.rs new file mode 100644 index 0000000..b50da02 --- /dev/null +++ b/renaser/apps/memoriosa/src/lib.rs @@ -0,0 +1,190 @@ +// ============================================================================= +// renaser :: apps/memoriosa — Fase 7c :: la app que recuerda entre vidas +// ----------------------------------------------------------------------------- +// La cronista deja huella, sí — pero en la RAIZ del grafo, un ancla unica que +// solo una app puede usar. `memoriosa` estrena la persistencia POR-APP: cada +// aplicacion tiene, en su `EntradaApp` del Manifiesto de Genesis, una ranura +// propia —`estado`— donde guardar lo suyo. Mil apps, mil memorias; ninguna +// pisa a otra. +// +// Lo que memoriosa recuerda es simple y visible: cuantas teclas se han pulsado +// EN TODA SU HISTORIA. En cada `tick` drena su canal de teclado; cada +// pulsacion suma uno y graba el recuento con `sys_estado_guardar`. El kernel lo +// ancla en el manifiesto. Al reiniciar, `init` lo relee con `sys_estado_cargar` +// y la app despierta con su cuenta intacta — una celda por tecla, como las dejo. +// +// El testigo de la esquina lo cuenta todo de un vistazo: verde si nacio limpia +// —disco recien sembrado—, ambar si desperto con memoria de vidas pasadas. +// ============================================================================= + +#![no_std] + +// --- Las capacidades que el kernel `renaser` inyecta a esta aplicacion. --- +#[link(wasm_import_module = "renaser")] +extern "C" { + /// Compone un bufer de pixeles (de ESTA memoria lineal) en la region que el + /// kernel asigno a esta aplicacion. + fn sys_render_frame(ptr: u32, len: u32); + + /// Extrae, sin bloquear, el siguiente scancode del canal de teclado propio + /// de esta app. Devuelve 0 si el canal esta vacio. + fn sys_get_scancode() -> u32; + + /// Copia el estado persistido de ESTA app en `salida`. Devuelve los bytes + /// copiados, 0 si no hay estado previo, o un valor negativo si fallo. + fn sys_estado_cargar(salida: u32, capacidad: u32) -> i32; + + /// Graba `datos` como el nuevo estado persistido de ESTA app. El kernel lo + /// ancla en el Manifiesto de Genesis: sobrevivira al reinicio. Devuelve 0 + /// si todo fue bien, un valor negativo si fallo. + fn sys_estado_guardar(datos: u32, datos_len: u32) -> i32; +} + +/// Sin sistema operativo bajo nosotros, un panico solo puede detenerse en seco. +#[panic_handler] +fn al_fallar(_: &core::panic::PanicInfo) -> ! { + loop {} +} + +// --- Geometria de la escena. El ancho y el alto DEBEN coincidir con la region +// que el kernel asigna a esta app. --- +const ANCHO: usize = 360; +const ALTO: usize = 80; + +/// Lado de paso de la rejilla de celdas, en pixeles. +const PASO: usize = 20; +/// Lado de una celda, en pixeles. +const LADO: usize = 16; +/// Celdas que caben en una fila. +const POR_FILA: usize = ANCHO / PASO; +/// Celdas que caben en la rejilla entera — el techo de lo que se pinta. +const MAX_CELDAS: usize = POR_FILA * (ALTO / PASO); + +/// Indigo casi negro: el fondo del lienzo de memoriosa. +const FONDO: u32 = 0x10_0E_22; +/// Violeta: una celda por cada tecla pulsada en toda la historia de la app. +const CELDA: u32 = 0x8C_6E_F2; +/// Verde: la app nacio limpia — disco recien sembrado, sin estado previo. +const TESTIGO_FRESCO: u32 = 0x35_C4_6A; +/// Ambar: la app desperto con memoria — leyo su estado de una vida anterior. +const TESTIGO_RECORDADO: u32 = 0xF2_B2_33; + +/// El lienzo de la aplicacion, en SU propia memoria lineal. +static mut LIENZO: [u32; ANCHO * ALTO] = [0; ANCHO * ALTO]; + +/// Los ocho bytes del recuento, de ida y de vuelta de la ranura de estado. +static mut ESTADO_IO: [u8; 8] = [0; 8]; + +/// El recuento vivo de pulsaciones — el estado que memoriosa persiste. +static mut CONTADOR: u64 = 0; + +/// ¿Desperto la app con un estado previo? Decide el color del testigo. +static mut RECORDADO: bool = false; + +/// Preparacion: el kernel la invoca UNA sola vez. memoriosa relee aqui su +/// estado persistido —el recuento de teclas de vidas anteriores— y pinta la +/// rejilla tal como la dejo la ultima vez que la maquina estuvo viva. +#[no_mangle] +pub extern "C" fn init() { + // Cargar el estado persistido. `8` => habia un recuento anclado; la app + // despierta con memoria. `0` => disco recien sembrado, sin estado previo. + let leidos = unsafe { sys_estado_cargar(core::ptr::addr_of_mut!(ESTADO_IO) as u32, 8) }; + if leidos == 8 { + // SEGURIDAD: lectura de un escalar `Copy` de un estatico propio. + unsafe { + CONTADOR = u64::from_le_bytes(*core::ptr::addr_of!(ESTADO_IO)); + RECORDADO = true; + } + } + pintar(); +} + +/// Un fotograma de trabajo. Drena el canal de teclado: cada tecla pulsada suma +/// uno al recuento. Si hubo alguna pulsacion, persiste el nuevo total —el +/// kernel lo ancla en el manifiesto— y redibuja la rejilla. +#[no_mangle] +pub extern "C" fn tick() { + let mut cambio = false; + loop { + let scancode = unsafe { sys_get_scancode() }; + if scancode == 0 { + break; // Canal vacio: nada mas que atender este fotograma. + } + // Make codes del set 1: `0x01..=0x7F` (bit 7 a cero) — una tecla + // PULSADA. Los break codes —tecla soltada— llevan el bit 7 y se + // ignoran: asi cada pulsacion fisica cuenta exactamente una vez. + if (1..=0x7F).contains(&scancode) { + // SEGURIDAD: incremento de un escalar estatico propio; el kernel + // jamas reentra el modulo mientras `tick` corre. + unsafe { + CONTADOR += 1; + } + cambio = true; + } + } + + if cambio { + // Persistir el nuevo recuento. El kernel lo graba como un objeto del + // grafo y reescribe el manifiesto: sobrevivira al proximo apagon. + // SEGURIDAD: escritura de un escalar `Copy` a un estatico propio. + unsafe { + *core::ptr::addr_of_mut!(ESTADO_IO) = CONTADOR.to_le_bytes(); + sys_estado_guardar(core::ptr::addr_of!(ESTADO_IO) as u32, 8); + } + pintar(); + } +} + +/// Pinta la cronica de pulsaciones: el fondo, una celda violeta por cada tecla +/// pulsada en toda la historia de la app y, en la esquina, el testigo que +/// delata si la app desperto con memoria o nacio limpia. +fn pintar() { + // SEGURIDAD: el kernel jamas reentra el modulo mientras `init` o `tick` + // corren; esta es la unica via de acceso a LIENZO durante esa ventana. + let lienzo: &mut [u32] = unsafe { &mut *core::ptr::addr_of_mut!(LIENZO) }; + for pixel in lienzo.iter_mut() { + *pixel = FONDO; + } + + // Una celda violeta por cada pulsacion registrada, dispuestas en rejilla. + // SEGURIDAD: lectura de escalares `Copy` de estaticos propios. + let contador = unsafe { *core::ptr::addr_of!(CONTADOR) }; + let celdas = (contador as usize).min(MAX_CELDAS); + for i in 0..celdas { + let x = (i % POR_FILA) * PASO + 2; + let y = (i / POR_FILA) * PASO + 2; + rellenar(lienzo, x, y, LADO, LADO, CELDA); + } + + // El testigo: ambar si la app leyo un estado de una vida anterior, verde + // si nacio en un disco recien sembrado, sin memoria que heredar. + let recordado = unsafe { *core::ptr::addr_of!(RECORDADO) }; + let testigo = if recordado { + TESTIGO_RECORDADO + } else { + TESTIGO_FRESCO + }; + rellenar(lienzo, ANCHO - 14, 4, 10, 10, testigo); + + // SEGURIDAD: `sys_render_frame` es una capacidad del host; el (ptr, len) + // describe nuestra propia memoria lineal y el host lo verifica sin piedad. + unsafe { + sys_render_frame(lienzo.as_ptr() as u32, (ANCHO * ALTO * 4) as u32); + } +} + +/// Rellena un rectangulo, recortado con firmeza a los limites del lienzo. +fn rellenar(lienzo: &mut [u32], x: usize, y: usize, ancho: usize, alto: usize, color: u32) { + let x1 = (x + ancho).min(ANCHO); + let y1 = (y + alto).min(ALTO); + let mut fila = y; + while fila < y1 { + let base = fila * ANCHO; + let mut col = x; + while col < x1 { + lienzo[base + col] = color; + col += 1; + } + fila += 1; + } +} diff --git a/renaser/boot/src/main.rs b/renaser/boot/src/main.rs index ca08214..594c5a3 100644 --- a/renaser/boot/src/main.rs +++ b/renaser/boot/src/main.rs @@ -108,12 +108,12 @@ struct AppGenesis { } /// 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. +/// forjado. Un saludo (`hola`), la `memoriosa` interactiva que recuerda entre +/// sesiones (Fase 7c), y tres demos de los guardarrailes del kernel: `discola` +/// (combustible), `glotona` (memoria) y `cronista` (la cronica de los arranques). 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: "hola", archivo: "app.wasm", region: (100, 120, 480, 560) }, + AppGenesis { nombre: "memoriosa", archivo: "memoriosa.wasm", region: (700, 120, 360, 80) }, 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) }, diff --git a/renaser/kernel/assets/memoriosa.wasm b/renaser/kernel/assets/memoriosa.wasm new file mode 100755 index 0000000000000000000000000000000000000000..b5c8177ca4741a4172cfa630fdb9850b86af06f0 GIT binary patch literal 1430 zcmZQbEY4+QU|?VrXH8(LuV(-ejP(f&Ak2`!SkJ&zz`|aXnwMCdS|m_hSsY)KnwOGV z6rWaC>3SeaRvnVDIc85xAwI2r319UB@BFmSU=Ftg>R=H?ewGBL1Z=4F;Ju(6b6 zCTBCSb6sX(oy^S0UGLcNr$Ui|*+GH9v7_a914EYMqB#c`%$OJy7#vx$lo)s!xE&c3 z8JQfI92^-mn80e37+iTl@+TmKr?esyvjdZZ0;8i)o)UwjNRAS-BU6zQivqKwP?i$2 z;{^r<5aB7U#Olw>puniW;3$-n4N)hQrO2iLQm)9Zz~K1cDr1fUqvHjJoGix+(+)H+ zWGQkeFgWfo1&NB}WI0}9%~IrKc2M9@WLDr*WKdvN{{u#EURDKW$4_@RvVdIA z?8uU($gIHN$e5+bpupwGn5D#}z~;%zz^%aHxPTjEE~n!QrYuEH1s0Gbivp`VF9SEX z0wajWsK5l~Ic|P(irw*5$8|{;pa7Cqs>&LC21U0}Tw04K3^f4Gj$q4E)>*3<9kn76UgY zgFpv_WzE3k0E)&221gEoMv#~zhdVEWBZDKO6~rly42}$%%ncyJ%$dP4!!6JPQplah z$W#w;7n6fP6G%vjk(Yr96w8c-u3$qLG$C5dAqK#0Y6hv6f||n*3XcXxe-s@~j5Sc} z92pfEco?|3*%_D}Kr}-aGfN&QmGkp~lU-(haiU&1D8Jlg;$+Z^5ArK>F3!yJDmL}V zF*Gc9jkkm=Gc*GwY~-xMsE=2@IY>Q9{$V1*0_04^Y=GAS3nUAmIgLdbpANHv%)FAs zB35&dBEJ&n;;dw&tdKCjDtEJ-c$4J(qEyR*#JtSp?99A$Lklo7J}JL6FQqs>IU_YW zn+=>r*}*xKLzf6cAjZX~<(9AuF*5MS#}}6*CTGVN$mnNpCGO`4f7MC#a6cptrrxq8drsyVBGO`wx7MCQe8tPe^>lx_gWu|A8I$%#2Rx|w+?nMJ9|CA#@#sYN;YdFk3*xurQJnPrJNrK#HdxuqqENja&y z={fmHi8;mE!g=|5B}It^1(|v2x@iR^AWv!Y7p1197NzDTr|One7Nizya};N$=jo 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). +/// superbloque. Gemelo de [`fijar_raiz`]. La Fase 7c lo invoca cada vez que una +/// app persiste su estado y el manifiesto debe re-anclarse. 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 d21103a..4a0ecaa 100644 --- a/renaser/kernel/src/main.rs +++ b/renaser/kernel/src/main.rs @@ -133,7 +133,7 @@ async fn tarea_sonda_disco() { /// 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) { +fn encender_app(ejecutor: &mut Executor, indice: usize, entrada: &manifiesto::EntradaApp) { 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 @@ -145,7 +145,9 @@ fn encender_app(ejecutor: &mut Executor, entrada: &manifiesto::EntradaApp) { return; } }; - match wasm::AplicacionWasm::cargar(&bytecode, region, entrada.techo_memoria as usize) { + // `indice` es la identidad de la app en el manifiesto: las capacidades de + // estado persistido (Fase 7c) la usan para hallar SU ranura `estado`. + match wasm::AplicacionWasm::cargar(&bytecode, region, entrada.techo_memoria as usize, indice) { Ok(app) => ejecutor.spawn(tarea_aplicacion(app)), Err(_) => consola::pintar_desalojo(region, Color::DESALOJO), } @@ -163,10 +165,11 @@ fn reportar(linea: &str) { } /// FASE 7 :: puebla el userspace DESDE EL GRAFO. Carga el Manifiesto de -/// 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. +/// Genesis que `boot` sembro en la imagen de disco, lo instala como el +/// manifiesto VIVO del kernel 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), @@ -192,8 +195,13 @@ fn cargar_userspace(ejecutor: &mut Executor) { } if let Some(m) = manifiesto { - for entrada in &m.apps { - encender_app(ejecutor, entrada); + // Instalar el manifiesto VIVO ANTES de instanciar las apps: el `init` + // de cada app puede consultar su estado persistido (Fase 7c), y esa + // consulta lee del manifiesto vivo. Se instala una copia; la otra se + // itera para encender cada app con su indice — su identidad. + manifiesto::instalar(m.clone()); + for (indice, entrada) in m.apps.iter().enumerate() { + encender_app(ejecutor, indice, entrada); } } } diff --git a/renaser/kernel/src/manifiesto.rs b/renaser/kernel/src/manifiesto.rs index 60586ed..7a07b93 100644 --- a/renaser/kernel/src/manifiesto.rs +++ b/renaser/kernel/src/manifiesto.rs @@ -7,17 +7,24 @@ // cuota, en que region— lo dicta este Manifiesto de Genesis, que tambien // habita el grafo. El superbloque guarda su hash en un ancla propia. // -// 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 7c. El manifiesto deja de ser de solo lectura. El kernel +// conserva una copia VIVA —`VIVO`, un `Mutex`—; cuando una app +// persiste su estado (`sys_estado_guardar`), el kernel actualiza la ranura +// `estado` de su `EntradaApp`, re-graba el manifiesto en el grafo y lo +// re-ancla. Asi el estado de cada app sobrevive, por separado, a un reinicio. // // 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. +// lo que es del kernel: cargar el manifiesto, traducir regiones, custodiar la +// copia viva y mutarla cuando una app graba su estado. // ============================================================================= +use alloc::vec::Vec; + +use spin::{Mutex, Once}; + +use formato::Hash; + use crate::almacen; use crate::grafico::RegionPantalla; @@ -25,6 +32,12 @@ use crate::grafico::RegionPantalla; // resto del kernel los nombre `manifiesto::EntradaApp` / `manifiesto::Manifiesto`. pub use formato::{EntradaApp, Manifiesto}; +/// El manifiesto VIVO del kernel: la copia en memoria, mutable, del Manifiesto +/// de Genesis. Las apps la actualizan al persistir su estado (Fase 7c); el +/// kernel la re-graba en el grafo y la re-ancla en el superbloque. Se instala +/// una sola vez, en el arranque, con [`instalar`]. +static VIVO: Once> = Once::new(); + /// 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 @@ -54,3 +67,45 @@ pub fn cargar() -> Result, &'static str> { let manifiesto = Manifiesto::deserializar(&objeto.datos)?; Ok(Some(manifiesto)) } + +/// Instala el manifiesto recien cargado como el manifiesto VIVO del kernel. Se +/// invoca una sola vez, en el arranque, ANTES de instanciar las apps — el +/// `init` de cada app ya consulta su estado persistido, y eso lee de aqui. +pub fn instalar(manifiesto: Manifiesto) { + VIVO.call_once(|| Mutex::new(manifiesto)); +} + +/// El hash del estado persistido de la app `indice`, si tiene uno anclado. Lo +/// consulta la capacidad `sys_estado_cargar` cuando una app despierta. +pub fn estado_de(indice: usize) -> Option { + VIVO.get() + .and_then(|vivo| vivo.lock().apps.get(indice).and_then(|app| app.estado)) +} + +/// Registra `hash` como el nuevo estado persistido de la app `indice`: muta el +/// manifiesto vivo, lo re-serializa, lo graba como un objeto NUEVO del grafo y +/// lo re-ancla en el superbloque. Desde esta llamada, el estado de esa app +/// sobrevive a un reinicio. La invoca la capacidad `sys_estado_guardar`. +pub fn fijar_estado(indice: usize, hash: Hash) -> Result<(), &'static str> { + let vivo = VIVO.get().ok_or("manifiesto :: no hay manifiesto vivo")?; + let mut manifiesto = vivo.lock(); + let entrada = manifiesto + .apps + .get_mut(indice) + .ok_or("manifiesto :: indice de app fuera de rango")?; + entrada.estado = Some(hash); + + // Re-grabar el manifiesto mutado. Sus `hijos` son, como en la siembra de + // `boot`, los objetos de bytecode deduplicados: el grafo lo sigue leyendo + // como el nodo padre del userspace. El objeto nuevo se ancla en el + // superbloque; el viejo queda en el log, inerte e inofensivo. + let bytes = manifiesto.serializar()?; + let mut hijos: Vec = Vec::new(); + for app in &manifiesto.apps { + if !hijos.contains(&app.bytecode) { + hijos.push(app.bytecode); + } + } + let nuevo = almacen::almacenar(bytes, hijos)?; + almacen::fijar_manifiesto(nuevo) +} diff --git a/renaser/kernel/src/wasm/env.rs b/renaser/kernel/src/wasm/env.rs index bbed9d1..1b7a3d7 100644 --- a/renaser/kernel/src/wasm/env.rs +++ b/renaser/kernel/src/wasm/env.rs @@ -11,7 +11,9 @@ // * sys_object_datos — leer la carga util de un objeto; // * sys_object_hijo — recorrer las aristas del DAG; // * sys_object_raiz — leer la raiz del grafo; -// * sys_object_fijar_raiz — coronar un objeto como raiz. +// * sys_object_fijar_raiz — coronar un objeto como raiz; +// * sys_estado_cargar — leer el estado persistido de la app (Fase 7c); +// * sys_estado_guardar — anclar el estado persistido de la app (Fase 7c). // // GUARDARRAIL: el kernel valida MATEMATICAMENTE todo puntero que el modulo le // entrega contra los limites reales de su memoria lineal. No se confia en que @@ -40,6 +42,10 @@ pub(crate) struct ContextoCapacidades { /// El techo de recursos de la aplicacion — hoy, su memoria lineal maxima. /// `wasmi` lo consulta en cada `memory.grow` via `Store::limiter`. pub(crate) limites: StoreLimits, + /// El indice de esta app en el Manifiesto de Genesis — su identidad. Las + /// capacidades de estado (Fase 7c) lo usan para hallar la `EntradaApp` + /// correcta: cada app persiste en SU ranura, jamas en la de otra. + pub(crate) indice_app: usize, } /// Recupera la memoria lineal exportada por el modulo. Que no la exporte es un @@ -345,5 +351,89 @@ pub(crate) fn enlazar_capacidades( }, )?; + // --- CAPACIDAD 8 :: sys_estado_cargar(salida, capacidad) -> i32 --- + // Copia el estado persistido de ESTA app —el objeto que su `EntradaApp` del + // manifiesto tiene anclado— en `salida`. Devuelve el numero de bytes + // copiados, 0 si la app no tiene estado previo, -1 si el objeto anclado no + // existe, -2 si `capacidad` no basta, -3 si el almacenamiento fallo. + enlazador.func_wrap( + "renaser", + "sys_estado_cargar", + |mut caller: Caller<'_, ContextoCapacidades>, + salida: u32, + capacidad: u32| + -> Result { + let indice = caller.data().indice_app; + // El hash del estado de esta app, segun el manifiesto vivo. + let hash = match crate::manifiesto::estado_de(indice) { + Some(hash) => hash, + None => return Ok(0), // Sin estado previo: nada que cargar. + }; + let objeto = match crate::almacen::recuperar(&hash) { + Ok(Some(objeto)) => objeto, + Ok(None) => return Ok(-1), + Err(_) => return Ok(-3), + }; + if objeto.datos.len() > capacidad as usize { + return Ok(-2); + } + + let memoria = obtener_memoria(&caller)?; + // Verificar que el destino cabe, y solo entonces copiar. + { + let m = memoria.data(&caller); + rango( + m, + salida, + objeto.datos.len(), + "WASM :: sys_estado_cargar desbordo la memoria lineal (salida)", + )?; + } + let n = objeto.datos.len(); + let m = memoria.data_mut(&mut caller); + m[salida as usize..salida as usize + n].copy_from_slice(&objeto.datos); + Ok(n as i32) + }, + )?; + + // --- CAPACIDAD 9 :: sys_estado_guardar(datos, datos_len) -> i32 --- + // Graba `datos` como el estado persistido de ESTA app: el kernel lo + // almacena como un objeto del grafo y ancla su hash en la `EntradaApp` de + // la app, re-grabando y re-anclando el manifiesto. El estado sobrevivira al + // reinicio. Devuelve 0 si se logro, -3 si el almacenamiento fallo. + enlazador.func_wrap( + "renaser", + "sys_estado_guardar", + |caller: Caller<'_, ContextoCapacidades>, + datos_ptr: u32, + datos_len: u32| + -> Result { + let indice = caller.data().indice_app; + let memoria = obtener_memoria(&caller)?; + // Leer el estado de la memoria lineal, con limites firmes. + let datos = { + let m = memoria.data(&caller); + rango( + m, + datos_ptr, + datos_len as usize, + "WASM :: sys_estado_guardar desbordo la memoria lineal (datos)", + )? + .to_vec() + }; + // Grabar el objeto de estado. Un fallo del almacen NO es culpa de + // la app: se le devuelve -3, y ella decide que hacer. + let hash = match crate::almacen::almacenar(datos, alloc::vec::Vec::new()) { + Ok(hash) => hash, + Err(_) => return Ok(-3), + }; + // Anclarlo: muta el manifiesto vivo, lo re-graba y lo re-ancla. + match crate::manifiesto::fijar_estado(indice, hash) { + Ok(()) => Ok(0), + Err(_) => Ok(-3), + } + }, + )?; + Ok(()) } diff --git a/renaser/kernel/src/wasm/mod.rs b/renaser/kernel/src/wasm/mod.rs index 75e4ad1..d908cef 100644 --- a/renaser/kernel/src/wasm/mod.rs +++ b/renaser/kernel/src/wasm/mod.rs @@ -85,11 +85,14 @@ impl AplicacionWasm { /// el reactor en cada pulso del reloj. /// /// `techo_memoria` es la cuota de memoria lineal de ESTA app, en bytes — - /// desde la Fase 7 la dicta su `EntradaApp` del manifiesto. + /// desde la Fase 7 la dicta su `EntradaApp` del manifiesto. `indice_app` es + /// su posicion en el manifiesto: su identidad para las capacidades de + /// estado persistido (Fase 7c). pub fn cargar( bytecode: &[u8], region: RegionPantalla, techo_memoria: usize, + indice_app: usize, ) -> Result { // 1. El motor, con metricas de combustible y compilacion ANTICIPADA: la // traduccion del modulo ocurre ahora, de modo que el `fuel` mida @@ -119,6 +122,7 @@ impl AplicacionWasm { region, canal, limites, + indice_app, }, ); // Ligar el limitador de recursos: `wasmi` lo consultara en cada