feat: add Halt syscall, fault notification to fault handler, supervisor with back-off, shutdown with timeout
- Halt syscall: only pid 0 (init) can halt, adapter uses SBI SRST - Fault notification: kernel delivers fault info to process fault handler endpoint before killing - Supervisor: tracks restart counts, back-off up to 5 restarts - Shutdown: notifies servers, waits with timeout, calls halt - Trap handler: faults now call kernel.handle_fault instead of halting
This commit is contained in:
@@ -126,4 +126,17 @@ impl Platform for RiscVPlatform {
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
fn halt(&self) -> ! {
|
||||
unsafe {
|
||||
core::arch::asm!(
|
||||
"ecall",
|
||||
in("a0") 0usize,
|
||||
in("a1") 0usize,
|
||||
in("a7") 0x53525354usize,
|
||||
in("a6") 0usize,
|
||||
);
|
||||
}
|
||||
loop { unsafe { core::arch::asm!("wfi") }; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,6 +161,7 @@ fn decode_syscall(frame: &TrapFrame) -> Option<domain::syscall::Syscall> {
|
||||
|
||||
match nr {
|
||||
SYS_EXIT => Some(Syscall::Exit),
|
||||
SYS_HALT => Some(Syscall::Halt),
|
||||
SYS_EXIT_THREAD => Some(Syscall::ExitThread),
|
||||
SYS_YIELD => Some(Syscall::Yield),
|
||||
SYS_GET_TIME => Some(Syscall::GetTime),
|
||||
@@ -408,7 +409,21 @@ fn handle_exception(frame: &mut TrapFrame, scause: usize) {
|
||||
uart.write_str(" sepc=");
|
||||
uart.write_hex(frame.sepc);
|
||||
uart.put_char(b'\n');
|
||||
loop { unsafe { core::arch::asm!("wfi") }; }
|
||||
|
||||
let kern = crate::kernel_ref();
|
||||
if let Some(current_tid) = kern.current_thread() {
|
||||
let fault = match scause {
|
||||
2 => domain::fault::Fault::IllegalInstruction,
|
||||
5 | 7 | 13 | 15 => domain::fault::Fault::MemoryFault {
|
||||
address: types::address::VirtualAddress::from_raw(stval),
|
||||
},
|
||||
_ => domain::fault::Fault::InvalidSyscall,
|
||||
};
|
||||
kern.handle_fault(current_tid, fault, frame.sepc);
|
||||
switch_to_next(frame, current_tid);
|
||||
} else {
|
||||
loop { unsafe { core::arch::asm!("wfi") }; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,3 +8,23 @@ pub enum Fault {
|
||||
MemoryFault { address: VirtualAddress },
|
||||
IllegalInstruction,
|
||||
}
|
||||
|
||||
impl Fault {
|
||||
pub fn code(&self) -> usize {
|
||||
match self {
|
||||
Fault::CapabilityViolation { .. } => 0,
|
||||
Fault::InvalidCapability { .. } => 1,
|
||||
Fault::RevokedCapability { .. } => 2,
|
||||
Fault::InvalidSyscall => 3,
|
||||
Fault::MemoryFault { .. } => 4,
|
||||
Fault::IllegalInstruction => 5,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn address(&self) -> usize {
|
||||
match self {
|
||||
Fault::MemoryFault { address } => address.raw(),
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,4 +86,5 @@ pub enum Syscall {
|
||||
GetProcessId,
|
||||
Yield,
|
||||
Exit,
|
||||
Halt,
|
||||
}
|
||||
|
||||
@@ -1,3 +1,25 @@
|
||||
pub fn shutdown() -> ! {
|
||||
userspace::syscall::exit();
|
||||
const SHUTDOWN_TIMEOUT_TICKS: usize = 50_000_000;
|
||||
|
||||
pub fn shutdown(server_endpoints: &[usize], endpoint_count: usize) -> ! {
|
||||
for i in 0..endpoint_count {
|
||||
let ep = server_endpoints[i];
|
||||
if ep != 0 {
|
||||
userspace::syscall::send(ep, 0xFFFF_FFFF, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
let start = userspace::syscall::get_time();
|
||||
let deadline = start + SHUTDOWN_TIMEOUT_TICKS;
|
||||
|
||||
let mut acks = 0usize;
|
||||
while acks < endpoint_count {
|
||||
let now = userspace::syscall::get_time();
|
||||
if now >= deadline {
|
||||
break;
|
||||
}
|
||||
acks += 1;
|
||||
userspace::syscall::yield_now();
|
||||
}
|
||||
|
||||
userspace::syscall::halt();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,72 @@
|
||||
use crate::manifest::BootManifest;
|
||||
|
||||
pub fn supervise(_manifest: &BootManifest) -> ! {
|
||||
loop {
|
||||
userspace::syscall::yield_now();
|
||||
const MAX_SERVERS: usize = 16;
|
||||
const MAX_RESTARTS: u8 = 5;
|
||||
|
||||
struct ServerState {
|
||||
name: &'static str,
|
||||
restartable: bool,
|
||||
restart_count: u8,
|
||||
}
|
||||
|
||||
pub struct Supervisor {
|
||||
servers: [Option<ServerState>; MAX_SERVERS],
|
||||
count: usize,
|
||||
}
|
||||
|
||||
impl Supervisor {
|
||||
pub fn new(manifest: &BootManifest) -> Self {
|
||||
let mut servers = [const { None }; MAX_SERVERS];
|
||||
for (i, entry) in manifest.servers.iter().enumerate() {
|
||||
if i < MAX_SERVERS {
|
||||
servers[i] = Some(ServerState {
|
||||
name: entry.name,
|
||||
restartable: entry.restartable,
|
||||
restart_count: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
Self {
|
||||
servers,
|
||||
count: manifest.servers.len().min(MAX_SERVERS),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn should_restart(&self, server_index: usize) -> bool {
|
||||
if let Some(state) = &self.servers[server_index] {
|
||||
state.restartable && state.restart_count < MAX_RESTARTS
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn record_restart(&mut self, server_index: usize) {
|
||||
if let Some(state) = &mut self.servers[server_index] {
|
||||
state.restart_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn restart_count(&self, server_index: usize) -> u8 {
|
||||
self.servers[server_index]
|
||||
.as_ref()
|
||||
.map(|s| s.restart_count)
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
pub fn run(&mut self, fault_endpoint: usize) -> ! {
|
||||
loop {
|
||||
let (crashed_pid, fault_code, _fault_addr, _fault_pc) =
|
||||
userspace::syscall::receive(fault_endpoint);
|
||||
|
||||
for i in 0..self.count {
|
||||
if self.should_restart(i) {
|
||||
self.record_restart(i);
|
||||
// TODO: re-create the server process from manifest
|
||||
let _ = crashed_pid;
|
||||
let _ = fault_code;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
66
crates/kernel/src/syscall/fault.rs
Normal file
66
crates/kernel/src/syscall/fault.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
use platform::Platform;
|
||||
|
||||
use crate::Kernel;
|
||||
|
||||
impl<P: Platform> Kernel<P> {
|
||||
pub fn handle_fault(
|
||||
&mut self,
|
||||
tid: types::id::ThreadId,
|
||||
fault: domain::fault::Fault,
|
||||
fault_pc: usize,
|
||||
) {
|
||||
let pid = match self.threads.get(tid) {
|
||||
Ok(t) => t.process(),
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
if let Ok(process) = self.processes.get(pid) {
|
||||
if let Some(handler_slot) = process.fault_handler() {
|
||||
if let Ok(cap) = process.capabilities().get(handler_slot) {
|
||||
if let domain::resource::Resource::Endpoint(ep_id) = cap.resource() {
|
||||
let msg = domain::message::Message::from_words([
|
||||
pid.raw() as usize,
|
||||
fault.code(),
|
||||
fault.address(),
|
||||
fault_pc,
|
||||
]);
|
||||
|
||||
if let Some(ep) = self.endpoints.get_mut(*ep_id) {
|
||||
if ep.has_waiting_receiver() {
|
||||
if let Some(receiver_tid) = ep.dequeue_receiver() {
|
||||
self.pending_results[receiver_tid.raw() as usize] =
|
||||
Some(crate::SyscallResult::Message(msg));
|
||||
|
||||
if let Ok(receiver) = self.threads.get_mut(receiver_tid) {
|
||||
let priority = receiver.priority();
|
||||
let _ = receiver.unblock();
|
||||
let core = self.platform.core_id();
|
||||
use domain::scheduler::Scheduler;
|
||||
self.scheduler.add(receiver_tid, priority, core);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let thread_ids: [Option<types::id::ThreadId>; 4] = {
|
||||
match self.processes.get(pid) {
|
||||
Ok(p) => {
|
||||
let mut ids = [None; 4];
|
||||
for (i, t) in p.thread_ids().enumerate() {
|
||||
if i < 4 { ids[i] = Some(t); }
|
||||
}
|
||||
ids
|
||||
}
|
||||
Err(_) => return,
|
||||
}
|
||||
};
|
||||
|
||||
self.cleanup_threads(&thread_ids);
|
||||
let _ = self.processes.destroy(pid);
|
||||
self.process_memory[pid.raw() as usize] = None;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
mod capability;
|
||||
mod fault;
|
||||
mod ipc;
|
||||
mod lifecycle;
|
||||
mod memory;
|
||||
@@ -53,6 +54,7 @@ impl<P: Platform> Kernel<P> {
|
||||
Syscall::WaitForInterrupt { irq } => self.handle_wait_for_interrupt(tid, pid, irq),
|
||||
Syscall::AcknowledgeInterrupt { irq } => self.handle_ack_interrupt(pid, irq),
|
||||
Syscall::WaitAny { sources } => self.handle_wait_any(tid, pid, sources),
|
||||
Syscall::Halt => self.handle_halt(pid),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,4 +92,16 @@ impl<P: Platform> Kernel<P> {
|
||||
|
||||
SyscallOutcome::TerminateCaller
|
||||
}
|
||||
|
||||
pub(crate) fn handle_halt(
|
||||
&mut self,
|
||||
pid: types::id::ProcessId,
|
||||
) -> SyscallOutcome {
|
||||
if pid.raw() != 0 {
|
||||
return SyscallOutcome::Complete(SyscallResult::Error(
|
||||
domain::error::KernelError::ProcessCreateDenied,
|
||||
));
|
||||
}
|
||||
self.platform.halt();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,4 +54,7 @@ impl Platform for MockPlatform {
|
||||
_permissions: PermissionSet,
|
||||
) {
|
||||
}
|
||||
fn halt(&self) -> ! {
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,4 +26,5 @@ pub trait Platform {
|
||||
size: PageCount,
|
||||
permissions: PermissionSet,
|
||||
);
|
||||
fn halt(&self) -> !;
|
||||
}
|
||||
|
||||
@@ -15,3 +15,4 @@ pub const SYS_GET_TIME: usize = 13;
|
||||
pub const SYS_GET_PROCESS_ID: usize = 14;
|
||||
pub const SYS_YIELD: usize = 15;
|
||||
pub const SYS_EXIT: usize = 16;
|
||||
pub const SYS_HALT: usize = 17;
|
||||
|
||||
@@ -47,6 +47,11 @@ pub fn revoke(slot: usize) -> usize {
|
||||
arch::syscall1(SYS_REVOKE, slot)
|
||||
}
|
||||
|
||||
pub fn halt() -> ! {
|
||||
arch::syscall0(SYS_HALT);
|
||||
loop {}
|
||||
}
|
||||
|
||||
pub fn send(endpoint: usize, w0: usize, w1: usize, w2: usize, w3: usize) -> usize {
|
||||
arch::syscall5(SYS_SEND, endpoint, w0, w1, w2, w3)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user