Support parent death signal & Refactor do_exit

This commit is contained in:
Jianfeng Jiang
2024-01-08 03:36:16 +00:00
committed by Tate, Hongliang Tian
parent e8c8c027c5
commit 57fc6a5402
14 changed files with 281 additions and 120 deletions

View File

@ -181,8 +181,6 @@ fn clone_child_thread(parent_context: &UserContext, clone_args: CloneArgs) -> Re
let child_tid = allocate_tid();
let child_thread = {
let is_main_thread = child_tid == current.pid();
let credentials = {
let credentials = credentials();
Credentials::new_from(&credentials)
@ -190,8 +188,7 @@ fn clone_child_thread(parent_context: &UserContext, clone_args: CloneArgs) -> Re
let thread_builder = PosixThreadBuilder::new(child_tid, child_user_space, credentials)
.process(Arc::downgrade(&current))
.sig_mask(sig_mask)
.is_main_thread(is_main_thread);
.sig_mask(sig_mask);
thread_builder.build()
};

View File

@ -4,7 +4,7 @@ use super::{process_table, Pid, Process, TermStatus};
use crate::{
prelude::*,
process::{
posix_thread::PosixThreadExt,
posix_thread::do_exit,
signal::{constants::SIGCHLD, signals::kernel::KernelSignal},
},
};
@ -20,17 +20,22 @@ pub fn do_exit_group(term_status: TermStatus) {
// Exit all threads
let threads = current.threads().lock().clone();
for thread in threads {
if thread.status().is_exited() {
continue;
}
thread.exit();
if let Some(posix_thread) = thread.as_posix_thread() {
let tid = thread.tid();
if let Err(e) = posix_thread.exit(tid, term_status) {
if let Err(e) = do_exit(thread, term_status) {
debug!("Ignore error when call exit: {:?}", e);
}
}
// Sends parent-death signal
// 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.
for (_, child) in current.children().lock().iter() {
let Some(signum) = child.parent_death_signal() else {
continue;
};
// FIXME: set pid of the signal
let signal = KernelSignal::new(signum);
child.enqueue_signal(signal);
}
// Close all files then exit the process
@ -61,7 +66,7 @@ pub fn do_exit_group(term_status: TermStatus) {
const INIT_PROCESS_PID: Pid = 1;
/// Get the init process
/// Gets the init process
fn get_init_process() -> Option<Arc<Process>> {
process_table::get_process(INIT_PROCESS_PID)
}

View File

@ -30,7 +30,6 @@ pub struct PosixThreadBuilder {
clear_child_tid: Vaddr,
sig_mask: SigMask,
sig_queues: SigQueues,
is_main_thread: bool,
}
impl PosixThreadBuilder {
@ -45,7 +44,6 @@ impl PosixThreadBuilder {
clear_child_tid: 0,
sig_mask: SigMask::new_empty(),
sig_queues: SigQueues::new(),
is_main_thread: true,
}
}
@ -69,12 +67,6 @@ impl PosixThreadBuilder {
self
}
#[allow(clippy::wrong_self_convention)]
pub fn is_main_thread(mut self, is_main_thread: bool) -> Self {
self.is_main_thread = is_main_thread;
self
}
pub fn sig_mask(mut self, sig_mask: SigMask) -> Self {
self.sig_mask = sig_mask;
self
@ -91,7 +83,6 @@ impl PosixThreadBuilder {
clear_child_tid,
sig_mask,
sig_queues,
is_main_thread,
} = self;
let thread = Arc::new_cyclic(|thread_ref| {
@ -104,7 +95,6 @@ impl PosixThreadBuilder {
let posix_thread = PosixThread {
process,
is_main_thread,
name: Mutex::new(thread_name),
set_child_tid: Mutex::new(set_child_tid),
clear_child_tid: Mutex::new(clear_child_tid),

View File

@ -0,0 +1,65 @@
// SPDX-License-Identifier: MPL-2.0
use super::{futex::futex_wake, robust_list::wake_robust_futex, PosixThread, PosixThreadExt};
use crate::{
prelude::*,
process::{do_exit_group, TermStatus},
thread::{thread_table, Thread, Tid},
util::write_val_to_user,
};
/// Exits the thread if the thread is a POSIX thread.
///
/// # Panics
///
/// If the thread is not a POSIX thread, this method will panic.
pub fn do_exit(thread: Arc<Thread>, term_status: TermStatus) -> Result<()> {
if thread.status().is_exited() {
return Ok(());
}
thread.exit();
let tid = thread.tid();
let posix_thread = thread.as_posix_thread().unwrap();
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 {
futex_wake(*clear_ctid, 1)?;
// FIXME: the correct write length?
write_val_to_user(*clear_ctid, &0u32).unwrap();
*clear_ctid = 0;
}
// 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)?;
Ok(())
}
/// Walks the robust futex list, marking futex dead and wake waiters.
/// It corresponds to Linux's exit_robust_list(), errors are silently ignored.
fn wake_robust_list(thread: &PosixThread, tid: Tid) {
let mut robust_list = thread.robust_list.lock();
let list_head = match *robust_list {
Some(robust_list_head) => robust_list_head,
None => return,
};
trace!("wake the rubust_list: {:?}", list_head);
for futex_addr in list_head.futexes() {
wake_robust_futex(futex_addr, tid).unwrap();
}
*robust_list = None;
}

View File

@ -3,11 +3,8 @@
#![allow(dead_code)]
use aster_rights::{ReadOp, WriteOp};
use futex::futex_wake;
use robust_list::wake_robust_futex;
use super::{
do_exit_group,
kill::SignalSenderIds,
signal::{
sig_mask::{SigMask, SigSet},
@ -16,24 +13,25 @@ use super::{
signals::Signal,
SigEvents, SigEventsFilter, SigStack,
},
Credentials, Process, TermStatus,
Credentials, Process,
};
use crate::{
events::Observer,
prelude::*,
process::signal::constants::SIGCONT,
thread::{thread_table, Tid},
thread::Tid,
time::{clocks::ProfClock, Timer, TimerManager},
util::write_val_to_user,
};
mod builder;
mod exit;
pub mod futex;
mod name;
mod posix_thread_ext;
mod robust_list;
pub use builder::PosixThreadBuilder;
pub use exit::do_exit;
pub use name::{ThreadName, MAX_THREAD_NAME_LEN};
pub use posix_thread_ext::PosixThreadExt;
pub use robust_list::RobustListHead;
@ -41,7 +39,6 @@ pub use robust_list::RobustListHead;
pub struct PosixThread {
// Immutable part
process: Weak<Process>,
is_main_thread: bool,
// Mutable part
name: Mutex<Option<ThreadName>>,
@ -224,13 +221,13 @@ impl PosixThread {
&self.robust_list
}
/// Whether the thread is main thread. For Posix thread, If a thread's tid is equal to pid, it's main thread.
pub fn is_main_thread(&self) -> bool {
self.is_main_thread
fn is_main_thread(&self, tid: Tid) -> bool {
let process = self.process();
let pid = process.pid();
tid == pid
}
/// whether the thread is the last running thread in process
pub fn is_last_thread(&self) -> bool {
fn is_last_thread(&self) -> bool {
let process = self.process.upgrade().unwrap();
let threads = process.threads().lock();
threads
@ -240,62 +237,6 @@ impl PosixThread {
== 0
}
/// Walks the robust futex list, marking futex dead and wake waiters.
/// It corresponds to Linux's exit_robust_list(), errors are silently ignored.
pub fn wake_robust_list(&self, tid: Tid) {
let mut robust_list = self.robust_list.lock();
let list_head = match *robust_list {
None => {
return;
}
Some(robust_list_head) => robust_list_head,
};
debug!("wake the rubust_list: {:?}", list_head);
for futex_addr in list_head.futexes() {
// debug!("futex addr = 0x{:x}", futex_addr);
wake_robust_futex(futex_addr, tid).unwrap();
}
debug!("wake robust futex success");
*robust_list = None;
}
/// Posix thread does not contains tid info. So we require tid as a parameter.
pub fn exit(&self, tid: Tid, term_status: TermStatus) -> Result<()> {
let mut clear_ctid = self.clear_child_tid().lock();
// If clear_ctid !=0 ,do a futex wake and write zero to the clear_ctid addr.
debug!("wake up ctid");
if *clear_ctid != 0 {
debug!("futex wake");
futex_wake(*clear_ctid, 1)?;
debug!("write ctid");
// FIXME: the correct write length?
debug!("ctid = 0x{:x}", *clear_ctid);
write_val_to_user(*clear_ctid, &0u32).unwrap();
debug!("clear ctid");
*clear_ctid = 0;
}
debug!("wake up ctid succeeds");
// exit the robust list: walk the robust list; mark futex words as dead and do futex wake
self.wake_robust_list(tid);
if tid != self.process().pid() {
// If the thread is not main thread. We don't remove main thread.
// Main thread are removed when the whole process is reaped.
thread_table::remove_thread(tid);
}
if self.is_main_thread() || self.is_last_thread() {
// exit current process.
debug!("self is main thread or last thread");
debug!("main thread: {}", self.is_main_thread());
debug!("last thread: {}", self.is_last_thread());
do_exit_group(term_status);
}
debug!("perform futex wake");
futex_wake(Arc::as_ptr(&self.process()) as Vaddr, 1)?;
Ok(())
}
/// Gets the read-only credentials of the thread.
pub fn credentials(&self) -> Credentials<ReadOp> {
self.credentials.dup().restrict()

View File

@ -181,12 +181,12 @@ impl<'a> ProcessBuilder<'a> {
threads,
executable_path.to_string(),
process_vm,
file_table,
fs,
file_table,
umask,
sig_dispositions,
resource_limits,
nice,
sig_dispositions,
)
};

View File

@ -7,7 +7,11 @@ use super::{
process_vm::{Heap, InitStackReader, ProcessVm},
rlimit::ResourceLimits,
signal::{
constants::SIGCHLD, sig_disposition::SigDispositions, sig_mask::SigMask, signals::Signal,
constants::SIGCHLD,
sig_disposition::SigDispositions,
sig_mask::SigMask,
sig_num::{AtomicSigNum, SigNum},
signals::Signal,
Pauser,
},
status::ProcessStatus,
@ -89,6 +93,8 @@ pub struct Process {
// Signal
/// Sig dispositions
sig_dispositions: Arc<Mutex<SigDispositions>>,
/// The signal that the process should receive when parent process exits.
parent_death_signal: AtomicSigNum,
/// A profiling clock measures the user CPU time and kernel CPU time of the current process.
prof_clock: Arc<ProfClock>,
@ -105,12 +111,14 @@ impl Process {
threads: Vec<Arc<Thread>>,
executable_path: String,
process_vm: ProcessVm,
file_table: Arc<Mutex<FileTable>>,
fs: Arc<RwMutex<FsResolver>>,
file_table: Arc<Mutex<FileTable>>,
umask: Arc<RwLock<FileCreationMask>>,
sig_dispositions: Arc<Mutex<SigDispositions>>,
resource_limits: ResourceLimits,
nice: Nice,
sig_dispositions: Arc<Mutex<SigDispositions>>,
) -> Arc<Self> {
let children_pauser = {
// SIGCHID does not interrupt pauser. Child process will
@ -135,6 +143,7 @@ impl Process {
fs,
umask,
sig_dispositions,
parent_death_signal: AtomicSigNum::new_empty(),
resource_limits: Mutex::new(resource_limits),
nice: Atomic::new(nice),
timer_manager: PosixTimerManager::new(&prof_clock, process_ref),
@ -587,6 +596,24 @@ impl Process {
posix_thread.enqueue_signal(Box::new(signal));
}
/// Clears the parent death signal.
pub fn clear_parent_death_signal(&self) {
self.parent_death_signal.clear();
}
/// Sets the parent death signal as `signum`.
pub fn set_parent_death_signal(&self, sig_num: SigNum) {
self.parent_death_signal.set(sig_num);
}
/// Returns the parent death signal.
///
/// The parent death signal is the signal will be sent to child processes
/// when the process exits.
pub fn parent_death_signal(&self) -> Option<SigNum> {
self.parent_death_signal.as_sig_num()
}
// ******************* Status ********************
fn set_runnable(&self) {
@ -642,12 +669,12 @@ mod test {
vec![],
String::new(),
ProcessVm::alloc(),
Arc::new(Mutex::new(FileTable::new())),
Arc::new(RwMutex::new(FsResolver::new())),
Arc::new(Mutex::new(FileTable::new())),
Arc::new(RwLock::new(FileCreationMask::default())),
Arc::new(Mutex::new(SigDispositions::default())),
ResourceLimits::default(),
Nice::default(),
Arc::new(Mutex::new(SigDispositions::default())),
)
}

View File

@ -1,5 +1,11 @@
// SPDX-License-Identifier: MPL-2.0
#![allow(dead_code)]
use core::sync::atomic::AtomicU8;
use atomic::Ordering;
use super::constants::*;
use crate::prelude::*;
@ -77,3 +83,46 @@ impl SigNum {
}
}
}
/// Atomic signal number.
///
/// This struct represents a signal number and is different from [SigNum]
/// in that it allows for an empty signal number.
pub struct AtomicSigNum(AtomicU8);
impl AtomicSigNum {
/// Creates a new empty atomic signal number
pub const fn new_empty() -> Self {
Self(AtomicU8::new(0))
}
/// Creates a new signal number with the specified value
pub const fn new(sig_num: SigNum) -> Self {
Self(AtomicU8::new(sig_num.as_u8()))
}
/// Determines whether the signal number is empty
pub fn is_empty(&self) -> bool {
self.0.load(Ordering::Relaxed) == 0
}
/// Returns the corresponding [`SigNum`]
pub fn as_sig_num(&self) -> Option<SigNum> {
let sig_num = self.0.load(Ordering::Relaxed);
if sig_num == 0 {
return None;
}
Some(SigNum::from_u8(sig_num))
}
/// Sets the new `sig_num`
pub fn set(&self, sig_num: SigNum) {
self.0.store(sig_num.as_u8(), Ordering::Relaxed)
}
/// Clears the signal number
pub fn clear(&self) {
self.0.store(0, Ordering::Relaxed)
}
}

View File

@ -16,7 +16,7 @@ use crate::{
process::{
check_executable_file, credentials_mut, load_program_to_vm,
posix_thread::{PosixThreadExt, ThreadName},
Credentials, MAX_ARGV_NUMBER, MAX_ARG_LEN, MAX_ENVP_NUMBER, MAX_ENV_LEN,
Credentials, Process, MAX_ARGV_NUMBER, MAX_ARG_LEN, MAX_ENVP_NUMBER, MAX_ENV_LEN,
},
util::{read_cstring_from_user, read_val_from_user},
};
@ -118,8 +118,8 @@ fn do_execve(
debug!("load elf in execve succeeds");
let credentials = credentials_mut();
set_uid_from_elf(&credentials, &elf_file)?;
set_gid_from_elf(&credentials, &elf_file)?;
set_uid_from_elf(&current, &credentials, &elf_file)?;
set_gid_from_elf(&current, &credentials, &elf_file)?;
// set executable path
current.set_executable_path(new_executable_path);
@ -177,10 +177,16 @@ fn read_cstring_vec(
}
/// Sets uid for credentials as the same of uid of elf file if elf file has `set_uid` bit.
fn set_uid_from_elf(credentials: &Credentials<WriteOp>, elf_file: &Arc<Dentry>) -> Result<()> {
fn set_uid_from_elf(
current: &Arc<Process>,
credentials: &Credentials<WriteOp>,
elf_file: &Arc<Dentry>,
) -> Result<()> {
if elf_file.mode()?.has_set_uid() {
let uid = elf_file.owner()?;
credentials.set_euid(uid);
current.clear_parent_death_signal();
}
// No matter whether the elf_file has `set_uid` bit, suid should be reset.
@ -189,10 +195,16 @@ fn set_uid_from_elf(credentials: &Credentials<WriteOp>, elf_file: &Arc<Dentry>)
}
/// Sets gid for credentials as the same of gid of elf file if elf file has `set_gid` bit.
fn set_gid_from_elf(credentials: &Credentials<WriteOp>, elf_file: &Arc<Dentry>) -> Result<()> {
fn set_gid_from_elf(
current: &Arc<Process>,
credentials: &Credentials<WriteOp>,
elf_file: &Arc<Dentry>,
) -> Result<()> {
if elf_file.mode()?.has_set_gid() {
let gid = elf_file.group()?;
credentials.set_egid(gid);
current.clear_parent_death_signal();
}
// No matter whether the the elf file has `set_gid` bit, sgid should be reset.

View File

@ -2,7 +2,7 @@
use crate::{
prelude::*,
process::{posix_thread::PosixThreadExt, TermStatus},
process::{posix_thread::do_exit, TermStatus},
syscall::SyscallReturn,
};
@ -10,14 +10,8 @@ pub fn sys_exit(exit_code: i32) -> Result<SyscallReturn> {
debug!("exid code = {}", exit_code);
let current_thread = current_thread!();
current_thread.exit();
let tid = current_thread.tid();
let pid = current!().pid();
debug!("tid = {}, pid = {}", tid, pid);
let posix_thread = current_thread.as_posix_thread().unwrap();
posix_thread.exit(tid, TermStatus::Exited(exit_code as _))?;
let term_status = TermStatus::Exited(exit_code as _);
do_exit(current_thread, term_status)?;
Ok(SyscallReturn::Return(0))
}

View File

@ -6,15 +6,35 @@
use super::SyscallReturn;
use crate::{
prelude::*,
process::posix_thread::{PosixThreadExt, MAX_THREAD_NAME_LEN},
util::{read_cstring_from_user, write_bytes_to_user},
process::{
posix_thread::{PosixThreadExt, MAX_THREAD_NAME_LEN},
signal::sig_num::SigNum,
},
util::{read_cstring_from_user, write_bytes_to_user, write_val_to_user},
};
pub fn sys_prctl(option: i32, arg2: u64, arg3: u64, arg4: u64, arg5: u64) -> Result<SyscallReturn> {
let prctl_cmd = PrctlCmd::from_args(option, arg2, arg3, arg4, arg5)?;
debug!("prctl cmd = {:x?}", prctl_cmd);
let current_thread = current_thread!();
let posix_thread = current_thread.as_posix_thread().unwrap();
match prctl_cmd {
PrctlCmd::PR_SET_PDEATHSIG(signum) => {
let current = current!();
current.set_parent_death_signal(signum);
}
PrctlCmd::PR_GET_PDEATHSIG(write_to_addr) => {
let write_val = {
let current = current!();
match current.parent_death_signal() {
None => 0i32,
Some(signum) => signum.as_u8() as i32,
}
};
write_val_to_user(write_to_addr, &write_val)?;
}
PrctlCmd::PR_GET_NAME(write_to_addr) => {
let thread_name = posix_thread.thread_name().lock();
if let Some(thread_name) = &*thread_name {
@ -35,6 +55,8 @@ pub fn sys_prctl(option: i32, arg2: u64, arg3: u64, arg4: u64, arg5: u64) -> Res
Ok(SyscallReturn::Return(0))
}
const PR_SET_PDEATHSIG: i32 = 1;
const PR_GET_PDEATHSIG: i32 = 2;
const PR_SET_NAME: i32 = 15;
const PR_GET_NAME: i32 = 16;
const PR_SET_TIMERSLACK: i32 = 29;
@ -43,6 +65,8 @@ const PR_GET_TIMERSLACK: i32 = 30;
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Copy)]
pub enum PrctlCmd {
PR_SET_PDEATHSIG(SigNum),
PR_GET_PDEATHSIG(Vaddr),
PR_SET_NAME(Vaddr),
PR_GET_NAME(Vaddr),
PR_SET_TIMERSLACK(u64),
@ -52,6 +76,11 @@ pub enum PrctlCmd {
impl PrctlCmd {
fn from_args(option: i32, arg2: u64, arg3: u64, arg4: u64, arg5: u64) -> Result<PrctlCmd> {
match option {
PR_SET_PDEATHSIG => {
let signum = SigNum::try_from(arg2 as u8)?;
Ok(PrctlCmd::PR_SET_PDEATHSIG(signum))
}
PR_GET_PDEATHSIG => Ok(PrctlCmd::PR_GET_PDEATHSIG(arg2 as _)),
PR_SET_NAME => Ok(PrctlCmd::PR_SET_NAME(arg2 as _)),
PR_GET_NAME => Ok(PrctlCmd::PR_GET_NAME(arg2 as _)),
PR_GET_TIMERSLACK => todo!(),

View File

@ -65,13 +65,13 @@ impl Thread {
&self.task
}
/// Run this thread at once.
/// Runs this thread at once.
pub fn run(&self) {
self.set_status(ThreadStatus::Running);
self.task.run();
}
pub fn exit(&self) {
pub(super) fn exit(&self) {
self.set_status(ThreadStatus::Exited);
}
@ -98,14 +98,16 @@ impl Thread {
self.tid
}
// The return type must be borrowed box, otherwise the downcast_ref will fail
/// Returns the associated data.
///
/// The return type must be borrowed box, otherwise the `downcast_ref` will fail.
#[allow(clippy::borrowed_box)]
pub fn data(&self) -> &Box<dyn Send + Sync + Any> {
&self.data
}
}
/// allocate a new pid for new process
/// Allocates a new tid for the new thread
pub fn allocate_tid() -> Tid {
TID_ALLOCATOR.fetch_add(1, Ordering::SeqCst)
}

View File

@ -23,6 +23,7 @@ itimer/timer_create
mmap/map_shared_anon
pthread/pthread_test
pty/open_pty
signal_c/parent_death_signal
signal_c/signal_test
"

View File

@ -0,0 +1,49 @@
// SPDX-License-Identifier: MPL-2.0
#define _GNU_SOURCE
#include <sys/prctl.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
void signal_handler(int signum)
{
if (signum == SIGTERM) {
printf("child process reveives SIGTERM\n");
exit(EXIT_SUCCESS);
}
}
int main()
{
pid_t pid = fork();
if (pid == -1) {
perror("fork");
return EXIT_FAILURE;
}
if (pid > 0) {
printf("Parent PID: %d\n", getpid());
// Ensure parent won't exit before child process runs
sleep(1);
} else {
printf("CHild PID: %d\n", getpid());
prctl(PR_SET_PDEATHSIG, SIGTERM);
struct sigaction sa;
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGTERM, &sa, NULL);
// Child waits for signal from parent death
while (1) {
sleep(1);
}
}
return EXIT_SUCCESS;
}