Fix wait4 failure of child process created with vfork
This commit is contained in:
parent
0ba7f80b21
commit
0b0fed947c
@ -66,7 +66,7 @@ pub fn do_exec(
|
|||||||
table::add_thread(new_process_ref.main_thread().unwrap());
|
table::add_thread(new_process_ref.main_thread().unwrap());
|
||||||
table::add_process(new_process_ref);
|
table::add_process(new_process_ref);
|
||||||
task::enqueue_and_exec(new_main_thread);
|
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
|
// Force exit all child threads of current process
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::process::do_vfork::reap_zombie_child_created_with_vfork;
|
||||||
use crate::signal::constants::*;
|
use crate::signal::constants::*;
|
||||||
use std::intrinsics::atomic_store;
|
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<isize> {
|
pub fn do_exit_group(status: i32, curr_user_ctxt: &mut CpuContext) -> Result<isize> {
|
||||||
if is_vforked_child_process() {
|
if is_vforked_child_process() {
|
||||||
let current = current!();
|
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 {
|
} else {
|
||||||
let term_status = TermStatus::Exited(status as u8);
|
let term_status = TermStatus::Exited(status as u8);
|
||||||
current!().process().force_exit(term_status);
|
current!().process().force_exit(term_status);
|
||||||
@ -89,6 +91,7 @@ fn exit_thread(term_status: TermStatus) {
|
|||||||
|
|
||||||
fn exit_process(thread: &ThreadRef, term_status: TermStatus) {
|
fn exit_process(thread: &ThreadRef, term_status: TermStatus) {
|
||||||
let process = thread.process();
|
let process = thread.process();
|
||||||
|
let pid = process.pid();
|
||||||
|
|
||||||
// Deadlock note: always lock parent first, then child.
|
// 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() {
|
if parent_inner.is_none() {
|
||||||
debug_assert!(parent.pid() == 0);
|
debug_assert!(parent.pid() == 0);
|
||||||
|
|
||||||
let pid = process.pid();
|
|
||||||
let main_tid = pid;
|
let main_tid = pid;
|
||||||
table::del_thread(main_tid).expect("tid must be in the table");
|
table::del_thread(main_tid).expect("tid must be in the table");
|
||||||
table::del_process(pid).expect("pid must be in the table");
|
table::del_process(pid).expect("pid must be in the table");
|
||||||
clean_pgrp_when_exit(process);
|
clean_pgrp_when_exit(process);
|
||||||
|
|
||||||
process_inner.exit(term_status, &idle_ref, &mut idle_inner);
|
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);
|
idle_inner.remove_zombie_child(pid);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -138,6 +144,9 @@ fn exit_process(thread: &ThreadRef, term_status: TermStatus) {
|
|||||||
|
|
||||||
process_inner.exit(term_status, &idle_ref, &mut idle_inner);
|
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
|
||||||
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
|
// Let new_process to adopt the children of current process
|
||||||
process_inner.exit(term_status, &new_parent_ref, &mut new_parent_inner);
|
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.
|
// Remove current process from parent process' zombie list.
|
||||||
if parent_inner.is_none() {
|
if parent_inner.is_none() {
|
||||||
debug_assert!(parent.pid() == 0);
|
debug_assert!(parent.pid() == 0);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use super::untrusted_event::{set_event, wait_event};
|
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::fs::FileTable;
|
||||||
use crate::interrupt::broadcast_interrupts;
|
use crate::interrupt::broadcast_interrupts;
|
||||||
use crate::prelude::*;
|
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
|
// 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.
|
// 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! {
|
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.
|
// 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
|
// K: parent pid, V: parent file table
|
||||||
static ref VFORK_PARENT_FILE_TABLES: SgxMutex<HashMap<pid_t, FileTable>> = SgxMutex::new(HashMap::new());
|
static ref VFORK_PARENT_FILE_TABLES: SgxMutex<HashMap<pid_t, FileTable>> = 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<HashMap<pid_t, Vec<ChildExitStatus>>> = SgxMutex::new(HashMap::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
@ -94,9 +105,14 @@ pub fn is_vforked_child_process() -> bool {
|
|||||||
pub fn vfork_return_to_parent(
|
pub fn vfork_return_to_parent(
|
||||||
mut context: *mut CpuContext,
|
mut context: *mut CpuContext,
|
||||||
current_ref: &ThreadRef,
|
current_ref: &ThreadRef,
|
||||||
|
child_exit_status: Option<TermStatus>, // If the child process exits, the exit status should be specified.
|
||||||
) -> Result<isize> {
|
) -> Result<isize> {
|
||||||
let child_pid = restore_parent_process(context, current_ref)?;
|
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
|
// Wake parent's child thread which are all sleeping
|
||||||
let current = current!();
|
let current = current!();
|
||||||
let child_threads = current.process().threads();
|
let child_threads = current.process().threads();
|
||||||
@ -112,6 +128,17 @@ pub fn vfork_return_to_parent(
|
|||||||
Ok(child_pid)
|
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<isize> {
|
fn restore_parent_process(mut context: *mut CpuContext, current_ref: &ThreadRef) -> Result<isize> {
|
||||||
let current_thread = current!();
|
let current_thread = current!();
|
||||||
let current_pid = current_ref.process().pid();
|
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<Vec<pid_t>> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use super::do_vfork::wait4_exit_child_created_with_vfork;
|
||||||
use super::pgrp::clean_pgrp_when_exit;
|
use super::pgrp::clean_pgrp_when_exit;
|
||||||
use super::process::{ProcessFilter, ProcessInner};
|
use super::process::{ProcessFilter, ProcessInner};
|
||||||
use super::wait::Waiter;
|
use super::wait::Waiter;
|
||||||
@ -53,10 +54,6 @@ pub fn do_wait4(child_filter: &ProcessFilter, options: WaitOptions) -> Result<(p
|
|||||||
})
|
})
|
||||||
.collect::<Vec<&ProcessRef>>();
|
.collect::<Vec<&ProcessRef>>();
|
||||||
|
|
||||||
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
|
// Return immediately if a child that we wait for has already exited
|
||||||
let zombie_child = unwaited_children
|
let zombie_child = unwaited_children
|
||||||
.iter()
|
.iter()
|
||||||
@ -67,6 +64,14 @@ pub fn do_wait4(child_filter: &ProcessFilter, options: WaitOptions) -> Result<(p
|
|||||||
return Ok((zombie_pid, exit_status));
|
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
|
// TODO: Support these options
|
||||||
if !options.supported() {
|
if !options.supported() {
|
||||||
warn!("Unsupported options contained. wait options: {:?}", options);
|
warn!("Unsupported options contained. wait options: {:?}", options);
|
||||||
|
@ -9,12 +9,29 @@
|
|||||||
// Note: This test intends to test the case that child process directly calls _exit()
|
// 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.
|
// after vfork. "exit", "_exit" and returning from main function are different.
|
||||||
// And here the exit function must be "_exit" to prevent undefined bevaviour.
|
// 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();
|
pid_t child_pid = vfork();
|
||||||
if (child_pid == 0) {
|
if (child_pid == 0) {
|
||||||
_exit(0);
|
_exit(0);
|
||||||
} else {
|
} else {
|
||||||
printf ("Comming back to parent process from child with pid = %d\n", child_pid);
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -167,7 +184,7 @@ int test_vfork_stop_child_thread() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static test_case_t test_cases[] = {
|
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_multiple_vfork_execve),
|
||||||
TEST_CASE(test_vfork_isolate_file_table),
|
TEST_CASE(test_vfork_isolate_file_table),
|
||||||
TEST_CASE(test_vfork_stop_child_thread),
|
TEST_CASE(test_vfork_stop_child_thread),
|
||||||
|
Loading…
Reference in New Issue
Block a user