mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-22 08:53:29 +00:00
Support WSTOPPED and WCONTINUED
This commit is contained in:
@ -543,11 +543,12 @@ fn create_child_process(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn set_parent_and_group(parent: &Process, child: &Arc<Process>) {
|
fn set_parent_and_group(parent: &Process, child: &Arc<Process>) {
|
||||||
// Lock order: process table -> children -> group of process
|
// Lock order: children of process -> process table -> group of process
|
||||||
// -> group inner -> session inner
|
// -> group inner -> session inner
|
||||||
|
let mut children_mut = parent.children().lock();
|
||||||
|
|
||||||
let mut process_table_mut = process_table::process_table_mut();
|
let mut process_table_mut = process_table::process_table_mut();
|
||||||
|
|
||||||
let mut children_mut = parent.children().lock();
|
|
||||||
let process_group_mut = parent.process_group.lock();
|
let process_group_mut = parent.process_group.lock();
|
||||||
|
|
||||||
let process_group = process_group_mut.upgrade().unwrap();
|
let process_group = process_group_mut.upgrade().unwrap();
|
||||||
|
@ -33,7 +33,7 @@ pub use process_vm::{
|
|||||||
pub use program_loader::{check_executable_file, ProgramToLoad};
|
pub use program_loader::{check_executable_file, ProgramToLoad};
|
||||||
pub use rlimit::ResourceType;
|
pub use rlimit::ResourceType;
|
||||||
pub use term_status::TermStatus;
|
pub use term_status::TermStatus;
|
||||||
pub use wait::{wait_child_exit, WaitOptions};
|
pub use wait::{do_wait, WaitOptions};
|
||||||
|
|
||||||
pub(super) fn init() {
|
pub(super) fn init() {
|
||||||
process::init();
|
process::init();
|
||||||
|
@ -199,6 +199,13 @@ impl PosixThread {
|
|||||||
*self.signalled_waker.lock() = None;
|
*self.signalled_waker.lock() = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Wakes up the signalled waker.
|
||||||
|
pub fn wake_signalled_waker(&self) {
|
||||||
|
if let Some(waker) = &*self.signalled_waker.lock() {
|
||||||
|
waker.wake_up();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Enqueues a thread-directed signal.
|
/// Enqueues a thread-directed signal.
|
||||||
///
|
///
|
||||||
/// This method does not perform permission checks on user signals. Therefore, unless the
|
/// This method does not perform permission checks on user signals. Therefore, unless the
|
||||||
@ -233,9 +240,7 @@ impl PosixThread {
|
|||||||
_sig_dispositions: MutexGuard<SigDispositions>,
|
_sig_dispositions: MutexGuard<SigDispositions>,
|
||||||
) {
|
) {
|
||||||
self.sig_queues.enqueue(signal);
|
self.sig_queues.enqueue(signal);
|
||||||
if let Some(waker) = &*self.signalled_waker.lock() {
|
self.wake_signalled_waker();
|
||||||
waker.wake_up();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the profiling clock of the current thread.
|
/// Returns a reference to the profiling clock of the current thread.
|
||||||
|
@ -18,6 +18,7 @@ use super::{
|
|||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
|
process::{status::StopWaitStatus, WaitOptions},
|
||||||
sched::{AtomicNice, Nice},
|
sched::{AtomicNice, Nice},
|
||||||
thread::{AsThread, Thread},
|
thread::{AsThread, Thread},
|
||||||
time::clocks::ProfClock,
|
time::clocks::ProfClock,
|
||||||
@ -672,6 +673,47 @@ impl Process {
|
|||||||
&self.status
|
&self.status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Stops the process.
|
||||||
|
//
|
||||||
|
// FIXME: `ptrace` is another reason that can cause a process to stop.
|
||||||
|
// Consider extending the method signature to support `ptrace` if necessary.
|
||||||
|
pub fn stop(&self, sig_num: SigNum) {
|
||||||
|
if self.status.stop_status().stop(sig_num) {
|
||||||
|
self.wake_up_parent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resumes the stopped process.
|
||||||
|
pub fn resume(&self) {
|
||||||
|
if self.status.stop_status().resume() {
|
||||||
|
self.wake_up_parent();
|
||||||
|
|
||||||
|
// Note that the resume function is called by the thread which deals with SIGCONT,
|
||||||
|
// since SIGCONT is handled by any thread in this process, we need to wake
|
||||||
|
// up other stopped threads in the same process.
|
||||||
|
for task in self.tasks.lock().as_slice() {
|
||||||
|
let posix_thread = task.as_posix_thread().unwrap();
|
||||||
|
posix_thread.wake_signalled_waker();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether the process is stopped.
|
||||||
|
pub fn is_stopped(&self) -> bool {
|
||||||
|
self.status.stop_status().is_stopped()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets and clears the stop status changes for the `wait` syscall.
|
||||||
|
pub(super) fn wait_stopped_or_continued(&self, options: WaitOptions) -> Option<StopWaitStatus> {
|
||||||
|
self.status.stop_status().wait(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wake_up_parent(&self) {
|
||||||
|
let parent_guard = self.parent.lock();
|
||||||
|
let parent = parent_guard.process().upgrade().unwrap();
|
||||||
|
parent.children_wait_queue.wake_all();
|
||||||
|
}
|
||||||
|
|
||||||
// ******************* Subreaper ********************
|
// ******************* Subreaper ********************
|
||||||
|
|
||||||
/// Sets the child subreaper attribute of the current process.
|
/// Sets the child subreaper attribute of the current process.
|
||||||
|
@ -140,12 +140,8 @@ pub fn handle_pending_signal(
|
|||||||
do_exit_group(TermStatus::Killed(sig_num));
|
do_exit_group(TermStatus::Killed(sig_num));
|
||||||
}
|
}
|
||||||
SigDefaultAction::Ign => {}
|
SigDefaultAction::Ign => {}
|
||||||
SigDefaultAction::Stop => {
|
SigDefaultAction::Stop => ctx.process.stop(sig_num),
|
||||||
let _ = ctx.thread.stop();
|
SigDefaultAction::Cont => ctx.process.resume(),
|
||||||
}
|
|
||||||
SigDefaultAction::Cont => {
|
|
||||||
let _ = ctx.thread.resume();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,10 @@
|
|||||||
|
|
||||||
use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
||||||
|
|
||||||
|
use ostd::sync::SpinLock;
|
||||||
|
|
||||||
use super::ExitCode;
|
use super::ExitCode;
|
||||||
|
use crate::process::{signal::sig_num::SigNum, WaitOptions};
|
||||||
|
|
||||||
/// The status of a process.
|
/// The status of a process.
|
||||||
///
|
///
|
||||||
@ -12,12 +15,14 @@ use super::ExitCode;
|
|||||||
/// 1. Whether the process is a zombie (i.e., all its threads have exited);
|
/// 1. Whether the process is a zombie (i.e., all its threads have exited);
|
||||||
/// 2. Whether the process is the vfork child, which shares the user-space virtual memory
|
/// 2. Whether the process is the vfork child, which shares the user-space virtual memory
|
||||||
/// with its parent process;
|
/// with its parent process;
|
||||||
/// 3. The exit code of the process.
|
/// 3. The exit code of the process;
|
||||||
|
/// 4. Whether the process is stopped (by a signal or ptrace).
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ProcessStatus {
|
pub struct ProcessStatus {
|
||||||
is_zombie: AtomicBool,
|
is_zombie: AtomicBool,
|
||||||
is_vfork_child: AtomicBool,
|
is_vfork_child: AtomicBool,
|
||||||
exit_code: AtomicU32,
|
exit_code: AtomicU32,
|
||||||
|
stop_status: StopStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ProcessStatus {
|
impl Default for ProcessStatus {
|
||||||
@ -26,6 +31,7 @@ impl Default for ProcessStatus {
|
|||||||
is_zombie: AtomicBool::new(false),
|
is_zombie: AtomicBool::new(false),
|
||||||
is_vfork_child: AtomicBool::new(false),
|
is_vfork_child: AtomicBool::new(false),
|
||||||
exit_code: AtomicU32::new(0),
|
exit_code: AtomicU32::new(0),
|
||||||
|
stop_status: StopStatus::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -71,3 +77,96 @@ impl ProcessStatus {
|
|||||||
self.exit_code.store(exit_code, Ordering::Relaxed);
|
self.exit_code.store(exit_code, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ProcessStatus {
|
||||||
|
pub(super) fn stop_status(&self) -> &StopStatus {
|
||||||
|
&self.stop_status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(super) struct StopStatus {
|
||||||
|
/// Indicates whether the process is stopped.
|
||||||
|
is_stopped: AtomicBool,
|
||||||
|
|
||||||
|
/// Indicates whether the process's status has changed and has not yet been waited on.
|
||||||
|
///
|
||||||
|
/// User programs may use the wait* syscalls to check for changes in
|
||||||
|
/// the process's status. This field will be set to `Some(_)` once the
|
||||||
|
/// process's status changes and will be set to `None` if the process
|
||||||
|
/// has already been waited on.
|
||||||
|
wait_status: SpinLock<Option<StopWaitStatus>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StopStatus {
|
||||||
|
pub(self) const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
is_stopped: AtomicBool::new(false),
|
||||||
|
wait_status: SpinLock::new(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stops the process by some signal.
|
||||||
|
///
|
||||||
|
/// The return value indicates whether the stop status has changed.
|
||||||
|
pub(super) fn stop(&self, signum: SigNum) -> bool {
|
||||||
|
// Hold the lock first to avoid race conditions
|
||||||
|
let mut wait_status = self.wait_status.lock();
|
||||||
|
|
||||||
|
if self.is_stopped.load(Ordering::Relaxed) {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
self.is_stopped.store(true, Ordering::Relaxed);
|
||||||
|
*wait_status = Some(StopWaitStatus::Stopped(signum));
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resumes the process.
|
||||||
|
///
|
||||||
|
/// The return value indicates whether the stop status has changed.
|
||||||
|
pub(super) fn resume(&self) -> bool {
|
||||||
|
// Hold the lock first to avoid race conditions
|
||||||
|
let mut wait_status = self.wait_status.lock();
|
||||||
|
|
||||||
|
if self.is_stopped.load(Ordering::Relaxed) {
|
||||||
|
self.is_stopped.store(false, Ordering::Relaxed);
|
||||||
|
*wait_status = Some(StopWaitStatus::Continue);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether the process is stopped.
|
||||||
|
pub(super) fn is_stopped(&self) -> bool {
|
||||||
|
self.is_stopped.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets and clears the stop status changes for the `wait` syscall.
|
||||||
|
pub(super) fn wait(&self, options: WaitOptions) -> Option<StopWaitStatus> {
|
||||||
|
let mut wait_status = self.wait_status.lock();
|
||||||
|
|
||||||
|
if options.contains(WaitOptions::WSTOPPED) {
|
||||||
|
if let Some(StopWaitStatus::Stopped(_)) = wait_status.as_ref() {
|
||||||
|
return wait_status.take();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.contains(WaitOptions::WCONTINUED) {
|
||||||
|
if let Some(StopWaitStatus::Continue) = wait_status.as_ref() {
|
||||||
|
return wait_status.take();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(super) enum StopWaitStatus {
|
||||||
|
// FIXME: A process can also be stopped by ptrace.
|
||||||
|
// Extend this enum to support ptrace.
|
||||||
|
Stopped(SigNum),
|
||||||
|
Continue,
|
||||||
|
}
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
#![expect(dead_code)]
|
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
process_filter::ProcessFilter,
|
process_filter::ProcessFilter,
|
||||||
signal::{constants::SIGCHLD, with_sigmask_changed},
|
signal::{constants::SIGCHLD, with_sigmask_changed},
|
||||||
@ -12,14 +10,16 @@ use crate::{
|
|||||||
process::{
|
process::{
|
||||||
posix_thread::{thread_table, AsPosixThread},
|
posix_thread::{thread_table, AsPosixThread},
|
||||||
process_table,
|
process_table,
|
||||||
|
signal::sig_num::SigNum,
|
||||||
|
status::StopWaitStatus,
|
||||||
},
|
},
|
||||||
|
time::clocks::ProfClock,
|
||||||
};
|
};
|
||||||
|
|
||||||
// The definition of WaitOptions is from Occlum
|
// The definition of WaitOptions is from Occlum
|
||||||
bitflags! {
|
bitflags! {
|
||||||
pub struct WaitOptions: u32 {
|
pub struct WaitOptions: u32 {
|
||||||
const WNOHANG = 0x1;
|
const WNOHANG = 0x1;
|
||||||
//Note: Below flags are not supported yet
|
|
||||||
const WSTOPPED = 0x2; // Same as WUNTRACED
|
const WSTOPPED = 0x2; // Same as WUNTRACED
|
||||||
const WEXITED = 0x4;
|
const WEXITED = 0x4;
|
||||||
const WCONTINUED = 0x8;
|
const WCONTINUED = 0x8;
|
||||||
@ -31,34 +31,56 @@ bitflags! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl WaitOptions {
|
impl WaitOptions {
|
||||||
pub fn supported(&self) -> bool {
|
pub fn check(&self) -> Result<()> {
|
||||||
let unsupported_flags = WaitOptions::all() - WaitOptions::WNOHANG;
|
if self.intersects(WaitOptions::WSTOPPED | WaitOptions::WCONTINUED)
|
||||||
!self.intersects(unsupported_flags)
|
&& self.contains(WaitOptions::WNOWAIT)
|
||||||
|
{
|
||||||
|
return_errno_with_message!(
|
||||||
|
Errno::EINVAL,
|
||||||
|
"WNOWAIT cannot be used toghther with WSTOPPED or WCONTINUED"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let supported_args = WaitOptions::WNOHANG
|
||||||
|
| WaitOptions::WSTOPPED
|
||||||
|
| WaitOptions::WCONTINUED
|
||||||
|
| WaitOptions::WNOWAIT;
|
||||||
|
if !supported_args.contains(*self) {
|
||||||
|
warn!(
|
||||||
|
"unsupported wait options are found: {:?}",
|
||||||
|
*self - supported_args
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wait_child_exit(
|
pub fn do_wait(
|
||||||
child_filter: ProcessFilter,
|
child_filter: ProcessFilter,
|
||||||
wait_options: WaitOptions,
|
wait_options: WaitOptions,
|
||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
) -> Result<Option<Arc<Process>>> {
|
) -> Result<Option<WaitStatus>> {
|
||||||
let current = ctx.process;
|
wait_options.check()?;
|
||||||
|
|
||||||
let zombie_child = with_sigmask_changed(
|
let zombie_child = with_sigmask_changed(
|
||||||
ctx,
|
ctx,
|
||||||
|sigmask| sigmask + SIGCHLD,
|
|sigmask| sigmask + SIGCHLD,
|
||||||
|| {
|
|| {
|
||||||
current.children_wait_queue().pause_until(|| {
|
ctx.process.children_wait_queue().pause_until(|| {
|
||||||
let unwaited_children = current
|
// Acquire the children lock at first to prevent race conditions.
|
||||||
.children()
|
// We want to ensure that multiple waiting threads
|
||||||
.lock()
|
// do not return the same waited process status.
|
||||||
|
let mut children_lock = ctx.process.children().lock();
|
||||||
|
|
||||||
|
let unwaited_children = children_lock
|
||||||
.values()
|
.values()
|
||||||
.filter(|child| match child_filter {
|
.filter(|child| match child_filter {
|
||||||
ProcessFilter::Any => true,
|
ProcessFilter::Any => true,
|
||||||
ProcessFilter::WithPid(pid) => child.pid() == pid,
|
ProcessFilter::WithPid(pid) => child.pid() == pid,
|
||||||
ProcessFilter::WithPgid(pgid) => child.pgid() == pgid,
|
ProcessFilter::WithPgid(pgid) => child.pgid() == pgid,
|
||||||
})
|
})
|
||||||
.cloned()
|
.collect::<Box<_>>();
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
if unwaited_children.is_empty() {
|
if unwaited_children.is_empty() {
|
||||||
return Some(Err(Error::with_message(
|
return Some(Err(Error::with_message(
|
||||||
@ -67,20 +89,15 @@ pub fn wait_child_exit(
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// return immediately if we find a zombie child
|
if let Some(status) = wait_zombie(&unwaited_children) {
|
||||||
let zombie_child = unwaited_children
|
if !wait_options.contains(WaitOptions::WNOWAIT) {
|
||||||
.iter()
|
reap_zombie_child(status.pid(), &mut children_lock);
|
||||||
.find(|child| child.status().is_zombie());
|
|
||||||
|
|
||||||
if let Some(zombie_child) = zombie_child {
|
|
||||||
let zombie_pid = zombie_child.pid();
|
|
||||||
if wait_options.contains(WaitOptions::WNOWAIT) {
|
|
||||||
// does not reap child, directly return
|
|
||||||
return Some(Ok(Some(zombie_child.clone())));
|
|
||||||
} else {
|
|
||||||
reap_zombie_child(current, zombie_pid);
|
|
||||||
return Some(Ok(Some(zombie_child.clone())));
|
|
||||||
}
|
}
|
||||||
|
return Some(Ok(Some(status)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(status) = wait_stopped_or_continued(&unwaited_children, wait_options) {
|
||||||
|
return Some(Ok(Some(status)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if wait_options.contains(WaitOptions::WNOHANG) {
|
if wait_options.contains(WaitOptions::WNOHANG) {
|
||||||
@ -96,17 +113,80 @@ pub fn wait_child_exit(
|
|||||||
Ok(zombie_child)
|
Ok(zombie_child)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum WaitStatus {
|
||||||
|
Zombie(Arc<Process>),
|
||||||
|
Stop(Arc<Process>, SigNum),
|
||||||
|
Continue(Arc<Process>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WaitStatus {
|
||||||
|
pub fn pid(&self) -> u32 {
|
||||||
|
self.process().pid()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn status_code(&self) -> u32 {
|
||||||
|
match self {
|
||||||
|
Self::Zombie(process) => process.status().exit_code(),
|
||||||
|
Self::Stop(_, sig_num) => ((sig_num.as_u8() as u32) << 8) | 0x7f,
|
||||||
|
Self::Continue(_) => 0xffff,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prof_clock(&self) -> &Arc<ProfClock> {
|
||||||
|
self.process().prof_clock()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process(&self) -> &Arc<Process> {
|
||||||
|
match self {
|
||||||
|
WaitStatus::Zombie(process)
|
||||||
|
| WaitStatus::Stop(process, _)
|
||||||
|
| WaitStatus::Continue(process) => process,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_zombie(unwaited_children: &[&Arc<Process>]) -> Option<WaitStatus> {
|
||||||
|
unwaited_children
|
||||||
|
.iter()
|
||||||
|
.find(|child| child.status().is_zombie())
|
||||||
|
.map(|child| WaitStatus::Zombie((*child).clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_stopped_or_continued(
|
||||||
|
unwaited_children: &[&Arc<Process>],
|
||||||
|
wait_options: WaitOptions,
|
||||||
|
) -> Option<WaitStatus> {
|
||||||
|
if !wait_options.intersects(WaitOptions::WSTOPPED | WaitOptions::WCONTINUED) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock order: children of process -> tasks of process
|
||||||
|
for process in unwaited_children.iter() {
|
||||||
|
let Some(stop_wait_status) = process.wait_stopped_or_continued(wait_options) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let wait_status = match stop_wait_status {
|
||||||
|
StopWaitStatus::Stopped(sig_num) => WaitStatus::Stop((*process).clone(), sig_num),
|
||||||
|
StopWaitStatus::Continue => WaitStatus::Continue((*process).clone()),
|
||||||
|
};
|
||||||
|
return Some(wait_status);
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
/// 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(pid: Pid, children_lock: &mut BTreeMap<Pid, Arc<Process>>) -> ExitCode {
|
||||||
let child_process = process.children().lock().remove(&pid).unwrap();
|
let child_process = children_lock.remove(&pid).unwrap();
|
||||||
assert!(child_process.status().is_zombie());
|
assert!(child_process.status().is_zombie());
|
||||||
|
|
||||||
for task in child_process.tasks().lock().as_slice() {
|
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());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lock order: session table -> group table -> process table -> group of process
|
// Lock order: children of process -> session table -> group table
|
||||||
// -> group inner -> session inner
|
// -> process table -> group of process -> group inner -> session inner
|
||||||
let mut session_table_mut = process_table::session_table_mut();
|
let mut session_table_mut = process_table::session_table_mut();
|
||||||
let mut group_table_mut = process_table::group_table_mut();
|
let mut group_table_mut = process_table::group_table_mut();
|
||||||
|
|
||||||
|
@ -3,12 +3,12 @@
|
|||||||
use super::{getrusage::rusage_t, SyscallReturn};
|
use super::{getrusage::rusage_t, SyscallReturn};
|
||||||
use crate::{
|
use crate::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
process::{wait_child_exit, ProcessFilter, WaitOptions},
|
process::{do_wait, ProcessFilter, WaitOptions},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn sys_wait4(
|
pub fn sys_wait4(
|
||||||
wait_pid: u64,
|
wait_pid: u64,
|
||||||
exit_status_ptr: u64,
|
status_ptr: u64,
|
||||||
wait_options: u32,
|
wait_options: u32,
|
||||||
rusage_addr: Vaddr,
|
rusage_addr: Vaddr,
|
||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
@ -16,31 +16,30 @@ pub fn sys_wait4(
|
|||||||
let wait_options = WaitOptions::from_bits(wait_options)
|
let wait_options = WaitOptions::from_bits(wait_options)
|
||||||
.ok_or_else(|| Error::with_message(Errno::EINVAL, "unknown wait option"))?;
|
.ok_or_else(|| Error::with_message(Errno::EINVAL, "unknown wait option"))?;
|
||||||
debug!(
|
debug!(
|
||||||
"pid = {}, exit_status_ptr = {}, wait_options: {:?}",
|
"pid = {}, status_ptr = {}, wait_options: {:?}",
|
||||||
wait_pid as i32, exit_status_ptr, wait_options
|
wait_pid as i32, status_ptr, wait_options
|
||||||
);
|
);
|
||||||
debug!("wait4 current pid = {}", ctx.process.pid());
|
debug!("wait4 current pid = {}", ctx.process.pid());
|
||||||
let process_filter = ProcessFilter::from_id(wait_pid as _);
|
let process_filter = ProcessFilter::from_id(wait_pid as _);
|
||||||
|
|
||||||
let waited_process =
|
let wait_status =
|
||||||
wait_child_exit(process_filter, wait_options, ctx).map_err(|err| match err.error() {
|
do_wait(process_filter, wait_options, ctx).map_err(|err| match err.error() {
|
||||||
Errno::EINTR => Error::new(Errno::ERESTARTSYS),
|
Errno::EINTR => Error::new(Errno::ERESTARTSYS),
|
||||||
_ => err,
|
_ => err,
|
||||||
})?;
|
})?;
|
||||||
let Some(process) = waited_process else {
|
let Some(wait_status) = wait_status else {
|
||||||
return Ok(SyscallReturn::Return(0 as _));
|
return Ok(SyscallReturn::Return(0 as _));
|
||||||
};
|
};
|
||||||
|
|
||||||
let (return_pid, exit_code) = (process.pid(), process.status().exit_code());
|
let (return_pid, status_code) = (wait_status.pid(), wait_status.status_code());
|
||||||
if exit_status_ptr != 0 {
|
if status_ptr != 0 {
|
||||||
ctx.user_space()
|
ctx.user_space().write_val(status_ptr as _, &status_code)?;
|
||||||
.write_val(exit_status_ptr as _, &exit_code)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if rusage_addr != 0 {
|
if rusage_addr != 0 {
|
||||||
let rusage = rusage_t {
|
let rusage = rusage_t {
|
||||||
ru_utime: process.prof_clock().user_clock().read_time().into(),
|
ru_utime: wait_status.prof_clock().user_clock().read_time().into(),
|
||||||
ru_stime: process.prof_clock().kernel_clock().read_time().into(),
|
ru_stime: wait_status.prof_clock().kernel_clock().read_time().into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
use super::SyscallReturn;
|
use super::SyscallReturn;
|
||||||
use crate::{
|
use crate::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
process::{wait_child_exit, ProcessFilter, WaitOptions},
|
process::{do_wait, ProcessFilter, WaitOptions},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn sys_waitid(
|
pub fn sys_waitid(
|
||||||
@ -19,12 +19,12 @@ pub fn sys_waitid(
|
|||||||
let wait_options = WaitOptions::from_bits(options as u32)
|
let wait_options = WaitOptions::from_bits(options as u32)
|
||||||
.ok_or(Error::with_message(Errno::EINVAL, "invalid options"))?;
|
.ok_or(Error::with_message(Errno::EINVAL, "invalid options"))?;
|
||||||
|
|
||||||
let waited_process =
|
let wait_status =
|
||||||
wait_child_exit(process_filter, wait_options, ctx).map_err(|err| match err.error() {
|
do_wait(process_filter, wait_options, ctx).map_err(|err| match err.error() {
|
||||||
Errno::EINTR => Error::new(Errno::ERESTARTSYS),
|
Errno::EINTR => Error::new(Errno::ERESTARTSYS),
|
||||||
_ => err,
|
_ => err,
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let pid = waited_process.map_or(0, |process| process.pid());
|
let pid = wait_status.map_or(0, |wait_status| wait_status.pid());
|
||||||
Ok(SyscallReturn::Return(pid as _))
|
Ok(SyscallReturn::Return(pid as _))
|
||||||
}
|
}
|
||||||
|
@ -2,14 +2,13 @@
|
|||||||
|
|
||||||
//! Posix thread implementation
|
//! Posix thread implementation
|
||||||
|
|
||||||
use core::sync::atomic::Ordering;
|
use core::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
use ostd::{
|
use ostd::{
|
||||||
cpu::{AtomicCpuSet, CpuSet},
|
cpu::{AtomicCpuSet, CpuSet},
|
||||||
task::Task,
|
task::Task,
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::status::{AtomicThreadStatus, ThreadStatus};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
sched::{SchedAttr, SchedPolicy},
|
sched::{SchedAttr, SchedPolicy},
|
||||||
@ -18,7 +17,6 @@ use crate::{
|
|||||||
pub mod exception;
|
pub mod exception;
|
||||||
pub mod kernel_thread;
|
pub mod kernel_thread;
|
||||||
pub mod oops;
|
pub mod oops;
|
||||||
pub mod status;
|
|
||||||
pub mod task;
|
pub mod task;
|
||||||
pub mod work_queue;
|
pub mod work_queue;
|
||||||
|
|
||||||
@ -52,7 +50,7 @@ pub struct Thread {
|
|||||||
|
|
||||||
// mutable part
|
// mutable part
|
||||||
/// Thread status
|
/// Thread status
|
||||||
status: AtomicThreadStatus,
|
is_exited: AtomicBool,
|
||||||
/// Thread CPU affinity
|
/// Thread CPU affinity
|
||||||
cpu_affinity: AtomicCpuSet,
|
cpu_affinity: AtomicCpuSet,
|
||||||
sched_attr: SchedAttr,
|
sched_attr: SchedAttr,
|
||||||
@ -69,7 +67,7 @@ impl Thread {
|
|||||||
Thread {
|
Thread {
|
||||||
task,
|
task,
|
||||||
data: Box::new(data),
|
data: Box::new(data),
|
||||||
status: AtomicThreadStatus::new(ThreadStatus::Init),
|
is_exited: AtomicBool::new(false),
|
||||||
cpu_affinity: AtomicCpuSet::new(cpu_affinity),
|
cpu_affinity: AtomicCpuSet::new(cpu_affinity),
|
||||||
sched_attr: SchedAttr::new(sched_policy),
|
sched_attr: SchedAttr::new(sched_policy),
|
||||||
}
|
}
|
||||||
@ -91,59 +89,16 @@ impl Thread {
|
|||||||
/// Runs this thread at once.
|
/// Runs this thread at once.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn run(&self) {
|
pub fn run(&self) {
|
||||||
self.status.store(ThreadStatus::Running, Ordering::Release);
|
|
||||||
self.task.upgrade().unwrap().run();
|
self.task.upgrade().unwrap().run();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether the thread is exited.
|
/// Returns whether the thread is exited.
|
||||||
pub fn is_exited(&self) -> bool {
|
pub fn is_exited(&self) -> bool {
|
||||||
self.status.load(Ordering::Acquire).is_exited()
|
self.is_exited.load(Ordering::Acquire)
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns whether the thread is stopped.
|
|
||||||
pub fn is_stopped(&self) -> bool {
|
|
||||||
self.status.load(Ordering::Acquire).is_stopped()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stops the thread if it is running.
|
|
||||||
///
|
|
||||||
/// If the previous status is not [`ThreadStatus::Running`], this function
|
|
||||||
/// returns [`Err`] with the previous state. Otherwise, it sets the status
|
|
||||||
/// to [`ThreadStatus::Stopped`] and returns [`Ok`] with the previous state.
|
|
||||||
///
|
|
||||||
/// This function only sets the status to [`ThreadStatus::Stopped`],
|
|
||||||
/// without initiating a reschedule.
|
|
||||||
pub fn stop(&self) -> core::result::Result<ThreadStatus, ThreadStatus> {
|
|
||||||
self.status.compare_exchange(
|
|
||||||
ThreadStatus::Running,
|
|
||||||
ThreadStatus::Stopped,
|
|
||||||
Ordering::AcqRel,
|
|
||||||
Ordering::Acquire,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resumes running the thread if it is stopped.
|
|
||||||
///
|
|
||||||
/// If the previous status is not [`ThreadStatus::Stopped`], this function
|
|
||||||
/// returns [`None`]. Otherwise, it sets the status to
|
|
||||||
/// [`ThreadStatus::Running`] and returns [`Some(())`].
|
|
||||||
///
|
|
||||||
/// This function only sets the status to [`ThreadStatus::Running`],
|
|
||||||
/// without initiating a reschedule.
|
|
||||||
pub fn resume(&self) -> Option<()> {
|
|
||||||
self.status
|
|
||||||
.compare_exchange(
|
|
||||||
ThreadStatus::Stopped,
|
|
||||||
ThreadStatus::Running,
|
|
||||||
Ordering::AcqRel,
|
|
||||||
Ordering::Acquire,
|
|
||||||
)
|
|
||||||
.ok()
|
|
||||||
.map(|_| ())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn exit(&self) {
|
pub(super) fn exit(&self) {
|
||||||
self.status.store(ThreadStatus::Exited, Ordering::Release);
|
self.is_exited.store(true, Ordering::Release);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the reference to the atomic CPU affinity.
|
/// Returns the reference to the atomic CPU affinity.
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
use core::sync::atomic::AtomicU8;
|
|
||||||
|
|
||||||
use atomic_integer_wrapper::define_atomic_version_of_integer_like_type;
|
|
||||||
use int_to_c_enum::TryFromInt;
|
|
||||||
|
|
||||||
define_atomic_version_of_integer_like_type!(ThreadStatus, try_from = true, {
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct AtomicThreadStatus(AtomicU8);
|
|
||||||
});
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, TryFromInt)]
|
|
||||||
#[repr(u8)]
|
|
||||||
pub enum ThreadStatus {
|
|
||||||
Init = 0,
|
|
||||||
Running = 1,
|
|
||||||
Exited = 2,
|
|
||||||
Stopped = 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ThreadStatus {
|
|
||||||
pub fn is_running(&self) -> bool {
|
|
||||||
*self == ThreadStatus::Running
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_exited(&self) -> bool {
|
|
||||||
*self == ThreadStatus::Exited
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_stopped(&self) -> bool {
|
|
||||||
*self == ThreadStatus::Stopped
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ThreadStatus> for u8 {
|
|
||||||
fn from(value: ThreadStatus) -> Self {
|
|
||||||
value as u8
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use ostd::{
|
use ostd::{
|
||||||
cpu::context::UserContext,
|
cpu::context::UserContext,
|
||||||
|
sync::Waiter,
|
||||||
task::{Task, TaskOptions},
|
task::{Task, TaskOptions},
|
||||||
user::{ReturnReason, UserContextApi, UserMode},
|
user::{ReturnReason, UserContextApi, UserMode},
|
||||||
};
|
};
|
||||||
@ -32,6 +33,7 @@ pub fn create_new_user_task(
|
|||||||
let current_posix_thread = current_thread.as_posix_thread().unwrap();
|
let current_posix_thread = current_thread.as_posix_thread().unwrap();
|
||||||
let current_thread_local = current_task.as_thread_local().unwrap();
|
let current_thread_local = current_task.as_thread_local().unwrap();
|
||||||
let current_process = current_posix_thread.process();
|
let current_process = current_posix_thread.process();
|
||||||
|
let (stop_waiter, _) = Waiter::new_pair();
|
||||||
|
|
||||||
let user_ctx = current_task
|
let user_ctx = current_task
|
||||||
.user_ctx()
|
.user_ctx()
|
||||||
@ -71,11 +73,13 @@ pub fn create_new_user_task(
|
|||||||
task: ¤t_task,
|
task: ¤t_task,
|
||||||
};
|
};
|
||||||
|
|
||||||
loop {
|
while !current_thread.is_exited() {
|
||||||
|
// Execute the user code
|
||||||
let return_reason = user_mode.execute(has_kernel_event_fn);
|
let return_reason = user_mode.execute(has_kernel_event_fn);
|
||||||
|
|
||||||
|
// Handle user events
|
||||||
let user_ctx = user_mode.context_mut();
|
let user_ctx = user_mode.context_mut();
|
||||||
let mut syscall_number = None;
|
let mut syscall_number = None;
|
||||||
// handle user event:
|
|
||||||
match return_reason {
|
match return_reason {
|
||||||
ReturnReason::UserException => handle_exception(&ctx, user_ctx),
|
ReturnReason::UserException => handle_exception(&ctx, user_ctx),
|
||||||
ReturnReason::UserSyscall => {
|
ReturnReason::UserSyscall => {
|
||||||
@ -85,23 +89,27 @@ pub fn create_new_user_task(
|
|||||||
ReturnReason::KernelEvent => {}
|
ReturnReason::KernelEvent => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Exit if the thread terminates
|
||||||
if current_thread.is_exited() {
|
if current_thread.is_exited() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle signals
|
||||||
handle_pending_signal(user_ctx, &ctx, syscall_number);
|
handle_pending_signal(user_ctx, &ctx, syscall_number);
|
||||||
// If current is suspended, wait for a signal to wake up self
|
|
||||||
while current_thread.is_stopped() {
|
// Handle signals while the thread is stopped
|
||||||
Thread::yield_now();
|
// FIXME: Currently, we handle all signals when the process is stopped.
|
||||||
debug!("{} is suspended.", current_posix_thread.tid());
|
// However, when the process is stopped, at least signals with user-provided handlers
|
||||||
|
// should not be handled; these signals should only be handled when the process is continued.
|
||||||
|
// Certain signals, such as SIGKILL, should be handled even if the process is stopped.
|
||||||
|
// We need to further investigate Linux behavior regarding which signals should be handled
|
||||||
|
// when the thread is stopped.
|
||||||
|
while !current_thread.is_exited() && current_process.is_stopped() {
|
||||||
|
let _ = stop_waiter.pause_until(|| (!current_process.is_stopped()).then_some(()));
|
||||||
handle_pending_signal(user_ctx, &ctx, None);
|
handle_pending_signal(user_ctx, &ctx, None);
|
||||||
}
|
}
|
||||||
if current_thread.is_exited() {
|
|
||||||
debug!("exit due to signal");
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
debug!("exit user loop");
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskOptions::new(|| {
|
TaskOptions::new(|| {
|
||||||
// TODO: If a kernel "oops" is caught, we should kill the entire
|
// TODO: If a kernel "oops" is caught, we should kill the entire
|
||||||
|
335
test/apps/process/wait4.c
Normal file
335
test/apps/process/wait4.c
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
#include "../network/test.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <linux/wait.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
static pid_t pid;
|
||||||
|
static int status;
|
||||||
|
|
||||||
|
FN_SETUP(fork_child)
|
||||||
|
{
|
||||||
|
pid = CHECK(fork());
|
||||||
|
|
||||||
|
if (pid == 0) {
|
||||||
|
// Child entering an infinite loop until killed by parent.
|
||||||
|
while (1) {
|
||||||
|
usleep(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
exit(EXIT_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parent process
|
||||||
|
sleep(1); // Ensure the child process is running
|
||||||
|
}
|
||||||
|
END_SETUP()
|
||||||
|
|
||||||
|
FN_TEST(stop_child)
|
||||||
|
{
|
||||||
|
// Stop the child process
|
||||||
|
TEST_SUCC(kill(pid, SIGSTOP));
|
||||||
|
TEST_RES(wait4(pid, &status, WSTOPPED, NULL),
|
||||||
|
_ret == pid && WIFSTOPPED(status) &&
|
||||||
|
WSTOPSIG(status) == SIGSTOP);
|
||||||
|
status = 0;
|
||||||
|
TEST_RES(wait4(pid, &status, WSTOPPED | WNOHANG, NULL),
|
||||||
|
_ret == 0 && status == 0);
|
||||||
|
}
|
||||||
|
END_TEST()
|
||||||
|
|
||||||
|
FN_TEST(continue_child)
|
||||||
|
{
|
||||||
|
TEST_SUCC(kill(pid, SIGCONT));
|
||||||
|
status = 0;
|
||||||
|
TEST_RES(wait4(pid, &status, WCONTINUED, NULL),
|
||||||
|
_ret == pid && WIFCONTINUED(status));
|
||||||
|
status = 0;
|
||||||
|
TEST_RES(wait4(pid, &status, WCONTINUED | WNOHANG, NULL),
|
||||||
|
_ret == 0 && status == 0);
|
||||||
|
}
|
||||||
|
END_TEST()
|
||||||
|
|
||||||
|
FN_TEST(wait_nowait)
|
||||||
|
{
|
||||||
|
TEST_SUCC(kill(pid, SIGSTOP));
|
||||||
|
|
||||||
|
status = 0;
|
||||||
|
TEST_ERRNO(wait4(pid, &status, WSTOPPED | WNOWAIT, NULL), EINVAL);
|
||||||
|
status = 0;
|
||||||
|
TEST_RES(wait4(pid, &status, WSTOPPED, NULL),
|
||||||
|
_ret == pid && WIFSTOPPED(status) &&
|
||||||
|
WSTOPSIG(status) == SIGSTOP);
|
||||||
|
status = 0;
|
||||||
|
TEST_RES(wait4(pid, &status, WSTOPPED | WNOHANG, NULL),
|
||||||
|
_ret == 0 && status == 0);
|
||||||
|
|
||||||
|
TEST_SUCC(kill(pid, SIGCONT));
|
||||||
|
|
||||||
|
status = 0;
|
||||||
|
TEST_ERRNO(wait4(pid, &status, WCONTINUED | WNOWAIT, NULL), EINVAL);
|
||||||
|
status = 0;
|
||||||
|
TEST_RES(wait4(pid, &status, WCONTINUED, NULL),
|
||||||
|
_ret == pid && WIFCONTINUED(status));
|
||||||
|
status = 0;
|
||||||
|
TEST_RES(wait4(pid, &status, WCONTINUED | WNOHANG, NULL),
|
||||||
|
_ret == 0 && status == 0);
|
||||||
|
}
|
||||||
|
END_TEST()
|
||||||
|
|
||||||
|
FN_TEST(wait_stopped_and_continued)
|
||||||
|
{
|
||||||
|
TEST_SUCC(kill(pid, SIGSTOP));
|
||||||
|
sleep(1);
|
||||||
|
TEST_SUCC(kill(pid, SIGCONT));
|
||||||
|
sleep(1);
|
||||||
|
|
||||||
|
status = 0;
|
||||||
|
TEST_RES(wait4(pid, &status, WSTOPPED | WCONTINUED, NULL),
|
||||||
|
_ret == pid && WIFCONTINUED(status));
|
||||||
|
status = 0;
|
||||||
|
TEST_RES(wait4(pid, &status, WSTOPPED | WCONTINUED | WNOHANG, NULL),
|
||||||
|
_ret == 0 && status == 0);
|
||||||
|
}
|
||||||
|
END_TEST()
|
||||||
|
|
||||||
|
FN_TEST(continue_not_stopped)
|
||||||
|
{
|
||||||
|
TEST_SUCC(kill(pid, SIGCONT));
|
||||||
|
status = 0;
|
||||||
|
TEST_RES(wait4(pid, &status, WCONTINUED | WNOHANG, NULL),
|
||||||
|
_ret == 0 && status == 0);
|
||||||
|
}
|
||||||
|
END_TEST()
|
||||||
|
|
||||||
|
FN_TEST(stop_continue_continue)
|
||||||
|
{
|
||||||
|
TEST_SUCC(kill(pid, SIGSTOP));
|
||||||
|
sleep(1);
|
||||||
|
TEST_SUCC(kill(pid, SIGCONT));
|
||||||
|
sleep(1);
|
||||||
|
TEST_SUCC(kill(pid, SIGCONT));
|
||||||
|
sleep(1);
|
||||||
|
|
||||||
|
status = 0;
|
||||||
|
TEST_RES(wait4(pid, &status, WSTOPPED | WNOHANG, NULL),
|
||||||
|
status == 0 && _ret == 0);
|
||||||
|
status = 0;
|
||||||
|
TEST_RES(wait4(pid, &status, WSTOPPED | WCONTINUED, NULL),
|
||||||
|
_ret == pid && WIFCONTINUED(status));
|
||||||
|
status = 0;
|
||||||
|
TEST_RES(wait4(pid, &status, WSTOPPED | WCONTINUED | WNOHANG, NULL),
|
||||||
|
_ret == 0 && status == 0);
|
||||||
|
}
|
||||||
|
END_TEST()
|
||||||
|
|
||||||
|
FN_TEST(stop_continue_stop)
|
||||||
|
{
|
||||||
|
TEST_SUCC(kill(pid, SIGSTOP));
|
||||||
|
sleep(1);
|
||||||
|
TEST_SUCC(kill(pid, SIGCONT));
|
||||||
|
sleep(1);
|
||||||
|
TEST_SUCC(kill(pid, SIGSTOP));
|
||||||
|
sleep(1);
|
||||||
|
|
||||||
|
status = 0;
|
||||||
|
TEST_RES(wait4(pid, &status, WCONTINUED | WNOHANG, NULL), status == 0);
|
||||||
|
status = 0;
|
||||||
|
TEST_RES(wait4(pid, &status, WSTOPPED, NULL),
|
||||||
|
_ret == pid && WIFSTOPPED(status) &&
|
||||||
|
WSTOPSIG(status) == SIGSTOP);
|
||||||
|
status = 0;
|
||||||
|
TEST_RES(wait4(pid, &status, WSTOPPED | WNOHANG, NULL),
|
||||||
|
_ret == 0 && status == 0);
|
||||||
|
|
||||||
|
// Restore the state
|
||||||
|
TEST_SUCC(kill(pid, SIGCONT));
|
||||||
|
status = 0;
|
||||||
|
TEST_RES(wait4(pid, &status, WCONTINUED, NULL),
|
||||||
|
_ret == pid && WIFCONTINUED(status));
|
||||||
|
status = 0;
|
||||||
|
TEST_RES(wait4(pid, &status, WCONTINUED | WSTOPPED | WNOHANG, NULL),
|
||||||
|
_ret == 0 && status == 0);
|
||||||
|
}
|
||||||
|
END_TEST()
|
||||||
|
|
||||||
|
FN_TEST(stop_stopped)
|
||||||
|
{
|
||||||
|
TEST_SUCC(kill(pid, SIGSTOP));
|
||||||
|
status = 0;
|
||||||
|
TEST_RES(wait4(pid, &status, WSTOPPED, NULL),
|
||||||
|
_ret == pid && WIFSTOPPED(status) &&
|
||||||
|
WSTOPSIG(status) == SIGSTOP);
|
||||||
|
|
||||||
|
TEST_SUCC(kill(pid, SIGSTOP));
|
||||||
|
sleep(1);
|
||||||
|
status = 0;
|
||||||
|
TEST_RES(wait4(pid, &status, WSTOPPED | WNOHANG, NULL),
|
||||||
|
_ret == 0 && status == 0);
|
||||||
|
}
|
||||||
|
END_TEST()
|
||||||
|
|
||||||
|
void *thread_func(void *arg)
|
||||||
|
{
|
||||||
|
while (1) {
|
||||||
|
sleep(0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void child_process()
|
||||||
|
{
|
||||||
|
pthread_t t1, t2;
|
||||||
|
|
||||||
|
CHECK(pthread_create(&t1, NULL, thread_func, NULL));
|
||||||
|
CHECK(pthread_create(&t2, NULL, thread_func, NULL));
|
||||||
|
|
||||||
|
pthread_join(t1, NULL);
|
||||||
|
pthread_join(t2, NULL);
|
||||||
|
|
||||||
|
exit(EXIT_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
FN_TEST(multithread)
|
||||||
|
{
|
||||||
|
pid_t child_pid = TEST_SUCC(fork());
|
||||||
|
|
||||||
|
if (child_pid == 0) {
|
||||||
|
child_process();
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep(1);
|
||||||
|
|
||||||
|
TEST_SUCC(kill(child_pid, SIGSTOP));
|
||||||
|
int status = 0;
|
||||||
|
TEST_RES(wait4(child_pid, &status, WSTOPPED, NULL),
|
||||||
|
_ret == child_pid && WIFSTOPPED(status) &&
|
||||||
|
WSTOPSIG(status) == SIGSTOP);
|
||||||
|
TEST_RES(wait4(child_pid, &status, WSTOPPED | WNOHANG, NULL),
|
||||||
|
_ret == 0);
|
||||||
|
|
||||||
|
TEST_SUCC(kill(child_pid, SIGKILL));
|
||||||
|
TEST_RES(wait4(child_pid, NULL, 0, NULL), _ret == child_pid);
|
||||||
|
}
|
||||||
|
END_TEST()
|
||||||
|
|
||||||
|
volatile int sigint_counter = 0;
|
||||||
|
volatile int sigtrap_counter = 0;
|
||||||
|
|
||||||
|
void handle_sigint(int signum, siginfo_t *_info, void *_context)
|
||||||
|
{
|
||||||
|
sigint_counter += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle_sigtrap(int signum, siginfo_t *_info, void *_context)
|
||||||
|
{
|
||||||
|
sigtrap_counter += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void child_process2(int *pipe_fds)
|
||||||
|
{
|
||||||
|
CHECK(close(pipe_fds[0]));
|
||||||
|
|
||||||
|
struct sigaction new_action = {};
|
||||||
|
new_action.sa_sigaction = handle_sigint;
|
||||||
|
CHECK(sigaction(SIGINT, &new_action, NULL));
|
||||||
|
|
||||||
|
new_action.sa_sigaction = handle_sigtrap;
|
||||||
|
CHECK(sigaction(SIGTRAP, &new_action, NULL));
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
usleep(100);
|
||||||
|
if (sigint_counter == 1) {
|
||||||
|
sigint_counter = 0;
|
||||||
|
CHECK(write(pipe_fds[1], "a", 1));
|
||||||
|
}
|
||||||
|
if (sigtrap_counter == 1) {
|
||||||
|
sigtrap_counter = 0;
|
||||||
|
CHECK(write(pipe_fds[1], "b", 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exit(EXIT_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
FN_TEST(nested_signals)
|
||||||
|
{
|
||||||
|
int pipe_fds[2];
|
||||||
|
TEST_SUCC(pipe(pipe_fds));
|
||||||
|
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
int fd = pipe_fds[i];
|
||||||
|
int flags = TEST_SUCC(fcntl(fd, F_GETFL, 0));
|
||||||
|
TEST_SUCC(fcntl(fd, F_SETFL, flags | O_NONBLOCK));
|
||||||
|
}
|
||||||
|
|
||||||
|
int child_pid = TEST_SUCC(fork());
|
||||||
|
|
||||||
|
if (child_pid == 0) {
|
||||||
|
child_process2(pipe_fds);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_SUCC(close(pipe_fds[1]));
|
||||||
|
sleep(1);
|
||||||
|
|
||||||
|
char buf[1] = { 0 };
|
||||||
|
|
||||||
|
// SIGINT -> SIGTRAP
|
||||||
|
TEST_SUCC(kill(child_pid, SIGINT));
|
||||||
|
sleep(1);
|
||||||
|
TEST_RES(read(pipe_fds[0], buf, 1), _ret == 1 && buf[0] == 'a');
|
||||||
|
|
||||||
|
TEST_SUCC(kill(child_pid, SIGTRAP));
|
||||||
|
sleep(1);
|
||||||
|
TEST_RES(read(pipe_fds[0], buf, 1), _ret == 1 && buf[0] == 'b');
|
||||||
|
|
||||||
|
// SIGSTOP -> SIGINT -> SIGTRAP -> SIGCONT
|
||||||
|
TEST_SUCC(kill(child_pid, SIGSTOP));
|
||||||
|
sleep(1);
|
||||||
|
|
||||||
|
// FIXME: The following two read pipe checks are commented out because
|
||||||
|
// Asterinas currently handles signals with user-provided handlers
|
||||||
|
// when the thread is stopped, while Linux does not.
|
||||||
|
TEST_SUCC(kill(child_pid, SIGINT));
|
||||||
|
sleep(1);
|
||||||
|
// TEST_ERRNO(read(pipe_fds[0], buf, 1), EAGAIN);
|
||||||
|
|
||||||
|
TEST_SUCC(kill(child_pid, SIGTRAP));
|
||||||
|
sleep(1);
|
||||||
|
// TEST_ERRNO(read(pipe_fds[0], buf, 1), EAGAIN);
|
||||||
|
|
||||||
|
TEST_RES(wait4(child_pid, &status, WSTOPPED, NULL),
|
||||||
|
_ret == child_pid && WIFSTOPPED(status) &&
|
||||||
|
WSTOPSIG(status) == SIGSTOP);
|
||||||
|
|
||||||
|
TEST_SUCC(kill(child_pid, SIGCONT));
|
||||||
|
sleep(1);
|
||||||
|
TEST_RES(read(pipe_fds[0], buf, 1), _ret == 1 && buf[0] == 'a');
|
||||||
|
TEST_RES(read(pipe_fds[0], buf, 1), _ret == 1 && buf[0] == 'b');
|
||||||
|
|
||||||
|
TEST_RES(wait4(child_pid, &status, WCONTINUED, NULL),
|
||||||
|
_ret == child_pid && WIFCONTINUED(status));
|
||||||
|
|
||||||
|
// SIGKILL
|
||||||
|
TEST_SUCC(kill(child_pid, SIGKILL));
|
||||||
|
TEST_RES(wait4(child_pid, NULL, 0, NULL), _ret == child_pid);
|
||||||
|
}
|
||||||
|
END_TEST()
|
||||||
|
|
||||||
|
FN_SETUP(kill_stopped)
|
||||||
|
{
|
||||||
|
CHECK(kill(pid, SIGKILL));
|
||||||
|
sleep(1);
|
||||||
|
CHECK_WITH(wait4(pid, &status, WSTOPPED, NULL),
|
||||||
|
_ret == pid && WIFSIGNALED(status) &&
|
||||||
|
WTERMSIG(status) == SIGKILL);
|
||||||
|
}
|
||||||
|
END_SETUP()
|
@ -33,6 +33,7 @@ mmap/mmap_readahead
|
|||||||
mmap/mmap_vmrss
|
mmap/mmap_vmrss
|
||||||
process/group_session
|
process/group_session
|
||||||
process/job_control
|
process/job_control
|
||||||
|
process/wait4
|
||||||
pthread/pthread_test
|
pthread/pthread_test
|
||||||
pty/open_pty
|
pty/open_pty
|
||||||
pty/pty_blocking
|
pty/pty_blocking
|
||||||
|
Reference in New Issue
Block a user