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