diff --git a/kernel/include/kernel/Process.h b/kernel/include/kernel/Process.h index 8d069905..b44d4246 100644 --- a/kernel/include/kernel/Process.h +++ b/kernel/include/kernel/Process.h @@ -188,6 +188,9 @@ namespace Kernel BAN::ErrorOr sys_tty_ctrl(int fildes, int command, int flags); + void set_stopped(bool stopped, int signal); + void wait_while_stopped(); + static BAN::ErrorOr kill(pid_t pid, int signal); BAN::ErrorOr sys_kill(pid_t pid, int signal); BAN::ErrorOr sys_sigaction(int signal, const struct sigaction* act, struct sigaction* oact); @@ -299,12 +302,11 @@ namespace Kernel } private: - struct ChildExitStatus + struct ChildWaitStatus { - pid_t pid { 0 }; - pid_t pgrp { 0 }; - int exit_code { 0 }; - bool exited { false }; + pid_t pid { 0 }; + pid_t pgrp { 0 }; + BAN::Optional status; }; Credentials m_credentials; @@ -320,6 +322,9 @@ namespace Kernel mutable Mutex m_process_lock; + BAN::Atomic m_stopped { false }; + ThreadBlocker m_stop_blocker; + VirtualFileSystem::File m_working_directory; VirtualFileSystem::File m_root_file; @@ -344,8 +349,9 @@ namespace Kernel BAN::Vector m_environ; BAN::String m_executable; - BAN::Vector m_child_exit_statuses; - ThreadBlocker m_child_exit_blocker; + BAN::Vector m_child_wait_statuses; + SpinLock m_child_wait_lock; + ThreadBlocker m_child_wait_blocker; BAN::Atomic m_is_exiting { false }; diff --git a/kernel/include/kernel/Thread.h b/kernel/include/kernel/Thread.h index 4a725dd6..05364498 100644 --- a/kernel/include/kernel/Thread.h +++ b/kernel/include/kernel/Thread.h @@ -53,7 +53,7 @@ namespace Kernel BAN::ErrorOr initialize_userspace(vaddr_t entry, BAN::Span argv, BAN::Span envp, BAN::Span auxv); // Returns true, if thread is going to trigger signal - bool is_interrupted_by_signal() const; + bool is_interrupted_by_signal(bool skip_stop_and_cont = false) const; // Returns true if pending signal can be added to thread bool can_add_signal_to_execute() const; @@ -63,6 +63,13 @@ namespace Kernel void add_signal(int signal); void set_suspend_signal_mask(uint64_t sigmask); + static bool is_stopping_signal(int signal); + static bool is_continuing_signal(int signal); + static bool is_terminating_signal(int signal); + static bool is_abnormal_terminating_signal(int signal); + static bool is_realtime_signal(int signal); + bool will_exit_because_of_signal() const; + BAN::ErrorOr sigaltstack(const stack_t* ss, stack_t* oss); // blocks current thread and returns either on unblock, eintr, spuriously or after timeout diff --git a/kernel/kernel/Process.cpp b/kernel/kernel/Process.cpp index 9e8cf817..935ece78 100644 --- a/kernel/kernel/Process.cpp +++ b/kernel/kernel/Process.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -295,21 +296,20 @@ namespace Kernel if (parent_process) { - LockGuard _(parent_process->m_process_lock); + SpinLockGuard _(parent_process->m_child_wait_lock); - for (auto& child : parent_process->m_child_exit_statuses) + for (auto& child : parent_process->m_child_wait_statuses) { if (child.pid != pid()) continue; - child.exit_code = __WGENEXITCODE(status, signal); - child.exited = true; + child.status = __WGENEXITCODE(status, signal); parent_process->add_pending_signal(SIGCHLD); if (!parent_process->m_threads.empty()) Processor::scheduler().unblock_thread(parent_process->m_threads.front()); - parent_process->m_child_exit_blocker.unblock(); + parent_process->m_child_wait_blocker.unblock(); break; } @@ -588,18 +588,22 @@ namespace Kernel LockGuard _(m_process_lock); - ChildExitStatus* child_exit_status = nullptr; - for (auto& child : m_child_exit_statuses) + ChildWaitStatus* child_exit_status = nullptr; + { - if (child.pid != 0) - continue; - child_exit_status = &child; - break; - } - if (child_exit_status == nullptr) - { - TRY(m_child_exit_statuses.emplace_back()); - child_exit_status = &m_child_exit_statuses.back(); + SpinLockGuard _(m_child_wait_lock); + for (auto& child : m_child_wait_statuses) + { + if (child.pid != 0) + continue; + child_exit_status = &child; + break; + } + if (child_exit_status == nullptr) + { + TRY(m_child_wait_statuses.emplace_back()); + child_exit_status = &m_child_wait_statuses.back(); + } } auto working_directory = TRY(m_working_directory.clone()); @@ -794,7 +798,7 @@ namespace Kernel // FIXME: Add WCONTINUED and WUNTRACED when stopped/continued processes are added const auto pid_matches = - [&](const ChildExitStatus& child) + [&](const ChildWaitStatus& child) { if (pid == -1) return true; @@ -805,47 +809,67 @@ namespace Kernel return child.pid == pid; }; - LockGuard _(m_process_lock); + pid_t child_pid = 0; + int child_status = 0; for (;;) { - pid_t exited_pid = 0; - int exit_code = 0; - { - bool found = false; - for (auto& child : m_child_exit_statuses) - { - if (!pid_matches(child)) - continue; - found = true; - if (!child.exited) - continue; - exited_pid = child.pid; - exit_code = child.exit_code; - child = {}; - break; - } + bool found = false; - if (!found) - return BAN::Error::from_errno(ECHILD); + SpinLockGuard sguard(m_child_wait_lock); + + for (auto& child : m_child_wait_statuses) + { + if (!pid_matches(child)) + continue; + + found = true; + if (!child.status.has_value()) + continue; + + const int status = child.status.value(); + + bool should_report = false; + if (WIFSTOPPED(status)) + should_report = !!(options & WUNTRACED); + else if (WIFCONTINUED(status)) + should_report = !!(options & WCONTINUED); + else + should_report = true; + + if (!should_report) + continue; + + child_pid = child.pid; + child_status = status; + child.status = {}; + + break; } - if (exited_pid != 0) - { - if (stat_loc) - { - TRY(validate_pointer_access(stat_loc, sizeof(stat_loc), true)); - *stat_loc = exit_code; - } - remove_pending_signal(SIGCHLD); - return exited_pid; - } + if (child_pid != 0) + break; + + if (!found) + return BAN::Error::from_errno(ECHILD); if (options & WNOHANG) return 0; - TRY(Thread::current().block_or_eintr_indefinite(m_child_exit_blocker, &m_process_lock)); + SpinLockGuardAsMutex smutex(sguard); + TRY(Thread::current().block_or_eintr_indefinite(m_child_wait_blocker, &smutex)); } + + LockGuard _(m_process_lock); + + if (stat_loc) + { + TRY(validate_pointer_access(stat_loc, sizeof(stat_loc), true)); + *stat_loc = child_status; + } + + remove_pending_signal(SIGCHLD); + return child_pid; } BAN::ErrorOr Process::sys_sleep(int seconds) @@ -2583,6 +2607,69 @@ namespace Kernel return 0; } + void Process::set_stopped(bool stopped, int signal) + { + SpinLockGuard _(m_signal_lock); + + Process* parent = nullptr; + + for_each_process( + [&parent, this](Process& process) -> BAN::Iteration + { + if (process.pid() != m_parent) + return BAN::Iteration::Continue; + parent = &process; + return BAN::Iteration::Break; + } + ); + + if (parent != nullptr) + { + { + SpinLockGuard _(parent->m_child_wait_lock); + + for (auto& child : parent->m_child_wait_statuses) + { + if (child.pid != pid()) + continue; + if (!child.status.has_value() || WIFCONTINUED(*child.status) || WIFSTOPPED(*child.status)) + child.status = stopped + ? __WGENSTOPCODE(signal) + : __WGENCONTCODE(); + break; + } + + parent->m_child_wait_blocker.unblock(); + } + + if (!(m_signal_handlers[SIGCHLD].sa_flags & SA_NOCLDSTOP)) + { + parent->add_pending_signal(SIGCHLD); + if (!parent->m_threads.empty()) + Processor::scheduler().unblock_thread(parent->m_threads.front()); + } + } + + m_stopped = stopped; + m_stop_blocker.unblock(); + } + + void Process::wait_while_stopped() + { + for (;;) + { + while (Thread::current().will_exit_because_of_signal()) + Thread::current().handle_signal(); + + SpinLockGuard guard(m_signal_lock); + if (!m_stopped) + break; + + SpinLockGuardAsMutex smutex(guard); + m_stop_blocker.block_indefinite(&smutex); + } + } + BAN::ErrorOr Process::kill(pid_t pid, int signal) { if (pid == 0 || pid == -1) @@ -2594,18 +2681,26 @@ namespace Kernel for_each_process( [&](Process& process) { - if (pid == process.pid() || -pid == process.pgrp()) + if (pid != process.pid() && -pid != process.pgrp()) + return BAN::Iteration::Continue; + + found = true; + + if (signal == 0) + ; + // NOTE: stopped signals go through thread's signal handling code + // because for example SIGTSTP can be ignored + else if (Thread::is_continuing_signal(signal)) + process.set_stopped(false, signal); + else { - found = true; - if (signal) - { - process.add_pending_signal(signal); - if (!process.m_threads.empty()) - Processor::scheduler().unblock_thread(process.m_threads.front()); - } - return (pid > 0) ? BAN::Iteration::Break : BAN::Iteration::Continue; + process.add_pending_signal(signal); + if (!process.m_threads.empty()) + Processor::scheduler().unblock_thread(process.m_threads.front()); + process.m_stop_blocker.unblock(); } - return BAN::Iteration::Continue; + + return (pid > 0) ? BAN::Iteration::Break : BAN::Iteration::Continue; } ); @@ -2623,8 +2718,17 @@ namespace Kernel if (pid == m_pid) { - if (signal) + if (signal == 0) + ; + // NOTE: stopped signals go through thread's signal handling code + // because for example SIGTSTP can be ignored + else if (Thread::is_continuing_signal(signal)) + set_stopped(false, signal); + else + { add_pending_signal(signal); + m_stop_blocker.unblock(); + } return 0; } diff --git a/kernel/kernel/Syscall.cpp b/kernel/kernel/Syscall.cpp index d5518742..2a6e1fae 100644 --- a/kernel/kernel/Syscall.cpp +++ b/kernel/kernel/Syscall.cpp @@ -46,6 +46,8 @@ namespace Kernel Processor::set_interrupt_state(InterruptState::Enabled); + Process::current().wait_while_stopped(); + BAN::ErrorOr ret = BAN::Error::from_errno(ENOSYS); const char* process_path = Process::current().name(); @@ -92,6 +94,8 @@ namespace Kernel if (ret.is_error() && ret.error().is_kernel_error()) Kernel::panic("Kernel error while returning to userspace {}", ret.error()); + Process::current().wait_while_stopped(); + Processor::set_interrupt_state(InterruptState::Disabled); auto& current_thread = Thread::current(); diff --git a/kernel/kernel/Thread.cpp b/kernel/kernel/Thread.cpp index e2e1052c..7b310a74 100644 --- a/kernel/kernel/Thread.cpp +++ b/kernel/kernel/Thread.cpp @@ -69,6 +69,77 @@ namespace Kernel s_default_sse_storage_initialized = true; } + bool Thread::is_stopping_signal(int signal) + { + switch(signal) + { + case SIGSTOP: + case SIGTSTP: + case SIGTTIN: + case SIGTTOU: + return true; + default: + return false; + } + } + + bool Thread::is_continuing_signal(int signal) + { + switch(signal) + { + case SIGCONT: + return true; + default: + return false; + } + } + + bool Thread::is_terminating_signal(int signal) + { + switch (signal) + { + case SIGALRM: + case SIGHUP: + case SIGINT: + case SIGKILL: + case SIGPIPE: + case SIGTERM: + case SIGUSR1: + case SIGUSR2: + case SIGPOLL: + case SIGPROF: + case SIGVTALRM: + return true; + default: + return false; + } + } + + bool Thread::is_abnormal_terminating_signal(int signal) + { + switch (signal) + { + case SIGABRT: + case SIGBUS: + case SIGFPE: + case SIGILL: + case SIGQUIT: + case SIGSEGV: + case SIGSYS: + case SIGTRAP: + case SIGXCPU: + case SIGXFSZ: + return true; + default: + return false; + } + } + + bool Thread::is_realtime_signal(int signal) + { + return SIGRTMIN <= signal && signal <= SIGRTMAX; + } + static bool is_default_ignored_signal(int signal) { switch (signal) @@ -79,7 +150,7 @@ namespace Kernel case SIGCANCEL: return true; default: - return false; + return Thread::is_realtime_signal(signal); } } @@ -458,7 +529,7 @@ namespace Kernel memset(&m_interrupt_registers, 0, sizeof(InterruptRegisters)); } - bool Thread::is_interrupted_by_signal() const + bool Thread::is_interrupted_by_signal(bool skip_stop_and_cont) const { if (!is_userspace() || m_state != State::Executing) return false; @@ -472,6 +543,8 @@ namespace Kernel { if (!(signals & ((uint64_t)1 << i))) continue; + if (skip_stop_and_cont && (is_stopping_signal(i) || is_continuing_signal(i))) + continue; vaddr_t signal_handler; { @@ -501,13 +574,23 @@ namespace Kernel return interrupt_stack.ip == (uintptr_t)signal_trampoline; } + bool Thread::will_exit_because_of_signal() const + { + const uint64_t full_pending_mask = m_signal_pending_mask | process().signal_pending_mask(); + const uint64_t signals = full_pending_mask & ~m_signal_block_mask; + for (size_t sig = _SIGMIN; sig <= _SIGMAX; sig++) + if (signals & (static_cast(1) << sig)) + if (is_terminating_signal(sig) || is_abnormal_terminating_signal(sig)) + return true; + return false; + } + bool Thread::handle_signal(int signal) { ASSERT(&Thread::current() == this); ASSERT(is_userspace()); auto state = m_signal_lock.lock(); - ASSERT(state == InterruptState::Disabled); auto& interrupt_stack = *reinterpret_cast(kernel_stack_top() - sizeof(InterruptStack)); ASSERT(GDT::is_user_segment(interrupt_stack.cs)); @@ -613,52 +696,31 @@ namespace Kernel } else { - switch (signal) + if (is_abnormal_terminating_signal(signal)) + { + process().exit(128 + signal, signal | 0x80); + ASSERT_NOT_REACHED(); + } + else if (is_terminating_signal(signal)) + { + process().exit(128 + signal, signal); + ASSERT_NOT_REACHED(); + } + else if (is_stopping_signal(signal)) + { + process().set_stopped(true, signal); + } + else if (is_continuing_signal(signal)) + { + process().set_stopped(false, signal); + } + else if (is_default_ignored_signal(signal)) { - // Abnormal termination of the process with additional actions. - case SIGABRT: - case SIGBUS: - case SIGFPE: - case SIGILL: - case SIGQUIT: - case SIGSEGV: - case SIGSYS: - case SIGTRAP: - case SIGXCPU: - case SIGXFSZ: - process().exit(128 + signal, signal | 0x80); - ASSERT_NOT_REACHED(); - // Abnormal termination of the process - case SIGALRM: - case SIGHUP: - case SIGINT: - case SIGKILL: - case SIGPIPE: - case SIGTERM: - case SIGUSR1: - case SIGUSR2: - case SIGPOLL: - case SIGPROF: - case SIGVTALRM: - process().exit(128 + signal, signal); - ASSERT_NOT_REACHED(); - - // Stop the process: - case SIGSTOP: - case SIGTSTP: - case SIGTTIN: - case SIGTTOU: - ASSERT_NOT_REACHED(); - - // Continue the process, if it is stopped; otherwise, ignore the signal. - case SIGCONT: - ASSERT_NOT_REACHED(); - - default: - if (is_default_ignored_signal(signal)) - break; - panic("Executing unhandled signal {}", signal); + } + else + { + panic("Executing unhandled signal {}", signal); } } @@ -739,20 +801,20 @@ namespace Kernel BAN::ErrorOr Thread::sleep_or_eintr_ns(uint64_t ns) { - if (is_interrupted_by_signal()) + if (is_interrupted_by_signal(true)) return BAN::Error::from_errno(EINTR); SystemTimer::get().sleep_ns(ns); - if (is_interrupted_by_signal()) + if (is_interrupted_by_signal(true)) return BAN::Error::from_errno(EINTR); return {}; } BAN::ErrorOr Thread::block_or_eintr_indefinite(ThreadBlocker& thread_blocker, BaseMutex* mutex) { - if (is_interrupted_by_signal()) + if (is_interrupted_by_signal(true)) return BAN::Error::from_errno(EINTR); thread_blocker.block_indefinite(mutex); - if (is_interrupted_by_signal()) + if (is_interrupted_by_signal(true)) return BAN::Error::from_errno(EINTR); return {}; } @@ -765,10 +827,10 @@ namespace Kernel BAN::ErrorOr Thread::block_or_eintr_or_waketime_ns(ThreadBlocker& thread_blocker, uint64_t wake_time_ns, bool etimedout, BaseMutex* mutex) { - if (is_interrupted_by_signal()) + if (is_interrupted_by_signal(true)) return BAN::Error::from_errno(EINTR); thread_blocker.block_with_wake_time_ns(wake_time_ns, mutex); - if (is_interrupted_by_signal()) + if (is_interrupted_by_signal(true)) return BAN::Error::from_errno(EINTR); if (etimedout && SystemTimer::get().ms_since_boot() >= wake_time_ns) return BAN::Error::from_errno(ETIMEDOUT); diff --git a/userspace/libraries/LibC/include/sys/wait.h b/userspace/libraries/LibC/include/sys/wait.h index 684e46ba..46a854a5 100644 --- a/userspace/libraries/LibC/include/sys/wait.h +++ b/userspace/libraries/LibC/include/sys/wait.h @@ -20,14 +20,17 @@ __BEGIN_DECLS #define WNOWAIT 0x10 #define WSTOPPED 0x20 -#define WEXITSTATUS(status) (((status) >> 8) & 0xFF) -#define WSTOPSIG(status) WEXITSTATUS(status) -#define WTERMSIG(status) ((status) & 0x7F) -#define WIFEXITED(status) (WTERMSIG(status) == 0) -#define WIFSIGNALED(status) (((status) & 0x7F) > 0 && ((status) & 0x7F) < 0x7F) -#define WIFSTOPPED(status) (((status) & 0xFF) == 0x7F) +#define WEXITSTATUS(status) (((status) >> 8) & 0xFF) +#define WSTOPSIG(status) WEXITSTATUS(status) +#define WTERMSIG(status) ((status) & 0x7F) +#define WIFEXITED(status) (WTERMSIG(status) == 0) +#define WIFSIGNALED(status) (((status) & 0x7F) > 0 && ((status) & 0x7F) < 0x7E) +#define WIFSTOPPED(status) (((status) & 0x7F) == 0x7F) +#define WIFCONTINUED(status) (((status) & 0x7F) == 0x7E) #define __WGENEXITCODE(ret, sig) (((ret) << 8) | (sig)) +#define __WGENSTOPCODE(sig) (((sig) << 8) | 0x7F) +#define __WGENCONTCODE() ( 0x7E) typedef enum {