add syscall wait4 and waitid

This commit is contained in:
Jianfeng Jiang 2022-09-27 19:51:18 +08:00
parent 21290f1cff
commit e29bb58d45
24 changed files with 366 additions and 55 deletions

View File

@ -89,7 +89,7 @@ impl<P> Drop for RcuReclaimer<P> {
wq.wake_one();
}
});
wq.wait_until(|| true);
wq.wait_until(None::<u8>, || Some(0));
}
}

View File

@ -1,69 +1,123 @@
use alloc::collections::VecDeque;
use spin::mutex::Mutex;
use crate::{debug, task::Task};
/// A wait queue.
///
/// One may wait on a wait queue to put its executing thread to sleep.
/// Multiple threads may be the waiters of a wait queue.
/// Other threads may invoke the `wake`-family methods of a wait queue to
/// wake up one or many waiter threads.
pub struct WaitQueue {}
pub struct WaitQueue<D: Clone + Eq + PartialEq> {
waiters: Mutex<VecDeque<Waiter<D>>>,
}
impl WaitQueue {
impl<D: Clone + Eq + PartialEq> WaitQueue<D> {
/// Creates a new instance.
pub fn new() -> Self {
todo!()
WaitQueue {
waiters: Mutex::new(VecDeque::new()),
}
}
/// Wait until some condition becomes true.
///
/// This method takes a closure that tests a user-given condition.
/// The method only returns if the condition becomes true.
/// The method only returns if the condition returns Some(_).
/// A waker thread should first make the condition true, then invoke the
/// `wake`-family method. This ordering is important to ensure that waiter
/// threads do not lose any wakeup notifiations.
///
/// By taking a condition closure, this wait-wakeup mechanism becomes
/// more efficient and robust.
pub fn wait_until<F>(&self, mut cond: F)
pub fn wait_until<F, R>(&self, data: D, mut cond: F) -> R
where
F: FnMut() -> bool,
F: FnMut() -> Option<R>,
{
let waiter = Waiter::new();
let waiter = Waiter::new(data);
self.enqueue(&waiter);
loop {
if (cond)() {
if let Some(r) = cond() {
self.dequeue(&waiter);
break;
return r;
}
waiter.wait();
}
self.dequeue(&waiter);
}
/// Wake one waiter thread, if there is one.
pub fn wake_one(&self) {
todo!()
if let Some(waiter) = self.waiters.lock().front_mut() {
waiter.wake_up();
}
}
/// Wake all waiter threads.
pub fn wake_all(&self) {
todo!()
self.waiters.lock().iter_mut().for_each(|waiter| {
waiter.wake_up();
});
}
fn enqueue(&self, waiter: &Waiter) {
todo!()
/// Wake all waiters if given condition returns true.
/// The condition will check the data carried by waiter if it satisfy some relation with cond_data
pub fn wake_all_on_condition<F, C>(&self, cond_data: &C, cond: F)
where
F: Fn(&D, &C) -> bool,
{
self.waiters.lock().iter_mut().for_each(|waiter| {
if cond(waiter.data(), cond_data) {
waiter.wake_up()
}
})
}
fn dequeue(&self, waiter: &Waiter) {
todo!()
fn enqueue(&self, waiter: &Waiter<D>) {
self.waiters.lock().push_back(waiter.clone());
}
fn dequeue(&self, waiter: &Waiter<D>) {
let mut waiters_lock = self.waiters.lock();
let len = waiters_lock.len();
let mut index = 0;
for i in 0..len {
if waiters_lock[i] == *waiter {
index = i;
break;
}
}
waiters_lock.remove(index);
drop(waiters_lock);
}
}
struct Waiter {}
#[derive(Debug, Clone, PartialEq, Eq)]
struct Waiter<D: Clone + Eq + PartialEq> {
is_woken_up: bool,
data: D,
}
impl Waiter {
pub fn new() -> Self {
todo!()
impl<D: Clone + Eq + PartialEq> Waiter<D> {
pub fn new(data: D) -> Self {
Waiter {
is_woken_up: false,
data,
}
}
pub fn wait(&self) {
todo!()
while !self.is_woken_up {
// yield the execution, to allow other task to contine
debug!("Waiter: wait");
Task::yield_now();
}
}
pub fn wake_up(&mut self) {
self.is_woken_up = true;
}
pub fn data(&self) -> &D {
&self.data
}
}

View File

@ -55,9 +55,9 @@ pub unsafe trait Pod: Copy + Sized + Debug {
/// FIXME: use derive instead
#[macro_export]
macro_rules! impl_pod_for {
($($token:tt),*/* define the input */) => {
($($pod_ty:ty),*/* define the input */) => {
/* define the expansion */
$(unsafe impl Pod for $token {})*
$(unsafe impl Pod for $pod_ty {})*
};
}

View File

@ -5,6 +5,9 @@
#![allow(unused_variables)]
#![feature(const_btree_new)]
#![feature(cstr_from_bytes_until_nul)]
#![feature(half_open_range_patterns)]
#![feature(exclusive_range_pattern)]
#![feature(btree_drain_filter)]
use alloc::ffi::CString;
use kxos_frame::{debug, info, println};

View File

@ -58,3 +58,12 @@ pub fn write_bytes_to_user(dest: Vaddr, src: &[u8]) {
.expect("[Internal error]Current should have vm space to write bytes to user");
vm_space.write_bytes(dest, src).expect("write bytes failed")
}
/// write val (Plain of Data type) to user space of current process.
pub fn write_val_to_user<T: Pod>(dest: Vaddr, val: T) {
let current = Process::current();
let vm_space = current
.vm_space()
.expect("[Internal error]Current should have vm space to write val to user");
vm_space.write_val(dest, &val).expect("write val failed");
}

View File

@ -1,38 +1,43 @@
use core::sync::atomic::{AtomicI32, AtomicUsize, Ordering};
use alloc::collections::BTreeMap;
use alloc::ffi::CString;
use alloc::{
sync::{Arc, Weak},
vec::Vec,
};
use alloc::sync::{Arc, Weak};
use kxos_frame::sync::WaitQueue;
use kxos_frame::{debug, task::Task, user::UserSpace, vm::VmSpace};
use spin::Mutex;
use crate::memory::mmap_area::MmapArea;
use crate::memory::user_heap::UserHeap;
use self::process_filter::ProcessFilter;
use self::status::ProcessStatus;
use self::task::create_user_task_from_elf;
use self::user_vm_data::UserVm;
pub mod fifo_scheduler;
pub mod process_filter;
pub mod status;
pub mod task;
pub mod user_vm_data;
pub mod wait;
static PID_ALLOCATOR: AtomicUsize = AtomicUsize::new(0);
const CHILDREN_CAPACITY: usize = 16;
pub type Pid = usize;
pub type Pgid = usize;
pub type ExitCode = i32;
/// Process stands for a set of tasks that shares the same userspace.
/// Currently, we only support one task inside a process.
pub struct Process {
// Immutable Part
pid: usize,
pid: Pid,
task: Arc<Task>,
filename: Option<CString>,
user_space: Option<Arc<UserSpace>>,
user_vm: Option<UserVm>,
waiting_children: WaitQueue<ProcessFilter>,
// Mutable Part
/// The exit code
@ -42,7 +47,7 @@ pub struct Process {
/// Parent process
parent: Mutex<Option<Weak<Process>>>,
/// Children processes
children: Mutex<Vec<Arc<Process>>>,
children: Mutex<BTreeMap<usize, Arc<Process>>>,
}
impl Process {
@ -52,7 +57,7 @@ impl Process {
let process = task
.data()
.downcast_ref::<Weak<Process>>()
.expect("[Internal Error] Task data should points to weak<process>");
.expect("[Internal Error] task data should points to weak<process>");
process
.upgrade()
.expect("[Internal Error] current process cannot be None")
@ -60,7 +65,7 @@ impl Process {
/// create a new process(not schedule it)
pub fn new(
pid: usize,
pid: Pid,
task: Arc<Task>,
exec_filename: Option<CString>,
user_vm: Option<UserVm>,
@ -74,13 +79,15 @@ impl Process {
let current_process = Process::current();
Some(Arc::downgrade(&current_process))
};
let children = Vec::with_capacity(CHILDREN_CAPACITY);
let children = BTreeMap::new();
let waiting_children = WaitQueue::new();
Self {
pid,
task,
filename: exec_filename,
user_space,
user_vm,
waiting_children,
exit_code: AtomicI32::new(0),
status: Mutex::new(ProcessStatus::Runnable),
parent: Mutex::new(parent),
@ -88,6 +95,10 @@ impl Process {
}
}
pub fn waiting_children(&self) -> &WaitQueue<ProcessFilter> {
&self.waiting_children
}
/// init a user process and send the process to scheduler
pub fn spawn_user_process(filename: CString, elf_file_content: &'static [u8]) -> Arc<Self> {
let process = Process::create_user_process(filename, elf_file_content);
@ -130,21 +141,35 @@ impl Process {
})
}
/// returns the pid
pub fn pid(&self) -> usize {
/// returns the pid of the process
pub fn pid(&self) -> Pid {
self.pid
}
/// returns the process group id of the process
pub fn pgid(&self) -> Pgid {
todo!()
}
/// add a child process
pub fn add_child(&self, child: Arc<Process>) {
debug!("process: {}, add child: {} ", self.pid(), child.pid());
self.children.lock().push(child);
let child_pid = child.pid();
self.children.lock().insert(child_pid, child);
}
fn set_parent(&self, parent: Weak<Process>) {
let _ = self.parent.lock().insert(parent);
}
fn parent(&self) -> Option<Arc<Process>> {
self.parent
.lock()
.as_ref()
.map(|parent| parent.upgrade())
.flatten()
}
/// Set the exit code when calling exit or exit_group
pub fn set_exit_code(&self, exit_code: i32) {
self.exit_code.store(exit_code, Ordering::Relaxed);
@ -153,17 +178,27 @@ impl Process {
/// Exit current process
/// Set the status of current process as Zombie
/// Move all children to init process
/// Wake up the parent wait queue if parent is waiting for self
pub fn exit(&self) {
self.status.lock().set_zombie();
// move children to the init process
let current_process = Process::current();
if !current_process.is_init_process() {
let init_process = get_init_process();
for child in self.children.lock().drain(..) {
child.set_parent(Arc::downgrade(&init_process));
init_process.add_child(child);
for (_, child_process) in self.children.lock().drain_filter(|_, _| true) {
child_process.set_parent(Arc::downgrade(&init_process));
init_process.add_child(child_process);
}
}
// wake up parent waiting children, if any
if let Some(parent) = current_process.parent() {
parent
.waiting_children()
.wake_all_on_condition(&current_process.pid(), |filter, pid| {
filter.contains_pid(*pid)
});
}
}
/// if the current process is init process
@ -215,6 +250,38 @@ impl Process {
}
}
/// Get child process with given pid
pub fn get_child_by_pid(&self, pid: Pid) -> Option<Arc<Process>> {
for (child_pid, child_process) in self.children.lock().iter() {
if *child_pid == pid {
return Some(child_process.clone());
}
}
None
}
/// free zombie child with pid, returns the exit code of child process
/// We current just remove the child from the children map.
pub fn reap_zombie_child(&self, pid: Pid) -> i32 {
let child_process = self.children.lock().remove(&pid).unwrap();
assert!(child_process.status() == ProcessStatus::Zombie);
child_process.exit_code()
}
/// Get any zombie child
pub fn get_zombie_child(&self) -> Option<Arc<Process>> {
for (_, child_process) in self.children.lock().iter() {
if child_process.status().is_zombie() {
return Some(child_process.clone());
}
}
None
}
pub fn exit_code(&self) -> i32 {
self.exit_code.load(Ordering::Relaxed)
}
/// whether the process has child process
pub fn has_child(&self) -> bool {
self.children.lock().len() != 0
@ -223,6 +290,10 @@ impl Process {
pub fn filename(&self) -> Option<&CString> {
self.filename.as_ref()
}
pub fn status(&self) -> ProcessStatus {
self.status.lock().clone()
}
}
/// Get the init process
@ -242,6 +313,6 @@ pub fn get_init_process() -> Arc<Process> {
}
/// allocate a new pid for new process
pub fn new_pid() -> usize {
pub fn new_pid() -> Pid {
PID_ALLOCATOR.fetch_add(1, Ordering::Release)
}

View File

@ -0,0 +1,49 @@
use super::{Pgid, Pid, Process};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProcessFilter {
Any,
WithPid(Pid),
WithPgid(Pgid),
}
impl ProcessFilter {
// used for waitid
pub fn from_which_and_id(which: u64, id: u64) -> Self {
// Does not support PID_FD now(which = 3)
// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/wait.h#L20
match which {
0 => ProcessFilter::Any,
1 => ProcessFilter::WithPid(id as Pid),
2 => ProcessFilter::WithPgid(id as Pgid),
_ => panic!("Unknown id type"),
}
}
// used for wait4
pub fn from_wait_pid(wait_pid: isize) -> Self {
// https://man7.org/linux/man-pages/man2/waitpid.2.html
if wait_pid < -1 {
// process group ID is equal to the absolute value of pid.
ProcessFilter::WithPgid((-wait_pid) as Pgid)
} else if wait_pid == -1 {
// wait for any child process
ProcessFilter::Any
} else if wait_pid == 0 {
// wait for any child process with same process group ID
let pgid = Process::current().pgid();
ProcessFilter::WithPgid(pgid)
} else {
// pid > 0. wait for the child whose process ID is equal to the value of pid.
ProcessFilter::WithPid(wait_pid as Pid)
}
}
pub fn contains_pid(&self, pid: Pid) -> bool {
match self {
ProcessFilter::Any => true,
ProcessFilter::WithPid(filter_pid) => *filter_pid == pid,
ProcessFilter::WithPgid(_) => todo!(),
}
}
}

View File

@ -1,4 +1,6 @@
#[derive(Debug, Clone, Copy)]
//! The process status
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProcessStatus {
Runnable,
Zombie,
@ -8,4 +10,8 @@ impl ProcessStatus {
pub fn set_zombie(&mut self) {
*self = ProcessStatus::Zombie;
}
pub fn is_zombie(&self) -> bool {
*self == ProcessStatus::Zombie
}
}

View File

@ -0,0 +1,58 @@
use bitflags::bitflags;
use super::{process_filter::ProcessFilter, ExitCode, Pid, Process};
// The definition of WaitOptions is from Occlum
bitflags! {
pub struct WaitOptions: u32 {
const WNOHANG = 0x1;
//Note: Below flags are not supported yet
const WSTOPPED = 0x2; // Same as WUNTRACED
const WEXITED = 0x4;
const WCONTINUED = 0x8;
const WNOWAIT = 0x01000000;
}
}
impl WaitOptions {
pub fn supported(&self) -> bool {
let unsupported_flags = WaitOptions::all() - WaitOptions::WNOHANG;
!self.intersects(unsupported_flags)
}
}
pub fn wait_child_exit(
process_filter: ProcessFilter,
wait_options: WaitOptions,
) -> (Pid, ExitCode) {
let current = Process::current();
let (pid, exit_code) = current.waiting_children().wait_until(process_filter, || {
let waited_child_process = match process_filter {
ProcessFilter::Any => current.get_zombie_child(),
ProcessFilter::WithPid(pid) => current.get_child_by_pid(pid as Pid),
ProcessFilter::WithPgid(pgid) => todo!(),
};
// some child process is exited
if let Some(waited_child_process) = waited_child_process {
let wait_pid = waited_child_process.pid();
let exit_code = waited_child_process.exit_code();
if wait_options.contains(WaitOptions::WNOWAIT) {
// does not reap child, directly return
return Some((wait_pid, exit_code));
} else {
let exit_code = current.reap_zombie_child(wait_pid);
return Some((wait_pid, exit_code));
}
}
if wait_options.contains(WaitOptions::WNOHANG) {
return Some((0, 0));
}
None
});
(pid, exit_code)
}

View File

@ -31,7 +31,7 @@ pub fn sys_arch_prctl(code: u64, addr: u64, context: &mut CpuContext) -> Syscall
Err(_) => SyscallResult::Return(-1),
Ok(code) => {
let res = do_arch_prctl(code, addr, context).unwrap();
SyscallResult::Return(res as i32)
SyscallResult::Return(res as _)
}
}
}

View File

@ -23,5 +23,5 @@ pub fn sys_brk(heap_end: u64) -> SyscallResult {
.expect("brk should work on process with user space");
let new_heap_end = user_heap.brk(new_heap_end, vm_space);
SyscallResult::Return(new_heap_end as i32)
SyscallResult::Return(new_heap_end as _)
}

View File

@ -7,6 +7,6 @@ use crate::{
pub fn sys_exit_group(exit_code: u64) -> SyscallResult {
debug!("[syscall][id={}][SYS_EXIT_GROUP]", SYS_EXIT_GROUP);
Process::current().set_exit_code(exit_code as i32);
SyscallResult::Exit(exit_code as i32)
Process::current().set_exit_code(exit_code as _);
SyscallResult::Exit(exit_code as _)
}

View File

@ -16,7 +16,7 @@ use super::SyscallResult;
pub fn sys_fork(parent_context: CpuContext) -> SyscallResult {
debug!("[syscall][id={}][SYS_FORK]", SYS_FORK);
let child_process = fork(parent_context);
SyscallResult::Return(child_process.pid() as i32)
SyscallResult::Return(child_process.pid() as _)
}
/// Fork a child process

View File

@ -8,5 +8,5 @@ pub fn sys_getpid() -> SyscallResult {
debug!("[syscall][id={}][SYS_GETPID]", SYS_GETPID);
let pid = Process::current().pid();
info!("[sys_getpid]: pid = {}", pid);
SyscallResult::Return(pid as i32)
SyscallResult::Return(pid as _)
}

View File

@ -8,5 +8,5 @@ pub fn sys_gettid() -> SyscallResult {
debug!("[syscall][id={}][SYS_GETTID]", SYS_GETTID);
// For single-thread process, tid is equal to pid
let tid = Process::current().pid();
SyscallResult::Return(tid as i32)
SyscallResult::Return(tid as _)
}

View File

@ -61,7 +61,7 @@ pub fn sys_mmap(
fd as usize,
offset as usize,
);
SyscallResult::Return(res as i32)
SyscallResult::Return(res as _)
}
pub fn do_sys_mmap(

View File

@ -19,6 +19,8 @@ use crate::syscall::mprotect::sys_mprotect;
use crate::syscall::readlink::sys_readlink;
use crate::syscall::tgkill::sys_tgkill;
use crate::syscall::uname::sys_uname;
use crate::syscall::wait4::sys_wait4;
use crate::syscall::waitid::sys_waitid;
use crate::syscall::write::sys_write;
use crate::syscall::writev::sys_writev;
@ -36,6 +38,8 @@ mod readlink;
mod sched_yield;
mod tgkill;
mod uname;
mod wait4;
mod waitid;
mod write;
mod writev;
@ -51,6 +55,7 @@ const SYS_SCHED_YIELD: u64 = 24;
const SYS_GETPID: u64 = 39;
const SYS_FORK: u64 = 57;
const SYS_EXIT: u64 = 60;
const SYS_WAIT4: u64 = 61;
const SYS_UNAME: u64 = 63;
const SYS_READLINK: u64 = 89;
const SYS_GETUID: u64 = 102;
@ -61,6 +66,7 @@ const SYS_ARCH_PRCTL: u64 = 158;
const SYS_GETTID: u64 = 186;
const SYS_EXIT_GROUP: u64 = 231;
const SYS_TGKILL: u64 = 234;
const SYS_WAITID: u64 = 247;
pub struct SyscallArgument {
syscall_number: u64,
@ -121,6 +127,7 @@ pub fn syscall_dispatch(
SYS_GETPID => sys_getpid(),
SYS_FORK => sys_fork(context.to_owned()),
SYS_EXIT => sys_exit(args[0] as _),
SYS_WAIT4 => sys_wait4(args[0], args[1], args[2]),
SYS_UNAME => sys_uname(args[0]),
SYS_READLINK => sys_readlink(args[0], args[1], args[2]),
SYS_GETUID => sys_getuid(),
@ -131,6 +138,7 @@ pub fn syscall_dispatch(
SYS_GETTID => sys_gettid(),
SYS_EXIT_GROUP => sys_exit_group(args[0]),
SYS_TGKILL => sys_tgkill(args[0], args[1], args[2]),
SYS_WAITID => sys_waitid(args[0], args[1], args[2], args[3], args[4]),
_ => panic!("Unsupported syscall number: {}", syscall_number),
}
}

View File

@ -20,7 +20,7 @@ pub fn sys_readlink(filename_ptr: u64, user_buf_ptr: u64, user_buf_len: u64) ->
user_buf_ptr as Vaddr,
user_buf_len as usize,
);
SyscallResult::Return(res as i32)
SyscallResult::Return(res as _)
}
/// do sys readlink

View File

@ -0,0 +1,24 @@
use crate::{
memory::write_val_to_user,
process::{process_filter::ProcessFilter, wait::wait_child_exit},
syscall::SYS_WAIT4,
};
use super::SyscallResult;
use crate::process::wait::WaitOptions;
use kxos_frame::debug;
pub fn sys_wait4(wait_pid: u64, exit_status_ptr: u64, wait_options: u64) -> SyscallResult {
debug!("[syscall][id={}][SYS_WAIT4]", SYS_WAIT4);
let wait_options = WaitOptions::from_bits(wait_options as u32).expect("Unknown wait options");
debug!("pid = {}", wait_pid as isize);
debug!("exit_status_ptr = {}", exit_status_ptr);
debug!("wait_options: {:?}", wait_options);
let process_filter = ProcessFilter::from_wait_pid(wait_pid as _);
let (return_pid, exit_code) = wait_child_exit(process_filter, wait_options);
if return_pid != 0 && exit_status_ptr != 0 {
write_val_to_user(exit_status_ptr as _, exit_code);
}
SyscallResult::Return(return_pid as _)
}

View File

@ -0,0 +1,18 @@
use crate::process::{process_filter::ProcessFilter, wait::wait_child_exit};
use super::SyscallResult;
use crate::process::wait::WaitOptions;
pub fn sys_waitid(
which: u64,
upid: u64,
infoq_addr: u64,
options: u64,
rusage_addr: u64,
) -> SyscallResult {
// FIXME: what does infoq and rusage use for?
let process_filter = ProcessFilter::from_which_and_id(which, upid);
let wait_options = WaitOptions::from_bits(options as u32).expect("Unknown wait options");
let (exit_code, pid) = wait_child_exit(process_filter, wait_options);
SyscallResult::Return(pid)
}

View File

@ -22,7 +22,7 @@ pub fn sys_write(fd: u64, user_buf_ptr: u64, user_buf_len: u64) -> SyscallResult
info!("Error message from user mode: {:?}", content);
}
SyscallResult::Return(user_buf_len as i32)
SyscallResult::Return(user_buf_len as _)
} else {
panic!("Unsupported fd number {}", fd);
}

View File

@ -18,7 +18,7 @@ pub struct IoVec {
pub fn sys_writev(fd: u64, io_vec_addr: u64, io_vec_count: u64) -> SyscallResult {
debug!("[syscall][id={}][SYS_WRITEV]", SYS_WRITEV);
let res = do_sys_writev(fd, io_vec_addr as Vaddr, io_vec_count as usize);
SyscallResult::Return(res as i32)
SyscallResult::Return(res as _)
}
pub fn do_sys_writev(fd: u64, io_vec_addr: Vaddr, io_vec_count: usize) -> usize {

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6d1d5aa2ef1105eb716219002763836a47e95cef22997064f6f60bfd4c7a43de
size 9528
oid sha256:cea612414dc19fcd03b563607ea9a453a3d3390b9f3b229ef8e56b08e4d4c8c5
size 9592

View File

@ -10,6 +10,7 @@ _start:
je _child # child process
jmp _parent # parent process
_parent:
call wait_child
call get_pid
call print_parent_message
call exit
@ -17,6 +18,16 @@ _child:
call get_pid
call print_child_message
call exit
wait_child:
mov %rax, %rdi # child process id
_loop:
mov $61, %rax # syscall number of wait4
mov $0, %rsi # exit status address
mov $1, %rdx # WNOHANG
syscall
cmp %rdi, %rax # The return value is the pid of child
jne _loop
ret
exit:
mov $60, %rax # syscall number of exit
mov $0, %rdi # exit code