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_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
 | ||||
|  | ||||
| @ -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<isize> { | ||||
|     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); | ||||
|  | ||||
| @ -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<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! { | ||||
| @ -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<TermStatus>, // If the child process exits, the exit status should be specified.
 | ||||
| ) -> Result<isize> { | ||||
|     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<isize> { | ||||
|     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<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::process::{ProcessFilter, ProcessInner}; | ||||
| use super::wait::Waiter; | ||||
| @ -53,10 +54,6 @@ pub fn do_wait4(child_filter: &ProcessFilter, options: WaitOptions) -> Result<(p | ||||
|         }) | ||||
|         .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
 | ||||
|     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); | ||||
|  | ||||
| @ -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), | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user