Re-introduce the process-wide sigqueues

This commit is contained in:
jiangjianfeng 2025-10-28 03:41:13 +00:00 committed by Tate, Hongliang Tian
parent c7058c7233
commit 00c79732e6
11 changed files with 295 additions and 134 deletions

View File

@ -17,7 +17,7 @@ use crate::{
signal::{
constants::{SIGCHLD, SIGKILL},
signals::kernel::KernelSignal,
SigStack,
HandlePendingSignal, SigStack,
},
ContextUnshareAdminApi, Credentials, Process, ProgramToLoad,
},
@ -192,7 +192,7 @@ fn wait_other_threads_exit(ctx: &Context) -> Result<()> {
let (waiter, waker) = Waiter::new_pair();
ctx.posix_thread.set_signalled_waker(waker.clone());
if ctx.posix_thread.has_pending_sigkill() {
if ctx.has_pending_sigkill() {
ctx.posix_thread.clear_signalled_waker();
return_errno_with_message!(Errno::EAGAIN, "the current thread has received SIGKILL");
}

View File

@ -6,15 +6,7 @@ use aster_rights::{ReadDupOp, ReadOp, WriteOp};
use ostd::sync::{RoArc, RwMutexReadGuard, Waker};
use super::{
kill::SignalSenderIds,
signal::{
sig_disposition::SigDispositions,
sig_mask::{AtomicSigMask, SigMask, SigSet},
sig_num::SigNum,
sig_queues::SigQueues,
signals::Signal,
SigEvents, SigEventsFilter,
},
signal::{sig_mask::AtomicSigMask, sig_num::SigNum, sig_queues::SigQueues, signals::Signal},
Credentials, Process,
};
use crate::{
@ -22,8 +14,11 @@ use crate::{
fs::{file_table::FileTable, thread_info::ThreadFsInfo},
prelude::*,
process::{
kill::SignalSenderIds,
namespace::nsproxy::NsProxy,
signal::constants::{SIGCONT, SIGKILL},
signal::{
constants::SIGCONT, sig_disposition::SigDispositions, SigEvents, SigEventsFilter,
},
Pid,
},
thread::{Thread, Tid},
@ -141,25 +136,12 @@ impl PosixThread {
&self.sig_mask
}
pub fn sig_pending(&self) -> SigSet {
self.sig_queues.sig_pending()
}
/// Returns whether the thread has some pending signals
/// that are not blocked.
pub fn has_pending(&self) -> bool {
let blocked = self.sig_mask().load(Ordering::Relaxed);
self.sig_queues.has_pending(blocked)
}
/// Returns whether the thread has pending SIGKILL signal.
pub fn has_pending_sigkill(&self) -> bool {
let blocked = SigSet::new_full() - SIGKILL;
self.sig_queues.has_pending(blocked)
pub(super) fn sig_queues(&self) -> &SigQueues {
&self.sig_queues
}
/// Returns whether the signal is blocked by the thread.
pub(in crate::process) fn has_signal_blocked(&self, signum: SigNum) -> bool {
pub fn has_signal_blocked(&self, signum: SigNum) -> bool {
// FIXME: Some signals cannot be blocked, even set in sig_mask.
self.sig_mask.contains(signum, Ordering::Relaxed)
}
@ -242,20 +224,12 @@ impl PosixThread {
/// Enqueues a thread-directed signal.
///
/// This method does not perform permission checks on user signals. Therefore, unless the
/// caller can ensure that there are no permission issues, this method should be used for
/// enqueue kernel signals or fault signals.
/// This method does not perform permission checks on user signals.
/// Therefore, unless the caller can ensure that there are no permission issues,
/// this method should be used to enqueue kernel signals or fault signals.
pub fn enqueue_signal(&self, signal: Box<dyn Signal>) {
let process = self.process();
let sig_dispositions = process.sig_dispositions().lock();
let sig_dispositions = sig_dispositions.lock();
let signum = signal.num();
if sig_dispositions.get(signum).will_ignore(signum) {
return;
}
self.enqueue_signal_locked(signal, &sig_dispositions);
self.sig_queues.enqueue(signal);
self.wake_signalled_waker();
}
/// Enqueues a thread-directed signal with locked dispositions.
@ -305,10 +279,6 @@ impl PosixThread {
self.prof_timer_manager.process_expired_timers();
}
pub fn dequeue_signal(&self, mask: &SigMask) -> Option<Box<dyn Signal>> {
self.sig_queues.dequeue(mask)
}
pub fn register_sigqueue_observer(
&self,
observer: Weak<dyn Observer<SigEvents>>,

View File

@ -21,7 +21,11 @@ use super::{
};
use crate::{
prelude::*,
process::{signal::Pollee, status::StopWaitStatus, UserNamespace, WaitOptions},
process::{
signal::{sig_queues::SigQueues, Pollee},
status::StopWaitStatus,
UserNamespace, WaitOptions,
},
sched::{AtomicNice, Nice},
thread::{AsThread, Thread},
time::clocks::ProfClock,
@ -120,6 +124,8 @@ pub struct Process {
// Signal
/// Sig dispositions
sig_dispositions: Mutex<Arc<Mutex<SigDispositions>>>,
/// The process-level sigqueue.
sig_queues: SigQueues,
/// The signal that the process should receive when parent process exits.
parent_death_signal: AtomicSigNum,
@ -234,6 +240,7 @@ impl Process {
is_child_subreaper: AtomicBool::new(false),
has_child_subreaper: AtomicBool::new(false),
sig_dispositions: Mutex::new(sig_dispositions),
sig_queues: SigQueues::new(),
parent_death_signal: AtomicSigNum::new_empty(),
exit_signal: AtomicSigNum::new_empty(),
resource_limits,
@ -621,44 +628,31 @@ impl Process {
&self.sig_dispositions
}
pub(super) fn sig_queues(&self) -> &SigQueues {
&self.sig_queues
}
/// Enqueues a process-directed signal.
///
/// This method should only be used for enqueue kernel signals and fault signals.
///
/// The signal may be delivered to any one of the threads that does not currently have the
/// signal blocked. If more than one of the threads have the signal unblocked, then this method
/// chooses an arbitrary thread to which to deliver the signal.
//
// TODO: Restrict this method with the access control tool.
/// This method does not perform permission checks on user signals.
/// Therefore, unless the caller can ensure that there are no permission issues,
/// this method should be used to enqueue kernel signals or fault signals.
pub fn enqueue_signal(&self, signal: impl Signal + Clone + 'static) {
if self.status.is_zombie() {
return;
}
let sig_dispositions = self.sig_dispositions.lock();
let sig_dispositions = sig_dispositions.lock();
self.sig_queues.enqueue(Box::new(signal));
// Drop the signal if it's ignored. See explanation at `enqueue_signal_locked`.
let signum = signal.num();
if sig_dispositions.get(signum).will_ignore(signum) {
return;
for task in self.tasks.lock().as_slice() {
let posix_thread = task.as_posix_thread().unwrap();
// FIXME: This behavior differs a bit from Linux.
// Linux wakes up a single thread that neither blocks the signal
// nor already has the same pending signal;
// for simplicity we wake up all threads.
// Reference: <https://elixir.bootlin.com/linux/v6.17/source/kernel/signal.c#L969>.
posix_thread.wake_signalled_waker();
}
let threads = self.tasks.lock();
// Enqueue the signal to the first thread that does not block the signal.
for thread in threads.as_slice() {
let posix_thread = thread.as_posix_thread().unwrap();
if !posix_thread.has_signal_blocked(signal.num()) {
posix_thread.enqueue_signal_locked(Box::new(signal), &sig_dispositions);
return;
}
}
// If all threads block the signal, enqueue the signal to the main thread.
let thread = threads.main();
let posix_thread = thread.as_posix_thread().unwrap();
posix_thread.enqueue_signal_locked(Box::new(signal), &sig_dispositions);
}
/// Clears the parent death signal.

View File

@ -4,6 +4,7 @@ pub mod c_types;
pub mod constants;
mod events;
mod pause;
mod pending;
mod poll;
pub mod sig_action;
pub mod sig_disposition;
@ -24,6 +25,7 @@ use ostd::{
user::UserContextApi,
};
pub use pause::{with_sigmask_changed, Pause};
pub use pending::HandlePendingSignal;
pub use poll::{PollAdaptor, PollHandle, Pollable, Pollee, Poller};
use sig_action::{SigAction, SigActionFlags, SigDefaultAction};
use sig_mask::SigMask;
@ -35,7 +37,11 @@ use crate::{
cpu::LinuxAbi,
current_userspace,
prelude::*,
process::{posix_thread::do_exit_group, signal::c_types::stack_t, TermStatus},
process::{
posix_thread::do_exit_group,
signal::{c_types::stack_t, signals::Signal},
TermStatus,
},
};
pub trait SignalContext {
@ -61,38 +67,11 @@ pub fn handle_pending_signal(
None
};
let posix_thread = ctx.posix_thread;
let current = ctx.process.as_ref();
let signal = {
let sig_mask = posix_thread.sig_mask().load(Ordering::Relaxed);
if let Some(signal) = posix_thread.dequeue_signal(&sig_mask) {
signal
} else {
return;
}
let Some((signal, sig_action)) = dequeue_pending_signal(ctx) else {
return;
};
let sig_num = signal.num();
trace!("sig_num = {:?}, sig_name = {}", sig_num, sig_num.sig_name());
let sig_action = {
let sig_dispositions = current.sig_dispositions().lock();
let mut sig_dispositions = sig_dispositions.lock();
let sig_action = sig_dispositions.get(sig_num);
if let SigAction::User { flags, .. } = &sig_action
&& flags.contains(SigActionFlags::SA_RESETHAND)
{
// In Linux, SA_RESETHAND corresponds to SA_ONESHOT,
// which means the user handler will be executed only once and then reset to the default.
// Refer to https://elixir.bootlin.com/linux/v6.0.9/source/kernel/signal.c#L2761.
sig_dispositions.set_default(sig_num);
}
sig_action
};
trace!("sig action: {:x?}", sig_action);
match sig_action {
SigAction::Ign => {
trace!("Ignore signal {:?}", sig_num);
@ -141,7 +120,7 @@ pub fn handle_pending_signal(
SigDefaultAction::Core | SigDefaultAction::Term => {
warn!(
"{:?}: terminating on signal {}",
current.executable_path(),
ctx.process.executable_path(),
sig_num.sig_name()
);
// We should exit current here, since we cannot restore a valid status from trap now.
@ -155,6 +134,43 @@ pub fn handle_pending_signal(
}
}
fn dequeue_pending_signal(ctx: &Context) -> Option<(Box<dyn Signal>, SigAction)> {
let posix_thread = ctx.posix_thread;
let sig_dispositions = ctx.process.sig_dispositions().lock();
let mut sig_dispositions = sig_dispositions.lock();
let sig_mask = posix_thread.sig_mask().load(Ordering::Relaxed);
let (signal, sig_num, sig_action) = loop {
let signal = ctx.dequeue_signal(&sig_mask)?;
let sig_num = signal.num();
let sig_action = sig_dispositions.get(sig_num);
if sig_action.will_ignore(sig_num) {
continue;
}
break (signal, sig_num, sig_action);
};
if let SigAction::User { flags, .. } = &sig_action
&& flags.contains(SigActionFlags::SA_RESETHAND)
{
// In Linux, SA_RESETHAND corresponds to SA_ONESHOT,
// which means the user handler will be executed only once and then reset to the default.
// Reference: <https://elixir.bootlin.com/linux/v6.0.9/source/kernel/signal.c#L2761>.
sig_dispositions.set_default(sig_num);
}
trace!(
"sig_num = {:?}, sig_name = {}, sig_action = {:#x?}",
signal.num(),
signal.num().sig_name(),
sig_action
);
Some((signal, sig_action))
}
#[expect(clippy::too_many_arguments)]
pub fn handle_user_signal(
ctx: &Context,

View File

@ -7,7 +7,7 @@ use ostd::sync::{WaitQueue, Waiter};
use super::sig_mask::SigMask;
use crate::{
prelude::*,
process::posix_thread::AsPosixThread,
process::{posix_thread::AsPosixThread, signal::HandlePendingSignal},
thread::AsThread,
time::wait::{ManagedTimeout, TimeoutExt},
};

View File

@ -0,0 +1,100 @@
// SPDX-License-Identifier: MPL-2.0
use core::sync::atomic::Ordering;
use crate::{
prelude::*,
process::{
posix_thread::PosixThread,
signal::{
constants::SIGKILL,
sig_mask::{SigMask, SigSet},
signals::Signal,
},
Process,
},
};
/// Trait for handling pending signals.
pub trait HandlePendingSignal {
/// Returns the thread's pending signal set.
///
/// This includes signals that are currently blocked or ignored.
fn pending_signals(&self) -> SigSet;
/// Returns if there are pending signals that are neither blocked nor ignored.
///
/// Note that ignored but not blocked signals may be dequeued silently.
fn has_pending(&self) -> bool;
/// Returns if a SIGKILL signal is pending.
fn has_pending_sigkill(&self) -> bool;
/// Dequeues the next pending signal that is not masked by `mask`.
///
/// Returns `None` if no such signal is available.
fn dequeue_signal(&self, mask: &SigMask) -> Option<Box<dyn Signal>>;
}
impl HandlePendingSignal for Context<'_> {
fn pending_signals(&self) -> SigSet {
self.posix_thread.sig_queues().sig_pending() | self.process.sig_queues().sig_pending()
}
fn has_pending(&self) -> bool {
let posix_thread = self.posix_thread;
let process = self.process.as_ref();
has_pending_signal(posix_thread, process)
}
fn has_pending_sigkill(&self) -> bool {
self.posix_thread.sig_queues().has_pending_signal(SIGKILL)
|| self.process.sig_queues().has_pending_signal(SIGKILL)
}
fn dequeue_signal(&self, mask: &SigMask) -> Option<Box<dyn Signal>> {
self.posix_thread
.sig_queues()
.dequeue(mask)
.or_else(|| self.process.sig_queues().dequeue(mask))
}
}
impl HandlePendingSignal for PosixThread {
fn pending_signals(&self) -> SigSet {
self.sig_queues().sig_pending() | self.process().sig_queues().sig_pending()
}
fn has_pending(&self) -> bool {
let process = self.process();
has_pending_signal(self, process.as_ref())
}
fn has_pending_sigkill(&self) -> bool {
self.sig_queues().has_pending_signal(SIGKILL)
|| self.process().sig_queues().has_pending_signal(SIGKILL)
}
fn dequeue_signal(&self, mask: &SigMask) -> Option<Box<dyn Signal>> {
self.sig_queues()
.dequeue(mask)
.or_else(|| self.process().sig_queues().dequeue(mask))
}
}
fn has_pending_signal(posix_thread: &PosixThread, process: &Process) -> bool {
// Fast path: No signals are pending.
if posix_thread.sig_queues().is_empty() && process.sig_queues().is_empty() {
return false;
}
// Slow path: Some signals are pending.
let sig_dispositions = process.sig_dispositions().lock();
let sig_dispositions = sig_dispositions.lock();
let blocked = posix_thread.sig_mask().load(Ordering::Relaxed);
posix_thread
.sig_queues()
.has_pending(blocked, &sig_dispositions)
|| process.sig_queues().has_pending(blocked, &sig_dispositions)
}

View File

@ -1,7 +1,10 @@
// SPDX-License-Identifier: MPL-2.0
use super::{constants::*, sig_action::SigAction, sig_num::SigNum};
use crate::{prelude::*, process::signal::sig_action::SigActionFlags};
use crate::{
prelude::*,
process::signal::{sig_action::SigActionFlags, signals::Signal},
};
#[derive(Copy, Clone)]
pub struct SigDispositions {
@ -53,6 +56,11 @@ impl SigDispositions {
fn num_to_idx(num: SigNum) -> usize {
(num.as_u8() - MIN_STD_SIG_NUM) as usize
}
pub fn will_ignore(&self, signal: &dyn Signal) -> bool {
let signum = signal.num();
self.get(signum).will_ignore(signum)
}
}
fn check_sigaction(sig_action: &SigAction) -> Result<()> {

View File

@ -12,6 +12,7 @@ use super::{
use crate::{
events::{Observer, Subject},
prelude::*,
process::signal::sig_disposition::SigDispositions,
};
pub struct SigQueues {
@ -67,12 +68,22 @@ impl SigQueues {
queues.sig_pending()
}
/// Returns whether there's some pending signals that are not blocked
pub fn has_pending(&self, blocked: SigMask) -> bool {
if self.is_empty() {
return false;
}
self.queues.lock().has_pending(blocked)
/// Returns whether there's some pending signals that are not blocked and not ignored.
///
/// Note that ignored but not blocked signals may be dequeued silently.
pub(in crate::process) fn has_pending(
&self,
blocked: SigMask,
sig_dispositions: &SigDispositions,
) -> bool {
let mut queues = self.queues.lock();
let (dequeued_signals, has_pending) = queues.has_pending(blocked, sig_dispositions);
self.count.fetch_sub(dequeued_signals, Ordering::Relaxed);
has_pending
}
pub(in crate::process) fn has_pending_signal(&self, signum: SigNum) -> bool {
self.queues.lock().has_pending_signal(signum)
}
pub fn register_observer(
@ -198,13 +209,69 @@ impl Queues {
None
}
/// Returns whether the `SigQueues` has some pending signals which are not blocked
fn has_pending(&self, blocked: SigMask) -> bool {
self.std_queues.iter().any(|signal| {
signal
.as_ref()
.is_some_and(|signal| !blocked.contains(signal.num()))
}) || self.rt_queues.iter().any(|rt_queue| !rt_queue.is_empty())
/// Returns whether the `SigQueues` has some pending signals which are not blocked.
///
/// Note that signals are ignored but not blocked may be dequeued silently.
fn has_pending(
&mut self,
blocked: SigMask,
sig_dispositions: &SigDispositions,
) -> (usize, bool) {
let mut dequeued_signals = 0;
let has_pending = self.std_queues.iter_mut().any(|signal| {
let Some(signal_ref) = signal.as_ref().map(AsRef::as_ref) else {
return false;
};
if blocked.contains(signal_ref.num()) {
return false;
}
// Dequeue signals that should be ignored but not blocked.
if sig_dispositions.will_ignore(signal_ref) {
dequeued_signals += 1;
*signal = None;
return false;
}
true
}) || self.rt_queues.iter_mut().any(|rt_queue| {
let Some(signal) = rt_queue.front() else {
return false;
};
if blocked.contains(signal.num()) {
return false;
}
// Dequeue signals that should be ignored but not blocked.
if sig_dispositions.will_ignore(signal.as_ref()) {
dequeued_signals += rt_queue.len();
rt_queue.clear();
return false;
}
true
});
(dequeued_signals, has_pending)
}
fn has_pending_signal(&self, signum: SigNum) -> bool {
if signum.is_std() {
self.get_std_queue(signum).is_some()
} else if signum.is_real_time() {
!self.get_rt_queue(signum).is_empty()
} else {
false
}
}
fn get_std_queue(&self, signum: SigNum) -> &Option<Box<dyn Signal>> {
debug_assert!(signum.is_std());
let idx = (signum.as_u8() - MIN_STD_SIG_NUM) as usize;
&self.std_queues[idx]
}
fn get_std_queue_mut(&mut self, signum: SigNum) -> &mut Option<Box<dyn Signal>> {
@ -213,6 +280,12 @@ impl Queues {
&mut self.std_queues[idx]
}
fn get_rt_queue(&self, signum: SigNum) -> &VecDeque<Box<dyn Signal>> {
debug_assert!(signum.is_real_time());
let idx = (signum.as_u8() - MIN_RT_SIG_NUM) as usize;
&self.rt_queues[idx]
}
fn get_rt_queue_mut(&mut self, signum: SigNum) -> &mut VecDeque<Box<dyn Signal>> {
debug_assert!(signum.is_real_time());
let idx = (signum.as_u8() - MIN_RT_SIG_NUM) as usize;

View File

@ -11,6 +11,7 @@ use crate::{
sig_action::SigAction,
sig_mask::SigSet,
sig_num::SigNum,
HandlePendingSignal,
},
},
};
@ -49,7 +50,10 @@ pub fn sys_rt_sigaction(
let sig_action_c = ctx.user_space().read_val::<sigaction_t>(sig_action_addr)?;
let sig_action = SigAction::from(sig_action_c);
trace!("sig action = {:?}", sig_action);
discard_signals_if_ignored(ctx, sig_num, &sig_action);
if sig_action.will_ignore(sig_num) {
discard_signals_if_ignored(ctx, sig_num);
}
sig_dispositions.set(sig_num, sig_action)?
} else {
sig_dispositions.get(sig_num)
@ -77,11 +81,7 @@ pub fn sys_rt_sigaction(
// pending and whose default action is to ignore the signal
// (for example, SIGCHLD), shall cause the pending signal to
// be discarded, whether or not it is blocked
fn discard_signals_if_ignored(ctx: &Context, signum: SigNum, sig_action: &SigAction) {
if !sig_action.will_ignore(signum) {
return;
}
fn discard_signals_if_ignored(ctx: &Context, signum: SigNum) {
let mask = SigSet::new_full() - signum;
for task in ctx.process.tasks().lock().as_slice() {
@ -89,6 +89,6 @@ fn discard_signals_if_ignored(ctx: &Context, signum: SigNum, sig_action: &SigAct
continue;
};
posix_thread.dequeue_signal(&mask);
while posix_thread.dequeue_signal(&mask).is_some() {}
}
}

View File

@ -3,7 +3,7 @@
use core::sync::atomic::Ordering;
use super::SyscallReturn;
use crate::prelude::*;
use crate::{prelude::*, process::signal::HandlePendingSignal};
pub fn sys_rt_sigpending(
u_set_ptr: Vaddr,
@ -24,7 +24,7 @@ pub fn sys_rt_sigpending(
fn do_rt_sigpending(set_ptr: Vaddr, ctx: &Context) -> Result<()> {
let combined_signals = {
let sig_mask_value = ctx.posix_thread.sig_mask().load(Ordering::Relaxed);
let sig_pending_value = ctx.posix_thread.sig_pending();
let sig_pending_value = ctx.pending_signals();
sig_mask_value & sig_pending_value
};

View File

@ -14,7 +14,7 @@ use crate::{
prelude::*,
process::{
posix_thread::{AsPosixThread, AsThreadLocal, ThreadLocal},
signal::handle_pending_signal,
signal::{handle_pending_signal, HandlePendingSignal},
},
syscall::handle_syscall,
thread::{exception::handle_exception, AsThread},
@ -61,8 +61,6 @@ pub fn create_new_user_task(
let _ = current_userspace!().write_val(child_tid_ptr, &current_posix_thread.tid());
}
let has_kernel_event_fn = || current_posix_thread.has_pending();
let ctx = Context {
process: current_process,
thread_local: current_thread_local,
@ -71,6 +69,8 @@ pub fn create_new_user_task(
task: &current_task,
};
let has_kernel_event_fn = || ctx.has_pending();
if is_init_process {
crate::init_in_first_process(&ctx);
}