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