diff --git a/src/libos/src/process/do_exec.rs b/src/libos/src/process/do_exec.rs index e177c50c..6dc7f0f2 100644 --- a/src/libos/src/process/do_exec.rs +++ b/src/libos/src/process/do_exec.rs @@ -66,7 +66,7 @@ pub fn do_exec( table::add_thread(new_process_ref.main_thread().unwrap()); table::add_process(new_process_ref); task::enqueue_and_exec(new_main_thread); - return vfork_return_to_parent(context, current_ref); + return vfork_return_to_parent(context, current_ref, None); } // Force exit all child threads of current process diff --git a/src/libos/src/process/do_exit.rs b/src/libos/src/process/do_exit.rs index 4db94c1a..fa23a458 100644 --- a/src/libos/src/process/do_exit.rs +++ b/src/libos/src/process/do_exit.rs @@ -1,3 +1,4 @@ +use crate::process::do_vfork::reap_zombie_child_created_with_vfork; use crate::signal::constants::*; use std::intrinsics::atomic_store; @@ -15,7 +16,8 @@ use crate::vm::USER_SPACE_VM_MANAGER; pub fn do_exit_group(status: i32, curr_user_ctxt: &mut CpuContext) -> Result { if is_vforked_child_process() { let current = current!(); - return vfork_return_to_parent(curr_user_ctxt as *mut _, ¤t); + let child_exit_status = TermStatus::Exited(status as u8); + return vfork_return_to_parent(curr_user_ctxt as *mut _, ¤t, Some(child_exit_status)); } else { let term_status = TermStatus::Exited(status as u8); current!().process().force_exit(term_status); @@ -89,6 +91,7 @@ fn exit_thread(term_status: TermStatus) { fn exit_process(thread: &ThreadRef, term_status: TermStatus) { let process = thread.process(); + let pid = process.pid(); // Deadlock note: always lock parent first, then child. @@ -123,13 +126,16 @@ fn exit_process(thread: &ThreadRef, term_status: TermStatus) { if parent_inner.is_none() { debug_assert!(parent.pid() == 0); - let pid = process.pid(); let main_tid = pid; table::del_thread(main_tid).expect("tid must be in the table"); table::del_process(pid).expect("pid must be in the table"); clean_pgrp_when_exit(process); process_inner.exit(term_status, &idle_ref, &mut idle_inner); + + // For vfork-and-exit children, just clean them to free the pid + let _ = reap_zombie_child_created_with_vfork(pid); + idle_inner.remove_zombie_child(pid); return; } @@ -138,6 +144,9 @@ fn exit_process(thread: &ThreadRef, term_status: TermStatus) { process_inner.exit(term_status, &idle_ref, &mut idle_inner); + // For vfork-and-exit children, just clean them to free the pid + let _ = reap_zombie_child_created_with_vfork(pid); + //Send SIGCHLD to parent send_sigchld_to(&parent); @@ -225,6 +234,9 @@ fn exit_process_for_execve( // Let new_process to adopt the children of current process process_inner.exit(term_status, &new_parent_ref, &mut new_parent_inner); + // For vfork-and-exit children, we don't need to adopt them here. + // Because the new parent process share the same pid with the old parent process. + // Remove current process from parent process' zombie list. if parent_inner.is_none() { debug_assert!(parent.pid() == 0); diff --git a/src/libos/src/process/do_vfork.rs b/src/libos/src/process/do_vfork.rs index b369c97e..1b6a60c9 100644 --- a/src/libos/src/process/do_vfork.rs +++ b/src/libos/src/process/do_vfork.rs @@ -1,5 +1,5 @@ use super::untrusted_event::{set_event, wait_event}; -use super::{ProcessRef, ThreadId, ThreadRef}; +use super::{ProcessFilter, ProcessRef, TermStatus, ThreadId, ThreadRef}; use crate::fs::FileTable; use crate::interrupt::broadcast_interrupts; use crate::prelude::*; @@ -24,10 +24,21 @@ use std::mem; // use vfork to replace fork. For multi-threaded applications, if vfork doesn't stop other child threads, the application // will be more likely to fail because the child process directly uses the VM and the file table of the parent process. +// The exit status of the child process which directly calls exit after vfork. +struct ChildExitStatus { + pid: pid_t, + status: TermStatus, +} + lazy_static! { // Store all the parents's file tables who call vfork. It will be recovered when the child exits or has its own task. // K: parent pid, V: parent file table static ref VFORK_PARENT_FILE_TABLES: SgxMutex> = SgxMutex::new(HashMap::new()); + // Store all the child process's exit status which are created with vfork and directly exit without calling execve. Because + // these children process are only allocated with a pid, they are not managed by the usual way including exit and wait. Use + // this special structure to record these children. + // K: parent pid, V: exit children created with vfork. + static ref EXIT_CHILDREN_STATUS: SgxMutex>> = SgxMutex::new(HashMap::new()); } thread_local! { @@ -94,9 +105,14 @@ pub fn is_vforked_child_process() -> bool { pub fn vfork_return_to_parent( mut context: *mut CpuContext, current_ref: &ThreadRef, + child_exit_status: Option, // If the child process exits, the exit status should be specified. ) -> Result { let child_pid = restore_parent_process(context, current_ref)?; + if let Some(term_status) = child_exit_status { + record_exit_child(current_ref.process().pid(), child_pid as pid_t, term_status); + } + // Wake parent's child thread which are all sleeping let current = current!(); let child_threads = current.process().threads(); @@ -112,6 +128,17 @@ pub fn vfork_return_to_parent( Ok(child_pid) } +fn record_exit_child(parent_pid: pid_t, child_pid: pid_t, child_exit_status: TermStatus) { + let child_exit_status = ChildExitStatus::new(child_pid, child_exit_status); + + let mut children_status = EXIT_CHILDREN_STATUS.lock().unwrap(); + if let Some(children) = children_status.get_mut(&parent_pid) { + children.push(child_exit_status); + } else { + children_status.insert(parent_pid, vec![child_exit_status]); + } +} + fn restore_parent_process(mut context: *mut CpuContext, current_ref: &ThreadRef) -> Result { let current_thread = current!(); let current_pid = current_ref.process().pid(); @@ -201,3 +228,64 @@ pub fn handle_force_stop() { } } } + +// Wait4 unwaited child which are created with vfork and directly exit without calling execve. +pub fn wait4_exit_child_created_with_vfork( + parent_pid: pid_t, + child_filter: &ProcessFilter, +) -> Option<(pid_t, i32)> { + let mut children_status = EXIT_CHILDREN_STATUS.lock().unwrap(); + if let Some(children) = children_status.get_mut(&parent_pid) { + let unwaited_child_idx = children.iter().position(|child| match child_filter { + ProcessFilter::WithAnyPid => true, + ProcessFilter::WithPid(pid) => pid == child.pid(), + ProcessFilter::WithPgid(pgid) => todo!(), // This case should be rare. + }); + + if let Some(child_idx) = unwaited_child_idx { + let child = children.remove(child_idx); + if children.is_empty() { + children_status.remove(&parent_pid); + } + return Some((*child.pid(), child.status().as_u32() as i32)); + } + } + + None +} + +// Reap all unwaited child which are created with vfork and directly exit without calling execve. +pub fn reap_zombie_child_created_with_vfork(parent_pid: pid_t) -> Option> { + let mut children_status = EXIT_CHILDREN_STATUS.lock().unwrap(); + + let children = children_status.remove(&parent_pid); + if children.is_none() { + warn!("no vforked children found"); + return None; + } + + Some( + children + .unwrap() + .into_iter() + .map(|child| child.pid) + .collect(), + ) +} + +impl ChildExitStatus { + fn new(child_pid: pid_t, status: TermStatus) -> Self { + Self { + pid: child_pid, + status, + } + } + + fn pid(&self) -> &pid_t { + &self.pid + } + + fn status(&self) -> &TermStatus { + &self.status + } +} diff --git a/src/libos/src/process/do_wait4.rs b/src/libos/src/process/do_wait4.rs index fbd7677a..d706a1b6 100644 --- a/src/libos/src/process/do_wait4.rs +++ b/src/libos/src/process/do_wait4.rs @@ -1,3 +1,4 @@ +use super::do_vfork::wait4_exit_child_created_with_vfork; use super::pgrp::clean_pgrp_when_exit; use super::process::{ProcessFilter, ProcessInner}; use super::wait::Waiter; @@ -53,10 +54,6 @@ pub fn do_wait4(child_filter: &ProcessFilter, options: WaitOptions) -> Result<(p }) .collect::>(); - if unwaited_children.len() == 0 { - return_errno!(ECHILD, "Cannot find any unwaited children"); - } - // Return immediately if a child that we wait for has already exited let zombie_child = unwaited_children .iter() @@ -67,6 +64,14 @@ pub fn do_wait4(child_filter: &ProcessFilter, options: WaitOptions) -> Result<(p return Ok((zombie_pid, exit_status)); } + // Check again for vfork-and-exit child process which doesn't have a real structure of a process + if let Some(child_status) = wait4_exit_child_created_with_vfork(process.pid(), child_filter) { + return Ok(child_status); + } else if unwaited_children.len() == 0 { + // No unwaited children or vforked children, return immediately + return_errno!(ECHILD, "Cannot find any unwaited children"); + } + // TODO: Support these options if !options.supported() { warn!("Unsupported options contained. wait options: {:?}", options); diff --git a/test/vfork/main.c b/test/vfork/main.c index 9943daa2..f2fa786b 100644 --- a/test/vfork/main.c +++ b/test/vfork/main.c @@ -9,12 +9,29 @@ // Note: This test intends to test the case that child process directly calls _exit() // after vfork. "exit", "_exit" and returning from main function are different. // And here the exit function must be "_exit" to prevent undefined bevaviour. -int test_vfork_exit() { +int test_vfork_exit_and_wait() { + int status = 0; pid_t child_pid = vfork(); if (child_pid == 0) { _exit(0); } else { printf ("Comming back to parent process from child with pid = %d\n", child_pid); + + // vfork again + pid_t child_pid_2 = vfork(); + if (child_pid_2 == 0) { + _exit(1); + } else { + printf ("Comming back to parent process from child with pid = %d\n", child_pid_2); + int ret = waitpid(child_pid, &status, WUNTRACED); + if (ret != child_pid || !WIFEXITED(status) || WEXITSTATUS(status) != 0) { + THROW_ERROR("wait child status error"); + } + ret = waitpid(child_pid_2, &status, WUNTRACED); + if (ret != child_pid_2 || !WIFEXITED(status) || WEXITSTATUS(status) != 1) { + THROW_ERROR("wait child status error"); + } + } } return 0; } @@ -167,7 +184,7 @@ int test_vfork_stop_child_thread() { } static test_case_t test_cases[] = { - TEST_CASE(test_vfork_exit), + TEST_CASE(test_vfork_exit_and_wait), TEST_CASE(test_multiple_vfork_execve), TEST_CASE(test_vfork_isolate_file_table), TEST_CASE(test_vfork_stop_child_thread),