From 33f0dbe40a37eee0ead3a4522d78197ff1d1e747 Mon Sep 17 00:00:00 2001 From: Ingo Weinhold Date: Mon, 3 Sep 2007 21:35:24 +0000 Subject: [PATCH] * Resolved TODO: waitpid() clears pending SIGCHLD, if the signal is blocked and no other child status is available. * Respect SA_NOCLDWAIT and ignored SIGCHLD in waitpid(): Unless a child status is available immediately, the thread shall block until all children are gone. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@22161 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- headers/private/kernel/ksignal.h | 2 + src/system/kernel/signal.cpp | 1 - src/system/kernel/team.cpp | 68 +++++++++++++++++++++++++------- 3 files changed, 55 insertions(+), 16 deletions(-) diff --git a/headers/private/kernel/ksignal.h b/headers/private/kernel/ksignal.h index ddb536cca9..38c34d6e50 100644 --- a/headers/private/kernel/ksignal.h +++ b/headers/private/kernel/ksignal.h @@ -12,6 +12,8 @@ #define KILL_SIGNALS ((1L << (SIGKILL - 1)) | (1L << (SIGKILLTHR - 1))) +#define SIGNAL_TO_MASK(signal) (1LL << (signal - 1)) + #ifdef __cplusplus extern "C" { diff --git a/src/system/kernel/signal.cpp b/src/system/kernel/signal.cpp index a7284108c9..f0d7c395be 100644 --- a/src/system/kernel/signal.cpp +++ b/src/system/kernel/signal.cpp @@ -33,7 +33,6 @@ #endif -#define SIGNAL_TO_MASK(signal) (1LL << (signal - 1)) #define BLOCKABLE_SIGNALS (~(KILL_SIGNALS | SIGNAL_TO_MASK(SIGSTOP))) #define STOP_SIGNALS \ (SIGNAL_TO_MASK(SIGSTOP) | SIGNAL_TO_MASK(SIGTSTP) \ diff --git a/src/system/kernel/team.cpp b/src/system/kernel/team.cpp index 0854bfafb4..81e3b65125 100644 --- a/src/system/kernel/team.cpp +++ b/src/system/kernel/team.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -1320,6 +1321,21 @@ get_job_control_entry(team_job_control_children* children, pid_t id) } +static job_control_entry* +get_job_control_entry(struct team* team, pid_t id, uint32 flags) +{ + job_control_entry* entry = get_job_control_entry(team->dead_children, id); + + if (entry == NULL && (flags & WCONTINUED) != 0) + entry = get_job_control_entry(team->continued_children, id); + + if (entry == NULL && (flags & WUNTRACED) != 0) + entry = get_job_control_entry(team->stopped_children, id); + + return entry; +} + + /*! This is the kernel backend for waitpid(). It is a bit more powerful when it comes to the reason why a thread has died than waitpid() can be. */ @@ -1327,7 +1343,8 @@ static thread_id wait_for_child(pid_t child, uint32 flags, int32 *_reason, status_t *_returnCode) { - struct team *team = thread_get_current_thread()->team; + struct thread* thread = thread_get_current_thread(); + struct team* team = thread->team; struct job_control_entry foundEntry; struct job_control_entry* freeDeathEntry = NULL; status_t status = B_OK; @@ -1339,18 +1356,14 @@ wait_for_child(pid_t child, uint32 flags, int32 *_reason, child = -team->group_id; } + bool ignoreFoundEntries = false; + bool ignoreFoundEntriesChecked = false; + while (true) { InterruptsSpinLocker locker(team_spinlock); // check whether any condition holds - job_control_entry* entry = get_job_control_entry(team->dead_children, - child); - - if (entry == NULL && (flags & WCONTINUED) != 0) - entry = get_job_control_entry(team->continued_children, child); - - if (entry == NULL && (flags & WUNTRACED) != 0) - entry = get_job_control_entry(team->stopped_children, child); + job_control_entry* entry = get_job_control_entry(team, child, flags); // If we don't have an entry yet, check whether there are any children // complying to the process group specification at all. @@ -1410,14 +1423,36 @@ wait_for_child(pid_t child, uint32 flags, int32 *_reason, locker.Unlock(); // we got our entry and can return to our caller - if (status == B_OK) + if (status == B_OK) { + if (ignoreFoundEntries) { + // ... unless we shall ignore found entries + delete freeDeathEntry; + freeDeathEntry = NULL; + continue; + } + break; + } + if (status != B_WOULD_BLOCK || (flags & WNOHANG) != 0) return status; status = deadWaitEntry.Wait(B_CAN_INTERRUPT); if (status == B_INTERRUPTED) return status; + + // If SA_NOCLDWAIT is set or SIGCHLD is ignored, we shall wait until + // all our children are dead and fail with ECHILD. We check the + // condition at this point. + if (!ignoreFoundEntriesChecked) { + struct sigaction& handler = thread->sig_action[SIGCHLD - 1]; + if ((handler.sa_flags & SA_NOCLDWAIT) != 0 + || handler.sa_handler == SIG_IGN) { + ignoreFoundEntries = true; + } + + ignoreFoundEntriesChecked = true; + } } delete freeDeathEntry; @@ -1443,11 +1478,14 @@ wait_for_child(pid_t child, uint32 flags, int32 *_reason, *_returnCode = foundEntry.status; *_reason = (foundEntry.signal << 16) | reason; - // TODO: From the Open Group Base Specs Issue 6: - // "... if SIGCHLD is blocked, if wait() or waitpid() return because - // the status of a child process is available, any pending SIGCHLD signal - // shall be cleared unless the status of another child process is - // available." + // If SIGCHLD is blocked, we shall clear pending SIGCHLDs, if no other child + // status is available. + if ((atomic_get(&thread->sig_block_mask) & SIGNAL_TO_MASK(SIGCHLD)) != 0) { + InterruptsSpinLocker locker(team_spinlock); + + if (get_job_control_entry(team, child, flags) == NULL) + atomic_and(&thread->sig_pending, ~SIGNAL_TO_MASK(SIGCHLD)); + } return foundEntry.thread; }