pub trait UserspaceKernelBoundary {
type StoredState: Default;
// Required methods
fn initial_process_app_brk_size(&self) -> usize;
unsafe fn initialize_process(
&self,
accessible_memory_start: *const u8,
app_brk: *const u8,
state: &mut Self::StoredState,
) -> Result<(), ()>;
unsafe fn set_syscall_return_value(
&self,
accessible_memory_start: *const u8,
app_brk: *const u8,
state: &mut Self::StoredState,
return_value: SyscallReturn,
) -> Result<(), ()>;
unsafe fn set_process_function(
&self,
accessible_memory_start: *const u8,
app_brk: *const u8,
state: &mut Self::StoredState,
upcall: FunctionCall,
) -> Result<(), ()>;
unsafe fn switch_to_process(
&self,
accessible_memory_start: *const u8,
app_brk: *const u8,
state: &mut Self::StoredState,
) -> (ContextSwitchReason, Option<*const u8>);
unsafe fn print_context(
&self,
accessible_memory_start: *const u8,
app_brk: *const u8,
state: &Self::StoredState,
writer: &mut dyn Write,
);
fn store_context(
&self,
state: &Self::StoredState,
out: &mut [u8],
) -> Result<usize, ErrorCode>;
}
Expand description
The UserspaceKernelBoundary
trait is implemented by the
architectural component of the chip implementation of Tock.
This trait allows the kernel to switch to and from processes in an architecture-independent manner.
Exactly how upcalls and return values are passed between kernelspace and
userspace is architecture specific. The architecture may use process memory
to store state when switching. Therefore, functions in this trait are passed
the bounds of process-accessible memory so that the architecture
implementation can verify it is reading and writing memory that the process
has valid access to. These bounds are passed through
accessible_memory_start
and app_brk
pointers.
Required Associated Types§
Sourcetype StoredState: Default
type StoredState: Default
Some architecture-specific struct containing per-process state that must be kept while the process is not running. For example, for keeping CPU registers that aren’t stored on the stack.
Implementations should not rely on the Default
constructor
(custom or derived) for any initialization of a process’s stored state.
The initialization must happen in the
initialize_process()
function.
Required Methods§
Sourcefn initial_process_app_brk_size(&self) -> usize
fn initial_process_app_brk_size(&self) -> usize
Called by the kernel during process creation to inform the kernel of the minimum amount of process-accessible RAM needed by a new process. This allows for architecture-specific process layout decisions, such as stack pointer initialization.
This returns the minimum number of bytes of process-accessible memory the kernel must allocate to a process so that a successful context switch is possible.
Some architectures may not need any allocated memory, and this should return 0. In general, implementations should try to pre-allocate the minimal amount of process-accessible memory (i.e. return as close to 0 as possible) to provide the most flexibility to the process. However, the return value will be nonzero for architectures where values are passed in memory between kernelspace and userspace during syscalls or a stack needs to be setup.
Sourceunsafe fn initialize_process(
&self,
accessible_memory_start: *const u8,
app_brk: *const u8,
state: &mut Self::StoredState,
) -> Result<(), ()>
unsafe fn initialize_process( &self, accessible_memory_start: *const u8, app_brk: *const u8, state: &mut Self::StoredState, ) -> Result<(), ()>
Called by the kernel after it has memory allocated to it but before it is allowed to begin executing. Allows for architecture-specific process setup, e.g. allocating a syscall stack frame.
This function must also initialize the stored state (if needed).
The kernel calls this function with the start of memory allocated to the
process by providing accessible_memory_start
. It also provides the
app_brk
pointer which marks the end of process-accessible memory. The
kernel guarantees that accessible_memory_start
will be word-aligned.
If successful, this function returns Ok()
. If the process syscall
state cannot be initialized with the available amount of memory, or for
any other reason, it should return Err()
.
This function may be called multiple times on the same process. For example, if a process crashes and is to be restarted, this must be called. Or if the process is moved this may need to be called.
§Safety
This function guarantees that it if needs to change process memory, it
will only change memory starting at accessible_memory_start
and before
app_brk
. The caller is responsible for guaranteeing that those
pointers are valid for the process.
Sourceunsafe fn set_syscall_return_value(
&self,
accessible_memory_start: *const u8,
app_brk: *const u8,
state: &mut Self::StoredState,
return_value: SyscallReturn,
) -> Result<(), ()>
unsafe fn set_syscall_return_value( &self, accessible_memory_start: *const u8, app_brk: *const u8, state: &mut Self::StoredState, return_value: SyscallReturn, ) -> Result<(), ()>
Set the return value the process should see when it begins executing again after the syscall. This will only be called after a process has called a syscall.
The process to set the return value for is specified by the state
value. The return_value
is the value that should be passed to the
process so that when it resumes executing it knows the return value of
the syscall it called.
§Safety
This function guarantees that it if needs to change process memory, it
will only change memory starting at accessible_memory_start
and before
app_brk
. The caller is responsible for guaranteeing that those
pointers are valid for the process.
Sourceunsafe fn set_process_function(
&self,
accessible_memory_start: *const u8,
app_brk: *const u8,
state: &mut Self::StoredState,
upcall: FunctionCall,
) -> Result<(), ()>
unsafe fn set_process_function( &self, accessible_memory_start: *const u8, app_brk: *const u8, state: &mut Self::StoredState, upcall: FunctionCall, ) -> Result<(), ()>
Set the function that the process should execute when it is resumed.
This has two major uses: 1) sets up the initial function call to
_start
when the process is started for the very first time; 2) tells
the process to execute a upcall function after calling yield()
.
Note: This method cannot be called in conjunction with
set_syscall_return_value
, as the injected function will clobber the
return value.
§Arguments
accessible_memory_start
is the address of the start of the process-accessible memory region for this process.app_brk
is the address of the current process break. This marks the end of the memory region the process has access to. Note, this is not the end of the entire memory region allocated to the process. Some memory above this address is still allocated for the process, but if the process tries to access it an MPU fault will occur.state
is the stored state for this process.upcall
is the function that should be executed when the process resumes.
§Return
Returns Ok(())
if the function was successfully enqueued for the
process. Returns Err(())
if the function was not, likely because there
is insufficient memory available to do so.
§Safety
This function guarantees that it if needs to change process memory, it
will only change memory starting at accessible_memory_start
and before
app_brk
. The caller is responsible for guaranteeing that those
pointers are valid for the process.
Sourceunsafe fn switch_to_process(
&self,
accessible_memory_start: *const u8,
app_brk: *const u8,
state: &mut Self::StoredState,
) -> (ContextSwitchReason, Option<*const u8>)
unsafe fn switch_to_process( &self, accessible_memory_start: *const u8, app_brk: *const u8, state: &mut Self::StoredState, ) -> (ContextSwitchReason, Option<*const u8>)
Context switch to a specific process.
This returns two values in a tuple.
- A
ContextSwitchReason
indicating why the process stopped executing and switched back to the kernel. - Optionally, the current stack pointer used by the process. This is optional because it is only for debugging in process.rs. By sharing the process’s stack pointer with process.rs users can inspect the state and see the stack depth, which might be useful for debugging.
§Safety
This function guarantees that it if needs to change process memory, it
will only change memory starting at accessible_memory_start
and before
app_brk
. The caller is responsible for guaranteeing that those
pointers are valid for the process.
Sourceunsafe fn print_context(
&self,
accessible_memory_start: *const u8,
app_brk: *const u8,
state: &Self::StoredState,
writer: &mut dyn Write,
)
unsafe fn print_context( &self, accessible_memory_start: *const u8, app_brk: *const u8, state: &Self::StoredState, writer: &mut dyn Write, )
Display architecture specific (e.g. CPU registers or status flags) data for a process identified by the stored state for that process.
§Safety
This function guarantees that it if needs to change process memory, it
will only change memory starting at accessible_memory_start
and before
app_brk
. The caller is responsible for guaranteeing that those
pointers are valid for the process.
Sourcefn store_context(
&self,
state: &Self::StoredState,
out: &mut [u8],
) -> Result<usize, ErrorCode>
fn store_context( &self, state: &Self::StoredState, out: &mut [u8], ) -> Result<usize, ErrorCode>
Store architecture specific (e.g. CPU registers or status flags) data for a process. On success returns the number of elements written to out.