Support stopping states in `proc/pid/stat`

This commit is contained in:
Wang Siyuan 2025-11-13 13:32:52 +00:00 committed by Ruihan Li
parent 7a7e62b318
commit 35ab40057a
7 changed files with 81 additions and 20 deletions

View File

@ -111,6 +111,8 @@ impl FileOps for StatFileOps {
SleepingState::Running => 'R',
SleepingState::Interruptible => 'S',
SleepingState::Uninterruptible => 'D',
SleepingState::StopBySignal => 'T',
SleepingState::StopByPtrace => 't',
}
};
let ppid = process.parent().pid();

View File

@ -100,6 +100,8 @@ impl FileOps for StatusFileOps {
SleepingState::Running => "R (running)",
SleepingState::Interruptible => "S (sleeping)",
SleepingState::Uninterruptible => "D (disk sleep)",
SleepingState::StopBySignal => "T (stopped)",
SleepingState::StopByPtrace => "t (tracing stop)",
}
};
writeln!(status_output, "State:\t{}", state).unwrap();

View File

@ -17,7 +17,7 @@ use crate::{
signal::{
constants::{SIGCHLD, SIGKILL},
signals::kernel::KernelSignal,
HandlePendingSignal, SigStack,
HandlePendingSignal, PauseReason, SigStack,
},
ContextUnshareAdminApi, Credentials, Process, ProgramToLoad,
},
@ -191,7 +191,8 @@ fn wait_other_threads_exit(ctx: &Context) -> Result<()> {
// Wait until any signal comes or any other thread exits.
let (waiter, waker) = Waiter::new_pair();
ctx.posix_thread.set_signalled_waker(waker.clone());
ctx.posix_thread
.set_signalled_waker(waker.clone(), PauseReason::Sleep);
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

@ -16,7 +16,11 @@ use crate::{
events::IoEvents,
fs::{file_table::FileTable, thread_info::ThreadFsInfo},
prelude::*,
process::{namespace::nsproxy::NsProxy, signal::PollHandle, Pid},
process::{
namespace::nsproxy::NsProxy,
signal::{PauseReason, PollHandle},
Pid,
},
thread::{Thread, Tid},
time::{clocks::ProfClock, Timer, TimerManager},
};
@ -64,8 +68,8 @@ pub struct PosixThread {
/// Thread-directed sigqueue
sig_queues: SigQueues,
/// The per-thread signal [`Waker`], which will be used to wake up the thread
/// when enqueuing a signal.
signalled_waker: SpinLock<Option<Arc<Waker>>>,
/// when enqueuing a signal, along with the reason why the thread is paused.
signalled_waker: SpinLock<Option<(Arc<Waker>, PauseReason)>>,
/// A profiling clock measures the user CPU time and kernel CPU time in the thread.
prof_clock: Arc<ProfClock>,
@ -143,7 +147,8 @@ impl PosixThread {
self.sig_mask.contains(signum, Ordering::Relaxed)
}
/// Sets the input [`Waker`] as the signalled waker of this thread.
/// Sets the input [`Waker`] as the signalled waker of this thread,
/// along with the reason why the thread is paused.
///
/// This approach can collaborate with signal-aware wait methods.
/// Once a signalled waker is set for a thread, it cannot be reset until it is cleared.
@ -152,10 +157,10 @@ impl PosixThread {
///
/// If setting a new waker before clearing the current thread's signalled waker
/// this method will panic.
pub fn set_signalled_waker(&self, waker: Arc<Waker>) {
pub fn set_signalled_waker(&self, waker: Arc<Waker>, reason: PauseReason) {
let mut signalled_waker = self.signalled_waker.lock();
assert!(signalled_waker.is_none());
*signalled_waker = Some(waker);
*signalled_waker = Some((waker, reason));
}
/// Clears the signalled waker of this thread.
@ -199,21 +204,34 @@ impl PosixThread {
// - If #3 happens after #2, B3 can observe the effect of A5 due to the
// release-acquire pair A8-B1.
// Therefore, the condition where both B2 and B3 see `None` will never happen.
//
// Similarly, this implementation prevents a process that has been stopped by
// a signal or ptrace from being incorrectly reported as sleeping in an
// (un)interruptible wait.
//
// FIXME: This implementation cannot prevent a stopped process from being
// reported as running when `crate::process::signal::handle_pending_signal`
// is called, but the pending signal is not a `SIGCONT`. However, is this
// actually a problem? We considered an approach to fix this issue, but it
// does not fully resolve it and has some drawbacks. For more details, see
// <https://github.com/asterinas/asterinas/pull/2491#issuecomment-3527958970>.
let signalled_waker = self.signalled_waker.lock();
let task = self.task.upgrade().unwrap();
match (
signalled_waker.is_some(),
signalled_waker.as_ref(),
task.schedule_info().cpu.get().is_none(),
) {
(true, true) => SleepingState::Interruptible,
(false, true) => SleepingState::Uninterruptible,
(Some((_, PauseReason::Sleep)), true) => SleepingState::Interruptible,
(Some((_, PauseReason::StopBySignal)), true) => SleepingState::StopBySignal,
(Some((_, PauseReason::StopByPtrace)), true) => SleepingState::StopByPtrace,
(None, true) => SleepingState::Uninterruptible,
(_, false) => SleepingState::Running,
}
}
/// Wakes up the signalled waker.
pub fn wake_signalled_waker(&self) {
if let Some(waker) = &*self.signalled_waker.lock() {
if let Some((waker, _)) = &*self.signalled_waker.lock() {
waker.wake_up();
}
}
@ -334,4 +352,8 @@ pub enum SleepingState {
Interruptible,
/// The thread is sleeping in an uninterruptible wait.
Uninterruptible,
/// The thread is stopped by a signal.
StopBySignal,
/// The thread is stopped by ptrace.
StopByPtrace,
}

View File

@ -22,7 +22,7 @@ use ostd::{
arch::cpu::context::{FpuContext, UserContext},
user::UserContextApi,
};
pub use pause::{with_sigmask_changed, Pause};
pub use pause::{with_sigmask_changed, Pause, PauseReason};
pub use pending::HandlePendingSignal;
pub use poll::{PollAdaptor, PollHandle, Pollable, Pollee, Poller};
use sig_action::{SigAction, SigActionFlags, SigDefaultAction};

View File

@ -37,7 +37,26 @@ pub trait Pause: WaitTimeout {
where
F: FnMut() -> Option<R>,
{
self.pause_until_or_timeout_impl(cond, None)
self.pause_until_or_timeout_impl(cond, None, PauseReason::Sleep)
}
/// Pauses until the condition is met or a signal interrupts.
///
/// This pause happens due to the reason specified. If the reason is `PauseReason::Sleep`,
/// the caller should use `pause_until` straightforwardly.
///
/// # Errors
///
/// This method will return an error with [`EINTR`] if a signal is received before the
/// condition is met.
///
/// [`EINTR`]: crate::error::Errno::EINTR
#[track_caller]
fn pause_until_by<F, R>(&self, cond: F, reason: PauseReason) -> Result<R>
where
F: FnMut() -> Option<R>,
{
self.pause_until_or_timeout_impl(cond, None, reason)
}
/// Pauses until the condition is met, the timeout is reached, or a signal interrupts.
@ -62,7 +81,7 @@ pub trait Pause: WaitTimeout {
Err(err) => return cond().ok_or(err),
};
self.pause_until_or_timeout_impl(cond, timeout_inner)
self.pause_until_or_timeout_impl(cond, timeout_inner, PauseReason::Sleep)
}
/// Pauses until the condition is met, the timeout is reached, or a signal interrupts.
@ -81,6 +100,7 @@ pub trait Pause: WaitTimeout {
&self,
cond: F,
timeout: Option<&ManagedTimeout>,
reason: PauseReason,
) -> Result<R>
where
F: FnMut() -> Option<R>;
@ -104,6 +124,7 @@ impl Pause for Waiter {
&self,
cond: F,
timeout: Option<&ManagedTimeout>,
reason: PauseReason,
) -> Result<R>
where
F: FnMut() -> Option<R>,
@ -129,7 +150,7 @@ impl Pause for Waiter {
Ok(())
};
posix_thread.set_signalled_waker(self.waker());
posix_thread.set_signalled_waker(self.waker(), reason);
let res = self.wait_until_or_timeout_cancelled(cond, cancel_cond, timeout);
posix_thread.clear_signalled_waker();
@ -150,7 +171,7 @@ impl Pause for Waiter {
.and_then(|thread| thread.as_posix_thread());
if let Some(posix_thread) = posix_thread_opt {
posix_thread.set_signalled_waker(self.waker());
posix_thread.set_signalled_waker(self.waker(), PauseReason::Sleep);
// Check `has_pending` after `set_signalled_waker` to avoid race conditions.
if posix_thread.has_pending() {
posix_thread.clear_signalled_waker();
@ -194,6 +215,7 @@ impl Pause for WaitQueue {
&self,
mut cond: F,
timeout: Option<&ManagedTimeout>,
reason: PauseReason,
) -> Result<R>
where
F: FnMut() -> Option<R>,
@ -208,7 +230,7 @@ impl Pause for WaitQueue {
self.enqueue(waiter.waker());
cond()
};
waiter.pause_until_or_timeout_impl(cond, timeout)
waiter.pause_until_or_timeout_impl(cond, timeout, reason)
}
fn pause_timeout(&self, _timeout: &TimeoutExt<'_>) -> Result<()> {
@ -216,6 +238,14 @@ impl Pause for WaitQueue {
}
}
/// The reason why a process is paused by a `pause`-family method.
pub enum PauseReason {
Sleep,
StopBySignal,
#[expect(dead_code)]
StopByPtrace,
}
/// Executes a closure after temporarily adjusting the signal mask of the current POSIX thread.
pub fn with_sigmask_changed<R>(
ctx: &Context,

View File

@ -14,7 +14,7 @@ use crate::{
prelude::*,
process::{
posix_thread::{AsPosixThread, AsThreadLocal, ThreadLocal},
signal::{handle_pending_signal, HandlePendingSignal},
signal::{handle_pending_signal, HandlePendingSignal, PauseReason},
},
syscall::handle_syscall,
thread::{exception::handle_exception, AsThread},
@ -112,7 +112,11 @@ pub fn create_new_user_task(
// We need to further investigate Linux behavior regarding which signals should be handled
// when the thread is stopped.
while !current_thread.is_exited() && ctx.process.is_stopped() {
let _ = stop_waiter.pause_until(|| (!ctx.process.is_stopped()).then_some(()));
let _ = stop_waiter.pause_until_by(
|| (!ctx.process.is_stopped()).then_some(()),
// We currently do not support ptrace.
PauseReason::StopBySignal,
);
handle_pending_signal(user_ctx, &ctx, None);
}
}