feat(userspace): log server sole console holder; init boots from manifest; adapter loads only init+storage

This commit is contained in:
2026-10-27 15:40:19 +01:00
parent 4049ba5007
commit a4f467f45d
21 changed files with 510 additions and 325 deletions

View File

@@ -5,13 +5,9 @@ fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
unsafe { &mut *crate::kernel_ptr() } unsafe { &mut *crate::kernel_ptr() }
} }
pub fn grant_all( /// Init gets storage's process and endpoint (slots 1 and 2), storage its
uart: &Uart, /// endpoint (slot 0); everything else init starts itself.
init_pid: ProcessId, pub fn grant_all(uart: &Uart, init_pid: ProcessId, storage_pid: ProcessId) {
storage_pid: ProcessId,
display_pid: ProcessId,
input_pid: ProcessId,
) {
let all_perms = types::permission::PermissionSet::empty() let all_perms = types::permission::PermissionSet::empty()
.with(types::permission::Permission::Read) .with(types::permission::Permission::Read)
.with(types::permission::Permission::Write) .with(types::permission::Permission::Write)
@@ -49,74 +45,5 @@ pub fn grant_all(
) )
.unwrap_or_else(|_| crate::halt(uart, "grant failed")); .unwrap_or_else(|_| crate::halt(uart, "grant failed"));
let display_ep = kern()
.create_endpoint(display_pid)
.unwrap_or_else(|_| crate::halt(uart, "display endpoint failed"));
kern()
.grant_capability(
display_pid,
domain::resource::Resource::Endpoint(display_ep),
ipc_perms,
)
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
kern()
.grant_capability(
init_pid,
domain::resource::Resource::Endpoint(display_ep),
all_perms,
)
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
kern()
.grant_capability(
init_pid,
domain::resource::Resource::Process(display_pid),
all_perms,
)
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
let input_ep = kern()
.create_endpoint(input_pid)
.unwrap_or_else(|_| crate::halt(uart, "input endpoint failed"));
kern()
.grant_capability(
input_pid,
domain::resource::Resource::Endpoint(input_ep),
ipc_perms,
)
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
kern()
.grant_capability(
init_pid,
domain::resource::Resource::Endpoint(input_ep),
all_perms,
)
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
kern()
.grant_capability(
init_pid,
domain::resource::Resource::Process(input_pid),
all_perms,
)
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
uart.write_str("k-os: capabilities granted\n"); uart.write_str("k-os: capabilities granted\n");
} }
/// Until the log server owns the console, the servers loaded here print
/// through a console cap of their own, its slot passed in `a0`.
pub fn grant_console(uart: &Uart, servers: &[super::loader::LoadedProcess]) {
let write =
types::permission::PermissionSet::empty().with(types::permission::Permission::Write);
for server in servers {
let slot = kern()
.grant_capability(server.pid, domain::resource::Resource::Console, write)
.unwrap_or_else(|_| crate::halt(uart, "console grant failed"));
if let Some(ctx) = kern().thread_context_mut(server.tid) {
ctx.registers_mut()[10] = types::start_arg::encode(Some(slot), None);
}
}
}

View File

@@ -12,10 +12,8 @@ const USER_VA_BASE: usize = 0x10000;
const USER_STACK_PAGES: usize = USER_VA_BASE / PAGE_SIZE - 1; const USER_STACK_PAGES: usize = USER_VA_BASE / PAGE_SIZE - 1;
const MAX_FILE_SIZE: usize = 64 * 1024; const MAX_FILE_SIZE: usize = 64 * 1024;
#[derive(Clone, Copy)]
pub struct LoadedProcess { pub struct LoadedProcess {
pub pid: ProcessId, pub pid: ProcessId,
pub tid: ThreadId,
} }
fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> { fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
@@ -50,30 +48,17 @@ pub fn load_init(uart: &Uart, blk: &mut VirtioBlk) -> LoadedProcess {
setup_thread(tid, entry); setup_thread(tid, entry);
uart.write_str("k-os: init loaded\n"); uart.write_str("k-os: init loaded\n");
LoadedProcess { pid, tid } LoadedProcess { pid }
} }
/// The servers boot loads, their budgets carved from init's. /// Storage's budget, carved from init's.
pub const STORAGE: Limits = Limits { const STORAGE: Limits = Limits {
pages: 256, pages: 256,
endpoints: 2, endpoints: 2,
notifications: 2, notifications: 2,
// A buffer per client (8) and the disk's DMA. // A buffer per client (8) and the disk's DMA.
regions: 12, regions: 12,
}; };
/// The framebuffer alone is ~1000 pages at 1280x800.
pub const DISPLAY: Limits = Limits {
pages: 1280,
endpoints: 2,
notifications: 2,
regions: 4,
};
pub const INPUT: Limits = Limits {
pages: 64,
endpoints: 2,
notifications: 2,
regions: 4,
};
pub fn load_server( pub fn load_server(
uart: &Uart, uart: &Uart,
@@ -81,10 +66,9 @@ pub fn load_server(
init: ProcessId, init: ProcessId,
filename: &[u8], filename: &[u8],
label: &str, label: &str,
limits: Limits,
) -> LoadedProcess { ) -> LoadedProcess {
let pid = kern() let pid = kern()
.create_process_internal(init, limits) .create_process_internal(init, STORAGE)
.unwrap_or_else(|| crate::halt(uart, "process create failed")); .unwrap_or_else(|| crate::halt(uart, "process create failed"));
let entry = load_elf(uart, blk, pid, filename); let entry = load_elf(uart, blk, pid, filename);
let tid = kern() let tid = kern()
@@ -95,7 +79,7 @@ pub fn load_server(
uart.write_str(label); uart.write_str(label);
uart.write_str(" loaded\n"); uart.write_str(" loaded\n");
LoadedProcess { pid, tid } LoadedProcess { pid }
} }
fn setup_thread(tid: ThreadId, entry: usize) { fn setup_thread(tid: ThreadId, entry: usize) {

View File

@@ -40,32 +40,14 @@ extern "C" fn kmain(hart: usize) -> ! {
boot::hardware::probe_block_device(&uart).unwrap_or_else(|| halt(&uart, "no block device")); boot::hardware::probe_block_device(&uart).unwrap_or_else(|| halt(&uart, "no block device"));
let init = boot::loader::load_init(&uart, &mut blk); let init = boot::loader::load_init(&uart, &mut blk);
use boot::loader::{DISPLAY, INPUT, STORAGE, load_server}; // Init needs storage to load anything else; the rest is init's to start.
let storage = load_server( let storage = boot::loader::load_server(&uart, &mut blk, init.pid, b"storage.elf", "storage");
&uart,
&mut blk,
init.pid,
b"storage.elf",
"storage",
STORAGE,
);
let display = load_server(
&uart,
&mut blk,
init.pid,
b"display.elf",
"display",
DISPLAY,
);
let input = load_server(&uart, &mut blk, init.pid, b"input.elf", "input", INPUT);
let board = board::devices().unwrap_or_else(|| halt(&uart, "bad board table")); let board = board::devices().unwrap_or_else(|| halt(&uart, "bad board table"));
kern_mut() kern_mut()
.register_board(init.pid, &board) .register_board(init.pid, &board)
.unwrap_or_else(|_| halt(&uart, "board registration failed")); .unwrap_or_else(|_| halt(&uart, "board registration failed"));
boot::caps::grant_all(&uart, init.pid, storage.pid, display.pid, input.pid); boot::caps::grant_all(&uart, init.pid, storage.pid);
// Last, so the servers' fixed slots stay where they expect them.
boot::caps::grant_console(&uart, &[storage, display, input]);
// After grant_all: init already relies on those slots. // After grant_all: init already relies on those slots.
let mut placed = [None; board::BOARD_DEVICES]; let mut placed = [None; board::BOARD_DEVICES];
let boot_caps = kern_mut() let boot_caps = kern_mut()

115
crates/init/src/launch.rs Normal file
View File

@@ -0,0 +1,115 @@
//! Starting a manifest program: load it, hand it what the manifest says
//! and nothing else, run it.
use libkos::device::Device as BoardDevice;
use libkos::fs::Storage;
use libkos::syscall;
use types::address::StackPointer;
use types::bounded::CapSlot;
use types::permission::{Permission, PermissionSet};
use crate::devices::Devices;
use crate::load::{self, LoadedProcess};
use crate::manifest::{Device, Program};
const USER_STACK_TOP: StackPointer = StackPointer::from_raw(0x10000);
const MAX_SERVED: usize = 8;
fn perms(list: &[Permission]) -> PermissionSet {
list.iter().fold(PermissionSet::empty(), |s, p| s.with(*p))
}
pub(crate) struct Launcher<'a> {
storage: &'a mut Storage,
scratch: &'a mut [u8],
devices: Devices,
/// The endpoint of every server started so far, by name.
served: [Option<(&'static str, CapSlot)>; MAX_SERVED],
}
impl<'a> Launcher<'a> {
pub(crate) fn new(storage: &'a mut Storage, scratch: &'a mut [u8], devices: Devices) -> Self {
Self { storage, scratch, devices, served: [None; MAX_SERVED] }
}
pub(crate) fn storage(&mut self) -> (&mut Storage, &mut [u8]) {
(self.storage, self.scratch)
}
fn endpoint(&self, name: &str) -> Option<CapSlot> {
self.served.iter().flatten().find(|(n, _)| *n == name).map(|(_, ep)| *ep)
}
/// What became of the program's device, if it has one.
pub(crate) fn launch(&mut self, p: &Program) -> Result<&'static str, &'static str> {
let loaded =
load::load_binary(self.storage, self.scratch, p.file, p.limits).ok_or("cannot load")?;
let child = loaded.process_cap;
let ep = match p.serves {
true => {
let ep = syscall::create_endpoint().ok_or("no endpoint")?;
// Read to receive on it; init keeps the original.
let server = perms(&[Permission::Read, Permission::Execute]);
syscall::delegate(ep, child, server).ok_or("cannot give its endpoint")?;
Some(ep)
}
false => None,
};
// A client only calls: Execute, never Read, so it cannot pose as the
// server and take other clients' requests.
for name in p.uses {
let ep = self.endpoint(name).ok_or("needs a server that is not running")?;
syscall::delegate(ep, child, perms(&[Permission::Execute])).ok_or("cannot give an endpoint")?;
}
let console = libkos::start::console()
.filter(|_| p.console)
.and_then(|c| syscall::delegate(c, child, perms(&[Permission::Write])));
start(&loaded, console);
let mut note = "";
if let Some(ep) = ep {
if let Some(free) = self.served.iter_mut().find(|s| s.is_none()) {
*free = Some((p.name, ep));
}
if let Some(device) = p.device {
note = self.give(device, ep);
}
if console.is_some() {
hand_over_console(ep);
note = ", console handed over";
}
}
Ok(note)
}
/// The driver waits for its device before serving, and runs without
/// one (headless) if the board has none.
fn give(&self, device: Device, ep: CapSlot) -> &'static str {
let (dev, given, none): (BoardDevice, _, _) = match device {
Device::Gpu => (self.devices.gpu, ", gpu handed over", ", no gpu"),
Device::Input => (self.devices.input, ", input device handed over", ", no input device"),
};
match libkos::device::give_device(ep, dev).is_ok() && dev.mmio.is_some() {
true => given,
false => none,
}
}
}
/// From here on init prints through the log server, like everyone else,
/// and holds no console of its own.
fn hand_over_console(log: CapSlot) {
if let Some(console) = libkos::start::console() {
syscall::drop_cap(console);
}
libkos::start::set_console(None);
libkos::start::set_log(Some(log));
}
/// Starts `program` with `console` and, once there is one, the log.
pub(crate) fn start(program: &LoadedProcess, console: Option<CapSlot>) {
let log = libkos::start::log()
.and_then(|l| syscall::delegate(l, program.process_cap, perms(&[Permission::Execute])));
let arg = types::start_arg::encode(console, log);
syscall::spawn_thread_in(program.process_cap, program.entry_point, USER_STACK_TOP, arg);
}

View File

@@ -3,224 +3,80 @@
extern crate alloc; extern crate alloc;
mod devices; mod devices;
mod launch;
mod load;
mod manifest;
mod selftest; mod selftest;
use types::address::{EntryPoint, StackPointer, VirtualAddress};
use types::bounded::{CapSlot, PageCount};
use types::budget::Limits;
use types::permission::{Permission, PermissionSet};
use libkos::fs::Storage; use libkos::fs::Storage;
use libkos::syscall; use libkos::syscall;
use types::badge::Badge;
use types::bounded::CapSlot;
use types::permission::{Permission, PermissionSet};
const PAGE_SIZE: usize = 4096;
const USER_CODE_BASE: usize = 0x10000;
pub(crate) const USER_STACK_TOP: StackPointer = StackPointer::from_raw(USER_CODE_BASE);
const USER_STACK_PAGES: usize = 4;
/// Where a program file is read to before it is loaded (16 pages: the
/// largest program init will load).
const SCRATCH_VA: usize = 0x30000;
const SCRATCH_PAGES: u16 = 16;
/// Where the buffer storage shares with init is mapped. /// Where the buffer storage shares with init is mapped.
const STORAGE_BUF_VA: usize = 0x0180_0000; const STORAGE_BUF_VA: usize = 0x0180_0000;
/// Boot loads storage before init and leaves its endpoint here.
const CREATE_CAP: CapSlot = CapSlot::const_new(0);
const STORAGE_EP: CapSlot = CapSlot::const_new(2); const STORAGE_EP: CapSlot = CapSlot::const_new(2);
const DISPLAY_EP: CapSlot = CapSlot::const_new(3);
const INPUT_EP: CapSlot = CapSlot::const_new(5);
/// Reads all of `filename` into `scratch`; its size, or `None` if it is
/// missing, unreadable or larger than `scratch`.
fn read_file(storage: &mut Storage, scratch: &mut [u8], filename: &[u8]) -> Option<usize> {
let handle = storage.open(filename).ok()?;
let size = storage.file_size(handle).ok().filter(|s| *s <= scratch.len());
let read = size.and_then(|s| storage.read(handle, &mut scratch[..s]).ok());
let _ = storage.close(handle);
read.filter(|r| Some(*r) == size)
}
pub(crate) struct LoadedProcess {
pub(crate) process_cap: CapSlot,
pub(crate) entry_point: EntryPoint,
}
pub(crate) fn load_binary(
storage: &mut Storage,
scratch: &mut [u8],
filename: &[u8],
limits: Limits,
) -> Option<LoadedProcess> {
let file_size = read_file(storage, scratch, filename)?;
let elf_data = &scratch[..file_size];
let elf_info = match elf::parse(elf_data) {
Ok(info) => info,
Err(_) => return None,
};
let process_cap = syscall::create_process(CREATE_CAP, limits)?;
for segment in elf_info.segments() {
let page_start = segment.vaddr & !0xFFF;
let offset_in_page = segment.vaddr & 0xFFF;
let total_size = offset_in_page + segment.mem_size;
let seg_pages = (total_size + PAGE_SIZE - 1) / PAGE_SIZE;
let page_count = PageCount::new(seg_pages as u16)?;
let local_va = syscall::request_pages(page_count)?;
let local_ptr = local_va.raw() as *mut u8;
unsafe {
core::ptr::write_bytes(local_ptr, 0, seg_pages * PAGE_SIZE);
}
let copy_len = segment.file_size.min(segment.mem_size);
let src = segment
.file_offset
.checked_add(copy_len)
.and_then(|end| elf_data.get(segment.file_offset..end));
if let Some(src) = src.filter(|_| copy_len > 0) {
// SAFETY: the pages just requested hold seg_pages * PAGE_SIZE
// bytes, which covers offset_in_page + copy_len.
unsafe {
core::ptr::copy_nonoverlapping(src.as_ptr(), local_ptr.add(offset_in_page), copy_len);
}
}
for p in 0..seg_pages {
let src_page = VirtualAddress::from_raw(local_va.raw() + p * PAGE_SIZE);
let dst_page = VirtualAddress::from_raw(page_start + p * PAGE_SIZE);
syscall::map_page(process_cap, src_page, dst_page, segment.flags.execute);
}
}
for i in 0..USER_STACK_PAGES {
let page_count = PageCount::new(1)?;
let stack_va = syscall::request_pages(page_count)?;
let dst = VirtualAddress::from_raw(USER_CODE_BASE - (USER_STACK_PAGES - i) * PAGE_SIZE);
syscall::map_page(process_cap, stack_va, dst, false);
}
Some(LoadedProcess {
process_cap,
entry_point: EntryPoint::from_raw(elf_info.entry_point),
})
}
struct ServerInfo {
filename: &'static [u8],
name: &'static str,
limits: Limits,
}
/// Enough for a small program: its image, stack and a little heap.
pub(crate) const SMALL: Limits = Limits {
pages: 64,
endpoints: 2,
notifications: 2,
regions: 2,
};
const SERVERS: &[ServerInfo] = &[
ServerInfo { filename: b"log.elf", name: "log", limits: SMALL },
ServerInfo { filename: b"permis.elf", name: "permis", limits: SMALL },
ServerInfo { filename: b"session.elf", name: "session", limits: SMALL },
];
const SNAKE: Limits = Limits { pages: 128, ..SMALL };
pub fn run() -> ! { pub fn run() -> ! {
libkos::start::set_console(devices::console()); libkos::start::set_console(devices::console());
libkos::println!("init: started (pid={:#x})", syscall::get_process_id().raw()); libkos::println!("init: started (pid={:#x})", syscall::get_process_id().raw());
// Drivers wait for their device before serving, so this comes first: // Storage waits for its disk before serving, so this comes first.
// storage needs its disk before init can load anything.
let found = devices::discover(); let found = devices::discover();
for (name, ep, dev) in [ let disk = libkos::device::give_device(STORAGE_EP, found.disk).is_ok() && found.disk.mmio.is_some();
("disk", STORAGE_EP, found.disk), libkos::println!("init: disk: {}", if disk { "handed to its driver" } else { "none" });
("gpu", DISPLAY_EP, found.gpu),
("input", INPUT_EP, found.input),
] {
let given = libkos::device::give_device(ep, dev).is_ok() && dev.mmio.is_some();
libkos::println!("init: {}: {}", name, if given { "handed to its driver" } else { "none" });
}
let Ok(mut storage) = Storage::attach(STORAGE_EP, STORAGE_BUF_VA) else { let Ok(mut storage) = Storage::attach(STORAGE_EP, STORAGE_BUF_VA) else {
libkos::println!("init: cannot reach storage, nothing to load"); libkos::println!("init: cannot reach storage, nothing to load");
idle(); idle();
}; };
let Some(scratch) = scratch() else { // Storage has nothing to print with yet; init speaks for it.
libkos::println!("init: storage: up");
let Some(scratch) = load::scratch() else {
libkos::println!("init: no memory to load programs into"); libkos::println!("init: no memory to load programs into");
idle(); idle();
}; };
let ipc_perms = PermissionSet::empty() let mut launcher = launch::Launcher::new(&mut storage, scratch, found);
.with(Permission::Read) launch_all(&mut launcher, manifest::SERVERS);
.with(Permission::Execute); give_storage_the_log();
for server in SERVERS {
libkos::print!("init: loading {}... ", server.name);
let loaded = match load_binary(&mut storage, scratch, server.filename, server.limits) {
Some(l) => l,
None => {
libkos::println!("SKIP");
continue;
}
};
if let Some(ep_cap) = syscall::create_endpoint() {
syscall::delegate(ep_cap, loaded.process_cap, ipc_perms);
start(&loaded, true);
}
libkos::println!("ok (entry={:#x})", loaded.entry_point.raw());
}
// The test image carries the SMP self-test; it runs instead of the // The test image carries the SMP self-test; it runs instead of the
// desktop and halts the machine. // desktop and halts the machine.
let (storage, scratch) = launcher.storage();
if storage.open(b"smptest.elf").is_ok_and(|h| storage.close(h).is_ok()) { if storage.open(b"smptest.elf").is_ok_and(|h| storage.close(h).is_ok()) {
libkos::println!("init: running the SMP self-test"); libkos::println!("init: running the SMP self-test");
selftest::run(&mut storage, scratch); selftest::run(storage, scratch);
}
libkos::print!("init: loading snake... ");
if let Some(snake) = load_binary(&mut storage, scratch, b"snake.elf", SNAKE) {
// A client only calls: Execute, never Read, so it cannot pose as
// the server and take other clients' requests.
let client = PermissionSet::empty().with(Permission::Execute);
syscall::delegate(INPUT_EP, snake.process_cap, client);
syscall::delegate(DISPLAY_EP, snake.process_cap, client);
start(&snake, false);
libkos::println!("ok");
} else {
libkos::println!("SKIP");
} }
launch_all(&mut launcher, manifest::APPS);
libkos::println!("init: all loaded, supervising"); libkos::println!("init: all loaded, supervising");
idle(); idle();
} }
/// Starts `program`, lending it init's console if `console`. fn launch_all(launcher: &mut launch::Launcher, programs: &[manifest::Program]) {
pub(crate) fn start(program: &LoadedProcess, console: bool) { for p in programs {
let write = PermissionSet::empty().with(Permission::Write); libkos::print!("init: loading {}... ", p.name);
let slot = libkos::start::console() match launcher.launch(p) {
.filter(|_| console) Ok(note) => libkos::println!("ok{}", note),
.and_then(|c| syscall::delegate(c, program.process_cap, write)); Err(why) => libkos::println!("SKIP ({})", why),
let arg = types::start_arg::encode(slot, None); }
syscall::spawn_thread_in(program.process_cap, program.entry_point, USER_STACK_TOP, arg); }
} }
/// A region of init's own to read program files into. /// Storage started before the log server; it gets a call-only copy of
fn scratch() -> Option<&'static mut [u8]> { /// the log endpoint now (Delegate only to send it: transfer strips it).
let region = syscall::create_region(PageCount::new(SCRATCH_PAGES)?).ok()?; fn give_storage_the_log() {
syscall::map_region( let Some(log) = libkos::start::log() else {
region, return;
VirtualAddress::from_raw(SCRATCH_VA), };
types::mapping::PagePerms::ReadWrite, let call_only = PermissionSet::empty().with(Permission::Execute).with(Permission::Delegate);
) if let Ok(copy) = syscall::mint(log, Badge::NONE, call_only) {
.ok()?; let _ = syscall::call(STORAGE_EP, [types::storage::op::SET_LOG, 0, 0, 0], Some(copy));
// SAFETY: the region is mapped read-write at SCRATCH_VA for good. syscall::drop_cap(copy);
Some(unsafe { core::slice::from_raw_parts_mut(SCRATCH_VA as *mut u8, SCRATCH_PAGES as usize * PAGE_SIZE) }) }
} }
/// Nothing to supervise yet: block on a notification nobody signals /// Nothing to supervise yet: block on a notification nobody signals

106
crates/init/src/load.rs Normal file
View File

@@ -0,0 +1,106 @@
//! Reading program files from storage and loading them into new processes.
use libkos::fs::Storage;
use libkos::syscall;
use types::address::{EntryPoint, VirtualAddress};
use types::bounded::{CapSlot, PageCount};
use types::budget::Limits;
const PAGE_SIZE: usize = 4096;
const USER_CODE_BASE: usize = 0x10000;
const USER_STACK_PAGES: usize = 4;
const CREATE_CAP: CapSlot = CapSlot::const_new(0);
/// Where a program file is read to before it is loaded (16 pages: the
/// largest program init will load).
const SCRATCH_VA: usize = 0x30000;
const SCRATCH_PAGES: u16 = 16;
pub(crate) struct LoadedProcess {
pub(crate) process_cap: CapSlot,
pub(crate) entry_point: EntryPoint,
}
/// Reads all of `filename` into `scratch`; its size, or `None` if it is
/// missing, unreadable or larger than `scratch`.
fn read_file(storage: &mut Storage, scratch: &mut [u8], filename: &[u8]) -> Option<usize> {
let handle = storage.open(filename).ok()?;
let size = storage.file_size(handle).ok().filter(|s| *s <= scratch.len());
let read = size.and_then(|s| storage.read(handle, &mut scratch[..s]).ok());
let _ = storage.close(handle);
read.filter(|r| Some(*r) == size)
}
pub(crate) fn load_binary(
storage: &mut Storage,
scratch: &mut [u8],
filename: &[u8],
limits: Limits,
) -> Option<LoadedProcess> {
let file_size = read_file(storage, scratch, filename)?;
let elf_data = &scratch[..file_size];
let elf_info = match elf::parse(elf_data) {
Ok(info) => info,
Err(_) => return None,
};
let process_cap = syscall::create_process(CREATE_CAP, limits)?;
for segment in elf_info.segments() {
let page_start = segment.vaddr & !0xFFF;
let offset_in_page = segment.vaddr & 0xFFF;
let total_size = offset_in_page + segment.mem_size;
let seg_pages = (total_size + PAGE_SIZE - 1) / PAGE_SIZE;
let page_count = PageCount::new(seg_pages as u16)?;
let local_va = syscall::request_pages(page_count)?;
let local_ptr = local_va.raw() as *mut u8;
unsafe {
core::ptr::write_bytes(local_ptr, 0, seg_pages * PAGE_SIZE);
}
let copy_len = segment.file_size.min(segment.mem_size);
let src = segment
.file_offset
.checked_add(copy_len)
.and_then(|end| elf_data.get(segment.file_offset..end));
if let Some(src) = src.filter(|_| copy_len > 0) {
// SAFETY: the pages just requested hold seg_pages * PAGE_SIZE
// bytes, which covers offset_in_page + copy_len.
unsafe {
core::ptr::copy_nonoverlapping(src.as_ptr(), local_ptr.add(offset_in_page), copy_len);
}
}
for p in 0..seg_pages {
let src_page = VirtualAddress::from_raw(local_va.raw() + p * PAGE_SIZE);
let dst_page = VirtualAddress::from_raw(page_start + p * PAGE_SIZE);
syscall::map_page(process_cap, src_page, dst_page, segment.flags.execute);
}
}
for i in 0..USER_STACK_PAGES {
let page_count = PageCount::new(1)?;
let stack_va = syscall::request_pages(page_count)?;
let dst = VirtualAddress::from_raw(USER_CODE_BASE - (USER_STACK_PAGES - i) * PAGE_SIZE);
syscall::map_page(process_cap, stack_va, dst, false);
}
Some(LoadedProcess {
process_cap,
entry_point: EntryPoint::from_raw(elf_info.entry_point),
})
}
/// A region of init's own to read program files into.
pub(crate) fn scratch() -> Option<&'static mut [u8]> {
let region = syscall::create_region(PageCount::new(SCRATCH_PAGES)?).ok()?;
syscall::map_region(
region,
VirtualAddress::from_raw(SCRATCH_VA),
types::mapping::PagePerms::ReadWrite,
)
.ok()?;
// SAFETY: the region is mapped read-write at SCRATCH_VA for good.
Some(unsafe { core::slice::from_raw_parts_mut(SCRATCH_VA as *mut u8, SCRATCH_PAGES as usize * PAGE_SIZE) })
}

View File

@@ -0,0 +1,55 @@
//! What init starts, in order: the system's boot policy, as data.
use types::budget::Limits;
/// A board device init hands to a program over its endpoint.
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum Device {
Gpu,
Input,
}
pub struct Program {
pub name: &'static str,
pub file: &'static [u8],
pub limits: Limits,
/// Serves an endpoint, which it finds in slot 0.
pub serves: bool,
pub device: Option<Device>,
/// Earlier programs' endpoints it may call, in its next slots.
pub uses: &'static [&'static str],
/// Takes the console over from init; everyone else prints through it.
pub console: bool,
}
/// Enough for a small program: its image, stack and a little heap.
pub const SMALL: Limits = Limits {
pages: 64,
endpoints: 2,
notifications: 2,
regions: 2,
};
const fn server(name: &'static str, file: &'static [u8], limits: Limits) -> Program {
Program { name, file, limits, serves: true, device: None, uses: &[], console: false }
}
/// Started on every boot. The log server comes first so the rest can print.
pub const SERVERS: &[Program] = &[
Program { console: true, ..server("log", b"log.elf", SMALL) },
Program {
device: Some(Device::Gpu),
// The framebuffer alone is ~1000 pages at 1280x800.
..server("display", b"display.elf", Limits { pages: 1280, regions: 4, ..SMALL })
},
Program { device: Some(Device::Input), ..server("input", b"input.elf", Limits { regions: 4, ..SMALL }) },
server("permis", b"permis.elf", SMALL),
server("session", b"session.elf", SMALL),
];
/// The desktop; the test image runs the SMP self-test instead.
pub const APPS: &[Program] = &[Program {
serves: false,
uses: &["input", "display"],
..server("snake", b"snake.elf", Limits { pages: 128, ..SMALL })
}];

View File

@@ -40,7 +40,7 @@ fn start_spinner(storage: &mut Storage, scratch: &mut [u8], page: CapSlot) -> Re
for i in 0..=THREADS { for i in 0..=THREADS {
word(i).store(0, Ordering::SeqCst); word(i).store(0, Ordering::SeqCst);
} }
let spinner = crate::load_binary(storage, scratch, b"smptest.elf", crate::SMALL).ok_or("cannot load smptest.elf")?; let spinner = crate::load::load_binary(storage, scratch, b"smptest.elf", crate::manifest::SMALL).ok_or("cannot load smptest.elf")?;
let rw = PermissionSet::empty() let rw = PermissionSet::empty()
.with(Permission::Read) .with(Permission::Read)
.with(Permission::Write) .with(Permission::Write)
@@ -49,7 +49,7 @@ fn start_spinner(storage: &mut Storage, scratch: &mut [u8], page: CapSlot) -> Re
if syscall::delegate(page, spinner.process_cap, rw).is_none() { if syscall::delegate(page, spinner.process_cap, rw).is_none() {
return Err("cannot give the spinner its page"); return Err("cannot give the spinner its page");
} }
crate::start(&spinner, true); crate::launch::start(&spinner, None);
if !wait_until(3000, || counters().iter().all(|c| *c > 0)) { if !wait_until(3000, || counters().iter().all(|c| *c > 0)) {
return Err("spinner threads did not all start"); return Err("spinner threads did not all start");
} }

View File

@@ -4,8 +4,13 @@ struct Writer;
impl fmt::Write for Writer { impl fmt::Write for Writer {
fn write_str(&mut self, s: &str) -> fmt::Result { fn write_str(&mut self, s: &str) -> fmt::Result {
// Without a console capability, output goes nowhere. // The log server first; a process without either prints nothing.
if let Some(console) = crate::start::console() { // A call, not a send: the text is out before the caller goes on.
if let Some(log) = crate::start::log() {
for chunk in s.as_bytes().chunks(types::log::TEXT_BYTES) {
let _ = crate::syscall::call(log, types::log::text_words(chunk), None);
}
} else if let Some(console) = crate::start::console() {
for &b in s.as_bytes() { for &b in s.as_bytes() {
crate::syscall::debug_putchar(console, b); crate::syscall::debug_putchar(console, b);
} }

View File

@@ -8,8 +8,9 @@ use types::start_arg;
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
static LIBKOS_START_ARG: AtomicUsize = AtomicUsize::new(0); static LIBKOS_START_ARG: AtomicUsize = AtomicUsize::new(0);
/// Overrides the start argument's console once set; `NONE` drops output. /// Override the start argument's slots once set; `NONE` means none.
static CONSOLE: AtomicUsize = AtomicUsize::new(UNSET); static CONSOLE: AtomicUsize = AtomicUsize::new(UNSET);
static LOG: AtomicUsize = AtomicUsize::new(UNSET);
const UNSET: usize = usize::MAX; const UNSET: usize = usize::MAX;
const NONE: usize = usize::MAX - 1; const NONE: usize = usize::MAX - 1;
@@ -19,18 +20,31 @@ fn arg() -> usize {
/// The console capability this process prints through, if any. /// The console capability this process prints through, if any.
pub fn console() -> Option<CapSlot> { pub fn console() -> Option<CapSlot> {
match CONSOLE.load(Ordering::Relaxed) { read(&CONSOLE, start_arg::console)
UNSET => start_arg::console(arg()),
v => CapSlot::new(u8::try_from(v).ok()?),
}
} }
/// For a process that gets its console some other way (init, from BootInfo). /// For a process that gets its console some other way (init, from BootInfo).
pub fn set_console(slot: Option<CapSlot>) { pub fn set_console(slot: Option<CapSlot>) {
CONSOLE.store(slot.map_or(NONE, |s| s.value() as usize), Ordering::Relaxed); write(&CONSOLE, slot);
} }
/// The log server endpoint this process was given, if any. /// The log server endpoint this process prints to, if any.
pub fn log() -> Option<CapSlot> { pub fn log() -> Option<CapSlot> {
start_arg::log(arg()) read(&LOG, start_arg::log)
}
/// For a process given its log endpoint after it started.
pub fn set_log(slot: Option<CapSlot>) {
write(&LOG, slot);
}
fn read(cell: &AtomicUsize, from_arg: fn(usize) -> Option<CapSlot>) -> Option<CapSlot> {
match cell.load(Ordering::Relaxed) {
UNSET => from_arg(arg()),
v => CapSlot::new(u8::try_from(v).ok()?),
}
}
fn write(cell: &AtomicUsize, slot: Option<CapSlot>) {
cell.store(slot.map_or(NONE, |s| s.value() as usize), Ordering::Relaxed);
} }

View File

@@ -8,7 +8,14 @@ pub mod server;
mod tests; mod tests;
pub fn run() -> ! { pub fn run() -> ! {
let mut srv = server::LogServer::new(); let console = libkos::start::console();
let mut srv = server::LogServer::with_output(|bytes: &[u8]| {
if let Some(console) = console {
for &b in bytes {
libkos::syscall::debug_putchar(console, b);
}
}
});
let ep = types::bounded::CapSlot::new(0).unwrap(); let ep = types::bounded::CapSlot::new(0).unwrap();
server_framework::serve(&mut srv, ep) server_framework::serve(&mut srv, ep)
} }

View File

@@ -13,6 +13,8 @@ pub enum LogRequest {
Write { level: LogLevel, tag: u16, payload: usize }, Write { level: LogLevel, tag: u16, payload: usize },
GetCount, GetCount,
GetEntry { index: usize }, GetEntry { index: usize },
/// Console text (`types::log`): `len` bytes of `bytes`.
Text { bytes: [u8; types::log::TEXT_BYTES], len: usize },
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@@ -50,6 +52,7 @@ impl Encode for LogRequest {
} }
LogRequest::GetCount => [1, 0, 0, 0], LogRequest::GetCount => [1, 0, 0, 0],
LogRequest::GetEntry { index } => [2, *index, 0, 0], LogRequest::GetEntry { index } => [2, *index, 0, 0],
LogRequest::Text { bytes, len } => types::log::text_words(&bytes[..*len]),
} }
} }
} }
@@ -64,7 +67,7 @@ impl Decode for LogRequest {
} }
1 => Some(LogRequest::GetCount), 1 => Some(LogRequest::GetCount),
2 => Some(LogRequest::GetEntry { index: words[1] }), 2 => Some(LogRequest::GetEntry { index: words[1] }),
_ => None, _ => types::log::text_of(words).map(|(bytes, len)| LogRequest::Text { bytes, len }),
} }
} }
} }

View File

@@ -10,18 +10,28 @@ struct LogEntry {
payload: usize, payload: usize,
} }
pub struct LogServer { /// Holds the console: every process's text goes out through `out`.
pub struct LogServer<O: FnMut(&[u8]) = fn(&[u8])> {
entries: [Option<LogEntry>; MAX_ENTRIES], entries: [Option<LogEntry>; MAX_ENTRIES],
count: usize, count: usize,
write_pos: usize, write_pos: usize,
out: O,
} }
impl LogServer { impl LogServer {
/// Text goes nowhere.
pub fn new() -> Self { pub fn new() -> Self {
Self::with_output(|_| {})
}
}
impl<O: FnMut(&[u8])> LogServer<O> {
pub fn with_output(out: O) -> Self {
Self { Self {
entries: [const { None }; MAX_ENTRIES], entries: [const { None }; MAX_ENTRIES],
count: 0, count: 0,
write_pos: 0, write_pos: 0,
out,
} }
} }
@@ -30,7 +40,7 @@ impl LogServer {
} }
} }
impl Server for LogServer { impl<O: FnMut(&[u8])> Server for LogServer<O> {
type Request = LogRequest; type Request = LogRequest;
type Response = LogResponse; type Response = LogResponse;
@@ -44,6 +54,10 @@ impl Server for LogServer {
} }
LogResponse::Ok LogResponse::Ok
} }
LogRequest::Text { bytes, len } => {
(self.out)(&bytes[..len.min(bytes.len())]);
LogResponse::Ok
}
LogRequest::GetCount => LogResponse::Count(self.count), LogRequest::GetCount => LogResponse::Count(self.count),
LogRequest::GetEntry { index } => { LogRequest::GetEntry { index } => {
if index >= self.count { if index >= self.count {

View File

@@ -1,6 +1,6 @@
use crate::protocol::*; use crate::protocol::*;
use crate::server::LogServer; use crate::server::LogServer;
use server_framework::Server; use server_framework::{Decode, Server};
#[test] #[test]
fn empty_server_has_zero_count() { fn empty_server_has_zero_count() {
@@ -41,3 +41,20 @@ fn ring_buffer_wraps() {
} }
assert_eq!(server.count(), 128); assert_eq!(server.count(), 128);
} }
#[test]
fn text_goes_out_through_the_console() {
let mut seen = [0u8; 64];
let mut n = 0;
{
let mut server = LogServer::with_output(|b: &[u8]| {
seen[n..n + b.len()].copy_from_slice(b);
n += b.len();
});
for chunk in [&b"init: "[..], b"started\n"] {
let words = types::log::text_words(chunk);
server.handle(LogRequest::decode(words).unwrap());
}
}
assert_eq!(&seen[..n], b"init: started\n");
}

View File

@@ -23,6 +23,8 @@ pub enum StorageRequest {
Create { path_len: usize }, Create { path_len: usize },
Delete { path_len: usize }, Delete { path_len: usize },
Mkdir { path_len: usize }, Mkdir { path_len: usize },
/// Carries the endpoint to print to.
SetLog,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -61,6 +63,7 @@ impl Encode for StorageRequest {
Create { path_len } => [op::CREATE, path_len, 0, 0], Create { path_len } => [op::CREATE, path_len, 0, 0],
Delete { path_len } => [op::DELETE, path_len, 0, 0], Delete { path_len } => [op::DELETE, path_len, 0, 0],
Mkdir { path_len } => [op::MKDIR, path_len, 0, 0], Mkdir { path_len } => [op::MKDIR, path_len, 0, 0],
SetLog => [op::SET_LOG, 0, 0, 0],
} }
} }
} }
@@ -79,6 +82,7 @@ impl Decode for StorageRequest {
op::CREATE => Create { path_len: w[1] }, op::CREATE => Create { path_len: w[1] },
op::DELETE => Delete { path_len: w[1] }, op::DELETE => Delete { path_len: w[1] },
op::MKDIR => Mkdir { path_len: w[1] }, op::MKDIR => Mkdir { path_len: w[1] },
op::SET_LOG => SetLog,
_ => return None, _ => return None,
}) })
} }

View File

@@ -31,6 +31,7 @@ pub struct StorageServer<F: FileSystem, B: Buffers> {
clients: [Option<Client>; MAX_CLIENTS], clients: [Option<Client>; MAX_CLIENTS],
/// The badge of the client that opened each handle. /// The badge of the client that opened each handle.
owners: [Option<usize>; MAX_HANDLES], owners: [Option<usize>; MAX_HANDLES],
log: Option<CapSlot>,
} }
impl<F: FileSystem, B: Buffers> StorageServer<F, B> { impl<F: FileSystem, B: Buffers> StorageServer<F, B> {
@@ -40,6 +41,26 @@ impl<F: FileSystem, B: Buffers> StorageServer<F, B> {
buffers, buffers,
clients: [const { None }; MAX_CLIENTS], clients: [const { None }; MAX_CLIENTS],
owners: [None; MAX_HANDLES], owners: [None; MAX_HANDLES],
log: None,
}
}
/// Where this server prints, once init has said.
pub fn log(&self) -> Option<CapSlot> {
self.log
}
/// Clients hold badged copies of the endpoint, so the unbadged sender is
/// whoever was given the endpoint itself: init.
fn set_log(&mut self, badge: usize, cap: Option<CapSlot>) -> StorageResponse {
match (badge, cap, self.log) {
(0, Some(cap), None) => {
self.log = Some(cap);
libkos::start::set_log(Some(cap));
libkos::println!("storage: printing through the log server");
StorageResponse::Ok
}
_ => StorageResponse::Error,
} }
} }
@@ -117,7 +138,7 @@ impl<F: FileSystem, B: Buffers> StorageServer<F, B> {
None => R::NotFound, None => R::NotFound,
}; };
let response = match request { let response = match request {
Attach => R::Error, Attach | SetLog => R::Error,
Open { path_len } => { Open { path_len } => {
let h = fs.open(&buf[path(path_len)]); let h = fs.open(&buf[path(path_len)]);
opened(owners, h) opened(owners, h)
@@ -195,9 +216,10 @@ impl<F: FileSystem, B: Buffers> Server for StorageServer<F, B> {
&mut self, &mut self,
badge: usize, badge: usize,
words: [usize; 4], words: [usize; 4],
_cap: Option<CapSlot>, cap: Option<CapSlot>,
) -> ([usize; 4], Option<CapSlot>) { ) -> ([usize; 4], Option<CapSlot>) {
match StorageRequest::decode(words) { match StorageRequest::decode(words) {
Some(StorageRequest::SetLog) => (self.set_log(badge, cap).encode(), None),
Some(request) => { Some(request) => {
let (response, cap) = self.serve(badge, request); let (response, cap) = self.serve(badge, request);
(response.encode(), cap) (response.encode(), cap)

View File

@@ -180,3 +180,18 @@ fn garbage_gets_a_malformed_reply() {
let (words, cap) = s.handle_call(0, [99, 0, 0, 0], None); let (words, cap) = s.handle_call(0, [99, 0, 0, 0], None);
assert_eq!((words[0], cap), (MALFORMED, None)); assert_eq!((words[0], cap), (MALFORMED, None));
} }
#[test]
fn only_the_unbadged_holder_sets_the_log_and_only_once() {
let mut s = server();
let log = CapSlot::new(7);
let set = |s: &mut Srv, badge| {
let (words, _) = s.handle_call(badge, StorageRequest::SetLog.encode(), log);
StorageResponse::decode(words).unwrap()
};
assert_eq!(set(&mut s, 5), StorageResponse::Error);
assert_eq!(s.log(), None);
assert_eq!(set(&mut s, 0), StorageResponse::Ok);
assert_eq!(s.log(), log);
assert_eq!(set(&mut s, 0), StorageResponse::Error);
}

View File

@@ -8,6 +8,7 @@ pub mod budget;
pub mod display; pub mod display;
pub mod generation; pub mod generation;
pub mod id; pub mod id;
pub mod log;
pub mod macros; pub mod macros;
pub mod mapping; pub mod mapping;
pub mod permission; pub mod permission;

49
crates/types/src/log.rs Normal file
View File

@@ -0,0 +1,49 @@
//! The log protocol's text message, shared by the log server and
//! `libkos::io` so the two cannot drift apart: word 0 is `TEXT` with the
//! length in bits 8..16, words 1..4 carry up to `TEXT_BYTES` bytes.
pub const TEXT: usize = 3;
pub const TEXT_BYTES: usize = 24;
/// `chunk` past `TEXT_BYTES` is cut off.
pub fn text_words(chunk: &[u8]) -> [usize; 4] {
let len = chunk.len().min(TEXT_BYTES);
let mut bytes = [0u8; TEXT_BYTES];
bytes[..len].copy_from_slice(&chunk[..len]);
let mut words = [TEXT | (len << 8), 0, 0, 0];
for (w, b) in words[1..].iter_mut().zip(bytes.as_chunks::<8>().0) {
*w = u64::from_le_bytes(*b) as usize;
}
words
}
/// The bytes of a text message and how many of them count.
pub fn text_of(words: [usize; 4]) -> Option<([u8; TEXT_BYTES], usize)> {
if words[0] & 0xFF != TEXT {
return None;
}
let len = (words[0] >> 8) & 0xFF;
if len > TEXT_BYTES {
return None;
}
let mut bytes = [0u8; TEXT_BYTES];
for (b, w) in bytes.as_chunks_mut::<8>().0.iter_mut().zip(&words[1..]) {
*b = (*w as u64).to_le_bytes();
}
Some((bytes, len))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn text_round_trips_and_is_cut_at_the_limit() {
let (bytes, len) = text_of(text_words(b"init: hello")).unwrap();
assert_eq!(&bytes[..len], b"init: hello");
let long = [b'x'; 30];
assert_eq!(text_of(text_words(&long)).unwrap().1, TEXT_BYTES);
assert_eq!(text_of([TEXT | (25 << 8), 0, 0, 0]), None);
assert_eq!(text_of([0, 0, 0, 0]), None);
}
}

View File

@@ -25,6 +25,8 @@ pub mod op {
pub const CREATE: usize = 7; pub const CREATE: usize = 7;
pub const DELETE: usize = 8; pub const DELETE: usize = 8;
pub const MKDIR: usize = 9; pub const MKDIR: usize = 9;
/// Carries the log endpoint; only from the unbadged holder (init).
pub const SET_LOG: usize = 10;
} }
/// Reply tags (word 0). /// Reply tags (word 0).

View File

@@ -67,17 +67,24 @@ No process is privileged by its id. At boot the kernel gives init:
- one `Console` capability (`Write`, `Delegate`). - one `Console` capability (`Write`, `Delegate`).
`DebugPutchar(console, byte)` needs a `Console` capability with `Write`; `DebugPutchar(console, byte)` needs a `Console` capability with `Write`;
there is no other way to reach the UART. Init lends its console to the there is no other way to reach the UART. Init prints with it only until it
servers it starts (snake gets none). Until the log server owns the console, has started the log server, then hands it over and drops its own; from then
boot code also grants one to the servers it loads itself. on the log server is the only holder, and every other process prints by
calling it (`types::log`) if init gave it a log endpoint.
A thread made by `SpawnThread`/`SpawnThreadIn` starts with the caller's A thread made by `SpawnThread`/`SpawnThreadIn` starts with the caller's
`arg` in `a0`. A process's first thread reads it as `types::start_arg`: `arg` in `a0`. A process's first thread reads it as `types::start_arg`:
the slots of the console and log capabilities it was handed. the slots of the console and log capabilities it was handed.
The board capabilities come after the capabilities boot code hands out Boot loads only init and storage (init needs it to read anything else):
for the built-in servers, so those slots are unchanged. Device regions init gets storage's process and endpoint in slots 1 and 2, before the
never overlap each other or the frame pool, and each device has exactly board capabilities. Everything else init starts from its manifest
(`init/src/manifest.rs`), handing each program only the endpoints, device
and budget listed there. Storage, started before the log server, receives
its log endpoint from init later (`SET_LOG`, accepted only from the
unbadged holder of its endpoint).
Device regions never overlap each other or the frame pool, and each device has exactly
one region, so delegating or revoking it means the same thing everywhere. one region, so delegating or revoking it means the same thing everywhere.
`RegionAddress(cap)` returns a region's physical base so a driver can `RegionAddress(cap)` returns a region's physical base so a driver can