Переглянути джерело

Add process builder & controller

徐启航 1 рік тому
батько
коміт
665ffefbf0

+ 3 - 2
h2o/kernel/src/sched/task.rs

@@ -114,7 +114,7 @@ fn exec(
     })
 }
 
-fn create(name: Option<String>, space: Arc<Space>) -> sv_call::Result<(Init, sv_call::Handle)> {
+fn create(name: Option<String>, space: Arc<Space>, init_chan: sv_call::Handle) -> sv_call::Result<(Init, sv_call::Handle)> {
     let cur = super::SCHED.with_current(|cur| Ok(cur.tid.clone()))?;
 
     let ty = cur.ty();
@@ -129,7 +129,8 @@ fn create(name: Option<String>, space: Arc<Space>) -> sv_call::Result<(Init, sv_
 
     let tid = tid::allocate(ti).map_err(|_| sv_call::EBUSY)?;
 
-    let kstack = ctx::Kstack::new(None, ty);
+    let mut kstack = ctx::Kstack::new(None, ty);
+    kstack.task_frame_mut().set_args(init_chan.raw() as _, 0);
     let ext_frame = ctx::ExtFrame::zeroed();
 
     let init = Init::new(tid, space, kstack, ext_frame);

+ 6 - 0
h2o/kernel/src/sched/task/ctx/x86_64.rs

@@ -110,6 +110,12 @@ impl Frame {
         self.rsi = entry.args[1];
     }
 
+    #[inline]
+    pub fn set_args(&mut self, arg0: u64, arg1: u64) {
+        self.rdi = arg0;
+        self.rsi = arg1;
+    }
+
     #[inline]
     pub fn syscall_args(&self) -> Syscall {
         Syscall {

+ 20 - 10
h2o/kernel/src/sched/task/syscall.rs

@@ -130,23 +130,33 @@ fn task_new(
     name: UserPtr<In>,
     name_len: usize,
     space: Handle,
+    init_chan: Handle,
     st: UserPtr<Out, Handle>,
 ) -> Result<Handle> {
     let name = get_name(name, name_len)?;
 
-    let new_space = if space == Handle::NULL {
-        SCHED.with_current(|cur| Ok(Arc::clone(cur.space())))?
-    } else {
-        SCHED.with_current(|cur| {
-            cur.space()
-                .handles()
-                .remove::<Space>(space)
-                .map(Ref::into_raw)
-        })?
+    let (init_chan, space) = SCHED.with_current(|cur| {
+        let handles = cur.space().handles();
+        let init_chan = if init_chan == Handle::NULL {
+            None
+        } else {
+            Some(handles.remove::<crate::sched::ipc::Channel>(init_chan)?)
+        };
+        if space == Handle::NULL {
+            Ok((init_chan, Arc::clone(cur.space())))
+        } else {
+            let space = handles.remove::<Space>(space)?;
+            Ok((init_chan, Ref::into_raw(space)))
+        }
+    })?;
+    let init_chan = match init_chan {
+        Some(obj) => PREEMPT.scope(|| space.handles().insert_ref(obj))?,
+        None => Handle::NULL,
     };
+
     let mut sus_slot = Arsc::try_new_uninit()?;
 
-    let (task, hdl) = super::create(name, Arc::clone(&new_space))?;
+    let (task, hdl) = super::create(name, Arc::clone(&space), init_chan)?;
 
     let task = super::Ready::block(
         super::IntoReady::into_ready(task, unsafe { crate::cpu::id() }, MIN_TIME_GRAN),

+ 4 - 0
h2o/kernel/syscall/task.json

@@ -61,6 +61,10 @@
                     "name": "space",
                     "ty": "Handle"
                 },
+                {
+                    "name": "init_chan",
+                    "ty": "Handle"
+                },
                 {
                     "name": "st",
                     "ty": "*mut Handle"

+ 1 - 1
h2o/tinit/src/test/task.rs

@@ -278,7 +278,7 @@ pub unsafe fn test(virt: &Virt) -> (*mut u8, *mut u8, Handle) {
 
     let mut st = Handle::NULL;
     let task = {
-        let t = sv_task_new(null_mut(), 0, Handle::NULL, &mut st)
+        let t = sv_task_new(null_mut(), 0, Handle::NULL, Handle::NULL, &mut st)
             .into_res()
             .expect("Failed to create task");
         let frame = Gpr {

+ 5 - 1
h2o/tinit/src/tmain.rs

@@ -171,7 +171,10 @@ extern "C" fn tmain(init_chan: sv_call::Handle) {
     let dl_args = StartupArgs {
         handles: [
             (HandleType::RootVirt.into(), Virt::into_raw(virt.clone())),
-            (HandleType::VdsoPhys.into(), Phys::into_raw(vdso_phys)),
+            (
+                HandleType::VdsoPhys.into(),
+                Phys::into_raw(vdso_phys.clone()),
+            ),
             (HandleType::ProgramPhys.into(), Phys::into_raw(bin)),
             (HandleType::LoadRpc.into(), Channel::into_raw(load_rpc.1)),
         ]
@@ -189,6 +192,7 @@ extern "C" fn tmain(init_chan: sv_call::Handle) {
     let exe_args = StartupArgs {
         handles: [
             (HandleType::RootVirt.into(), Virt::into_raw(virt)),
+            (HandleType::VdsoPhys.into(), Phys::into_raw(vdso_phys)),
             (
                 HandleType::BootfsPhys.into(),
                 Phys::into_raw(bootfs_phys.clone()),

+ 7 - 0
src/lib/h2o_async/src/ipc.rs

@@ -24,6 +24,13 @@ pub trait AsyncObject: Object {
     where
         Self: 'a;
 
+    /// The generic async reactor API for kernel objects.
+    ///
+    /// # Note
+    ///
+    /// The corresponding kernel objects should have implemented their own
+    /// proactor API, which is better in performance, and should be
+    /// preferred to use instead of this method.
     fn try_wait_with<'a>(
         &'a self,
         disp: &'a DispSender,

+ 0 - 1
src/lib/h2o_async/src/time.rs

@@ -102,4 +102,3 @@ impl Stream for Intervals<'_> {
         fut.poll(cx).map(Some)
     }
 }
-

+ 1 - 0
src/lib/h2o_fs/Cargo.toml

@@ -9,6 +9,7 @@ runtime = ["solvent-async/runtime", "solvent-rpc/runtime"]
 
 [dependencies]
 # Local crates
+elfload = {path = "../elfload"}
 solvent = {path = "../h2o_rs"}
 solvent-async = {path = "../h2o_async", default-features = false}
 solvent-core = {path = "../h2o_std/core"}

+ 1 - 1
src/lib/h2o_fs/src/file.rs

@@ -8,7 +8,7 @@ use async_trait::async_trait;
 use solvent::prelude::Phys;
 use solvent_async::io::Stream;
 use solvent_core::io::RawStream;
-use solvent_rpc::io::{Error, file::PhysOptions};
+use solvent_rpc::io::{file::PhysOptions, Error};
 
 #[cfg(feature = "runtime")]
 pub use self::handle::{handle, handle_mapped};

+ 1 - 1
src/lib/h2o_fs/src/file/stream.rs

@@ -236,7 +236,7 @@ mod runtime {
         async fn seek(&mut self, pos: SeekFrom) -> Result<usize, Error> {
             Ok(self.stream()?.seek(pos).await?)
         }
-        
+
         #[inline]
         async fn phys(&self, options: PhysOptions) -> Result<Phys, Error> {
             self.inner.phys(options).await

+ 27 - 0
src/lib/h2o_fs/src/fs.rs

@@ -173,6 +173,29 @@ impl Node {
         }
     }
 
+    fn export(
+        self: Arsc<Self>,
+        path: PathBuf,
+        output: &mut impl Extend<(PathBuf, EntryClient)>,
+    ) -> Result<(), Error> {
+        match *self {
+            Node::Dir(ref dir) => {
+                let entries = dir.lock();
+                output.extend_reserve(entries.len().saturating_sub(1));
+                for (name, node) in entries.iter() {
+                    node.clone().export(path.join(name), output)?;
+                }
+            }
+            Node::Remote(ref remote) => {
+                let (t, conn) = Channel::new();
+                remote.clone_connection(conn)?;
+                let remote = EntryClient::from(t);
+                output.extend(Some((path, remote)));
+            }
+        }
+        Ok(())
+    }
+
     fn copy_local(
         self: Arsc<Self>,
         src: PathBuf,
@@ -360,6 +383,10 @@ impl LocalFs {
         }
     }
 
+    pub fn export(&self, output: &mut impl Extend<(PathBuf, EntryClient)>) -> Result<(), Error> {
+        self.root.clone().export(PathBuf::new(), output)
+    }
+
     pub fn unlink<P: AsRef<Path>>(&self, path: P, expect_dir: bool) -> Result<(), Error> {
         let path = self.canonicalize(path)?;
         let parent = path

+ 3 - 0
src/lib/h2o_fs/src/lib.rs

@@ -1,6 +1,8 @@
 #![no_std]
 #![feature(btree_drain_filter)]
+#![feature(extend_one)]
 #![feature(result_option_inspect)]
+#![feature(slice_ptr_get)]
 
 use alloc::{
     string::{String, ToString},
@@ -22,6 +24,7 @@ pub mod fs;
 pub mod loader;
 #[cfg(feature = "runtime")]
 pub mod mem;
+pub mod process;
 #[cfg(feature = "runtime")]
 pub mod rpc;
 

+ 5 - 4
src/lib/h2o_fs/src/loader.rs

@@ -1,4 +1,5 @@
 use alloc::vec::Vec;
+use core::borrow::Borrow;
 
 use futures::StreamExt;
 use solvent::prelude::Phys;
@@ -13,12 +14,12 @@ use solvent_rpc::{
     Protocol, Server,
 };
 
-pub async fn get_object_from_dir<D: AsRef<DirectoryClient>, P: AsRef<Path>>(
+pub async fn get_object_from_dir<D: Borrow<DirectoryClient>, P: AsRef<Path>>(
     dir: D,
     path: P,
 ) -> Result<Phys, Error> {
     let (file, server) = File::channel();
-    let dir = dir.as_ref();
+    let dir = dir.borrow();
     dir.open(
         path.as_ref().into(),
         OpenOptions::READ,
@@ -28,7 +29,7 @@ pub async fn get_object_from_dir<D: AsRef<DirectoryClient>, P: AsRef<Path>>(
     file.phys(PhysOptions::Copy).await?
 }
 
-pub async fn get_object<D: AsRef<DirectoryClient>, P: AsRef<Path>>(
+pub async fn get_object<D: Borrow<DirectoryClient>, P: AsRef<Path>>(
     dir: impl Iterator<Item = D>,
     path: P,
 ) -> Option<Phys> {
@@ -42,7 +43,7 @@ pub async fn get_object<D: AsRef<DirectoryClient>, P: AsRef<Path>>(
     None
 }
 
-pub async fn serve<D: AsRef<DirectoryClient>>(
+pub async fn serve<D: Borrow<DirectoryClient>>(
     server: LoaderServer,
     dir: impl Iterator<Item = D> + Clone,
 ) {

+ 13 - 7
src/lib/h2o_fs/src/mem/dir/builder.rs

@@ -2,6 +2,7 @@ use alloc::{
     collections::{btree_map::Entry as MapEntry, BTreeMap},
     string::String,
 };
+use core::mem;
 
 use solvent_core::{
     path::{Path, PathBuf},
@@ -102,11 +103,13 @@ impl Builder {
         Ok(self)
     }
 
-    pub fn build(self) -> Arsc<MemDir> {
-        let entries = self.entries.into_iter().map(|(name, entry)| match entry {
-            BuilderInner::Dir(builder) => (name, builder.build() as Arsc<dyn Entry>),
-            BuilderInner::Entry(entry) => (name, entry),
-        });
+    pub fn build(&mut self) -> Arsc<MemDir> {
+        let entries = mem::take(&mut self.entries)
+            .into_iter()
+            .map(|(name, entry)| match entry {
+                BuilderInner::Dir(mut builder) => (name, builder.build() as Arsc<dyn Entry>),
+                BuilderInner::Entry(entry) => (name, entry),
+            });
         Arsc::new(MemDir {
             entries: entries.collect(),
             perm: self.perm,
@@ -114,8 +117,11 @@ impl Builder {
     }
 
     #[inline]
-    pub fn build_mut<F: FileInserter + 'static>(self, file_inserter: Arsc<F>) -> Arsc<MemDirMut> {
-        self.build_mut_inner("".into(), file_inserter)
+    pub fn build_mut<F: FileInserter + 'static>(
+        &mut self,
+        file_inserter: Arsc<F>,
+    ) -> Arsc<MemDirMut> {
+        mem::take(self).build_mut_inner("".into(), file_inserter)
     }
 
     fn build_mut_inner<F: FileInserter + 'static>(

+ 8 - 3
src/lib/h2o_fs/src/mem/file.rs

@@ -5,7 +5,10 @@ use async_trait::async_trait;
 use solvent::prelude::{Channel, Phys};
 use solvent_async::io::Stream;
 use solvent_core::{io::RawStream, path::Path, sync::Arsc};
-use solvent_rpc::io::{file::{FileServer, PhysOptions}, Error, FileType, Metadata, OpenOptions, Permission};
+use solvent_rpc::io::{
+    file::{FileServer, PhysOptions},
+    Error, FileType, Metadata, OpenOptions, Permission,
+};
 
 use crate::{
     dir::EventTokens,
@@ -110,9 +113,11 @@ impl File for MemFile {
 
     async fn phys(&self, options: PhysOptions) -> Result<Phys, Error> {
         if self.locked.load(Acquire) {
-            return Err(Error::WouldBlock)
+            return Err(Error::WouldBlock);
         }
         let copy = options == PhysOptions::Copy;
-        self.phys.create_sub(0, self.phys.len(), copy).map_err(Error::Other)
+        self.phys
+            .create_sub(0, self.phys.len(), copy)
+            .map_err(Error::Other)
     }
 }

+ 174 - 0
src/lib/h2o_fs/src/process.rs

@@ -0,0 +1,174 @@
+mod builder;
+
+use core::{mem, ops::Deref, ptr::NonNull};
+
+use solvent::task::{SuspendToken, Task};
+use solvent_rpc::SerdePacket;
+
+pub use self::builder::{Builder, Error as BuildError};
+
+pub enum Error {
+    Exited(usize),
+    Started,
+    Start(solvent::error::Error),
+    Join(solvent::error::Error),
+    TryJoin(solvent::error::Error),
+    Suspend(solvent::error::Error),
+    Kill(solvent::error::Error),
+    Wait(solvent::error::Error),
+}
+
+#[derive(SerdePacket)]
+pub struct InitProcess {
+    task: Task,
+    entry: NonNull<u8>,
+    stack: NonNull<u8>,
+    vdso_base: NonNull<u8>,
+    suspend_token: SuspendToken,
+}
+
+unsafe impl Send for InitProcess {}
+
+impl Deref for InitProcess {
+    type Target = SuspendToken;
+
+    #[inline]
+    fn deref(&self) -> &Self::Target {
+        &self.suspend_token
+    }
+}
+
+impl InitProcess {
+    pub fn start(self) -> Result<Process, Error> {
+        let InitProcess {
+            task,
+            entry,
+            stack,
+            vdso_base,
+            suspend_token,
+        } = self;
+        let mut gpr = suspend_token.read_gpr().map_err(Error::Start)?;
+        gpr.rip = entry.as_ptr() as _;
+        gpr.rsp = stack.as_ptr() as _;
+        gpr.rsi = vdso_base.as_ptr() as _;
+        suspend_token.write_gpr(&gpr).map_err(Error::Start)?;
+        Ok(Process::new(task))
+    }
+}
+
+enum ProcessState {
+    Started(Task),
+    Exited(usize),
+}
+
+impl ProcessState {
+    fn kill(&mut self) -> Result<(), Error> {
+        match self {
+            ProcessState::Started(task) => task.kill().map_err(Error::Kill),
+            ProcessState::Exited(status) => Err(Error::Exited(*status)),
+        }
+    }
+
+    fn join(&mut self) -> Result<usize, Error> {
+        let status = match mem::replace(self, ProcessState::Exited(0)) {
+            ProcessState::Started(task) => task.join().map_err(Error::Join)?,
+            ProcessState::Exited(status) => status,
+        };
+        Ok(status)
+    }
+
+    fn try_join(&mut self) -> Result<usize, Error> {
+        let status = match mem::replace(self, ProcessState::Exited(0)) {
+            ProcessState::Started(task) => match task.try_join() {
+                Ok(status) => status,
+                Err((err, task)) => {
+                    *self = ProcessState::Started(task);
+                    return Err(Error::TryJoin(err));
+                }
+            },
+            ProcessState::Exited(status) => status,
+        };
+        *self = ProcessState::Exited(status);
+        Ok(status)
+    }
+}
+
+pub struct Process(ProcessState);
+
+unsafe impl Send for Process {}
+unsafe impl Sync for Process {}
+
+impl Process {
+    fn new(task: Task) -> Self {
+        Process(ProcessState::Started(task))
+    }
+
+    #[inline]
+    pub fn builder() -> Builder {
+        Default::default()
+    }
+
+    pub fn suspend(&self) -> Result<SuspendToken, Error> {
+        match self.0 {
+            ProcessState::Started(ref task) => Ok(task.suspend().map_err(Error::Suspend)?),
+            ProcessState::Exited(status) => Err(Error::Exited(status)),
+        }
+    }
+
+    #[inline]
+    pub fn kill(&mut self) -> Result<(), Error> {
+        self.0.kill()
+    }
+
+    #[inline]
+    pub fn join(&mut self) -> Result<usize, Error> {
+        self.0.join()
+    }
+
+    #[inline]
+    pub fn try_join(&mut self) -> Result<usize, Error> {
+        self.0.try_join()
+    }
+}
+
+#[cfg(feature = "runtime")]
+mod runtime {
+    use core::{
+        future::Future,
+        mem,
+        pin::Pin,
+        task::{Context, Poll},
+    };
+
+    use futures::pin_mut;
+    use solvent::prelude::SIG_READ;
+    use solvent_async::ipc::AsyncObject;
+
+    use super::{Error, Process};
+    use crate::process::ProcessState;
+
+    // TODO: Replace with proactor API.
+    impl Future for Process {
+        type Output = Result<usize, Error>;
+
+        fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+            let fut = async move {
+                let status = match mem::replace(&mut self.0, ProcessState::Exited(0)) {
+                    ProcessState::Started(task) => {
+                        task.try_wait_with(&solvent_async::dispatch(), true, SIG_READ)
+                            .await
+                            .map_err(Error::Wait)?;
+                        task.join().map_err(Error::Join)?
+                    }
+                    ProcessState::Exited(status) => status,
+                };
+                self.0 = ProcessState::Exited(status);
+                Ok(status)
+            };
+            pin_mut!(fut);
+            fut.poll(cx)
+        }
+    }
+}
+#[cfg(feature = "runtime")]
+pub use runtime::*;

+ 582 - 0
src/lib/h2o_fs/src/process/builder.rs

@@ -0,0 +1,582 @@
+use alloc::{
+    collections::{btree_map::Entry as MapEntry, BTreeMap},
+    ffi::{CString, NulError},
+    string::{String, ToString},
+    vec,
+    vec::Vec,
+};
+use core::{mem, num::NonZeroUsize, ptr::NonNull};
+
+use solvent::{
+    prelude::{drop_raw, Channel, Feature, Flags, Handle, Object, Phys, Space, Virt, PAGE_SIZE},
+    task::{Task, DEFAULT_STACK_SIZE},
+};
+use solvent_core::{path::PathBuf, sync::Lazy};
+#[cfg(feature = "runtime")]
+use solvent_rpc::{io::dir::DirectoryClient, loader::LoaderClient};
+use solvent_rpc::{io::entry::EntrySyncClient, loader::LoaderSyncClient};
+use svrt::{HandleInfo, HandleType, StartupArgs};
+
+use super::{InitProcess, Process};
+
+const INTERP: &str = "lib/ld-oceanic.so";
+
+pub enum Error {
+    LoadPhys(elfload::Error),
+    FieldMissing(&'static str),
+    InvalidCStr(NulError),
+    DepNotFound(CString),
+    Rpc(solvent_rpc::Error),
+    VdsoMap(solvent::error::Error),
+    StackAlloc(solvent::error::Error),
+    SendStartupArgs(solvent::error::Error),
+    TaskExec(solvent::error::Error),
+}
+
+impl From<elfload::Error> for Error {
+    #[inline]
+    fn from(value: elfload::Error) -> Self {
+        Error::LoadPhys(value)
+    }
+}
+
+#[derive(Default)]
+pub struct Builder {
+    local_fs: BTreeMap<PathBuf, EntrySyncClient>,
+    handles: BTreeMap<HandleInfo, Handle>,
+    executable: Option<(Phys, String)>,
+    loader: Option<LoaderSyncClient>,
+    vdso: Option<Phys>,
+    args: Vec<String>,
+    environ: BTreeMap<String, String>,
+}
+
+impl Builder {
+    #[inline]
+    pub fn new() -> Self {
+        Default::default()
+    }
+
+    #[inline]
+    pub fn local_fs<I>(&mut self, iter: I) -> &mut Self
+    where
+        I: Iterator<Item = (PathBuf, EntrySyncClient)>,
+    {
+        self.local_fs.extend(iter);
+        self
+    }
+
+    /// # Safety
+    ///
+    /// The caller must ensure that the handles are with their own ownerships.
+    #[inline]
+    pub unsafe fn handles<I>(&mut self, iter: I) -> &mut Self
+    where
+        I: Iterator<Item = (HandleInfo, Handle)>,
+    {
+        self.handles.extend(iter);
+        self
+    }
+
+    pub fn executable(&mut self, executable: Phys, name: String) -> Result<&mut Self, Phys> {
+        if self.executable.is_some() {
+            return Err(executable);
+        }
+        let executable = executable
+            .reduce_features(Feature::READ | Feature::EXECUTE)
+            .expect("Failed to adjust features for executable");
+        self.executable = Some((executable, name.clone()));
+        self.args.insert(0, name);
+        Ok(self)
+    }
+
+    pub fn loader_sync(&mut self, loader: LoaderSyncClient) -> Result<&mut Self, LoaderSyncClient> {
+        if self.loader.is_some() {
+            return Err(loader);
+        }
+        self.loader = Some(loader);
+        Ok(self)
+    }
+
+    #[cfg(feature = "runtime")]
+    pub fn loader(&mut self, loader: LoaderClient) -> Result<&mut Self, LoaderClient> {
+        use solvent_rpc::Client;
+        if self.loader.is_some() {
+            return Err(loader);
+        }
+        self.loader = Some(loader.into_sync().unwrap());
+        Ok(self)
+    }
+
+    #[cfg(feature = "runtime")]
+    pub fn load_dirs(
+        &mut self,
+        dirs: Vec<DirectoryClient>,
+    ) -> Result<&mut Self, Vec<DirectoryClient>> {
+        use solvent_rpc::{loader::Loader, Protocol};
+
+        if self.loader.is_some() {
+            return Err(dirs);
+        }
+        let (client, server) = Loader::channel();
+        let task = crate::loader::serve(server, dirs.into_iter());
+        solvent_async::spawn(task).detach();
+
+        Ok(self.loader(client).unwrap())
+    }
+
+    #[inline]
+    pub fn vdso(&mut self, vdso: Phys) -> &mut Self {
+        self.vdso = Some(vdso);
+        self
+    }
+
+    #[inline]
+    pub fn args<S, I>(&mut self, args: I) -> &mut Self
+    where
+        S: Into<String>,
+        I: IntoIterator<Item = S>,
+    {
+        self.args.extend(args.into_iter().map(Into::into));
+        self
+    }
+
+    #[inline]
+    pub fn arg<S>(&mut self, arg: S) -> &mut Self
+    where
+        S: Into<String>,
+    {
+        self.args.push(arg.into());
+        self
+    }
+
+    pub fn environ<K, V>(&mut self, key: K, value: V) -> &mut Self
+    where
+        K: Into<String>,
+        V: Into<String>,
+    {
+        self.environ.insert(key.into(), value.into());
+        self
+    }
+
+    pub fn environs<K, V, I>(&mut self, iter: I) -> &mut Self
+    where
+        K: Into<String>,
+        V: Into<String>,
+        I: IntoIterator<Item = (K, V)>,
+    {
+        self.environ.extend(
+            iter.into_iter()
+                .map(|(key, value)| (key.into(), value.into())),
+        );
+        self
+    }
+
+    #[inline]
+    pub fn append_environ<K, V>(&mut self, key: K, value: V) -> &mut Self
+    where
+        K: Into<String>,
+        V: AsRef<str>,
+    {
+        append_environ(&mut self.environ, key.into(), value.as_ref());
+        self
+    }
+
+    pub fn append_environs<K, V, I>(&mut self, iter: I) -> &mut Self
+    where
+        K: Into<String>,
+        V: AsRef<str>,
+        I: IntoIterator<Item = (K, V)>,
+    {
+        for (key, value) in iter {
+            self.append_environ(key, value);
+        }
+        self
+    }
+
+    pub fn build_sync(&mut self) -> Result<Process, Error> {
+        let Builder {
+            local_fs,
+            handles,
+            executable,
+            loader,
+            vdso,
+            args,
+            environ,
+        } = mem::take(self);
+        let (executable, name) = executable.ok_or_else(|| Error::FieldMissing("executable"))?;
+        let loader = loader.ok_or_else(|| Error::FieldMissing("loader"))?;
+        let vdso = vdso.unwrap_or_else(self::vdso);
+
+        let interp_path = match elfload::get_interp(&executable)? {
+            Some(bytes) => CString::new(bytes),
+            None => CString::new(INTERP),
+        }
+        .map_err(Error::InvalidCStr)?;
+
+        let interp = loader
+            .get_object(vec![interp_path.clone()])
+            .map_err(Error::Rpc)?
+            .map_err(|_| Error::DepNotFound(interp_path))?
+            .pop()
+            .unwrap();
+
+        let build_args = build_end(
+            interp, executable, vdso, loader, handles, local_fs, args, environ, name,
+        )?;
+
+        let proc = Process::new(
+            Task::exec(
+                Some(&build_args.name),
+                Some(build_args.space),
+                build_args.entry,
+                build_args.stack,
+                Some(build_args.init_chan),
+                build_args.vdso_base.as_ptr() as _,
+            )
+            .map_err(Error::TaskExec)?,
+        );
+
+        Ok(proc)
+    }
+
+    pub fn build_non_start_sync(&mut self) -> Result<InitProcess, Error> {
+        let Builder {
+            local_fs,
+            handles,
+            executable,
+            loader,
+            vdso,
+            args,
+            environ,
+        } = mem::take(self);
+        let (executable, name) = executable.ok_or_else(|| Error::FieldMissing("executable"))?;
+        let loader = loader.ok_or_else(|| Error::FieldMissing("loader"))?;
+        let vdso = vdso.unwrap_or_else(self::vdso);
+
+        let interp_path = match elfload::get_interp(&executable)? {
+            Some(bytes) => CString::new(bytes),
+            None => CString::new(INTERP),
+        }
+        .map_err(Error::InvalidCStr)?;
+
+        let interp = loader
+            .get_object(vec![interp_path.clone()])
+            .map_err(Error::Rpc)?
+            .map_err(|_| Error::DepNotFound(interp_path))?
+            .pop()
+            .unwrap();
+
+        let build_args = build_end(
+            interp, executable, vdso, loader, handles, local_fs, args, environ, name,
+        )?;
+
+        let (task, suspend_token) = Task::new(
+            Some(&build_args.name),
+            Some(build_args.space),
+            Some(build_args.init_chan),
+        );
+        let proc = InitProcess {
+            task,
+            entry: build_args.entry,
+            stack: build_args.stack,
+            vdso_base: build_args.vdso_base,
+            suspend_token,
+        };
+
+        Ok(proc)
+    }
+
+    #[cfg(feature = "runtime")]
+    pub async fn build(&mut self) -> Result<Process, Error> {
+        use solvent_rpc::sync::Client;
+
+        let Builder {
+            local_fs,
+            handles,
+            executable,
+            loader,
+            vdso,
+            args,
+            environ,
+        } = mem::take(self);
+        let (executable, name) = executable.ok_or_else(|| Error::FieldMissing("executable"))?;
+        let loader = loader
+            .ok_or_else(|| Error::FieldMissing("loader"))?
+            .into_async()
+            .unwrap();
+        let vdso = vdso.unwrap_or_else(self::vdso);
+
+        let interp_path = match elfload::get_interp(&executable)? {
+            Some(bytes) => CString::new(bytes),
+            None => CString::new(INTERP),
+        }
+        .map_err(Error::InvalidCStr)?;
+
+        let interp = loader
+            .get_object(vec![interp_path.clone()])
+            .await
+            .map_err(Error::Rpc)?
+            .map_err(|_| Error::DepNotFound(interp_path))?
+            .pop()
+            .unwrap();
+
+        let build_args = build_end(
+            interp,
+            executable,
+            vdso,
+            solvent_rpc::Client::into_sync(loader).unwrap(),
+            handles,
+            local_fs,
+            args,
+            environ,
+            name,
+        )?;
+
+        let proc = Process::new(
+            Task::exec(
+                Some(&build_args.name),
+                Some(build_args.space),
+                build_args.entry,
+                build_args.stack,
+                Some(build_args.init_chan),
+                build_args.vdso_base.as_ptr() as _,
+            )
+            .map_err(Error::TaskExec)?,
+        );
+
+        Ok(proc)
+    }
+
+    #[cfg(feature = "runtime")]
+    pub async fn build_non_start(&mut self) -> Result<InitProcess, Error> {
+        use solvent_rpc::sync::Client;
+
+        let Builder {
+            local_fs,
+            handles,
+            executable,
+            loader,
+            vdso,
+            args,
+            environ,
+        } = mem::take(self);
+        let (executable, name) = executable.ok_or_else(|| Error::FieldMissing("executable"))?;
+        let loader = loader
+            .ok_or_else(|| Error::FieldMissing("loader"))?
+            .into_async()
+            .unwrap();
+        let vdso = vdso.unwrap_or_else(self::vdso);
+
+        let interp_path = match elfload::get_interp(&executable)? {
+            Some(bytes) => CString::new(bytes),
+            None => CString::new(INTERP),
+        }
+        .map_err(Error::InvalidCStr)?;
+
+        let interp = loader
+            .get_object(vec![interp_path.clone()])
+            .await
+            .map_err(Error::Rpc)?
+            .map_err(|_| Error::DepNotFound(interp_path))?
+            .pop()
+            .unwrap();
+
+        let build_args = build_end(
+            interp,
+            executable,
+            vdso,
+            solvent_rpc::Client::into_sync(loader).unwrap(),
+            handles,
+            local_fs,
+            args,
+            environ,
+            name,
+        )?;
+
+        let (task, suspend_token) = Task::new(
+            Some(&build_args.name),
+            Some(build_args.space),
+            Some(build_args.init_chan),
+        );
+        let proc = InitProcess {
+            task,
+            entry: build_args.entry,
+            stack: build_args.stack,
+            vdso_base: build_args.vdso_base,
+            suspend_token,
+        };
+
+        Ok(proc)
+    }
+}
+
+struct BuildArgs {
+    name: String,
+    space: Space,
+    entry: NonNull<u8>,
+    stack: NonNull<u8>,
+    init_chan: Channel,
+    vdso_base: NonNull<u8>,
+}
+
+#[allow(clippy::too_many_arguments)]
+fn build_end(
+    interp: Phys,
+    executable: Phys,
+    vdso: Phys,
+    loader: LoaderSyncClient,
+    handles: BTreeMap<HandleInfo, Handle>,
+    local_fs: BTreeMap<PathBuf, EntrySyncClient>,
+    args: Vec<String>,
+    environ: BTreeMap<String, String>,
+    name: String,
+) -> Result<BuildArgs, Error> {
+    let (space, root_virt) = Space::new();
+
+    let loaded = elfload::load(&interp, true, &root_virt)?;
+    elfload::load(&executable, true, &root_virt)?;
+
+    let vdso_base = root_virt.map_vdso(vdso.clone()).map_err(Error::VdsoMap)?;
+
+    let (stack_size, stack_flags) = pass_stack(loaded.stack);
+
+    let stack = allocate_stack(&root_virt, stack_size, stack_flags).map_err(Error::StackAlloc)?;
+
+    let entry = unsafe { NonNull::new_unchecked(loaded.entry as *mut u8) };
+
+    let (me, child) = Channel::new();
+
+    let child = child
+        .reduce_features(Feature::SEND | Feature::READ)
+        .expect("Failed to reduce features for read");
+    let me = me
+        .reduce_features(Feature::SEND | Feature::WRITE)
+        .expect("Failed to reduce features for write");
+
+    let dl_args = StartupArgs {
+        handles: [
+            (
+                HandleType::RootVirt.into(),
+                Virt::into_raw(root_virt.clone()),
+            ),
+            (HandleType::VdsoPhys.into(), Phys::into_raw(vdso)),
+            (HandleType::ProgramPhys.into(), Phys::into_raw(executable)),
+            (
+                HandleType::LoadRpc.into(),
+                Channel::into_raw(loader.try_into().unwrap()),
+            ),
+        ]
+        .into_iter()
+        .collect(),
+        args: vec![0],
+        env: vec![0],
+    };
+
+    let mut packet = Default::default();
+    dl_args
+        .send(&me, &mut packet)
+        .map_err(Error::SendStartupArgs)?;
+
+    startup_args(handles, local_fs, args, environ, root_virt)
+        .send(&me, &mut packet)
+        .map_err(Error::SendStartupArgs)?;
+
+    Ok(BuildArgs {
+        name,
+        space,
+        entry,
+        stack,
+        init_chan: child,
+        vdso_base: vdso_base.as_non_null_ptr(),
+    })
+}
+
+fn vdso() -> Phys {
+    static VDSO: Lazy<Phys> = Lazy::new(|| unsafe {
+        Phys::from_raw(svrt::take_startup_handle(HandleType::VdsoPhys.into()))
+    });
+    VDSO.clone()
+}
+
+fn allocate_stack(
+    root_virt: &Virt,
+    size: usize,
+    flags: Flags,
+) -> Result<NonNull<u8>, solvent::error::Error> {
+    unsafe {
+        let virt = root_virt.allocate(None, Virt::page_aligned(size + 2 * PAGE_SIZE))?;
+
+        let phys = Phys::allocate(size, Default::default())?;
+
+        let range = virt.map_phys(Some(PAGE_SIZE), phys, flags)?;
+
+        Ok(NonNull::new_unchecked(range.as_mut_ptr().add(range.len())))
+    }
+}
+
+fn append_environ(environ: &mut BTreeMap<String, String>, key: String, value: &str) {
+    match environ.entry(key) {
+        MapEntry::Vacant(ent) => {
+            ent.insert(value.to_string());
+        }
+        MapEntry::Occupied(mut ent) => {
+            ent.get_mut().push(',');
+            *ent.get_mut() += value
+        }
+    }
+}
+
+#[inline]
+fn pass_stack(stack: Option<(usize, Flags)>) -> (usize, Flags) {
+    let flags = Flags::READABLE | Flags::WRITABLE | Flags::USER_ACCESS;
+    stack.map_or((DEFAULT_STACK_SIZE, flags), |stack| {
+        (
+            NonZeroUsize::new(stack.0).map_or(DEFAULT_STACK_SIZE, |size| size.get()),
+            stack.1,
+        )
+    })
+}
+
+fn startup_args(
+    mut handles: BTreeMap<HandleInfo, Handle>,
+    local_fs: BTreeMap<PathBuf, EntrySyncClient>,
+    args: Vec<String>,
+    mut environ: BTreeMap<String, String>,
+    root_virt: Virt,
+) -> StartupArgs {
+    local_fs
+        .into_iter()
+        .enumerate()
+        .for_each(|(index, (path, entry))| {
+            let hinfo = HandleInfo::new()
+                .with_handle_type(HandleType::LocalFs)
+                .with_additional(index as u16);
+            let handle = Channel::into_raw(entry.try_into().unwrap());
+
+            append_environ(&mut environ, "LFS".into(), &path.to_string_lossy());
+            if let Some(old) = handles.insert(hinfo, handle) {
+                let _ = unsafe { drop_raw(old) };
+            }
+        });
+    handles.insert(HandleType::RootVirt.into(), Virt::into_raw(root_virt));
+    let args = args
+        .into_iter()
+        .flat_map(|arg| arg.into_bytes().into_iter().chain([0]))
+        .collect::<Vec<_>>();
+    let environ = environ
+        .into_iter()
+        .flat_map(|(key, value)| {
+            key.into_bytes()
+                .into_iter()
+                .chain([b'='])
+                .chain(value.into_bytes())
+                .chain([0])
+        })
+        .collect::<Vec<_>>();
+    StartupArgs {
+        handles,
+        args,
+        env: environ,
+    }
+}

+ 14 - 1
src/lib/h2o_rpc/core/src/packet.rs

@@ -1,5 +1,5 @@
 use alloc::{boxed::Box, collections::BTreeMap, ffi::CString, format, string::String, vec::Vec};
-use core::{array, iter, mem};
+use core::{array, iter, mem, ptr::NonNull};
 
 use solvent::{
     impl_obj_for,
@@ -248,6 +248,19 @@ impl<T: SerdePacket, E: SerdePacket> SerdePacket for Result<T, E> {
     }
 }
 
+impl SerdePacket for NonNull<u8> {
+    #[inline]
+    fn serialize(self, ser: &mut Serializer) -> Result<(), Error> {
+        usize::serialize(self.as_ptr() as _, ser)
+    }
+
+    fn deserialize(de: &mut Deserializer) -> Result<Self, Error> {
+        let value = usize::deserialize(de)?;
+        NonNull::new(value as *mut u8)
+            .ok_or_else(|| Error::TypeMismatch("The pointer is null".into()))
+    }
+}
+
 impl<T: SerdePacket> SerdePacket for Box<T> {
     #[inline]
     fn serialize(self, ser: &mut Serializer) -> Result<(), Error> {

+ 11 - 0
src/lib/h2o_rs/src/obj.rs

@@ -104,6 +104,17 @@ pub trait Object: private::Sealed + fmt::Debug {
     }
 }
 
+/// # Errors
+///
+/// This function will return an error if the handle is invalid.
+///
+/// # Safety
+///
+/// The caller must guarantee that the ownership is with the handle.
+pub unsafe fn drop_raw(handle: Handle) -> Result {
+    unsafe { sv_call::sv_obj_drop(handle) }.into_res()
+}
+
 #[macro_export]
 macro_rules! impl_obj {
     ($name:ident, $num:ident) => {

+ 14 - 8
src/lib/h2o_rs/src/task.rs

@@ -7,11 +7,8 @@ use core::{
     time::Duration,
 };
 
-use sv_call::{
-    ipc::SIG_READ,
-    task::{ctx::Gpr, *},
-    Error, Handle, SV_SUSPENDTOKEN, SV_TASK,
-};
+pub use sv_call::task::{ctx::Gpr, *};
+use sv_call::{ipc::SIG_READ, Error, Handle, SV_SUSPENDTOKEN, SV_TASK};
 
 use crate::{error::Result, ipc::Channel, mem::Space, obj::Object};
 
@@ -22,7 +19,11 @@ crate::impl_obj!(Task, SV_TASK);
 crate::impl_obj!(@DROP, Task);
 
 impl Task {
-    pub fn try_new(name: Option<&str>, space: Option<Space>) -> Result<(Self, SuspendToken)> {
+    pub fn try_new(
+        name: Option<&str>,
+        space: Option<Space>,
+        init_chan: Option<Channel>,
+    ) -> Result<(Self, SuspendToken)> {
         let name = name.map(|name| name.as_bytes());
         let mut st = Handle::NULL;
         let handle = unsafe {
@@ -30,6 +31,7 @@ impl Task {
                 name.map_or(null(), |name| name.as_ptr()),
                 name.map_or(0, |name| name.len()),
                 space.map_or(Handle::NULL, Space::into_raw),
+                init_chan.map_or(Handle::NULL, Channel::into_raw),
                 &mut st,
             )
             .into_res()?
@@ -38,8 +40,12 @@ impl Task {
         Ok(unsafe { (Self::from_raw(handle), SuspendToken::from_raw(st)) })
     }
 
-    pub fn new(name: Option<&str>, space: Option<Space>) -> (Self, SuspendToken) {
-        Self::try_new(name, space).expect("Failed to create a task")
+    pub fn new(
+        name: Option<&str>,
+        space: Option<Space>,
+        init_chan: Option<Channel>,
+    ) -> (Self, SuspendToken) {
+        Self::try_new(name, space, init_chan).expect("Failed to create a task")
     }
 
     pub fn exec(

+ 1 - 1
src/lib/svrt/src/sa.rs

@@ -70,7 +70,7 @@ impl Ord for HandleInfo {
 
 pub(crate) const STARTUP_ARGS: usize = 0x1873ddab8;
 
-#[derive(SerdePacket)]
+#[derive(SerdePacket, Default)]
 pub struct StartupArgs {
     pub handles: BTreeMap<HandleInfo, Handle>,
     pub args: Vec<u8>,