diff --git a/kernel/src/process/clone.rs b/kernel/src/process/clone.rs index e3c13163..15b7883e 100644 --- a/kernel/src/process/clone.rs +++ b/kernel/src/process/clone.rs @@ -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 { diff --git a/kernel/src/process/exit.rs b/kernel/src/process/exit.rs index 48c5cad2..0f6deee4 100644 --- a/kernel/src/process/exit.rs +++ b/kernel/src/process/exit.rs @@ -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. diff --git a/kernel/src/process/mod.rs b/kernel/src/process/mod.rs index faa15c6a..ea31cf80 100644 --- a/kernel/src/process/mod.rs +++ b/kernel/src/process/mod.rs @@ -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}; diff --git a/kernel/src/process/posix_thread/posix_thread_ext.rs b/kernel/src/process/posix_thread/posix_thread_ext.rs index cca24beb..46625ba4 100644 --- a/kernel/src/process/posix_thread/posix_thread_ext.rs +++ b/kernel/src/process/posix_thread/posix_thread_ext.rs @@ -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(); diff --git a/kernel/src/process/process_vm/mod.rs b/kernel/src/process/process_vm/mod.rs index 12ebcc39..4f44c36b 100644 --- a/kernel/src/process/process_vm/mod.rs +++ b/kernel/src/process/process_vm/mod.rs @@ -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>) { + *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::::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(); +} diff --git a/kernel/src/process/program_loader/mod.rs b/kernel/src/process/program_loader/mod.rs index bb177036..5daf6a0e 100644 --- a/kernel/src/process/program_loader/mod.rs +++ b/kernel/src/process/program_loader/mod.rs @@ -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, envp: Vec, - 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, + envp: Vec, + recursion_limit: usize, + ) -> Result { + 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<()> { diff --git a/kernel/src/process/status.rs b/kernel/src/process/status.rs index 4771a311..ba279a8d 100644 --- a/kernel/src/process/status.rs +++ b/kernel/src/process/status.rs @@ -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 { diff --git a/kernel/src/syscall/execve.rs b/kernel/src/syscall/execve.rs index 4175ee2c..b9bb0288 100644 --- a/kernel/src/syscall/execve.rs +++ b/kernel/src/syscall/execve.rs @@ -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.