feat(init): spawn service over IPC with per-client allowance refunded on child death; up to 4 death watchers; spawntest on test image
This commit is contained in:
@@ -14,3 +14,4 @@ hw-virtio = { workspace = true }
|
||||
types = { workspace = true }
|
||||
|
||||
libkos = { workspace = true }
|
||||
server-framework = { workspace = true }
|
||||
|
||||
@@ -5,12 +5,14 @@ use libkos::device::Device as BoardDevice;
|
||||
use libkos::fs::Storage;
|
||||
use libkos::syscall;
|
||||
use types::address::StackPointer;
|
||||
use types::badge::Badge;
|
||||
use types::bounded::CapSlot;
|
||||
use types::permission::{Permission, PermissionSet};
|
||||
|
||||
use crate::devices::Devices;
|
||||
use crate::load::{self, LoadedProcess};
|
||||
use crate::manifest::{Device, Program};
|
||||
use crate::spawn::Spawner;
|
||||
|
||||
const USER_STACK_TOP: StackPointer = StackPointer::from_raw(0x10000);
|
||||
const MAX_SERVED: usize = 8;
|
||||
@@ -25,11 +27,22 @@ pub(crate) struct Launcher<'a> {
|
||||
devices: Devices,
|
||||
/// The endpoint of every server started so far, by name.
|
||||
served: [Option<(&'static str, CapSlot)>; MAX_SERVED],
|
||||
/// Init's own endpoint, for the spawn service.
|
||||
pub(crate) spawn_ep: CapSlot,
|
||||
/// Bound to init's thread: spawned children's deaths, a bit each.
|
||||
pub(crate) deaths: CapSlot,
|
||||
pub(crate) spawner: Spawner,
|
||||
}
|
||||
|
||||
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 new(
|
||||
storage: &'a mut Storage,
|
||||
scratch: &'a mut [u8],
|
||||
devices: Devices,
|
||||
) -> Result<Self, &'static str> {
|
||||
let spawn_ep = syscall::create_endpoint().ok_or("no spawn endpoint")?;
|
||||
let deaths = syscall::create_notification().map_err(|_| "no death notification")?;
|
||||
Ok(Self { storage, scratch, devices, served: [None; MAX_SERVED], spawn_ep, deaths, spawner: Spawner::new() })
|
||||
}
|
||||
|
||||
pub(crate) fn storage(&mut self) -> (&mut Storage, &mut [u8]) {
|
||||
@@ -61,6 +74,13 @@ impl<'a> Launcher<'a> {
|
||||
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")?;
|
||||
}
|
||||
if let Some(allowance) = p.spawns {
|
||||
self.give_spawn(child, allowance)?;
|
||||
}
|
||||
if p.system_control {
|
||||
let control = crate::devices::system_control().ok_or("no SystemControl to give")?;
|
||||
syscall::delegate(control, child, perms(&[Permission::Execute])).ok_or("cannot give SystemControl")?;
|
||||
}
|
||||
let console = libkos::start::console()
|
||||
.filter(|_| p.console)
|
||||
.and_then(|c| syscall::delegate(c, child, perms(&[Permission::Write])));
|
||||
@@ -82,6 +102,17 @@ impl<'a> Launcher<'a> {
|
||||
Ok(note)
|
||||
}
|
||||
|
||||
/// A badged copy of init's endpoint: the badge names the client and so
|
||||
/// its allowance.
|
||||
fn give_spawn(&mut self, child: CapSlot, allowance: types::budget::Limits) -> Result<(), &'static str> {
|
||||
let badge: Badge = self.spawner.add_client(allowance).ok_or("too many spawn clients")?;
|
||||
let minted = perms(&[Permission::Execute, Permission::Delegate]);
|
||||
let copy = syscall::mint(self.spawn_ep, badge, minted).map_err(|_| "cannot mint")?;
|
||||
let given = syscall::delegate(copy, child, perms(&[Permission::Execute]));
|
||||
syscall::drop_cap(copy);
|
||||
given.map(|_| ()).ok_or("cannot give the spawn endpoint")
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
|
||||
@@ -7,6 +7,7 @@ mod launch;
|
||||
mod load;
|
||||
mod manifest;
|
||||
mod selftest;
|
||||
mod spawn;
|
||||
|
||||
use libkos::fs::Storage;
|
||||
use libkos::syscall;
|
||||
@@ -39,21 +40,31 @@ pub fn run() -> ! {
|
||||
idle();
|
||||
};
|
||||
|
||||
let mut launcher = launch::Launcher::new(&mut storage, scratch, found);
|
||||
let mut launcher = match launch::Launcher::new(&mut storage, scratch, found) {
|
||||
Ok(l) => l,
|
||||
Err(why) => {
|
||||
libkos::println!("init: {}", why);
|
||||
idle();
|
||||
}
|
||||
};
|
||||
launch_all(&mut launcher, manifest::SERVERS);
|
||||
give_storage_the_log();
|
||||
|
||||
// The test image carries the SMP self-test; it runs instead of the
|
||||
// desktop and halts the machine.
|
||||
// The test image carries the SMP self-test; it and the tests run
|
||||
// instead of the desktop, and the last one halts the machine.
|
||||
let (storage, scratch) = launcher.storage();
|
||||
if storage.open(b"smptest.elf").is_ok_and(|h| storage.close(h).is_ok()) {
|
||||
libkos::println!("init: running the SMP self-test");
|
||||
selftest::run(storage, scratch);
|
||||
launch_all(&mut launcher, manifest::TESTS);
|
||||
} else {
|
||||
launch_all(&mut launcher, manifest::APPS);
|
||||
}
|
||||
launch_all(&mut launcher, manifest::APPS);
|
||||
|
||||
libkos::println!("init: all loaded, supervising");
|
||||
idle();
|
||||
libkos::println!("init: all loaded, serving spawns");
|
||||
let _ = syscall::bind_notification(launcher.deaths);
|
||||
let ep = launcher.spawn_ep;
|
||||
server_framework::serve(&mut launcher, ep)
|
||||
}
|
||||
|
||||
fn launch_all(launcher: &mut launch::Launcher, programs: &[manifest::Program]) {
|
||||
|
||||
@@ -20,6 +20,10 @@ pub struct Program {
|
||||
pub uses: &'static [&'static str],
|
||||
/// Takes the console over from init; everyone else prints through it.
|
||||
pub console: bool,
|
||||
/// May have init spawn programs, whose limits together stay within this.
|
||||
pub spawns: Option<Limits>,
|
||||
/// May halt the machine.
|
||||
pub system_control: bool,
|
||||
}
|
||||
|
||||
/// Enough for a small program: its image, stack and a little heap.
|
||||
@@ -31,7 +35,17 @@ pub const SMALL: Limits = Limits {
|
||||
};
|
||||
|
||||
const fn server(name: &'static str, file: &'static [u8], limits: Limits) -> Program {
|
||||
Program { name, file, limits, serves: true, device: None, uses: &[], console: false }
|
||||
Program {
|
||||
name,
|
||||
file,
|
||||
limits,
|
||||
serves: true,
|
||||
device: None,
|
||||
uses: &[],
|
||||
console: false,
|
||||
spawns: None,
|
||||
system_control: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Started on every boot. The log server comes first so the rest can print.
|
||||
@@ -47,6 +61,15 @@ pub const SERVERS: &[Program] = &[
|
||||
server("session", b"session.elf", SMALL),
|
||||
];
|
||||
|
||||
/// Run on the test image after the SMP self-test, instead of the desktop:
|
||||
/// spawns through init, then reports and halts.
|
||||
pub const TESTS: &[Program] = &[Program {
|
||||
serves: false,
|
||||
spawns: Some(Limits { pages: 128, endpoints: 2, notifications: 2, regions: 2 }),
|
||||
system_control: true,
|
||||
..server("spawntest", b"spawntst.elf", SMALL)
|
||||
}];
|
||||
|
||||
/// The desktop; the test image runs the SMP self-test instead.
|
||||
pub const APPS: &[Program] = &[Program {
|
||||
serves: false,
|
||||
|
||||
@@ -121,12 +121,13 @@ fn timer_is_on_time(events: CapSlot) -> Result<(), &'static str> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Runs the test, reports, and halts the machine.
|
||||
pub fn run(storage: &mut Storage, scratch: &mut [u8]) -> ! {
|
||||
match run_phases(storage, scratch) {
|
||||
Ok(()) => libkos::println!("SMPTEST PASS"),
|
||||
Err(why) => libkos::println!("SMPTEST FAIL: {}", why),
|
||||
}
|
||||
/// Runs the hart checks; on failure reports and halts the machine, else
|
||||
/// returns so the spawn test can run (and report the whole result).
|
||||
pub fn run(storage: &mut Storage, scratch: &mut [u8]) {
|
||||
let Err(why) = run_phases(storage, scratch) else {
|
||||
return;
|
||||
};
|
||||
libkos::println!("SMPTEST FAIL: {}", why);
|
||||
match crate::devices::system_control() {
|
||||
Some(control) => syscall::halt(control),
|
||||
None => {
|
||||
|
||||
178
crates/init/src/spawn.rs
Normal file
178
crates/init/src/spawn.rs
Normal file
@@ -0,0 +1,178 @@
|
||||
//! Init's spawn service (`types::spawn`): loads a program for a client and
|
||||
//! hands it the process, unstarted. Each client has an allowance from the
|
||||
//! manifest; a child's limits come out of it and return when the child dies.
|
||||
|
||||
use libkos::syscall;
|
||||
use server_framework::{Decode, Encode, MALFORMED, Server};
|
||||
use types::badge::Badge;
|
||||
use types::bounded::CapSlot;
|
||||
use types::budget::Limits;
|
||||
use types::permission::{Permission, PermissionSet};
|
||||
use types::spawn::{MAX_NAME, NONE, reply};
|
||||
|
||||
use crate::launch::Launcher;
|
||||
use crate::load;
|
||||
|
||||
pub(crate) const MAX_CLIENTS: usize = 4;
|
||||
/// Death signals carry the child's index as a bit.
|
||||
const MAX_CHILDREN: usize = 8;
|
||||
|
||||
struct Child {
|
||||
client: usize,
|
||||
limits: Limits,
|
||||
process: CapSlot,
|
||||
}
|
||||
|
||||
pub(crate) struct Spawner {
|
||||
/// What each client (badge = index + 1) may still give its children.
|
||||
allowances: [Option<Limits>; MAX_CLIENTS],
|
||||
children: [Option<Child>; MAX_CHILDREN],
|
||||
}
|
||||
|
||||
fn fits(want: Limits, left: Limits) -> bool {
|
||||
want.pages <= left.pages
|
||||
&& want.endpoints <= left.endpoints
|
||||
&& want.notifications <= left.notifications
|
||||
&& want.regions <= left.regions
|
||||
}
|
||||
|
||||
fn add(a: Limits, b: Limits) -> Limits {
|
||||
Limits {
|
||||
pages: a.pages + b.pages,
|
||||
endpoints: a.endpoints + b.endpoints,
|
||||
notifications: a.notifications + b.notifications,
|
||||
regions: a.regions + b.regions,
|
||||
}
|
||||
}
|
||||
|
||||
fn sub(a: Limits, b: Limits) -> Limits {
|
||||
Limits {
|
||||
pages: a.pages - b.pages,
|
||||
endpoints: a.endpoints - b.endpoints,
|
||||
notifications: a.notifications - b.notifications,
|
||||
regions: a.regions - b.regions,
|
||||
}
|
||||
}
|
||||
|
||||
impl Spawner {
|
||||
pub(crate) const fn new() -> Self {
|
||||
Self { allowances: [None; MAX_CLIENTS], children: [const { None }; MAX_CHILDREN] }
|
||||
}
|
||||
|
||||
/// The badge for a new client with `allowance`.
|
||||
pub(crate) fn add_client(&mut self, allowance: Limits) -> Option<Badge> {
|
||||
let i = self.allowances.iter().position(Option::is_none)?;
|
||||
self.allowances[i] = Some(allowance);
|
||||
Some(Badge::new(i + 1))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Request {
|
||||
name: [u8; MAX_NAME],
|
||||
len: usize,
|
||||
limits: Limits,
|
||||
}
|
||||
|
||||
impl Decode for Request {
|
||||
fn decode(words: [usize; 4]) -> Option<Self> {
|
||||
types::spawn::parse(words).map(|(name, len, limits)| Self { name, len, limits })
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Reply([usize; 4]);
|
||||
|
||||
impl Encode for Reply {
|
||||
fn encode(&self) -> [usize; 4] {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
fn refused(tag: usize) -> ([usize; 4], Option<CapSlot>) {
|
||||
([tag, 0, 0, 0], None)
|
||||
}
|
||||
|
||||
impl Launcher<'_> {
|
||||
fn spawn(&mut self, badge: usize, r: &Request) -> ([usize; 4], Option<CapSlot>) {
|
||||
let Some(client) = badge.checked_sub(1).filter(|c| *c < MAX_CLIENTS) else {
|
||||
return refused(reply::ERROR);
|
||||
};
|
||||
let Some(left) = self.spawner.allowances[client] else {
|
||||
return refused(reply::ERROR);
|
||||
};
|
||||
if !fits(r.limits, left) {
|
||||
return refused(reply::OVER_ALLOWANCE);
|
||||
}
|
||||
let Some(index) = self.spawner.children.iter().position(Option::is_none) else {
|
||||
return refused(reply::ERROR);
|
||||
};
|
||||
let name = &r.name[..r.len];
|
||||
let (storage, scratch) = self.storage();
|
||||
match storage.open(name) {
|
||||
Ok(h) => {
|
||||
let _ = storage.close(h);
|
||||
}
|
||||
Err(_) => return refused(reply::NOT_FOUND),
|
||||
}
|
||||
let Some(loaded) = load::load_binary(storage, scratch, name, r.limits) else {
|
||||
return refused(reply::ERROR);
|
||||
};
|
||||
if !self.watch(loaded.process_cap, index) {
|
||||
let _ = syscall::kill(loaded.process_cap);
|
||||
syscall::drop_cap(loaded.process_cap);
|
||||
return refused(reply::ERROR);
|
||||
}
|
||||
let call_only = PermissionSet::empty().with(Permission::Execute);
|
||||
let log = libkos::start::log().and_then(|l| syscall::delegate(l, loaded.process_cap, call_only));
|
||||
self.spawner.allowances[client] = Some(sub(left, r.limits));
|
||||
self.spawner.children[index] = Some(Child { client, limits: r.limits, process: loaded.process_cap });
|
||||
let log = log.map_or(NONE, |s| s.value() as usize);
|
||||
([reply::OK, loaded.entry_point.raw(), log, 0], Some(loaded.process_cap))
|
||||
}
|
||||
|
||||
/// Has the child's death signal bit `index` on init's death notification.
|
||||
fn watch(&self, process: CapSlot, index: usize) -> bool {
|
||||
let signal = PermissionSet::empty().with(Permission::Write);
|
||||
let Ok(copy) = syscall::mint(self.deaths, Badge::new(1 << index), signal) else {
|
||||
return false;
|
||||
};
|
||||
let watched = syscall::watch_death(process, copy).is_ok();
|
||||
syscall::drop_cap(copy);
|
||||
watched
|
||||
}
|
||||
|
||||
/// Children behind `bits` died: their limits go back to their clients.
|
||||
fn reap(&mut self, bits: usize) {
|
||||
for (i, slot) in self.spawner.children.iter_mut().enumerate() {
|
||||
if bits & (1 << i) == 0 {
|
||||
continue;
|
||||
}
|
||||
if let Some(child) = slot.take() {
|
||||
if let Some(Some(left)) = self.spawner.allowances.get_mut(child.client) {
|
||||
*left = add(*left, child.limits);
|
||||
}
|
||||
syscall::drop_cap(child.process);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Server for Launcher<'_> {
|
||||
type Request = Request;
|
||||
type Response = Reply;
|
||||
|
||||
/// Without a badge there is no client, so no allowance.
|
||||
fn handle(&mut self, _request: Request) -> Reply {
|
||||
Reply(refused(reply::ERROR).0)
|
||||
}
|
||||
|
||||
fn handle_call(&mut self, badge: usize, words: [usize; 4], _cap: Option<CapSlot>) -> ([usize; 4], Option<CapSlot>) {
|
||||
match Request::decode(words) {
|
||||
Some(r) => self.spawn(badge, &r),
|
||||
None => ([MALFORMED, 0, 0, 0], None),
|
||||
}
|
||||
}
|
||||
|
||||
fn signal(&mut self, bits: usize) {
|
||||
self.reap(bits);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user