diff --git a/kernel/src/fs/procfs/pid/task/stat.rs b/kernel/src/fs/procfs/pid/task/stat.rs index 269457638..56ed1be73 100644 --- a/kernel/src/fs/procfs/pid/task/stat.rs +++ b/kernel/src/fs/procfs/pid/task/stat.rs @@ -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(); diff --git a/kernel/src/fs/procfs/pid/task/status.rs b/kernel/src/fs/procfs/pid/task/status.rs index 7e502cdab..6b9e30e00 100644 --- a/kernel/src/fs/procfs/pid/task/status.rs +++ b/kernel/src/fs/procfs/pid/task/status.rs @@ -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(); diff --git a/kernel/src/process/execve.rs b/kernel/src/process/execve.rs index 3a0a26a4a..828a19bb1 100644 --- a/kernel/src/process/execve.rs +++ b/kernel/src/process/execve.rs @@ -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"); diff --git a/kernel/src/process/posix_thread/mod.rs b/kernel/src/process/posix_thread/mod.rs index 3b9a0d584..2ca42df9d 100644 --- a/kernel/src/process/posix_thread/mod.rs +++ b/kernel/src/process/posix_thread/mod.rs @@ -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>>, + /// when enqueuing a signal, along with the reason why the thread is paused. + signalled_waker: SpinLock, PauseReason)>>, /// A profiling clock measures the user CPU time and kernel CPU time in the thread. prof_clock: Arc, @@ -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) { + pub fn set_signalled_waker(&self, waker: Arc, 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 + // . 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, } diff --git a/kernel/src/process/signal/mod.rs b/kernel/src/process/signal/mod.rs index 13c3fc3db..44c1d1a23 100644 --- a/kernel/src/process/signal/mod.rs +++ b/kernel/src/process/signal/mod.rs @@ -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}; diff --git a/kernel/src/process/signal/pause.rs b/kernel/src/process/signal/pause.rs index c60e22568..ea1d304cb 100644 --- a/kernel/src/process/signal/pause.rs +++ b/kernel/src/process/signal/pause.rs @@ -37,7 +37,26 @@ pub trait Pause: WaitTimeout { where F: FnMut() -> Option, { - 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(&self, cond: F, reason: PauseReason) -> Result + where + F: FnMut() -> Option, + { + 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 where F: FnMut() -> Option; @@ -104,6 +124,7 @@ impl Pause for Waiter { &self, cond: F, timeout: Option<&ManagedTimeout>, + reason: PauseReason, ) -> Result where F: FnMut() -> Option, @@ -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 where F: FnMut() -> Option, @@ -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( ctx: &Context, diff --git a/kernel/src/thread/task.rs b/kernel/src/thread/task.rs index 3f889e0fc..eef82ab90 100644 --- a/kernel/src/thread/task.rs +++ b/kernel/src/thread/task.rs @@ -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); } }