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 {
|
match nr {
|
||||||
SYS_EXIT => Some(Syscall::Exit),
|
SYS_EXIT => Some(Syscall::Exit),
|
||||||
|
SYS_HALT => Some(Syscall::Halt),
|
||||||
SYS_EXIT_THREAD => Some(Syscall::ExitThread),
|
SYS_EXIT_THREAD => Some(Syscall::ExitThread),
|
||||||
SYS_YIELD => Some(Syscall::Yield),
|
SYS_YIELD => Some(Syscall::Yield),
|
||||||
SYS_GET_TIME => Some(Syscall::GetTime),
|
SYS_GET_TIME => Some(Syscall::GetTime),
|
||||||
@@ -408,7 +409,21 @@ fn handle_exception(frame: &mut TrapFrame, scause: usize) {
|
|||||||
uart.write_str(" sepc=");
|
uart.write_str(" sepc=");
|
||||||
uart.write_hex(frame.sepc);
|
uart.write_hex(frame.sepc);
|
||||||
uart.put_char(b'\n');
|
uart.put_char(b'\n');
|
||||||
|
|
||||||
|
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") }; }
|
loop { unsafe { core::arch::asm!("wfi") }; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,3 +8,23 @@ pub enum Fault {
|
|||||||
MemoryFault { address: VirtualAddress },
|
MemoryFault { address: VirtualAddress },
|
||||||
IllegalInstruction,
|
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,
|
GetProcessId,
|
||||||
Yield,
|
Yield,
|
||||||
Exit,
|
Exit,
|
||||||
|
Halt,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,25 @@
|
|||||||
pub fn shutdown() -> ! {
|
const SHUTDOWN_TIMEOUT_TICKS: usize = 50_000_000;
|
||||||
userspace::syscall::exit();
|
|
||||||
|
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;
|
use crate::manifest::BootManifest;
|
||||||
|
|
||||||
pub fn supervise(_manifest: &BootManifest) -> ! {
|
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 {
|
loop {
|
||||||
userspace::syscall::yield_now();
|
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 capability;
|
||||||
|
mod fault;
|
||||||
mod ipc;
|
mod ipc;
|
||||||
mod lifecycle;
|
mod lifecycle;
|
||||||
mod memory;
|
mod memory;
|
||||||
@@ -53,6 +54,7 @@ impl<P: Platform> Kernel<P> {
|
|||||||
Syscall::WaitForInterrupt { irq } => self.handle_wait_for_interrupt(tid, pid, irq),
|
Syscall::WaitForInterrupt { irq } => self.handle_wait_for_interrupt(tid, pid, irq),
|
||||||
Syscall::AcknowledgeInterrupt { irq } => self.handle_ack_interrupt(pid, irq),
|
Syscall::AcknowledgeInterrupt { irq } => self.handle_ack_interrupt(pid, irq),
|
||||||
Syscall::WaitAny { sources } => self.handle_wait_any(tid, pid, sources),
|
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
|
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,
|
_permissions: PermissionSet,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
fn halt(&self) -> ! {
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,4 +26,5 @@ pub trait Platform {
|
|||||||
size: PageCount,
|
size: PageCount,
|
||||||
permissions: PermissionSet,
|
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_GET_PROCESS_ID: usize = 14;
|
||||||
pub const SYS_YIELD: usize = 15;
|
pub const SYS_YIELD: usize = 15;
|
||||||
pub const SYS_EXIT: usize = 16;
|
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)
|
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 {
|
pub fn send(endpoint: usize, w0: usize, w1: usize, w2: usize, w3: usize) -> usize {
|
||||||
arch::syscall5(SYS_SEND, endpoint, w0, w1, w2, w3)
|
arch::syscall5(SYS_SEND, endpoint, w0, w1, w2, w3)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user