Add initial ProcessMaanger interfaces.

- new ProcessManager component (aka kata-process-manager)

NB: interfaces/ProcessManagerBindings.h is manually generated by cbindgen
for the moment; e.g.
  cargo install cbindgen;
  cd components/ProcessManager; cbindgen -c cbindgen.toml \
      -o ../../interfaces/ProcessManagerBindings.h kata-proc-common

Change-Id: I153c6b193c6ba8e376b87a2563dc8543753f0b42
GitOrigin-RevId: 18c354f14cbec6ce01c020136fe9aefd88248ee9
This commit is contained in:
Sam Leffler 2021-06-11 12:09:31 -07:00
parent c61d7890a7
commit e35c69ae4c
12 changed files with 471 additions and 0 deletions

View File

@ -29,6 +29,19 @@ DeclareCAmkESComponent(DebugConsole
LIBS kata_debug_console
)
RustAddLibrary(
kata_process_manager
SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/components/ProcessManager
TARGET "riscv32imc-unknown-none-elf"
LIB_FILENAME libkata_process_manager.a
)
DeclareCAmkESComponent(ProcessManager
LIBS kata_process_manager
INCLUDES interfaces
)
DeclareCAmkESComponent(UartDriver
SOURCES components/UartDriver/src/driver.c
)

View File

@ -0,0 +1,6 @@
[workspace]
members = [
"kata-proc-common",
"kata-proc-manager",
]

View File

@ -0,0 +1,13 @@
// Kata OS ProcessManager services.
import <ProcessControlInterface.camkes>;
import <PackageManagementInterface.camkes>;
import <SeL4DebugInterface.camkes>;
component ProcessManager {
control;
provides ProcessControlInterface proc_ctrl;
provides PackageManagementInterface proc_mgmt;
uses SeL4DebugInterface sel4debug;
}

View File

@ -0,0 +1,7 @@
language = "C"
include_guard = "__PROCESS_MANAGER_BINDINGS_H__"
autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
no_includes = true
[export]
include = ["Bundle", "BundleId", "BundleIdArray"]

View File

@ -0,0 +1,6 @@
[package]
name = "kata-proc-common"
version = "0.1.0"
edition = "2018"
[dependencies]

View File

@ -0,0 +1,105 @@
//! Kata OS process management support
#![cfg_attr(not(test), no_std)]
use core::ops::{Index, IndexMut};
// NB: struct's marked repr(C) are processed by cbindgen to get a .h file
// used in camkes interfaces.
// Max bundles that can be installed at one time.
pub const MAX_BUNDLES: usize = 10;
// Max/fixed size of a BundleId; this is stopgap for C compatibility
pub const MAX_BUNDLE_ID_SIZE: usize = 32;
#[repr(C)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct BundleId {
pub id: [u8; MAX_BUNDLE_ID_SIZE],
}
impl BundleId {
pub fn empty(value: u8) -> Self {
BundleId {
id: [value; MAX_BUNDLE_ID_SIZE],
}
}
pub fn new() -> Self {
BundleId::empty(0)
}
pub fn is_zero(&self) -> bool {
self.id == [0; MAX_BUNDLE_ID_SIZE]
}
}
#[repr(C)]
pub struct BundleIdArray {
pub ids: [BundleId; MAX_BUNDLES], // TODO(sleffler): placeholder
}
impl BundleIdArray {
pub fn new() -> Self {
BundleIdArray {
ids: [BundleId::new(); MAX_BUNDLES],
}
}
pub fn len(&self) -> usize {
self.ids.iter().filter(|&id| !id.is_zero()).count()
}
}
impl Index<usize> for BundleIdArray {
type Output = BundleId;
fn index(&self, index: usize) -> &Self::Output {
&self.ids[index]
}
}
impl IndexMut<usize> for BundleIdArray {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.ids[index]
}
}
// TODO(sleffler): Bundle should come from whomever implements install+uninstall
// for ProcessManagerInterface
#[repr(C)]
pub struct Bundle {
pub something: u32, // TODO(sleffler): placeholder
}
impl Bundle {
pub fn new() -> Self {
Bundle { something: 0 }
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ProcessManagerError {
BundleNotFound,
BundleFound,
NoSpace,
// Generic errors for interface failures.
InstallFailed,
UninstallFailed,
StartFailed,
StopFailed,
}
// Abstract interface for injecting fakes, etc.
pub trait ProcessManagerInterface {
fn install(&self, bundle: &Bundle) -> Result<(), ProcessManagerError>;
fn uninstall(&self, bundle: &Bundle) -> Result<(), ProcessManagerError>;
fn start(&self, bundle: &Bundle) -> Result<(), ProcessManagerError>;
fn stop(&self, bundle: &Bundle) -> Result<(), ProcessManagerError>;
}
pub trait PackageManagementInterface<'a> {
fn install(
&mut self,
bundle_id: &BundleId,
bundle: &'a Bundle,
) -> Result<(), ProcessManagerError>;
fn uninstall(&mut self, bundle_id: &BundleId) -> Result<(), ProcessManagerError>;
}
pub trait ProcessControlInterface {
fn start(&mut self, bundle_id: &BundleId) -> Result<(), ProcessManagerError>;
fn stop(&mut self, bundle_id: &BundleId) -> Result<(), ProcessManagerError>;
fn get_running_bundles(&self) -> BundleIdArray;
}

View File

@ -0,0 +1,16 @@
[package]
name = "kata-process-manager"
version = "0.1.0"
description = "Kata OS ProcessManager services"
edition = "2018"
[dependencies]
arrayvec = { version = "0.7", default-features = false }
cstr_core = { version = "0.2.3", default-features = false }
kata-proc-common = { path = "../kata-proc-common" }
panic-halt = "0.2.0"
[lib]
name = "kata_process_manager"
path = "src/run.rs"
crate-type = ["staticlib"]

View File

@ -0,0 +1,269 @@
//! Kata OS process management support
// TODO(sleffler): need locking? (maybe guarantee single-threading via ipc)
#![cfg_attr(not(test), no_std)]
#![feature(const_fn_trait_bound)] // NB: for ProcessManager::empty using manager: None
#[cfg(not(test))]
extern crate panic_halt;
use arrayvec::ArrayVec;
use core::marker::Sync;
use cstr_core::CStr;
use kata_proc_common as proc;
use proc::*;
// Prints/logs a message to the console.
// Temporary workaround for LoggerInterface not working
fn syslog(_level: i32, msg: &str) {
// Print |str| on the consule using the SeL4 debug syscall.
// NB: for now the message must be explicitly \0-terminated.
fn sel4_putstr(msg: &str) {
extern "C" {
fn sel4debug_put_string(msg: *const cstr_core::c_char);
}
unsafe {
sel4debug_put_string(CStr::from_bytes_with_nul(msg.as_bytes()).unwrap().as_ptr());
}
}
sel4_putstr(msg); // NB:assumes caller includes \0
}
// Bundle state tracks start/stop operations.
#[derive(Debug, Eq, PartialEq)]
enum BundleState {
Stopped,
Running,
}
// We track the Bundle & ProcessControlInterface state.
// NB: assume storage manager (or other) owns Bundle
struct BundleData<'a> {
state: BundleState,
bundle: &'a Bundle,
}
impl<'b> BundleData<'b> {
fn new(bundle: &'b Bundle) -> Self {
BundleData {
state: BundleState::Stopped,
bundle: bundle,
}
}
}
// The ProcessManager presents the PackageManagementInterface (for loading
// applications from storage) and the ProcessControlInterface (for starting
// and stopping associated applications). The interface to the underlying
// system(s) are abstracted through the ProcessManagerInterface. One instance
// of the ProcessManager is created at start and accessed through SeL4 RPC's
// (from other components).
pub struct ProcessManager<'a> {
// TODO(sleffler): Option is for empty which is meant for static setup
manager: Option<&'a (dyn ProcessManagerInterface + Sync)>,
// TODO(sleffler): hash table (requires missing deps)
ids: ArrayVec<BundleId, MAX_BUNDLES>,
bundles: ArrayVec<BundleData<'a>, MAX_BUNDLES>,
}
impl<'a> ProcessManager<'a> {
// Creates a new ProcessManager instance.
pub fn new(manager: &'a (dyn ProcessManagerInterface + Sync)) -> ProcessManager<'a> {
ProcessManager {
manager: Some(manager),
ids: ArrayVec::<BundleId, MAX_BUNDLES>::new(),
bundles: ArrayVec::<BundleData, MAX_BUNDLES>::new(),
}
}
// Creates an incomplete ProcessManager instance for static initialization.
// The instance must be followed with an init() call to complete setup.
pub const fn empty() -> Self {
ProcessManager {
manager: None,
ids: ArrayVec::<BundleId, MAX_BUNDLES>::new_const(),
bundles: ArrayVec::<BundleData, MAX_BUNDLES>::new_const(),
}
}
// Completes initialization of an instance created with empty().
pub fn init(&mut self, manager: &'a (dyn ProcessManagerInterface + Sync)) {
self.manager = Some(manager);
}
// Returns the index of |bundle_id| if previously installed.
fn get_bundle_index(&self, bundle_id: &BundleId) -> Option<usize> {
self.ids.iter().position(|x| bundle_id == x)
}
}
impl<'a> PackageManagementInterface<'a> for ProcessManager<'a> {
fn install(
&mut self,
bundle_id: &BundleId,
bundle: &'a Bundle,
) -> Result<(), ProcessManagerError> {
match self.get_bundle_index(bundle_id) {
Some(_) => Err(ProcessManagerError::BundleFound),
None => {
if self.ids.is_full() {
return Err(ProcessManagerError::NoSpace);
}
self.manager.unwrap().install(bundle)?;
self.bundles.push(BundleData::new(bundle));
self.ids.push(*bundle_id);
Ok(())
}
}
}
fn uninstall(&mut self, bundle_id: &BundleId) -> Result<(), ProcessManagerError> {
match self.get_bundle_index(bundle_id) {
None => Err(ProcessManagerError::BundleNotFound),
Some(index) => {
let bundle = &mut self.bundles[index];
// TODO(sleffler): remove private state regardless of error?
self.manager.unwrap().uninstall(bundle.bundle)?;
self.bundles.remove(index);
self.ids.remove(index);
Ok(())
}
}
}
}
impl<'a> ProcessControlInterface for ProcessManager<'a> {
fn start(&mut self, bundle_id: &BundleId) -> Result<(), ProcessManagerError> {
match self.get_bundle_index(bundle_id) {
Some(index) => {
let bundle = &mut self.bundles[index];
if bundle.state == BundleState::Stopped {
self.manager.unwrap().start(bundle.bundle)?;
}
bundle.state = BundleState::Running;
Ok(())
}
None => Err(ProcessManagerError::BundleNotFound),
}
}
fn stop(&mut self, bundle_id: &BundleId) -> Result<(), ProcessManagerError> {
match self.get_bundle_index(bundle_id) {
Some(index) => {
let bundle = &mut self.bundles[index];
if bundle.state == BundleState::Running {
self.manager.unwrap().stop(bundle.bundle)?;
}
bundle.state = BundleState::Stopped;
Ok(())
}
None => Err(ProcessManagerError::BundleNotFound), // XXX ignore & return true?
}
}
fn get_running_bundles(&self) -> BundleIdArray {
let mut result = BundleIdArray::new();
for (index, (&id, _bundle)) in self
.ids
.iter()
.zip(self.bundles.iter())
.filter(|(_, bundle)| matches!(bundle.state, BundleState::Running))
.enumerate()
{
result[index] = id;
}
result
}
}
// TODO(sleffler): move to init or similar if a thread isn't needed
#[no_mangle]
pub extern "C" fn run() {
// Setup the userland address spaces, lifecycles, and system introspection
// for third-party applications.
syslog(0, "ProcessManager::run\n\0");
}
#[cfg(test)]
mod tests {
use super::*;
use proc::ProcessManagerError as pme;
struct FakeManager {}
impl ProcessManagerInterface for FakeManager {
fn install(&self, _bundle: &Bundle) -> Result<(), pme> {
Ok(())
}
fn uninstall(&self, _bundle: &Bundle) -> Result<(), pme> {
Ok(())
}
fn start(&self, _bundle: &Bundle) -> Result<(), pme> {
Ok(())
}
fn stop(&self, _bundle: &Bundle) -> Result<(), pme> {
Ok(())
}
}
#[test]
fn test_pkg_mgmt() {
let bundle_id = BundleId::empty(1);
let bundle = Bundle::new();
let fake = tests::FakeManager {};
let mut mgr = ProcessManager::new(&fake);
// Not installed, should fail.
assert_eq!(mgr.uninstall(&bundle_id).err(), Some(pme::BundleNotFound));
// Install the bundle.
assert!(mgr.install(&bundle_id, &bundle).is_ok());
// Re-install the same bundle should fail.
assert_eq!(
mgr.install(&bundle_id, &bundle).err(),
Some(pme::BundleFound)
);
// Now uninstalling the bundle should work.
assert!(mgr.uninstall(&bundle_id).is_ok());
}
#[test]
fn test_proc_ctrl() {
let bundle_id = BundleId::empty(2);
let bundle = Bundle::new();
let fake = tests::FakeManager {};
let mut mgr = ProcessManager::new(&fake);
assert!(mgr.install(&bundle_id, &bundle).is_ok());
assert!(mgr.stop(&bundle_id).is_ok());
assert!(mgr.start(&bundle_id).is_ok());
let running = mgr.get_running_bundles();
assert_eq!(running.len(), 1);
assert_eq!(running[0], bundle_id);
assert!(mgr.stop(&bundle_id).is_ok());
// After stopping the bundle we should see nothing running.
let running = mgr.get_running_bundles();
assert_eq!(running.len(), 0);
}
#[test]
fn test_empty_init() {
let bundle_id = BundleId { id: [1; 32] };
let bundle = Bundle::new();
let fake = tests::FakeManager {};
let mut mgr = ProcessManager::empty();
mgr.init(&fake);
// Not installed, should fail.
assert_eq!(mgr.uninstall(&bundle_id).err(), Some(pme::BundleNotFound));
// Install the bundle.
assert!(mgr.install(&bundle_id, &bundle).is_ok());
// Re-install the same bundle should fail.
assert_eq!(
mgr.install(&bundle_id, &bundle).err(),
Some(pme::BundleFound)
);
// Now uninstalling the bundle should work.
assert!(mgr.uninstall(&bundle_id).is_ok());
}
}

View File

@ -0,0 +1,6 @@
procedure PackageManagementInterface {
include <ProcessManagerBindings.h>;
bool install(in string bundleId, in Bundle bundle);
bool uninstall(in string bundleId);
};

View File

@ -0,0 +1,7 @@
procedure ProcessControlInterface {
include <ProcessManagerBindings.h>;
bool start(in string bundleId);
bool stop(in string bundleId);
BundleIdArray get_running_bundles();
};

View File

@ -0,0 +1,20 @@
#ifndef __PROCESS_MANAGER_BINDINGS_H__
#define __PROCESS_MANAGER_BINDINGS_H__
/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */
#define MAX_BUNDLES 10
typedef struct Bundle {
uint32_t something;
} Bundle;
typedef struct BundleId {
uint8_t id[32];
} BundleId;
typedef struct BundleIdArray {
struct BundleId ids[MAX_BUNDLES];
} BundleIdArray;
#endif /* __PROCESS_MANAGER_BINDINGS_H__ */

View File

@ -15,6 +15,7 @@ import <std_connector.camkes>;
import "interfaces/uart.idl4";
import "components/UartDriver/UartDriver.camkes";
import "components/DebugConsole/DebugConsole.camkes";
import "components/ProcessManager/ProcessManager.camkes";
import "components/SeL4Debug/SeL4Debug.camkes";
component UART {
@ -29,6 +30,7 @@ assembly {
component UART uart;
component UartDriver drv;
component DebugConsole debug_console;
component ProcessManager process_manager;
component SeL4Debug sel4debug;
connection seL4HardwareMMIO uart_mem(from drv.mem, to uart.mem);
@ -39,6 +41,7 @@ assembly {
// Connect the SeL4Debug interface of each component that needs access.
connection seL4RPCCall SeL4DebugInterface(from debug_console.sel4debug,
from process_manager.sel4debug,
to sel4debug.sel4debug);
connection seL4SharedData tx_channel(