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:
+186
-16
@@ -3,21 +3,40 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Un kernel bare-metal no nace solo: alguien debe fusionarlo con un cargador,
|
||||
// sellarlo en una imagen de disco arrancable y entregarlo al hardware. Esa es
|
||||
// la unica mision de este orquestador de ANFITRION.
|
||||
// la mision de este orquestador de ANFITRION.
|
||||
//
|
||||
// Desde la Fase 7b hace algo mas: SIEMBRA el grafo. El kernel ya no empotra
|
||||
// el userspace —ni un solo `include_bytes!` de un `.wasm`—; en su lugar, este
|
||||
// constructor pre-puebla el disco de objetos con el bytecode de las apps de
|
||||
// genesis y el Manifiesto de Genesis que dicta cuales arrancan, en que region
|
||||
// y con que cuota. Para ello habla el MISMO formato del grafo que el kernel,
|
||||
// a traves de la crate compartida `formato`.
|
||||
//
|
||||
// El flujo es deliberadamente lineal y sin ambiguedad:
|
||||
//
|
||||
// 1. Localizar el ELF nativo del kernel (lo inyecta la dep. de artefacto).
|
||||
// 2. Fusionarlo con el cargador UEFI en una imagen de disco GPT.
|
||||
// 3. Lanzar QEMU con esa imagen y el firmware OVMF.
|
||||
// 3. Sembrar el disco de objetos: el grafo poblado con el bytecode del
|
||||
// userspace y el Manifiesto de Genesis (Fase 7b).
|
||||
// 4. Lanzar QEMU con la imagen, el disco de objetos y el firmware OVMF.
|
||||
//
|
||||
// Cada paso que pueda fallar lo hace en voz alta, con un mensaje accionable:
|
||||
// preferimos un error claro a un arranque silencioso hacia la nada.
|
||||
// =============================================================================
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
// El formato del grafo de objetos en disco — el MISMO nucleo `no_std` que
|
||||
// enlaza el kernel. Gracias a el, lo que `boot` siembra y lo que el kernel lee
|
||||
// es, byte a byte, el mismo idioma.
|
||||
use formato::{
|
||||
EntradaApp, Hash, Manifiesto, Objeto, SuperBloque, MAGIA, MAX_OBJETO, TAM_SECTOR,
|
||||
VERSION_MANIFIESTO, VERSION_SUPERBLOQUE,
|
||||
};
|
||||
|
||||
/// Ruta del ELF del kernel, ya compilado para `x86_64-unknown-none`.
|
||||
///
|
||||
/// La dependencia de artefacto define esta variable de entorno en tiempo de
|
||||
@@ -35,7 +54,8 @@ const NOMBRE_IMAGEN: &str = "renaser-uefi.img";
|
||||
/// directorio de trabajo —la raiz del repo—, comun a `boot` y a QEMU.
|
||||
const NOMBRE_DISCO: &str = "target/disk.img";
|
||||
|
||||
/// Tamaño del disco de objetos: 32 MiB. Se crea como fichero disperso.
|
||||
/// Tamaño del disco de objetos: 32 MiB. La imagen sembrada ocupa solo unos
|
||||
/// pocos KiB; el resto queda a cero —espacio libre para que el grafo crezca—.
|
||||
const TAM_DISCO: u64 = 32 * 1024 * 1024;
|
||||
|
||||
fn main() {
|
||||
@@ -47,7 +67,7 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
/// Ejecuta, en orden, las tres operaciones de la Fase 1.5.
|
||||
/// Ejecuta, en orden, las operaciones de la Fase 1.5.
|
||||
fn orquestar() -> Result<(), String> {
|
||||
// --- 1. Localizar el artefacto del kernel. ---
|
||||
let kernel = Path::new(KERNEL_ELF);
|
||||
@@ -67,7 +87,7 @@ fn orquestar() -> Result<(), String> {
|
||||
.create_disk_image(&imagen)
|
||||
.map_err(|e| format!("la crate `bootloader` no pudo crear la imagen UEFI: {e:?}"))?;
|
||||
|
||||
// --- 3. Garantizar el disco de objetos del grafo persistente. ---
|
||||
// --- 3. Garantizar —y, si es virgen, SEMBRAR— el disco de objetos. ---
|
||||
preparar_disco_objetos()?;
|
||||
|
||||
// --- 4. Lanzar QEMU sobre esa imagen. ---
|
||||
@@ -75,35 +95,185 @@ fn orquestar() -> Result<(), String> {
|
||||
lanzar_qemu(&imagen, &ovmf)
|
||||
}
|
||||
|
||||
/// Garantiza la existencia del disco de objetos del grafo persistente. Si no
|
||||
/// existe, lo forja como un fichero disperso de 32 MiB, ENTERAMENTE A CERO: el
|
||||
/// kernel, al no hallar la firma de su superbloque, lo formateara como un grafo
|
||||
/// virgen. Si ya existe, lo respeta — el grafo perdura entre arranques.
|
||||
// =============================================================================
|
||||
// Fase 7b — la siembra del grafo: el userspace nace de la imagen de disco
|
||||
// =============================================================================
|
||||
|
||||
/// Una app de genesis: su nombre legible, el `.wasm` que la encarna y la
|
||||
/// ventana del framebuffer que habitara — `(x, y, ancho, alto)` en pixeles.
|
||||
struct AppGenesis {
|
||||
nombre: &'static str,
|
||||
archivo: &'static str,
|
||||
region: (u32, u32, u32, u32),
|
||||
}
|
||||
|
||||
/// El userspace de genesis — las cinco aplicaciones que pueblan un disco recien
|
||||
/// forjado, con las regiones de la Fase 6.2. `app.wasm` aparece dos veces —dos
|
||||
/// instancias del mismo bytecode—; el grafo, direccionado por contenido, guarda
|
||||
/// su objeto una sola vez.
|
||||
const GENESIS: [AppGenesis; 5] = [
|
||||
AppGenesis { nombre: "hola-izq", archivo: "app.wasm", region: (100, 120, 480, 560) },
|
||||
AppGenesis { nombre: "hola-der", archivo: "app.wasm", region: (700, 120, 480, 560) },
|
||||
AppGenesis { nombre: "discola", archivo: "discola.wasm", region: (60, 700, 360, 80) },
|
||||
AppGenesis { nombre: "glotona", archivo: "glotona.wasm", region: (460, 700, 360, 80) },
|
||||
AppGenesis { nombre: "cronista", archivo: "cronista.wasm", region: (860, 700, 360, 80) },
|
||||
];
|
||||
|
||||
/// Techo de memoria lineal de cada app de genesis: 4 MiB. Un modulo que intente
|
||||
/// crecer su memoria mas alla es desalojado por el kernel.
|
||||
const TECHO_GENESIS: u32 = 4 * 1024 * 1024;
|
||||
|
||||
/// Garantiza la existencia del disco de objetos del grafo persistente. Si ya
|
||||
/// existe, lo RESPETA — el grafo perdura entre arranques (la cuenta de la
|
||||
/// cronista, el estado del userspace). Si no existe, lo forja Y LO SIEMBRA:
|
||||
/// graba el grafo ya poblado con el bytecode de las apps y su Manifiesto de
|
||||
/// Genesis. El kernel jamas vuelve a empotrar una sola app.
|
||||
fn preparar_disco_objetos() -> Result<(), String> {
|
||||
let disco = Path::new(NOMBRE_DISCO);
|
||||
if disco.is_file() {
|
||||
println!("[renaser/boot] disco de objetos presente :: {}", disco.display());
|
||||
println!(
|
||||
"[renaser/boot] disco de objetos presente :: {} — el grafo perdura",
|
||||
disco.display()
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
if let Some(directorio) = disco.parent() {
|
||||
std::fs::create_dir_all(directorio)
|
||||
.map_err(|e| format!("no se pudo crear el directorio del disco de objetos: {e}"))?;
|
||||
}
|
||||
// Forjar el disco: un fichero disperso, a cero, de 32 MiB. El kernel
|
||||
// escribira su superbloque la primera vez que lo monte.
|
||||
let fichero = std::fs::File::create(disco)
|
||||
|
||||
// Sembrar el grafo: el bytecode del userspace y el Manifiesto de Genesis.
|
||||
let (imagen, objetos) = sembrar_grafo()?;
|
||||
if imagen.len() as u64 > TAM_DISCO {
|
||||
return Err(format!(
|
||||
"el grafo sembrado ({} bytes) no cabe en el disco de objetos ({TAM_DISCO} bytes)",
|
||||
imagen.len()
|
||||
));
|
||||
}
|
||||
|
||||
// Escribir la imagen sembrada y extender el fichero a 32 MiB: el log queda
|
||||
// al principio; el resto, a cero —`set_len` lo deja disperso—.
|
||||
let mut fichero = std::fs::File::create(disco)
|
||||
.map_err(|e| format!("no se pudo crear el disco de objetos «{}»: {e}", disco.display()))?;
|
||||
fichero
|
||||
.write_all(&imagen)
|
||||
.map_err(|e| format!("no se pudo escribir el grafo sembrado: {e}"))?;
|
||||
fichero
|
||||
.set_len(TAM_DISCO)
|
||||
.map_err(|e| format!("no se pudo dimensionar el disco de objetos: {e}"))?;
|
||||
println!(
|
||||
"[renaser/boot] disco de objetos forjado :: {} ({} MiB, virgen)",
|
||||
disco.display(),
|
||||
TAM_DISCO / (1024 * 1024)
|
||||
"[renaser/boot] disco de objetos sembrado :: {} ({objetos} objetos, manifiesto anclado)",
|
||||
disco.display()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Lee el bytecode `.wasm` de una app de genesis desde `kernel/assets/`. La
|
||||
/// ruta se ancla al directorio de ESTE crate —no al de trabajo—: el
|
||||
/// constructor funciona se invoque desde donde se invoque.
|
||||
fn leer_wasm(archivo: &str) -> Result<Vec<u8>, String> {
|
||||
let ruta = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../kernel/assets")
|
||||
.join(archivo);
|
||||
std::fs::read(&ruta)
|
||||
.map_err(|e| format!("no se pudo leer el bytecode «{}»: {e}", ruta.display()))
|
||||
}
|
||||
|
||||
/// Anexa un objeto al log: compone su registro `[longitud][payload][relleno]`,
|
||||
/// lo añade a la imagen y avanza el cursor. Devuelve el hash del objeto — su
|
||||
/// identidad en el grafo direccionado por contenido.
|
||||
fn anexar_objeto(log: &mut Vec<u8>, cursor: &mut u64, payload: &[u8]) -> Result<Hash, String> {
|
||||
if payload.is_empty() || payload.len() > MAX_OBJETO {
|
||||
return Err(format!(
|
||||
"un objeto del grafo tiene un tamaño invalido: {} bytes",
|
||||
payload.len()
|
||||
));
|
||||
}
|
||||
let hash = formato::hash(payload);
|
||||
log.extend_from_slice(&formato::componer_registro(payload));
|
||||
*cursor += formato::sectores_registro(payload.len());
|
||||
Ok(hash)
|
||||
}
|
||||
|
||||
/// Siembra el grafo de objetos de un disco virgen: graba el bytecode de cada
|
||||
/// app de genesis como un objeto del grafo, compone el Manifiesto de Genesis
|
||||
/// —con sus regiones y cuotas—, lo graba con las aristas hacia los objetos de
|
||||
/// bytecode, y forja el superbloque que lo ancla. Devuelve la imagen del disco
|
||||
/// (superbloque en el sector 0 + el log de registros) y el numero de objetos
|
||||
/// sembrados. Habla, byte a byte, el formato que el kernel leera al montar.
|
||||
fn sembrar_grafo() -> Result<(Vec<u8>, usize), String> {
|
||||
// El log de registros: del sector 1 en adelante. El sector 0 es el
|
||||
// superbloque, que aun no podemos escribir —no conocemos el cursor final—.
|
||||
let mut log: Vec<u8> = Vec::new();
|
||||
let mut cursor: u64 = 1;
|
||||
|
||||
// --- 1. Los objetos de bytecode, DEDUPLICADOS por archivo. Dos apps que
|
||||
// comparten el mismo `.wasm` comparten un unico objeto del grafo. ---
|
||||
let mut hash_de: BTreeMap<&str, Hash> = BTreeMap::new();
|
||||
let mut hijos_manifiesto: Vec<Hash> = Vec::new();
|
||||
let mut apps: Vec<EntradaApp> = Vec::new();
|
||||
|
||||
for app in &GENESIS {
|
||||
let bytecode = match hash_de.get(app.archivo) {
|
||||
// Ya grabado: el grafo no guarda dos veces el mismo contenido.
|
||||
Some(&hash) => hash,
|
||||
None => {
|
||||
let datos = leer_wasm(app.archivo)?;
|
||||
let objeto = Objeto { datos, hijos: Vec::new() };
|
||||
let payload = objeto.serializar().map_err(|e| e.to_string())?;
|
||||
let hash = anexar_objeto(&mut log, &mut cursor, &payload)?;
|
||||
hash_de.insert(app.archivo, hash);
|
||||
hijos_manifiesto.push(hash);
|
||||
hash
|
||||
}
|
||||
};
|
||||
let (x, y, ancho, alto) = app.region;
|
||||
apps.push(EntradaApp {
|
||||
nombre: app.nombre.to_string(),
|
||||
bytecode,
|
||||
region_x: x,
|
||||
region_y: y,
|
||||
region_ancho: ancho,
|
||||
region_alto: alto,
|
||||
techo_memoria: TECHO_GENESIS,
|
||||
estado: None,
|
||||
});
|
||||
}
|
||||
|
||||
// --- 2. El objeto del Manifiesto de Genesis. Sus `hijos` son los objetos
|
||||
// de bytecode: el grafo lo lee como el nodo padre del userspace. ---
|
||||
let manifiesto = Manifiesto { version: VERSION_MANIFIESTO, apps };
|
||||
let man_datos = manifiesto.serializar().map_err(|e| e.to_string())?;
|
||||
let man_objeto = Objeto { datos: man_datos, hijos: hijos_manifiesto };
|
||||
let man_payload = man_objeto.serializar().map_err(|e| e.to_string())?;
|
||||
let hash_manifiesto = anexar_objeto(&mut log, &mut cursor, &man_payload)?;
|
||||
|
||||
// El grafo sembrado: un objeto por cada `.wasm` unico, mas el manifiesto.
|
||||
let objetos = hash_de.len() + 1;
|
||||
|
||||
// --- 3. El superbloque: el ancla del grafo, en el sector 0. `raiz` queda
|
||||
// vacia —el userspace la fija; `manifiesto` apunta a la genesis. ---
|
||||
let superbloque = SuperBloque {
|
||||
magia: MAGIA,
|
||||
version: VERSION_SUPERBLOQUE,
|
||||
cursor,
|
||||
raiz: None,
|
||||
manifiesto: Some(hash_manifiesto),
|
||||
};
|
||||
let sb_bytes = superbloque.serializar().map_err(|e| e.to_string())?;
|
||||
if sb_bytes.len() > TAM_SECTOR {
|
||||
return Err("el superbloque sembrado no cabe en un sector".to_string());
|
||||
}
|
||||
|
||||
// La imagen: el superbloque en el sector 0 (relleno a cero) y, tras el, el
|
||||
// log de registros que acabamos de componer.
|
||||
let mut imagen = vec![0u8; TAM_SECTOR];
|
||||
imagen[..sb_bytes.len()].copy_from_slice(&sb_bytes);
|
||||
imagen.extend_from_slice(&log);
|
||||
|
||||
Ok((imagen, objetos))
|
||||
}
|
||||
|
||||
/// Calcula la ruta de la imagen: junto al propio ELF del kernel, es decir,
|
||||
/// dentro de `target/`. Una ubicacion predecible y siempre escribible.
|
||||
fn ruta_imagen(kernel: &Path) -> PathBuf {
|
||||
|
||||
Reference in New Issue
Block a user