Rewrite exit() and exit_group()

This commit is contained in:
Ruihan Li 2024-11-23 22:08:38 +08:00 committed by Tate, Hongliang Tian
parent 5efc32b6ea
commit 35c20620bc
24 changed files with 364 additions and 249 deletions

View File

@ -23,7 +23,7 @@ impl CmdlineFileOps {
impl FileOps for CmdlineFileOps { impl FileOps for CmdlineFileOps {
fn data(&self) -> Result<Vec<u8>> { fn data(&self) -> Result<Vec<u8>> {
let cmdline_output = if self.0.is_zombie() { let cmdline_output = if self.0.status().is_zombie() {
// Returns 0 characters for zombie process. // Returns 0 characters for zombie process.
Vec::new() Vec::new()
} else { } else {

View File

@ -24,7 +24,7 @@ impl FdDirOps {
.parent(parent) .parent(parent)
.build() .build()
.unwrap(); .unwrap();
let main_thread = process_ref.main_thread().unwrap(); let main_thread = process_ref.main_thread();
let file_table = main_thread.as_posix_thread().unwrap().file_table().lock(); let file_table = main_thread.as_posix_thread().unwrap().file_table().lock();
let weak_ptr = Arc::downgrade(&fd_inode); let weak_ptr = Arc::downgrade(&fd_inode);
file_table.register_observer(weak_ptr); file_table.register_observer(weak_ptr);
@ -51,7 +51,7 @@ impl DirOps for FdDirOps {
let fd = name let fd = name
.parse::<FileDesc>() .parse::<FileDesc>()
.map_err(|_| Error::new(Errno::ENOENT))?; .map_err(|_| Error::new(Errno::ENOENT))?;
let main_thread = self.0.main_thread().unwrap(); let main_thread = self.0.main_thread();
let file_table = main_thread.as_posix_thread().unwrap().file_table().lock(); let file_table = main_thread.as_posix_thread().unwrap().file_table().lock();
file_table file_table
.get_file(fd) .get_file(fd)
@ -67,7 +67,7 @@ impl DirOps for FdDirOps {
this.downcast_ref::<ProcDir<FdDirOps>>().unwrap().this() this.downcast_ref::<ProcDir<FdDirOps>>().unwrap().this()
}; };
let mut cached_children = this.cached_children().write(); let mut cached_children = this.cached_children().write();
let main_thread = self.0.main_thread().unwrap(); let main_thread = self.0.main_thread();
let file_table = main_thread.as_posix_thread().unwrap().file_table().lock(); let file_table = main_thread.as_posix_thread().unwrap().file_table().lock();
for (fd, file) in file_table.fds_and_files() { for (fd, file) in file_table.fds_and_files() {
cached_children.put_entry_if_not_found(&fd.to_string(), || { cached_children.put_entry_if_not_found(&fd.to_string(), || {

View File

@ -33,7 +33,7 @@ impl PidDirOps {
.volatile() .volatile()
.build() .build()
.unwrap(); .unwrap();
let main_thread = process_ref.main_thread().unwrap(); let main_thread = process_ref.main_thread();
let file_table = main_thread.as_posix_thread().unwrap().file_table().lock(); let file_table = main_thread.as_posix_thread().unwrap().file_table().lock();
let weak_ptr = Arc::downgrade(&pid_inode); let weak_ptr = Arc::downgrade(&pid_inode);
file_table.register_observer(weak_ptr); file_table.register_observer(weak_ptr);

View File

@ -30,7 +30,7 @@ impl StatFileOps {
impl FileOps for StatFileOps { impl FileOps for StatFileOps {
fn data(&self) -> Result<Vec<u8>> { fn data(&self) -> Result<Vec<u8>> {
let process = &self.0; let process = &self.0;
let main_thread = process.main_thread().unwrap(); let main_thread = process.main_thread();
let file_table = main_thread.as_posix_thread().unwrap().file_table(); let file_table = main_thread.as_posix_thread().unwrap().file_table();
let mut stat_output = String::new(); let mut stat_output = String::new();
@ -43,7 +43,7 @@ impl FileOps for StatFileOps {
process.parent().pid(), process.parent().pid(),
process.parent().pid(), process.parent().pid(),
file_table.lock().len(), file_table.lock().len(),
process.tasks().lock().len() process.tasks().lock().as_slice().len(),
) )
.unwrap(); .unwrap();
Ok(stat_output.into_bytes()) Ok(stat_output.into_bytes())

View File

@ -72,7 +72,7 @@ impl StatusFileOps {
impl FileOps for StatusFileOps { impl FileOps for StatusFileOps {
fn data(&self) -> Result<Vec<u8>> { fn data(&self) -> Result<Vec<u8>> {
let process = &self.0; let process = &self.0;
let main_thread = process.main_thread().unwrap(); let main_thread = process.main_thread();
let file_table = main_thread.as_posix_thread().unwrap().file_table(); let file_table = main_thread.as_posix_thread().unwrap().file_table();
let mut status_output = String::new(); let mut status_output = String::new();
@ -82,7 +82,12 @@ impl FileOps for StatusFileOps {
writeln!(status_output, "PPid:\t{}", process.parent().pid()).unwrap(); writeln!(status_output, "PPid:\t{}", process.parent().pid()).unwrap();
writeln!(status_output, "TracerPid:\t{}", process.parent().pid()).unwrap(); // Assuming TracerPid is the same as PPid writeln!(status_output, "TracerPid:\t{}", process.parent().pid()).unwrap(); // Assuming TracerPid is the same as PPid
writeln!(status_output, "FDSize:\t{}", file_table.lock().len()).unwrap(); writeln!(status_output, "FDSize:\t{}", file_table.lock().len()).unwrap();
writeln!(status_output, "Threads:\t{}", process.tasks().lock().len()).unwrap(); writeln!(
status_output,
"Threads:\t{}",
process.tasks().lock().as_slice().len()
)
.unwrap();
Ok(status_output.into_bytes()) Ok(status_output.into_bytes())
} }
} }

View File

@ -63,7 +63,7 @@ impl DirOps for ThreadDirOps {
impl DirOps for TaskDirOps { impl DirOps for TaskDirOps {
fn lookup_child(&self, this_ptr: Weak<dyn Inode>, name: &str) -> Result<Arc<dyn Inode>> { fn lookup_child(&self, this_ptr: Weak<dyn Inode>, name: &str) -> Result<Arc<dyn Inode>> {
for task in self.0.tasks().lock().iter() { for task in self.0.tasks().lock().as_slice() {
if task.as_posix_thread().unwrap().tid() != name.parse::<u32>().unwrap() { if task.as_posix_thread().unwrap().tid() != name.parse::<u32>().unwrap() {
continue; continue;
} }
@ -78,7 +78,7 @@ impl DirOps for TaskDirOps {
this.downcast_ref::<ProcDir<TaskDirOps>>().unwrap().this() this.downcast_ref::<ProcDir<TaskDirOps>>().unwrap().this()
}; };
let mut cached_children = this.cached_children().write(); let mut cached_children = this.cached_children().write();
for task in self.0.tasks().lock().iter() { for task in self.0.tasks().lock().as_slice() {
cached_children.put_entry_if_not_found( cached_children.put_entry_if_not_found(
&format!("{}", task.as_posix_thread().unwrap().tid()), &format!("{}", task.as_posix_thread().unwrap().tid()),
|| ThreadDirOps::new_inode(self.0.clone(), this_ptr.clone()), || ThreadDirOps::new_inode(self.0.clone(), this_ptr.clone()),

View File

@ -150,14 +150,14 @@ fn init_thread() {
) )
.expect("Run init process failed."); .expect("Run init process failed.");
// Wait till initproc become zombie. // Wait till initproc become zombie.
while !initproc.is_zombie() { while !initproc.status().is_zombie() {
// We don't have preemptive scheduler now. // We don't have preemptive scheduler now.
// The long running init thread should yield its own execution to allow other tasks to go on. // The long running init thread should yield its own execution to allow other tasks to go on.
Thread::yield_now(); Thread::yield_now();
} }
// TODO: exit via qemu isa debug device should not be the only way. // TODO: exit via qemu isa debug device should not be the only way.
let exit_code = if initproc.exit_code() == 0 { let exit_code = if initproc.status().exit_code() == 0 {
QemuExitCode::Success QemuExitCode::Success
} else { } else {
QemuExitCode::Failed QemuExitCode::Failed

View File

@ -260,7 +260,11 @@ fn clone_child_task(
thread_builder.build() thread_builder.build()
}; };
process.tasks().lock().push(child_task.clone()); process
.tasks()
.lock()
.insert(child_task.clone())
.map_err(|_| Error::with_message(Errno::EINTR, "the process has exited"))?;
let child_posix_thread = child_task.as_posix_thread().unwrap(); let child_posix_thread = child_task.as_posix_thread().unwrap();
clone_parent_settid(child_tid, clone_args.parent_tid, clone_flags)?; clone_parent_settid(child_tid, clone_args.parent_tid, clone_flags)?;

View File

@ -1,75 +1,74 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
use super::{process_table, Pid, Process, TermStatus}; use super::{posix_thread::PosixThread, process_table, Pid, Process};
use crate::{ use crate::{prelude::*, process::signal::signals::kernel::KernelSignal};
prelude::*,
process::{
posix_thread::{do_exit, AsPosixThread},
signal::signals::kernel::KernelSignal,
},
thread::AsThread,
};
pub fn do_exit_group(term_status: TermStatus) { /// Exits the current POSIX process.
let current = current!(); ///
debug!("exit group was called"); /// This is for internal use. Do NOT call this directly. When the last thread in the process exits,
if current.is_zombie() { /// [`do_exit`] or [`do_exit_group`] will invoke this method automatically.
return; ///
} /// [`do_exit`]: crate::process::posix_thread::do_exit
current.set_zombie(term_status); /// [`do_exit_group`]: crate::process::posix_thread::do_exit_group
pub(super) fn exit_process(current_thread: &PosixThread, current_process: &Process) {
current_process.status().set_zombie();
// Exit all threads // FIXME: This is obviously wrong in a number of ways, since different threads can have
let tasks = current.tasks().lock().clone(); // different file tables, and different processes can share the same file table.
for task in tasks { current_thread.file_table().lock().close_all();
let thread = task.as_thread().unwrap();
let posix_thread = thread.as_posix_thread().unwrap();
if let Err(e) = do_exit(thread, posix_thread, term_status) {
debug!("Ignore error when call exit: {:?}", e);
}
}
// Sends parent-death signal send_parent_death_signal(current_process);
// FIXME: according to linux spec, the signal should be sent when a posix thread which
// creates child process exits, not when the whole process exits group. move_children_to_init(current_process);
for (_, child) in current.children().lock().iter() {
send_child_death_signal(current_process);
}
/// Sends parent-death signals to the children.
//
// FIXME: According to the Linux implementation, the signal should be sent when the POSIX thread
// that created the child exits, not when the whole process exits. For more details, see the
// "CAVEATS" section in <https://man7.org/linux/man-pages/man2/pr_set_pdeathsig.2const.html>.
fn send_parent_death_signal(current_process: &Process) {
for (_, child) in current_process.children().lock().iter() {
let Some(signum) = child.parent_death_signal() else { let Some(signum) = child.parent_death_signal() else {
continue; continue;
}; };
// FIXME: set pid of the signal // FIXME: Set `si_pid` in the `siginfo_t` argument.
let signal = KernelSignal::new(signum); let signal = KernelSignal::new(signum);
child.enqueue_signal(signal); child.enqueue_signal(signal);
} }
}
// Close all files then exit the process. /// Moves the children to the init process.
// fn move_children_to_init(current_process: &Process) {
// FIXME: This is obviously wrong in a number of ways, since different threads can have if is_init_process(current_process) {
// different file tables, and different processes can share the same file table. return;
let main_thread = current.main_thread().unwrap();
let mut files = main_thread.as_posix_thread().unwrap().file_table().lock();
files.close_all();
drop(files);
// Move children to the init process
if !is_init_process(&current) {
if let Some(init_process) = get_init_process() {
let mut init_children = init_process.children().lock();
for (_, child_process) in current.children().lock().extract_if(|_, _| true) {
let mut parent = child_process.parent.lock();
init_children.insert(child_process.pid(), child_process.clone());
parent.set_process(&init_process);
}
}
} }
let parent = current.parent().lock().process(); let Some(init_process) = get_init_process() else {
if let Some(parent) = parent.upgrade() { return;
// Notify parent
if let Some(signal) = current.exit_signal().map(KernelSignal::new) {
parent.enqueue_signal(signal);
};
parent.children_wait_queue().wake_all();
}; };
let mut init_children = init_process.children().lock();
for (_, child_process) in current_process.children().lock().extract_if(|_, _| true) {
let mut parent = child_process.parent.lock();
init_children.insert(child_process.pid(), child_process.clone());
parent.set_process(&init_process);
}
}
/// Sends a child-death signal to the parent.
fn send_child_death_signal(current_process: &Process) {
let Some(parent) = current_process.parent().lock().process().upgrade() else {
return;
};
if let Some(signal) = current_process.exit_signal().map(KernelSignal::new) {
parent.enqueue_signal(signal);
};
parent.children_wait_queue().wake_all();
} }
const INIT_PROCESS_PID: Pid = 1; const INIT_PROCESS_PID: Pid = 1;

View File

@ -123,7 +123,7 @@ fn kill_process(process: &Process, signal: Option<UserSignal>, ctx: &Context) ->
let sender_ids = current_thread_sender_ids(signum.as_ref(), ctx); let sender_ids = current_thread_sender_ids(signum.as_ref(), ctx);
let mut permitted_thread = None; let mut permitted_thread = None;
for task in tasks.iter() { for task in tasks.as_slice() {
let posix_thread = task.as_posix_thread().unwrap(); let posix_thread = task.as_posix_thread().unwrap();
// First check permission // First check permission

View File

@ -15,12 +15,12 @@ pub mod rlimit;
pub mod signal; pub mod signal;
mod status; mod status;
pub mod sync; pub mod sync;
mod task_set;
mod term_status; mod term_status;
mod wait; mod wait;
pub use clone::{clone_child, CloneArgs, CloneFlags}; pub use clone::{clone_child, CloneArgs, CloneFlags};
pub use credentials::{Credentials, Gid, Uid}; pub use credentials::{Credentials, Gid, Uid};
pub use exit::do_exit_group;
pub use kill::{kill, kill_all, kill_group, tgkill}; pub use kill::{kill, kill_all, kill_group, tgkill};
pub use process::{ pub use process::{
ExitCode, JobControl, Pgid, Pid, Process, ProcessBuilder, ProcessGroup, Session, Sid, Terminal, ExitCode, JobControl, Pgid, Pid, Process, ProcessBuilder, ProcessGroup, Session, Sid, Terminal,

View File

@ -1,66 +1,134 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
use super::{futex::futex_wake, robust_list::wake_robust_futex, thread_table, PosixThread}; use ostd::task::{CurrentTask, Task};
use super::{
futex::futex_wake, robust_list::wake_robust_futex, thread_table, AsPosixThread, PosixThread,
};
use crate::{ use crate::{
current_userspace, current_userspace,
prelude::*, prelude::*,
process::{do_exit_group, TermStatus}, process::{
thread::{Thread, Tid}, exit::exit_process,
signal::{constants::SIGKILL, signals::kernel::KernelSignal},
task_set::TaskSet,
TermStatus,
},
thread::AsThread,
}; };
/// Exits the thread if the thread is a POSIX thread. /// Exits the current POSIX thread.
/// ///
/// # Panics /// # Panics
/// ///
/// If the thread is not a POSIX thread, this method will panic. /// If the current thread is not a POSIX thread, this method will panic.
pub fn do_exit(thread: &Thread, posix_thread: &PosixThread, term_status: TermStatus) -> Result<()> { pub fn do_exit(term_status: TermStatus) {
if thread.is_exited() { exit_internal(term_status, false);
return Ok(());
}
thread.exit();
let tid = posix_thread.tid;
let mut clear_ctid = posix_thread.clear_child_tid().lock();
// If clear_ctid !=0 ,do a futex wake and write zero to the clear_ctid addr.
if *clear_ctid != 0 {
// FIXME: the correct write length?
if let Err(e) = current_userspace!().write_val(*clear_ctid, &0u32) {
debug!("Ignore error during exit process: {:?}", e);
}
futex_wake(*clear_ctid, 1, None)?;
*clear_ctid = 0;
}
drop(clear_ctid);
// exit the robust list: walk the robust list; mark futex words as dead and do futex wake
wake_robust_list(posix_thread, tid);
if tid != posix_thread.process().pid() {
// We don't remove main thread.
// The main thread is removed when the process is reaped.
thread_table::remove_thread(tid);
}
if posix_thread.is_main_thread(tid) || posix_thread.is_last_thread() {
// exit current process.
do_exit_group(term_status);
}
futex_wake(Arc::as_ptr(&posix_thread.process()) as Vaddr, 1, None)?;
Ok(())
} }
/// Walks the robust futex list, marking futex dead and wake waiters. /// Kills all threads and exits the current POSIX process.
/// It corresponds to Linux's exit_robust_list(), errors are silently ignored. ///
fn wake_robust_list(thread: &PosixThread, tid: Tid) { /// # Panics
let mut robust_list = thread.robust_list.lock(); ///
/// If the current thread is not a POSIX thread, this method will panic.
pub fn do_exit_group(term_status: TermStatus) {
exit_internal(term_status, true);
}
/// Exits the current POSIX thread or process.
fn exit_internal(term_status: TermStatus, is_exiting_group: bool) {
let current_task = Task::current().unwrap();
let current_thread = current_task.as_thread().unwrap();
let posix_thread = current_thread.as_posix_thread().unwrap();
let posix_process = posix_thread.process();
let is_last_thread = {
let mut tasks = posix_process.tasks().lock();
let has_exited_group = tasks.has_exited_group();
if is_exiting_group && !has_exited_group {
sigkill_other_threads(&current_task, &tasks);
tasks.set_exited_group();
}
// According to Linux's behavior, the last thread's exit code will become the process's
// exit code, so here we should just overwrite the old value (if any).
if !has_exited_group {
posix_process.status().set_exit_code(term_status.as_u32());
}
// We should only change the thread status when running as the thread, so no race
// conditions can occur in between.
if current_thread.is_exited() {
return;
}
current_thread.exit();
tasks.remove_exited(&current_task)
};
wake_clear_ctid(posix_thread);
wake_robust_list(posix_thread);
// According to Linux behavior, the main thread shouldn't be removed from the table until the
// process is reaped by its parent.
if posix_thread.tid() != posix_process.pid() {
thread_table::remove_thread(posix_thread.tid());
}
if is_last_thread {
exit_process(posix_thread, &posix_process);
}
}
/// Sends `SIGKILL` to all other threads in the current process.
///
/// This is only needed when initiating an `exit_group` for the first time.
fn sigkill_other_threads(current_task: &CurrentTask, task_set: &TaskSet) {
for task in task_set.as_slice() {
if core::ptr::eq(current_task.as_ref(), task.as_ref()) {
continue;
}
task.as_posix_thread()
.unwrap()
.enqueue_signal(Box::new(KernelSignal::new(SIGKILL)));
}
}
/// Writes zero to `clear_child_tid` and performs a futex wake.
fn wake_clear_ctid(current_thread: &PosixThread) {
let mut clear_ctid = current_thread.clear_child_tid().lock();
if *clear_ctid == 0 {
return;
}
let _ = current_userspace!()
.write_val(*clear_ctid, &0u32)
.inspect_err(|err| debug!("exit: cannot clear the child TID: {:?}", err));
let _ = futex_wake(*clear_ctid, 1, None)
.inspect_err(|err| debug!("exit: cannot wake the futex on the child TID: {:?}", err));
*clear_ctid = 0;
}
/// Walks the robust futex list, marking futex dead and waking waiters.
///
/// This corresponds to Linux's `exit_robust_list`. Errors are silently ignored.
fn wake_robust_list(current_thread: &PosixThread) {
let mut robust_list = current_thread.robust_list.lock();
let list_head = match *robust_list { let list_head = match *robust_list {
Some(robust_list_head) => robust_list_head, Some(robust_list_head) => robust_list_head,
None => return, None => return,
}; };
trace!("wake the rubust_list: {:?}", list_head);
trace!("exit: wake up the rubust list: {:?}", list_head);
for futex_addr in list_head.futexes() { for futex_addr in list_head.futexes() {
wake_robust_futex(futex_addr, tid).unwrap(); let _ = wake_robust_futex(futex_addr, current_thread.tid)
.inspect_err(|err| debug!("exit: cannot wake up the robust futex: {:?}", err));
} }
*robust_list = None; *robust_list = None;
} }

View File

@ -24,7 +24,7 @@ use crate::{
fs::{file_table::FileTable, thread_info::ThreadFsInfo}, fs::{file_table::FileTable, thread_info::ThreadFsInfo},
prelude::*, prelude::*,
process::signal::constants::SIGCONT, process::signal::constants::SIGCONT,
thread::{AsThread, Thread, Tid}, thread::{Thread, Tid},
time::{clocks::ProfClock, Timer, TimerManager}, time::{clocks::ProfClock, Timer, TimerManager},
}; };
@ -37,7 +37,7 @@ mod robust_list;
pub mod thread_table; pub mod thread_table;
pub use builder::PosixThreadBuilder; pub use builder::PosixThreadBuilder;
pub use exit::do_exit; pub use exit::{do_exit, do_exit_group};
pub use name::{ThreadName, MAX_THREAD_NAME_LEN}; pub use name::{ThreadName, MAX_THREAD_NAME_LEN};
pub use posix_thread_ext::{create_posix_task_from_executable, AsPosixThread}; pub use posix_thread_ext::{create_posix_task_from_executable, AsPosixThread};
pub use robust_list::RobustListHead; pub use robust_list::RobustListHead;
@ -285,20 +285,6 @@ impl PosixThread {
&self.robust_list &self.robust_list
} }
fn is_main_thread(&self, tid: Tid) -> bool {
let process = self.process();
let pid = process.pid();
tid == pid
}
fn is_last_thread(&self) -> bool {
let process = self.process.upgrade().unwrap();
let tasks = process.tasks().lock();
tasks
.iter()
.all(|task| task.as_thread().unwrap().is_exited())
}
/// Gets the read-only credentials of the thread. /// Gets the read-only credentials of the thread.
pub fn credentials(&self) -> Credentials<ReadOp> { pub fn credentials(&self) -> Credentials<ReadOp> {
self.credentials.dup().restrict() self.credentials.dup().restrict()

View File

@ -135,19 +135,15 @@ impl<'a> ProcessBuilder<'a> {
let nice = nice.or_else(|| Some(Nice::default())).unwrap(); let nice = nice.or_else(|| Some(Nice::default())).unwrap();
let process = { let process = Process::new(
let threads = Vec::new(); pid,
Process::new( parent,
pid, executable_path.to_string(),
parent, process_vm,
threads, resource_limits,
executable_path.to_string(), nice,
process_vm, sig_dispositions,
resource_limits, );
nice,
sig_dispositions,
)
};
let task = if let Some(thread_builder) = main_thread_builder { let task = if let Some(thread_builder) = main_thread_builder {
let builder = thread_builder.process(Arc::downgrade(&process)); let builder = thread_builder.process(Arc::downgrade(&process));
@ -164,9 +160,7 @@ impl<'a> ProcessBuilder<'a> {
)? )?
}; };
process.tasks().lock().push(task); process.tasks().lock().insert(task).unwrap();
process.set_runnable();
Ok(process) Ok(process)
} }

View File

@ -14,7 +14,8 @@ use super::{
signals::Signal, signals::Signal,
}, },
status::ProcessStatus, status::ProcessStatus,
Credentials, TermStatus, task_set::TaskSet,
Credentials,
}; };
use crate::{ use crate::{
device::tty::open_ntty_as_controlling_terminal, device::tty::open_ntty_as_controlling_terminal,
@ -71,7 +72,7 @@ pub struct Process {
/// The executable path. /// The executable path.
executable_path: RwLock<String>, executable_path: RwLock<String>,
/// The threads /// The threads
tasks: Mutex<Vec<Arc<Task>>>, tasks: Mutex<TaskSet>,
/// Process status /// Process status
status: ProcessStatus, status: ProcessStatus,
/// Parent process /// Parent process
@ -174,7 +175,6 @@ impl Process {
fn new( fn new(
pid: Pid, pid: Pid,
parent: Weak<Process>, parent: Weak<Process>,
tasks: Vec<Arc<Task>>,
executable_path: String, executable_path: String,
process_vm: ProcessVm, process_vm: ProcessVm,
@ -190,11 +190,11 @@ impl Process {
Arc::new_cyclic(|process_ref: &Weak<Process>| Self { Arc::new_cyclic(|process_ref: &Weak<Process>| Self {
pid, pid,
tasks: Mutex::new(tasks), tasks: Mutex::new(TaskSet::new()),
executable_path: RwLock::new(executable_path), executable_path: RwLock::new(executable_path),
process_vm, process_vm,
children_wait_queue, children_wait_queue,
status: ProcessStatus::new_uninit(), status: ProcessStatus::default(),
parent: ParentProcess::new(parent), parent: ParentProcess::new(parent),
children: Mutex::new(BTreeMap::new()), children: Mutex::new(BTreeMap::new()),
process_group: Mutex::new(Weak::new()), process_group: Mutex::new(Weak::new()),
@ -267,9 +267,9 @@ impl Process {
pub fn run(&self) { pub fn run(&self) {
let tasks = self.tasks.lock(); let tasks = self.tasks.lock();
// when run the process, the process should has only one thread // when run the process, the process should has only one thread
debug_assert!(tasks.len() == 1); debug_assert!(tasks.as_slice().len() == 1);
debug_assert!(self.is_runnable()); debug_assert!(!self.status().is_zombie());
let task = tasks[0].clone(); let task = tasks.main().clone();
// should not hold the lock when run thread // should not hold the lock when run thread
drop(tasks); drop(tasks);
let thread = task.as_thread().unwrap(); let thread = task.as_thread().unwrap();
@ -292,7 +292,7 @@ impl Process {
&self.timer_manager &self.timer_manager
} }
pub fn tasks(&self) -> &Mutex<Vec<Arc<Task>>> { pub fn tasks(&self) -> &Mutex<TaskSet> {
&self.tasks &self.tasks
} }
@ -312,15 +312,8 @@ impl Process {
&self.nice &self.nice
} }
pub fn main_thread(&self) -> Option<Arc<Thread>> { pub fn main_thread(&self) -> Arc<Thread> {
self.tasks self.tasks.lock().main().as_thread().unwrap().clone()
.lock()
.iter()
.find_map(|task| {
let thread = task.as_thread().unwrap();
(thread.as_posix_thread().unwrap().tid() == self.pid).then_some(thread)
})
.cloned()
} }
// *********** Parent and child *********** // *********** Parent and child ***********
@ -621,7 +614,7 @@ impl Process {
/// ///
/// TODO: restrict these method with access control tool. /// TODO: restrict these method with access control tool.
pub fn enqueue_signal(&self, signal: impl Signal + Clone + 'static) { pub fn enqueue_signal(&self, signal: impl Signal + Clone + 'static) {
if self.is_zombie() { if self.status.is_zombie() {
return; return;
} }
@ -629,7 +622,7 @@ impl Process {
// Enqueue signal to the first thread that does not block the signal // Enqueue signal to the first thread that does not block the signal
let threads = self.tasks.lock(); let threads = self.tasks.lock();
for thread in threads.iter() { for thread in threads.as_slice() {
let posix_thread = thread.as_posix_thread().unwrap(); let posix_thread = thread.as_posix_thread().unwrap();
if !posix_thread.has_signal_blocked(signal.num()) { if !posix_thread.has_signal_blocked(signal.num()) {
posix_thread.enqueue_signal(Box::new(signal)); posix_thread.enqueue_signal(Box::new(signal));
@ -637,8 +630,8 @@ impl Process {
} }
} }
// If all threads block the signal, enqueue signal to the first thread // If all threads block the signal, enqueue signal to the main thread
let thread = threads.iter().next().unwrap(); let thread = threads.main();
let posix_thread = thread.as_posix_thread().unwrap(); let posix_thread = thread.as_posix_thread().unwrap();
posix_thread.enqueue_signal(Box::new(signal)); posix_thread.enqueue_signal(Box::new(signal));
} }
@ -671,24 +664,9 @@ impl Process {
// ******************* Status ******************** // ******************* Status ********************
fn set_runnable(&self) { /// Returns a reference to the process status.
self.status.set_runnable(); pub fn status(&self) -> &ProcessStatus {
} &self.status
fn is_runnable(&self) -> bool {
self.status.is_runnable()
}
pub fn is_zombie(&self) -> bool {
self.status.is_zombie()
}
pub fn set_zombie(&self, term_status: TermStatus) {
self.status.set_zombie(term_status);
}
pub fn exit_code(&self) -> ExitCode {
self.status.exit_code()
} }
} }
@ -711,7 +689,6 @@ mod test {
Process::new( Process::new(
pid, pid,
parent, parent,
vec![],
String::new(), String::new(),
ProcessVm::alloc(), ProcessVm::alloc(),
ResourceLimits::default(), ResourceLimits::default(),

View File

@ -19,7 +19,7 @@ use crate::{
}, },
prelude::*, prelude::*,
process::{ process::{
do_exit_group, posix_thread::do_exit_group,
process_vm::{AuxKey, AuxVec, ProcessVm}, process_vm::{AuxKey, AuxVec, ProcessVm},
TermStatus, TermStatus,
}, },

View File

@ -31,7 +31,7 @@ use crate::{
cpu::LinuxAbi, cpu::LinuxAbi,
current_userspace, current_userspace,
prelude::*, prelude::*,
process::{do_exit_group, TermStatus}, process::{posix_thread::do_exit_group, TermStatus},
}; };
pub trait SignalContext { pub trait SignalContext {

View File

@ -1,59 +1,57 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
#![allow(dead_code)] //! The process status.
//! The process status use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use core::sync::atomic::{AtomicU64, Ordering}; use super::ExitCode;
use super::{ExitCode, TermStatus}; /// The status of a process.
/// The status of process.
/// ///
/// The `ProcessStatus` can be viewed as two parts, /// This maintains:
/// the highest 32 bits is the value of `TermStatus`, if any, /// 1. Whether the process is a zombie (i.e., all its threads have exited);
/// the lowest 32 bits is the value of status. /// 2. The exit code of the process.
#[derive(Debug)] #[derive(Debug)]
pub struct ProcessStatus(AtomicU64); pub struct ProcessStatus {
is_zombie: AtomicBool,
exit_code: AtomicU32,
}
#[repr(u8)] impl Default for ProcessStatus {
enum Status { fn default() -> Self {
Uninit = 0, Self {
Runnable = 1, is_zombie: AtomicBool::new(false),
Zombie = 2, exit_code: AtomicU32::new(0),
}
}
} }
impl ProcessStatus { impl ProcessStatus {
const LOW_MASK: u64 = 0xffff_ffff; /// Returns whether the process is a zombie process.
pub fn new_uninit() -> Self {
Self(AtomicU64::new(Status::Uninit as u64))
}
pub fn set_zombie(&self, term_status: TermStatus) {
let new_val = (term_status.as_u32() as u64) << 32 | Status::Zombie as u64;
self.0.store(new_val, Ordering::Relaxed);
}
pub fn is_zombie(&self) -> bool { pub fn is_zombie(&self) -> bool {
self.0.load(Ordering::Relaxed) & Self::LOW_MASK == Status::Zombie as u64 // Use the `Acquire` memory order to make the exit code visible.
self.is_zombie.load(Ordering::Acquire)
} }
pub fn set_runnable(&self) { /// Sets the process to be a zombie process.
let new_val = Status::Runnable as u64;
self.0.store(new_val, Ordering::Relaxed);
}
pub fn is_runnable(&self) -> bool {
self.0.load(Ordering::Relaxed) & Self::LOW_MASK == Status::Runnable as u64
}
/// Returns the exit code.
/// ///
/// If the process is not exited, the exit code is zero. /// This method should be called when the process completes its exit. The current thread must
/// But if exit code is zero, the process may or may not exit. /// be the last thread in the process, so that no threads belonging to the process can run
pub fn exit_code(&self) -> ExitCode { /// after it.
let val = self.0.load(Ordering::Relaxed); pub(super) fn set_zombie(&self) {
(val >> 32 & Self::LOW_MASK) as ExitCode // Use the `Release` memory order to make the exit code visible.
self.is_zombie.store(true, Ordering::Release);
}
}
impl ProcessStatus {
/// Returns the exit code.
pub fn exit_code(&self) -> ExitCode {
self.exit_code.load(Ordering::Relaxed)
}
/// Sets the exit code.
pub(super) fn set_exit_code(&self, exit_code: ExitCode) {
self.exit_code.store(exit_code, Ordering::Relaxed);
} }
} }

View File

@ -0,0 +1,86 @@
// SPDX-License-Identifier: MPL-2.0
//! Task sets.
use ostd::task::{CurrentTask, Task};
use crate::prelude::*;
/// A task set that maintains all tasks in a POSIX process.
pub struct TaskSet {
tasks: Vec<Arc<Task>>,
has_exited_main: bool,
has_exited_group: bool,
}
impl TaskSet {
/// Creates a new task set.
pub(super) fn new() -> Self {
Self {
tasks: Vec::new(),
has_exited_main: false,
has_exited_group: false,
}
}
/// Inserts a new task to the task set.
///
/// This method will fail if [`Self::set_exited_group`] has been called before.
pub(super) fn insert(&mut self, task: Arc<Task>) -> core::result::Result<(), Arc<Task>> {
if self.has_exited_group {
return Err(task);
}
self.tasks.push(task);
Ok(())
}
/// Removes the exited task from the task set if necessary.
///
/// The task will be removed from the task set if the corresponding thread is not the main
/// thread.
///
/// This method will return true if there are no more alive tasks in the task set.
///
/// # Panics
///
/// This method will panic if the task is not in the task set.
pub(super) fn remove_exited(&mut self, task: &CurrentTask) -> bool {
let position = self
.tasks
.iter()
.position(|some_task| core::ptr::eq(some_task.as_ref(), task.as_ref()))
.unwrap();
if position == 0 {
assert!(!self.has_exited_main);
self.has_exited_main = true;
} else {
self.tasks.swap_remove(position);
}
self.has_exited_main && self.tasks.len() == 1
}
/// Sets a flag that denotes that an `exit_group` has been initiated.
pub(super) fn set_exited_group(&mut self) {
self.has_exited_group = true;
}
/// Returns whether an `exit_group` has been initiated.
pub(super) fn has_exited_group(&self) -> bool {
self.has_exited_group
}
}
impl TaskSet {
/// Returns a slice of the tasks in the task set.
pub fn as_slice(&self) -> &[Arc<Task>] {
self.tasks.as_slice()
}
/// Returns the main task/thread.
pub fn main(&self) -> &Arc<Task> {
&self.tasks[0]
}
}

View File

@ -62,7 +62,9 @@ pub fn wait_child_exit(
} }
// return immediately if we find a zombie child // return immediately if we find a zombie child
let zombie_child = unwaited_children.iter().find(|child| child.is_zombie()); let zombie_child = unwaited_children
.iter()
.find(|child| child.status().is_zombie());
if let Some(zombie_child) = zombie_child { if let Some(zombie_child) = zombie_child {
let zombie_pid = zombie_child.pid(); let zombie_pid = zombie_child.pid();
@ -90,8 +92,8 @@ pub fn wait_child_exit(
/// Free zombie child with pid, returns the exit code of child process. /// Free zombie child with pid, returns the exit code of child process.
fn reap_zombie_child(process: &Process, pid: Pid) -> ExitCode { fn reap_zombie_child(process: &Process, pid: Pid) -> ExitCode {
let child_process = process.children().lock().remove(&pid).unwrap(); let child_process = process.children().lock().remove(&pid).unwrap();
assert!(child_process.is_zombie()); assert!(child_process.status().is_zombie());
for task in &*child_process.tasks().lock() { for task in child_process.tasks().lock().as_slice() {
thread_table::remove_thread(task.as_posix_thread().unwrap().tid()); thread_table::remove_thread(task.as_posix_thread().unwrap().tid());
} }
@ -122,5 +124,5 @@ fn reap_zombie_child(process: &Process, pid: Pid) -> ExitCode {
} }
process_table_mut.remove(child_process.pid()); process_table_mut.remove(child_process.pid());
child_process.exit_code() child_process.status().exit_code()
} }

View File

@ -6,11 +6,11 @@ use crate::{
syscall::SyscallReturn, syscall::SyscallReturn,
}; };
pub fn sys_exit(exit_code: i32, ctx: &Context) -> Result<SyscallReturn> { pub fn sys_exit(exit_code: i32, _ctx: &Context) -> Result<SyscallReturn> {
debug!("exid code = {}", exit_code); debug!("exid code = {}", exit_code);
let term_status = TermStatus::Exited(exit_code as _); let term_status = TermStatus::Exited(exit_code as _);
do_exit(ctx.thread, ctx.posix_thread, term_status)?; do_exit(term_status);
Ok(SyscallReturn::Return(0)) Ok(SyscallReturn::Return(0))
} }

View File

@ -2,7 +2,7 @@
use crate::{ use crate::{
prelude::*, prelude::*,
process::{do_exit_group, TermStatus}, process::{posix_thread::do_exit_group, TermStatus},
syscall::SyscallReturn, syscall::SyscallReturn,
}; };

View File

@ -72,12 +72,8 @@ fn get_processes(prio_target: PriorityTarget) -> Result<Vec<Arc<Process>>> {
let processes: Vec<Arc<Process>> = process_table::process_table_mut() let processes: Vec<Arc<Process>> = process_table::process_table_mut()
.iter() .iter()
.filter(|process| { .filter(|process| {
let Some(main_thread) = process.main_thread() else { let main_thread = process.main_thread();
return false; let posix_thread = main_thread.as_posix_thread().unwrap();
};
let Some(posix_thread) = main_thread.as_posix_thread() else {
return false;
};
uid == posix_thread.credentials().ruid() uid == posix_thread.credentials().ruid()
}) })
.cloned() .cloned()

View File

@ -31,7 +31,7 @@ pub fn sys_wait4(
return Ok(SyscallReturn::Return(0 as _)); return Ok(SyscallReturn::Return(0 as _));
}; };
let (return_pid, exit_code) = (process.pid(), process.exit_code()); let (return_pid, exit_code) = (process.pid(), process.status().exit_code());
if exit_status_ptr != 0 { if exit_status_ptr != 0 {
ctx.user_space() ctx.user_space()
.write_val(exit_status_ptr as _, &exit_code)?; .write_val(exit_status_ptr as _, &exit_code)?;