mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-08 12:56:48 +00:00
Add real vfork logics
This commit is contained in:
parent
43e43ca133
commit
e4f07b76a0
@ -195,8 +195,18 @@ pub fn clone_child(
|
||||
Ok(child_tid)
|
||||
} else {
|
||||
let child_process = clone_child_process(ctx, parent_context, clone_args)?;
|
||||
if clone_args.flags.contains(CloneFlags::CLONE_VFORK) {
|
||||
child_process.status().set_vfork_child(true);
|
||||
}
|
||||
|
||||
child_process.run();
|
||||
|
||||
if child_process.status().is_vfork_child() {
|
||||
let cond = || (!child_process.status().is_vfork_child()).then_some(());
|
||||
let current = ctx.process;
|
||||
current.children_wait_queue().wait_until(cond);
|
||||
}
|
||||
|
||||
let child_pid = child_process.pid();
|
||||
Ok(child_pid)
|
||||
}
|
||||
@ -435,8 +445,10 @@ fn clone_user_ctx(
|
||||
// The return value of child thread is zero
|
||||
child_context.set_syscall_ret(0);
|
||||
|
||||
if clone_flags.contains(CloneFlags::CLONE_VM) {
|
||||
// if parent and child shares the same address space, a new stack must be specified.
|
||||
if clone_flags.contains(CloneFlags::CLONE_VM) && !clone_flags.contains(CloneFlags::CLONE_VFORK)
|
||||
{
|
||||
// If parent and child shares the same address space and not in vfork situation,
|
||||
// a new stack must be specified.
|
||||
debug_assert!(new_sp != 0);
|
||||
}
|
||||
if new_sp != 0 {
|
||||
|
@ -14,6 +14,7 @@ use crate::{prelude::*, process::signal::signals::kernel::KernelSignal};
|
||||
/// [`do_exit_group`]: crate::process::posix_thread::do_exit_group
|
||||
pub(super) fn exit_process(thread_local: &ThreadLocal, current_process: &Process) {
|
||||
current_process.status().set_zombie();
|
||||
current_process.status().set_vfork_child(false);
|
||||
|
||||
// FIXME: This is obviously wrong in a number of ways, since different threads can have
|
||||
// different file tables, and different processes can share the same file table.
|
||||
@ -25,7 +26,7 @@ pub(super) fn exit_process(thread_local: &ThreadLocal, current_process: &Process
|
||||
|
||||
send_child_death_signal(current_process);
|
||||
|
||||
current_process.lock_root_vmar().clear();
|
||||
current_process.lock_root_vmar().set_vmar(None);
|
||||
}
|
||||
|
||||
/// Sends parent-death signals to the children.
|
||||
|
@ -26,8 +26,10 @@ pub use process::{
|
||||
ExitCode, JobControl, Pgid, Pid, Process, ProcessBuilder, ProcessGroup, Session, Sid, Terminal,
|
||||
};
|
||||
pub use process_filter::ProcessFilter;
|
||||
pub use process_vm::{MAX_ARGV_NUMBER, MAX_ARG_LEN, MAX_ENVP_NUMBER, MAX_ENV_LEN};
|
||||
pub use program_loader::{check_executable_file, load_program_to_vm};
|
||||
pub use process_vm::{
|
||||
renew_vm_and_map, MAX_ARGV_NUMBER, MAX_ARG_LEN, MAX_ENVP_NUMBER, MAX_ENV_LEN,
|
||||
};
|
||||
pub use program_loader::{check_executable_file, ProgramToLoad};
|
||||
pub use rlimit::ResourceType;
|
||||
pub use term_status::TermStatus;
|
||||
pub use wait::{wait_child_exit, WaitOptions};
|
||||
|
@ -9,7 +9,7 @@ use crate::{
|
||||
thread_info::ThreadFsInfo,
|
||||
},
|
||||
prelude::*,
|
||||
process::{process_vm::ProcessVm, program_loader::load_program_to_vm, Credentials, Process},
|
||||
process::{process_vm::ProcessVm, program_loader::ProgramToLoad, Credentials, Process},
|
||||
thread::{AsThread, Thread, Tid},
|
||||
};
|
||||
|
||||
@ -48,7 +48,10 @@ pub fn create_posix_task_from_executable(
|
||||
let fs_resolver = fs.resolver().read();
|
||||
let fs_path = FsPath::new(AT_FDCWD, executable_path)?;
|
||||
let elf_file = fs.resolver().read().lookup(&fs_path)?;
|
||||
load_program_to_vm(process_vm, elf_file, argv, envp, &fs_resolver, 1)?
|
||||
let program_to_load =
|
||||
ProgramToLoad::build_from_file(elf_file, &fs_resolver, argv, envp, 1)?;
|
||||
process_vm.clear_and_map();
|
||||
program_to_load.load_to_vm(process_vm, &fs_resolver)?
|
||||
};
|
||||
|
||||
let mut user_ctx = UserContext::default();
|
||||
|
@ -14,7 +14,7 @@ mod init_stack;
|
||||
|
||||
use aster_rights::Full;
|
||||
pub use heap::Heap;
|
||||
use ostd::sync::MutexGuard;
|
||||
use ostd::{sync::MutexGuard, task::disable_preempt};
|
||||
|
||||
pub use self::{
|
||||
heap::USER_HEAP_SIZE_LIMIT,
|
||||
@ -83,9 +83,12 @@ impl ProcessVmarGuard<'_> {
|
||||
self.inner.as_ref().unwrap()
|
||||
}
|
||||
|
||||
/// Clears the VMAR of the binding process.
|
||||
pub(super) fn clear(&mut self) {
|
||||
*self.inner = None;
|
||||
/// Sets a new VMAR for the binding process.
|
||||
///
|
||||
/// If the `new_vmar` is `None`, this method will remove the
|
||||
/// current VMAR.
|
||||
pub(super) fn set_vmar(&mut self, new_vmar: Option<Vmar<Full>>) {
|
||||
*self.inner = new_vmar;
|
||||
}
|
||||
}
|
||||
|
||||
@ -160,10 +163,25 @@ impl ProcessVm {
|
||||
&self.heap
|
||||
}
|
||||
|
||||
/// Clears existing mappings and then maps stack and heap vmo.
|
||||
pub(super) fn clear_and_map(&self) {
|
||||
/// Clears existing mappings and then maps the heap VMO to the current VMAR.
|
||||
pub fn clear_and_map(&self) {
|
||||
let root_vmar = self.lock_root_vmar();
|
||||
root_vmar.get().clear().unwrap();
|
||||
self.heap.alloc_and_map_vm(&root_vmar.get()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
/// Renews the [`ProcessVm`] of the current process and then maps the heap VMO to the new VMAR.
|
||||
pub fn renew_vm_and_map(ctx: &Context) {
|
||||
let process_vm = ctx.process.vm();
|
||||
let mut root_vmar = process_vm.lock_root_vmar();
|
||||
|
||||
let new_vmar = Vmar::<Full>::new_root();
|
||||
let guard = disable_preempt();
|
||||
*ctx.thread_local.root_vmar().borrow_mut() = Some(new_vmar.dup().unwrap());
|
||||
new_vmar.vm_space().activate();
|
||||
root_vmar.set_vmar(Some(new_vmar));
|
||||
drop(guard);
|
||||
|
||||
process_vm.heap.alloc_and_map_vm(root_vmar.get()).unwrap();
|
||||
}
|
||||
|
@ -17,56 +17,89 @@ use crate::{
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
/// Load an executable to root vmar, including loading programme image, preparing heap and stack,
|
||||
/// initializing argv, envp and aux tables.
|
||||
/// About recursion_limit: recursion limit is used to limit th recursion depth of shebang executables.
|
||||
/// If the interpreter(the program behind #!) of shebang executable is also a shebang,
|
||||
/// then it will trigger recursion. We will try to setup root vmar for the interpreter.
|
||||
/// I guess for most cases, setting the recursion_limit as 1 should be enough.
|
||||
/// because the interpreter is usually an elf binary(e.g., /bin/bash)
|
||||
pub fn load_program_to_vm(
|
||||
process_vm: &ProcessVm,
|
||||
/// Represents an executable file that is ready to be loaded into memory and executed.
|
||||
///
|
||||
/// This struct encapsulates the ELF file to be executed along with its header data,
|
||||
/// the `argv` and the `envp` which is required for the program execution.
|
||||
pub struct ProgramToLoad {
|
||||
elf_file: Dentry,
|
||||
file_header: Box<[u8; PAGE_SIZE]>,
|
||||
argv: Vec<CString>,
|
||||
envp: Vec<CString>,
|
||||
fs_resolver: &FsResolver,
|
||||
recursion_limit: usize,
|
||||
) -> Result<(String, ElfLoadInfo)> {
|
||||
let abs_path = elf_file.abs_path();
|
||||
let inode = elf_file.inode();
|
||||
let file_header = {
|
||||
// read the first page of file header
|
||||
let mut file_header_buffer = Box::new([0u8; PAGE_SIZE]);
|
||||
inode.read_bytes_at(0, &mut *file_header_buffer)?;
|
||||
file_header_buffer
|
||||
};
|
||||
if let Some(mut new_argv) = parse_shebang_line(&*file_header)? {
|
||||
if recursion_limit == 0 {
|
||||
return_errno_with_message!(Errno::ELOOP, "the recursieve limit is reached");
|
||||
}
|
||||
new_argv.extend_from_slice(&argv);
|
||||
let interpreter = {
|
||||
let filename = new_argv[0].to_str()?.to_string();
|
||||
let fs_path = FsPath::new(AT_FDCWD, &filename)?;
|
||||
fs_resolver.lookup(&fs_path)?
|
||||
}
|
||||
|
||||
impl ProgramToLoad {
|
||||
/// Constructs a new `ProgramToLoad` from a file, handling shebang interpretation if needed.
|
||||
///
|
||||
/// About `recursion_limit`: recursion limit is used to limit th recursion depth of shebang executables.
|
||||
/// If the interpreter(the program behind #!) of shebang executable is also a shebang,
|
||||
/// then it will trigger recursion. We will try to setup root vmar for the interpreter.
|
||||
/// I guess for most cases, setting the `recursion_limit` as 1 should be enough.
|
||||
/// because the interpreter is usually an elf binary(e.g., /bin/bash)
|
||||
pub fn build_from_file(
|
||||
elf_file: Dentry,
|
||||
fs_resolver: &FsResolver,
|
||||
argv: Vec<CString>,
|
||||
envp: Vec<CString>,
|
||||
recursion_limit: usize,
|
||||
) -> Result<Self> {
|
||||
let inode = elf_file.inode();
|
||||
let file_header = {
|
||||
// read the first page of file header
|
||||
let mut file_header_buffer = Box::new([0u8; PAGE_SIZE]);
|
||||
inode.read_bytes_at(0, &mut *file_header_buffer)?;
|
||||
file_header_buffer
|
||||
};
|
||||
check_executable_file(&interpreter)?;
|
||||
return load_program_to_vm(
|
||||
process_vm,
|
||||
interpreter,
|
||||
new_argv,
|
||||
if let Some(mut new_argv) = parse_shebang_line(&*file_header)? {
|
||||
if recursion_limit == 0 {
|
||||
return_errno_with_message!(Errno::ELOOP, "the recursieve limit is reached");
|
||||
}
|
||||
new_argv.extend_from_slice(&argv);
|
||||
let interpreter = {
|
||||
let filename = new_argv[0].to_str()?.to_string();
|
||||
let fs_path = FsPath::new(AT_FDCWD, &filename)?;
|
||||
fs_resolver.lookup(&fs_path)?
|
||||
};
|
||||
check_executable_file(&interpreter)?;
|
||||
return Self::build_from_file(
|
||||
interpreter,
|
||||
fs_resolver,
|
||||
new_argv,
|
||||
envp,
|
||||
recursion_limit - 1,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
elf_file,
|
||||
file_header,
|
||||
argv,
|
||||
envp,
|
||||
fs_resolver,
|
||||
recursion_limit - 1,
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
process_vm.clear_and_map();
|
||||
/// Loads the executable into the specified virtual memory space.
|
||||
///
|
||||
/// Returns a tuple containing:
|
||||
/// 1. The absolute path of the loaded executable.
|
||||
/// 2. Information about the ELF loading process.
|
||||
pub fn load_to_vm(
|
||||
self,
|
||||
process_vm: &ProcessVm,
|
||||
fs_resolver: &FsResolver,
|
||||
) -> Result<(String, ElfLoadInfo)> {
|
||||
let abs_path = self.elf_file.abs_path();
|
||||
let elf_load_info = load_elf_to_vm(
|
||||
process_vm,
|
||||
&*self.file_header,
|
||||
self.elf_file,
|
||||
fs_resolver,
|
||||
self.argv,
|
||||
self.envp,
|
||||
)?;
|
||||
|
||||
let elf_load_info =
|
||||
load_elf_to_vm(process_vm, &*file_header, elf_file, fs_resolver, argv, envp)?;
|
||||
|
||||
Ok((abs_path, elf_load_info))
|
||||
Ok((abs_path, elf_load_info))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_executable_file(dentry: &Dentry) -> Result<()> {
|
||||
|
@ -10,10 +10,13 @@ use super::ExitCode;
|
||||
///
|
||||
/// This maintains:
|
||||
/// 1. Whether the process is a zombie (i.e., all its threads have exited);
|
||||
/// 2. The exit code of the process.
|
||||
/// 2. Whether the process is the vfork child, which shares the user-space virtual memory
|
||||
/// with its parent process;
|
||||
/// 3. The exit code of the process.
|
||||
#[derive(Debug)]
|
||||
pub struct ProcessStatus {
|
||||
is_zombie: AtomicBool,
|
||||
is_vfork_child: AtomicBool,
|
||||
exit_code: AtomicU32,
|
||||
}
|
||||
|
||||
@ -21,6 +24,7 @@ impl Default for ProcessStatus {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
is_zombie: AtomicBool::new(false),
|
||||
is_vfork_child: AtomicBool::new(false),
|
||||
exit_code: AtomicU32::new(0),
|
||||
}
|
||||
}
|
||||
@ -44,6 +48,18 @@ impl ProcessStatus {
|
||||
}
|
||||
}
|
||||
|
||||
impl ProcessStatus {
|
||||
/// Returns whether the process is the vfork child.
|
||||
pub fn is_vfork_child(&self) -> bool {
|
||||
self.is_vfork_child.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
/// Sets whether the process is the vfork child.
|
||||
pub fn set_vfork_child(&self, is_vfork_child: bool) {
|
||||
self.is_vfork_child.store(is_vfork_child, Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
||||
impl ProcessStatus {
|
||||
/// Returns the exit code.
|
||||
pub fn exit_code(&self) -> ExitCode {
|
||||
|
@ -15,8 +15,8 @@ use crate::{
|
||||
},
|
||||
prelude::*,
|
||||
process::{
|
||||
check_executable_file, load_program_to_vm, posix_thread::ThreadName, Credentials, Process,
|
||||
MAX_ARGV_NUMBER, MAX_ARG_LEN, MAX_ENVP_NUMBER, MAX_ENV_LEN,
|
||||
check_executable_file, posix_thread::ThreadName, renew_vm_and_map, Credentials, Process,
|
||||
ProgramToLoad, MAX_ARGV_NUMBER, MAX_ARG_LEN, MAX_ENVP_NUMBER, MAX_ENV_LEN,
|
||||
},
|
||||
};
|
||||
|
||||
@ -118,11 +118,27 @@ fn do_execve(
|
||||
drop(closed_files);
|
||||
|
||||
debug!("load program to root vmar");
|
||||
let (new_executable_path, elf_load_info) = {
|
||||
let fs_resolver = &*posix_thread.fs().resolver().read();
|
||||
let process_vm = process.vm();
|
||||
load_program_to_vm(process_vm, elf_file.clone(), argv, envp, fs_resolver, 1)?
|
||||
};
|
||||
let fs_resolver = &*posix_thread.fs().resolver().read();
|
||||
let program_to_load =
|
||||
ProgramToLoad::build_from_file(elf_file.clone(), fs_resolver, argv, envp, 1)?;
|
||||
|
||||
let process_vm = process.vm();
|
||||
if process.status().is_vfork_child() {
|
||||
renew_vm_and_map(ctx);
|
||||
|
||||
// Resumes the parent process.
|
||||
process.status().set_vfork_child(false);
|
||||
let parent = process.parent().lock().process().upgrade().unwrap();
|
||||
parent.children_wait_queue().wake_all();
|
||||
} else {
|
||||
// FIXME: Currently, the efficiency of replacing the VMAR is lower than that
|
||||
// of directly clearing the VMAR. Therefore, if not in vfork case we will only
|
||||
// clear the VMAR.
|
||||
process_vm.clear_and_map();
|
||||
}
|
||||
|
||||
let (new_executable_path, elf_load_info) =
|
||||
program_to_load.load_to_vm(process_vm, fs_resolver)?;
|
||||
|
||||
// After the program has been successfully loaded, the virtual memory of the current process
|
||||
// is initialized. Hence, it is necessary to clear the previously recorded robust list.
|
||||
|
Loading…
x
Reference in New Issue
Block a user