* Introduced new job_control_entry structure which, among other things,

is used instead of death_entry for team::dead_children.
* Added team::{stopped,continued}_children, which, analoguously to
  dead_children, are used to track the state of stopped/continued
  children.
* A team does have a job_control_entry, which is allocated at team
  creation time. It will be inserted into the parent's
  {stopped,continued}_children lists as the team's main thread is
  stopped/continued and removed when waitpid() retrieves the child
  state. When the team dies the entry is detached from the team and goes
  into the parent's dead_children list.
* Removed the wait_for_any field from team_dead_children. It was solely
  used to avoid deletion of the contained entries in certain situations.
  wait_for_child() (the waitpid() backend) always deletes an entry now,
  regardless of whether other threads are waiting; that's in
  accordance with the waidpid() specification. wait_for_thread() removes
  the entry only, if the caller is the parent of the respective team.
* Introduced team_set_job_control_state() which performes the job
  control entry transitions between the respective lists and wakes up
  threads waiting in wait_for_child(). It is invoked on team death and
  when the team's main thread receives job control signals.
* Reorganized wait_for_child(). It handles WCONTINUED and WUNTRACED now,
  too. Removed a block that interpreted the supplied ID as thread ID.
* Added missing parts in waitpid().

Job control starts to work, though it seems to have some glitches.



git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@22088 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Ingo Weinhold
2007-08-28 03:29:14 +00:00
parent c9e5503e5e
commit 24bcf55926
5 changed files with 348 additions and 156 deletions
+5 -2
View File
@@ -29,13 +29,16 @@ status_t team_get_address_space(team_id id,
struct vm_address_space **_addressSpace);
char **user_team_get_arguments(void);
int user_team_get_arg_count(void);
status_t team_get_death_entry(struct team *team, thread_id child,
struct death_entry *death, struct death_entry **_freeDeath);
struct job_control_entry* team_get_death_entry(struct team *team,
thread_id child, bool* _deleteEntry);
bool team_is_valid(team_id id);
struct team *team_get_team_struct_locked(team_id id);
int32 team_max_teams(void);
int32 team_used_teams(void);
void team_set_job_control_state(struct team* team, job_control_state newState,
int signal, bool threadsLocked);
status_t start_watching_team(team_id team, void (*hook)(team_id, void *),
void *data);
status_t stop_watching_team(team_id team, void (*hook)(team_id, void *),
+41 -6
View File
@@ -40,8 +40,17 @@ enum team_state {
TEAM_STATE_DEATH // being killed
};
typedef enum job_control_state {
JOB_CONTROL_STATE_NONE,
JOB_CONTROL_STATE_STOPPED,
JOB_CONTROL_STATE_CONTINUED,
JOB_CONTROL_STATE_DEAD
} job_control_state;
#define THREAD_RETURN_EXIT 0x1
#define THREAD_RETURN_INTERRUPTED 0x2
#define THREAD_STOPPED 0x3
#define THREAD_CONTINUED 0x4
struct image;
// defined in image.c
@@ -83,23 +92,44 @@ struct team_watcher {
// this is a soft limit for the number of dead entries in a team
typedef struct team_dead_children team_dead_children;
typedef struct team_job_control_children team_job_control_children;
typedef struct job_control_entry job_control_entry;
#ifdef __cplusplus
#include <condition_variable.h>
#include <util/DoublyLinkedList.h>
struct team_dead_children {
ConditionVariable<team_dead_children> condition_variable;
// wait for dead child entries
struct list list;
struct job_control_entry : DoublyLinkedListLinkImpl<job_control_entry> {
job_control_state state; // current team job control state
thread_id thread; // main thread ID == team ID
// valid while state != JOB_CONTROL_STATE_DEAD
struct team* team;
// valid when state == JOB_CONTROL_STATE_DEAD
pid_t group_id;
status_t status;
uint16 reason;
uint16 signal;
};
typedef DoublyLinkedList<job_control_entry> JobControlEntryList;
struct team_job_control_children {
ConditionVariable<team_job_control_children> condition_variable;
JobControlEntryList entries;
};
struct team_dead_children : team_job_control_children {
uint32 count;
vint32 wait_for_any; // count of wait_for_child() that wait for any
// child
bigtime_t kernel_time;
bigtime_t user_time;
};
#endif // __cplusplus
@@ -120,7 +150,12 @@ struct team {
int pending_signals;
void *io_context;
sem_id death_sem; // semaphore to wait on for dying threads
team_dead_children *dead_children;
team_job_control_children *stopped_children;
team_job_control_children *continued_children;
struct job_control_entry* job_control_entry;
struct vm_address_space *address_space;
struct thread *main_thread;
struct thread *thread_list;
+25 -3
View File
@@ -7,6 +7,9 @@
/* POSIX signals handling routines */
#include <stddef.h>
#include <string.h>
#include <OS.h>
#include <KernelExport.h>
@@ -19,9 +22,8 @@
#include <team.h>
#include <thread.h>
#include <user_debugger.h>
#include <util/AutoLock.h>
#include <stddef.h>
#include <string.h>
//#define TRACE_SIGNAL
#ifdef TRACE_SIGNAL
@@ -133,13 +135,26 @@ handle_signals(struct thread *thread)
switch (signal) {
case SIGCHLD:
case SIGWINCH:
case SIGCONT:
case SIGURG:
// notify the debugger
if (debugSignal)
notify_debugger(thread, signal, handler, false);
continue;
case SIGCONT:
// notify the debugger
if (debugSignal
&& !notify_debugger(thread, signal, handler, false))
continue;
// notify threads waiting for team state changes
if (thread == thread->team->main_thread) {
InterruptsSpinLocker locker(team_spinlock);
team_set_job_control_state(thread->team,
JOB_CONTROL_STATE_CONTINUED, signal, false);
}
continue;
case SIGSTOP:
case SIGTSTP:
case SIGTTIN:
@@ -151,6 +166,13 @@ handle_signals(struct thread *thread)
thread->next_state = B_THREAD_SUSPENDED;
reschedule = true;
// notify threads waiting for team state changes
if (thread == thread->team->main_thread) {
InterruptsSpinLocker locker(team_spinlock);
team_set_job_control_state(thread->team,
JOB_CONTROL_STATE_STOPPED, signal, false);
}
continue;
case SIGQUIT:
+238 -112
View File
@@ -33,6 +33,7 @@
#include <vfs.h>
#include <vm.h>
#include <vm_address_space.h>
#include <util/AutoLock.h>
#include <util/khash.h>
//#define TRACE_TEAM
@@ -564,32 +565,63 @@ create_team_struct(const char *name, bool kernel)
team->dead_threads_kernel_time = 0;
team->dead_threads_user_time = 0;
team->dead_children
= (team_dead_children*)malloc(sizeof(team_dead_children));
// dead children
team->dead_children = new(nothrow) team_dead_children;
if (team->dead_children == NULL)
return NULL;
MemoryDeleter deadChildrenDeleter(team->dead_children);
ObjectDeleter<team_dead_children> deadChildrenDeleter(team->dead_children);
list_init(&team->dead_children->list);
team->dead_children->count = 0;
team->dead_children->wait_for_any = 0;
team->dead_children->kernel_time = 0;
team->dead_children->user_time = 0;
team->dead_children->condition_variable.Publish(team->dead_children,
"dead children");
// stopped children
team->stopped_children = new(nothrow) team_job_control_children;
if (team->stopped_children == NULL)
return NULL;
ObjectDeleter<team_job_control_children> stoppedChildrenDeleter(
team->stopped_children);
// continued children
team->continued_children = new(nothrow) team_job_control_children;
if (team->continued_children == NULL)
return NULL;
ObjectDeleter<team_job_control_children> continuedChildrenDeleter(
team->continued_children);
// job control entry
team->job_control_entry = new(nothrow) job_control_entry;
if (team->job_control_entry == NULL)
return NULL;
ObjectDeleter<job_control_entry> jobControlEntryDeleter(
team->job_control_entry);
team->job_control_entry->state = JOB_CONTROL_STATE_NONE;
team->job_control_entry->thread = team->id;
team->job_control_entry->team = team;
list_init(&team->image_list);
list_init(&team->watcher_list);
clear_team_debug_info(&team->debug_info, true);
if (arch_team_init_team_struct(team, kernel) < 0) {
team->dead_children->condition_variable.Unpublish();
if (arch_team_init_team_struct(team, kernel) < 0)
return NULL;
}
// publish dead/stopped/continued children condition vars
team->dead_children->condition_variable.Publish(team->dead_children,
"dead children");
team->stopped_children->condition_variable.Publish(team->stopped_children,
"stopped children");
team->continued_children->condition_variable.Publish(
team->continued_children, "continued children");
// keep all allocated structures
jobControlEntryDeleter.Detach();
continuedChildrenDeleter.Detach();
stoppedChildrenDeleter.Detach();
deadChildrenDeleter.Detach();
teamDeleter.Detach();
return team;
}
@@ -597,16 +629,19 @@ create_team_struct(const char *name, bool kernel)
static void
delete_team_struct(struct team *team)
{
struct death_entry *death;
team->stopped_children->condition_variable.Unpublish();
team->continued_children->condition_variable.Unpublish();
team->dead_children->condition_variable.Unpublish();
while ((death = (struct death_entry*)list_remove_head_item(
&team->dead_children->list)) != NULL) {
free(death);
}
while (job_control_entry* entry = team->dead_children->entries.RemoveHead())
delete entry;
free(team->dead_children);
delete team->job_control_entry;
// usually already NULL and transferred to the parent
delete team->continued_children;
delete team->stopped_children;
delete team->dead_children;
free(team);
}
@@ -1262,17 +1297,40 @@ has_children_in_group(struct team *parent, pid_t groupID)
}
/** 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.
*/
static job_control_entry*
get_job_control_entry(team_job_control_children* children, pid_t id)
{
for (JobControlEntryList::Iterator it = children->entries.GetIterator();
job_control_entry* entry = it.Next();) {
if (id > 0) {
if (entry->thread == id)
return entry;
} else if (id == -1) {
return entry;
} else {
pid_t processGroup
= (entry->team ? entry->team->group_id : entry->group_id);
if (processGroup == -id)
return entry;
}
}
return NULL;
}
/*! 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.
*/
static thread_id
wait_for_child(thread_id child, uint32 flags, int32 *_reason, status_t *_returnCode)
wait_for_child(pid_t child, uint32 flags, int32 *_reason,
status_t *_returnCode)
{
struct team *team = thread_get_current_thread()->team;
struct death_entry death, *freeDeath = NULL;
struct job_control_entry foundEntry;
struct job_control_entry* freeDeathEntry = NULL;
status_t status = B_OK;
bool childrenExist = false;
TRACE(("wait_for_child(child = %ld, flags = %ld)\n", child, flags));
@@ -1280,52 +1338,34 @@ wait_for_child(thread_id child, uint32 flags, int32 *_reason, status_t *_returnC
// wait for all children in the process group of the calling team
child = -team->group_id;
}
if (child < 0) {
// we need to make sure the death entries won't get deleted too soon
atomic_add(&team->dead_children->wait_for_any, 1);
}
while (true) {
ConditionVariableEntry<team_dead_children> waitEntry;
InterruptsSpinLocker locker(team_spinlock);
cpu_status state = disable_interrupts();
GRAB_THREAD_LOCK();
// check whether any condition holds
job_control_entry* entry = get_job_control_entry(team->dead_children,
child);
if (child > 0 && !childrenExist) {
// wait for the specified child
if (entry == NULL && (flags & WCONTINUED) != 0)
entry = get_job_control_entry(team->continued_children, child);
struct thread *childThread = thread_get_thread_struct_locked(child);
if (childThread != NULL) {
// team is still running, so we would need to block
if ((flags & WNOHANG) != 0)
status = B_WOULD_BLOCK;
if (entry == NULL && (flags & WUNTRACED) != 0)
entry = get_job_control_entry(team->stopped_children, child);
// make sure this child is one of ours
if (childThread->team->parent != team)
status = ECHILD;
childrenExist = true;
}
}
RELEASE_THREAD_LOCK();
if (status != B_OK) {
restore_interrupts(state);
return status;
}
// see if there is any death entry for us already
GRAB_TEAM_LOCK();
status = team_get_death_entry(team, child, &death, &freeDeath);
if (status < B_OK) {
// there is no matching death entry
if (child == -1)
// If we don't have an entry yet, check whether there are any children
// complying to the process group specification at all.
if (entry == NULL) {
// No success yet -- check whether there are any children we could
// wait for.
bool childrenExist = false;
if (child == -1) {
childrenExist = team->children != NULL;
else if (child < -1)
} else if (child < -1) {
childrenExist = has_children_in_group(team, -child);
} else {
if (struct team* childTeam = team_get_team_struct_locked(child))
childrenExist = team->parent == team;
}
if (!childrenExist) {
// there is no child we could wait for
@@ -1334,41 +1374,76 @@ wait_for_child(thread_id child, uint32 flags, int32 *_reason, status_t *_returnC
// the children we're waiting for are still running
status = B_WOULD_BLOCK;
}
} else {
// got something
foundEntry = *entry;
if (entry->state == JOB_CONTROL_STATE_DEAD) {
// The child is dead. Reap its death entry.
freeDeathEntry = entry;
team->dead_children->entries.Remove(entry);
team->dead_children->count--;
} else {
// The child is well. Reset its job control state.
team_set_job_control_state(entry->team,
JOB_CONTROL_STATE_NONE, 0, false);
}
}
// If we haven't got anything yet, add prepare for waiting for the
// condition variables.
ConditionVariableEntry<team_dead_children> deadWaitEntry;
ConditionVariableEntry<team_job_control_children> continuedWaitEntry;
ConditionVariableEntry<team_job_control_children> stoppedWaitEntry;
if (status == B_WOULD_BLOCK && (flags & WNOHANG) == 0) {
// We need to hold the team lock when changing this counter,
// but of course only if we really will wait later
waitEntry.Add(team->dead_children);
deadWaitEntry.Add(team->dead_children);
if ((flags & WCONTINUED) != 0) {
continuedWaitEntry.Add(team->continued_children,
&deadWaitEntry);
}
if ((flags & WUNTRACED) != 0)
stoppedWaitEntry.Add(team->stopped_children, &deadWaitEntry);
}
RELEASE_TEAM_LOCK();
restore_interrupts(state);
locker.Unlock();
// we got our death entry and can return to our caller
// we got our entry and can return to our caller
if (status == B_OK)
break;
if (status != B_WOULD_BLOCK || (flags & WNOHANG) != 0)
goto err;
return status;
status = waitEntry.Wait(B_CAN_INTERRUPT);
status = deadWaitEntry.Wait(B_CAN_INTERRUPT);
if (status == B_INTERRUPTED)
goto err;
return status;
}
free(freeDeath);
delete freeDeathEntry;
// when we got here, we have a valid death entry, and
// already got unregistered from the team or group
*_returnCode = death.status;
*_reason = (death.signal << 16) | death.reason;
int reason = 0;
switch (foundEntry.state) {
case JOB_CONTROL_STATE_DEAD:
reason = foundEntry.reason;
break;
case JOB_CONTROL_STATE_STOPPED:
reason = THREAD_STOPPED;
break;
case JOB_CONTROL_STATE_CONTINUED:
reason = THREAD_CONTINUED;
break;
case JOB_CONTROL_STATE_NONE:
// can't happen
break;
}
return death.thread;
*_returnCode = foundEntry.status;
*_reason = (foundEntry.signal << 16) | reason;
err:
if (child < 0)
atomic_add(&team->dead_children->wait_for_any, -1);
return status;
return foundEntry.thread;
}
@@ -1466,45 +1541,29 @@ team_used_teams(void)
}
/** Fills the provided death entry if it's in the team.
* The \a child parameter follows waitpid() semantics.
* You need to have the team lock held when calling this function.
*/
status_t
team_get_death_entry(struct team *team, thread_id child, struct death_entry *death,
struct death_entry **_freeDeath)
/*! Fills the provided death entry if it's in the team.
You need to have the team lock held when calling this function.
*/
job_control_entry*
team_get_death_entry(struct team *team, thread_id child, bool* _deleteEntry)
{
struct death_entry *entry = NULL;
if (child <= 0)
return NULL;
// find matching death entry structure
if (child == 0) {
// wait for all children in the process group of the calling team
child = -team->group_id;
}
while ((entry = (struct death_entry*)list_get_next_item(
&team->dead_children->list, entry)) != NULL) {
if (child != -1 && entry->thread != child && entry->group_id != -child)
continue;
// we found one
*death = *entry;
// only remove the death entry if there aren't any other interested parties
if ((child < 0 && atomic_add(&team->dead_children->wait_for_any, -1) == 1)
|| (child > 0 && team->dead_children->wait_for_any == 0)) {
list_remove_link(entry);
job_control_entry* entry = get_job_control_entry(team->dead_children,
child);
if (entry) {
// remove the entry only, if the caller is the parent of the found team
if (team_get_current_team_id() == entry->thread) {
team->dead_children->entries.Remove(entry);
team->dead_children->count--;
*_freeDeath = entry;
*_deleteEntry = true;
} else {
*_deleteEntry = false;
}
return B_OK;
}
return B_ERROR;
return entry;
}
@@ -1722,7 +1781,6 @@ team_delete_team(struct team *team)
remove_images(team);
vfs_free_io_context(team->io_context);
// ToDo: should our death_entries be moved one level up?
delete_team_struct(team);
// notify the debugger, that the team is gone
@@ -1787,6 +1845,74 @@ team_get_address_space(team_id id, vm_address_space **_addressSpace)
}
/*! Sets the team's job control state.
Interrupts must be disabled and the team lock being held.
\a threadsLocked indicates whether the thread lock is being held, too.
*/
void
team_set_job_control_state(struct team* team, job_control_state newState,
int signal, bool threadsLocked)
{
if (team == NULL || team->job_control_entry == NULL)
return;
// don't touch anything, if the state stays the same or the team is already
// dead
job_control_entry* entry = team->job_control_entry;
if (entry->state == newState || entry->state == JOB_CONTROL_STATE_DEAD)
return;
// remove from the old list
switch (entry->state) {
case JOB_CONTROL_STATE_NONE:
// entry is in no list ATM
break;
case JOB_CONTROL_STATE_DEAD:
// can't get here
break;
case JOB_CONTROL_STATE_STOPPED:
team->parent->stopped_children->entries.Remove(entry);
break;
case JOB_CONTROL_STATE_CONTINUED:
team->parent->continued_children->entries.Remove(entry);
break;
}
entry->state = newState;
entry->signal = signal;
// add to new list
team_job_control_children* childList = NULL;
switch (entry->state) {
case JOB_CONTROL_STATE_NONE:
// entry doesn't get into any list
break;
case JOB_CONTROL_STATE_DEAD:
childList = team->parent->dead_children;
team->parent->dead_children->count++;
// When a child dies, we need to notify all lists, since that might
// have been the last of the parent's children, and a waiting
// parent thread wouldn't wake up otherwise.
team->parent->stopped_children->condition_variable.NotifyAll(
threadsLocked);
team->parent->continued_children->condition_variable.NotifyAll(
threadsLocked);
break;
case JOB_CONTROL_STATE_STOPPED:
childList = team->parent->stopped_children;
break;
case JOB_CONTROL_STATE_CONTINUED:
childList = team->parent->continued_children;
break;
}
if (childList != NULL) {
childList->entries.Add(entry);
childList->condition_variable.NotifyAll(threadsLocked);
}
}
/** Adds a hook to the team that is called as soon as this
* team goes away.
* This call might get public in the future.
+39 -33
View File
@@ -1094,7 +1094,6 @@ thread_exit(void)
struct thread *thread = thread_get_current_thread();
struct process_group *freeGroup = NULL;
struct team *team = thread->team;
struct death_entry *death = NULL;
thread_id parentID = -1;
bool deleteTeam = false;
sem_id cachedDeathSem = -1;
@@ -1122,20 +1121,11 @@ thread_exit(void)
delete_area_etc(team, area);
}
struct job_control_entry *death = NULL;
if (team != team_get_kernel_team()) {
if (team->main_thread == thread) {
// this was the main thread in this team, so we will delete that as well
deleteTeam = true;
// put a death entry into the dead children list of our parent
death = (struct death_entry *)malloc(sizeof(struct death_entry));
if (death != NULL) {
death->group_id = team->group_id;
death->thread = thread->id;
death->status = thread->exit.status;
death->reason = thread->exit.reason;
death->signal = thread->exit.signal;
}
}
// remove this thread from the current team and add it to the kernel
@@ -1161,22 +1151,30 @@ thread_exit(void)
// remember who our parent was so we can send a signal
parentID = parent->id;
if (death != NULL) {
// insert death entry into the parent's list
// Set the team job control state to "dead" and detach the job
// control entry from our team struct.
team_set_job_control_state(team, JOB_CONTROL_STATE_DEAD, 0, true);
death = team->job_control_entry;
team->job_control_entry = NULL;
list_add_link_to_tail(&parent->dead_children->list, death);
if (++parent->dead_children->count > MAX_DEAD_CHILDREN) {
death = (struct death_entry*)list_remove_head_item(
&parent->dead_children->list);
if (death != NULL) {
death->team = NULL;
death->group_id = team->group_id;
death->thread = thread->id;
death->status = thread->exit.status;
death->reason = thread->exit.reason;
death->signal = thread->exit.signal;
// team_set_job_control_state() already moved our entry
// into the parent's list. We just check the soft limit of
// death entries.
if (parent->dead_children->count > MAX_DEAD_CHILDREN) {
death = parent->dead_children->entries.RemoveHead();
parent->dead_children->count--;
} else
death = NULL;
RELEASE_THREAD_LOCK();
// notify listeners that a new death entry is available
// TODO: should that be moved to handle_signal() (for SIGCHLD)?
parent->dead_children->condition_variable.NotifyOne();
} else
RELEASE_THREAD_LOCK();
@@ -1198,9 +1196,9 @@ thread_exit(void)
team_delete_process_group(freeGroup);
team_delete_team(team);
// we need to remove any death entry that made it to here
// we need to delete any death entry that made it to here
if (death != NULL)
free(death);
delete death;
send_signal_etc(parentID, SIGCHLD, B_DO_NOT_RESCHEDULE);
cachedDeathSem = -1;
@@ -1251,12 +1249,12 @@ thread_exit(void)
thread->exit.sem = -1;
// fill all death entries
death = NULL;
while ((death = (struct death_entry*)list_get_next_item(
&thread->exit.waiters, death)) != NULL) {
death->status = thread->exit.status;
death->reason = thread->exit.reason;
death->signal = thread->exit.signal;
death_entry* entry = NULL;
while ((entry = (struct death_entry*)list_get_next_item(
&thread->exit.waiters, entry)) != NULL) {
entry->status = thread->exit.status;
entry->reason = thread->exit.reason;
entry->signal = thread->exit.signal;
}
RELEASE_THREAD_LOCK();
@@ -1513,7 +1511,8 @@ wait_for_thread_etc(thread_id id, uint32 flags, bigtime_t timeout,
status_t *_returnCode)
{
sem_id exitSem = B_BAD_THREAD_ID;
struct death_entry death, *freeDeath = NULL;
struct death_entry death;
job_control_entry* freeDeath = NULL;
struct thread *thread;
cpu_status state;
status_t status = B_OK;
@@ -1540,8 +1539,15 @@ wait_for_thread_etc(thread_id id, uint32 flags, bigtime_t timeout,
// find its death entry in our team
GRAB_TEAM_LOCK();
status = team_get_death_entry(thread_get_current_thread()->team,
id, &death, &freeDeath);
bool deleteEntry;
freeDeath = team_get_death_entry(thread_get_current_thread()->team, id,
&deleteEntry);
if (freeDeath != NULL) {
death.status = freeDeath->status;
if (!deleteEntry)
freeDeath = NULL;
} else
status = B_BAD_THREAD_ID;
RELEASE_TEAM_LOCK();
}
@@ -1553,7 +1559,7 @@ wait_for_thread_etc(thread_id id, uint32 flags, bigtime_t timeout,
if (_returnCode)
*_returnCode = death.status;
free(freeDeath);
delete freeDeath;
return B_OK;
}