Added new kernel thread "undertaker" which gets rid of dead thread

remains. This replaces the previous mechanism of switching the thread to
a dedicated death stack. We might consider moving more cleanup work to
the undertaker, but that seems a little more involved.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@25704 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Ingo Weinhold
2008-05-29 14:28:31 +00:00
parent 2620242fd5
commit 9101535322
3 changed files with 72 additions and 199 deletions
-2
View File
@@ -23,8 +23,6 @@ extern spinlock thread_spinlock;
#define GRAB_THREAD_LOCK() acquire_spinlock(&thread_spinlock) #define GRAB_THREAD_LOCK() acquire_spinlock(&thread_spinlock)
#define RELEASE_THREAD_LOCK() release_spinlock(&thread_spinlock) #define RELEASE_THREAD_LOCK() release_spinlock(&thread_spinlock)
extern struct thread_queue dead_q;
extern spinlock team_spinlock; extern spinlock team_spinlock;
// NOTE: TEAM lock can be held over a THREAD lock acquisition, // NOTE: TEAM lock can be held over a THREAD lock acquisition,
// but not the other way (to avoid deadlock) // but not the other way (to avoid deadlock)
-3
View File
@@ -173,9 +173,6 @@ scheduler_reschedule(void)
TRACE(("reschedule(): suspending thread %ld\n", oldThread->id)); TRACE(("reschedule(): suspending thread %ld\n", oldThread->id));
break; break;
case THREAD_STATE_FREE_ON_RESCHED: case THREAD_STATE_FREE_ON_RESCHED:
// This will hopefully be eliminated once the slab
// allocator is done
thread_enqueue(oldThread, &dead_q);
break; break;
default: default:
TRACE(("not enqueueing thread %ld into run q. next_state = %ld\n", oldThread->id, oldThread->next_state)); TRACE(("not enqueueing thread %ld into run q. next_state = %ld\n", oldThread->id, oldThread->next_state));
+60 -182
View File
@@ -54,15 +54,6 @@
#define THREAD_MAX_MESSAGE_SIZE 65536 #define THREAD_MAX_MESSAGE_SIZE 65536
// used to pass messages between thread_exit and thread_exit2
struct thread_exit_args {
struct thread *thread;
area_id old_kernel_stack;
uint32 death_stack;
sem_id death_sem;
team_id original_team_id;
};
struct thread_key { struct thread_key {
thread_id id; thread_id id;
@@ -81,22 +72,27 @@ static thread_id sNextThreadID = 1;
static int32 sMaxThreads = 4096; static int32 sMaxThreads = 4096;
static int32 sUsedThreads = 0; static int32 sUsedThreads = 0;
// death stacks - used temporarily as a thread cleans itself up struct UndertakerEntry : DoublyLinkedListLinkImpl<UndertakerEntry> {
struct death_stack { struct thread* thread;
area_id area; team_id teamID;
addr_t address; sem_id deathSem;
bool in_use;
UndertakerEntry(struct thread* thread, team_id teamID, sem_id deathSem)
:
thread(thread),
teamID(teamID),
deathSem(deathSem)
{
}
}; };
static struct death_stack *sDeathStacks;
static unsigned int sNumDeathStacks; static DoublyLinkedList<UndertakerEntry> sUndertakerEntries;
static unsigned int volatile sDeathStackBitmap; static ConditionVariable sUndertakerCondition;
static sem_id sDeathStackSem;
static spinlock sDeathStackLock = 0;
// The dead queue is used as a pool from which to retrieve and reuse previously // The dead queue is used as a pool from which to retrieve and reuse previously
// allocated thread structs when creating a new thread. It should be gone once // allocated thread structs when creating a new thread. It should be gone once
// the slab allocator is in. // the slab allocator is in.
struct thread_queue dead_q; static struct thread_queue dead_q;
static void thread_kthread_entry(void); static void thread_kthread_entry(void);
static void thread_kthread_exit(void); static void thread_kthread_exit(void);
@@ -539,143 +535,58 @@ create_thread(thread_creation_attributes& attributes, bool kernel)
} }
/*! static status_t
Finds a free death stack for us and allocates it. undertaker(void* /*args*/)
Must be called with interrupts enabled.
*/
static uint32
get_death_stack(void)
{ {
cpu_status state; while (true) {
uint32 bit; // wait for a thread to bury
int32 i; ConditionVariableEntry conditionEntry;
acquire_sem(sDeathStackSem); InterruptsSpinLocker locker(thread_spinlock);
sUndertakerCondition.Add(&conditionEntry);
locker.Unlock();
// grab the death stack and thread locks, find a free spot and release conditionEntry.Wait();
state = disable_interrupts(); locker.Lock();
UndertakerEntry* _entry = sUndertakerEntries.RemoveHead();
locker.Unlock();
acquire_spinlock(&sDeathStackLock); if (_entry == NULL)
GRAB_THREAD_LOCK(); continue;
bit = sDeathStackBitmap; UndertakerEntry entry = *_entry;
bit = (~bit) & ~((~bit) - 1); // we need a copy, since the original entry is on the thread's stack
sDeathStackBitmap |= bit;
RELEASE_THREAD_LOCK(); // we've got an entry
release_spinlock(&sDeathStackLock); struct thread* thread = entry.thread;
restore_interrupts(state);
// sanity checks
if (!bit)
panic("get_death_stack: couldn't find free stack!\n");
if (bit & (bit - 1))
panic("get_death_stack: impossible bitmap result!\n");
// bit to number
for (i = -1; bit; i++) {
bit >>= 1;
}
TRACE(("get_death_stack: returning %#lx\n", sDeathStacks[i].address));
return (uint32)i;
}
/*! Returns the thread's death stack to the pool.
Interrupts must be disabled and the sDeathStackLock be held.
*/
static void
put_death_stack(uint32 index)
{
TRACE(("put_death_stack...: passed %lu\n", index));
if (index >= sNumDeathStacks)
panic("put_death_stack: passed invalid stack index %ld\n", index);
if (!(sDeathStackBitmap & (1 << index)))
panic("put_death_stack: passed invalid stack index %ld\n", index);
GRAB_THREAD_LOCK();
sDeathStackBitmap &= ~(1 << index);
RELEASE_THREAD_LOCK();
release_sem_etc(sDeathStackSem, 1, B_DO_NOT_RESCHEDULE);
// we must not hold the thread lock when releasing a semaphore
}
static void
thread_exit2(void *_args)
{
struct thread_exit_args args;
// copy the arguments over, since the source is probably on the kernel
// stack we're about to delete
memcpy(&args, _args, sizeof(struct thread_exit_args));
// we can't let the interrupts disabled at this point
enable_interrupts();
TRACE(("thread_exit2, running on death stack %#lx\n", args.death_stack));
// delete the old kernel stack area // delete the old kernel stack area
TRACE(("thread_exit2: deleting old kernel stack id %ld for thread %ld\n", delete_area(thread->kernel_stack_area);
args.old_kernel_stack, args.thread->id));
delete_area(args.old_kernel_stack);
// remove this thread from all of the global lists // remove this thread from all of the global lists
TRACE(("thread_exit2: removing thread %ld from global lists\n",
args.thread->id));
disable_interrupts(); disable_interrupts();
GRAB_TEAM_LOCK(); GRAB_TEAM_LOCK();
remove_thread_from_team(team_get_kernel_team(), args.thread); remove_thread_from_team(team_get_kernel_team(), thread);
RELEASE_TEAM_LOCK(); RELEASE_TEAM_LOCK();
enable_interrupts(); enable_interrupts();
// needed for the debugger notification below // needed for the debugger notification below
TRACE(("thread_exit2: done removing thread from lists\n")); if (entry.deathSem >= 0)
release_sem_etc(entry.deathSem, 1, B_DO_NOT_RESCHEDULE);
if (args.death_sem >= 0)
release_sem_etc(args.death_sem, 1, B_DO_NOT_RESCHEDULE);
// notify the debugger // notify the debugger
if (args.original_team_id >= 0 if (entry.teamID >= 0
&& args.original_team_id != team_get_kernel_team_id()) { && entry.teamID != team_get_kernel_team_id()) {
user_debug_thread_deleted(args.original_team_id, args.thread->id); user_debug_thread_deleted(entry.teamID, thread->id);
} }
disable_interrupts(); // free the thread structure
thread_enqueue(thread, &dead_q);
// Set the next state to be gone: this will cause the thread structure // TODO: Use the slab allocator!
// to be returned to a ready pool upon reschedule. }
// Note, we need to have disabled interrupts at this point, or else
// we could get rescheduled too early.
args.thread->next_state = THREAD_STATE_FREE_ON_RESCHED;
// return the death stack and reschedule one last time
// Note that we need to hold sDeathStackLock until we've got the thread
// lock. Otherwise someone else might grab our stack in the meantime.
acquire_spinlock(&sDeathStackLock);
put_death_stack(args.death_stack);
GRAB_THREAD_LOCK();
release_spinlock(&sDeathStackLock);
scheduler_reschedule();
// requires thread lock to be held
// never get to here
panic("thread_exit2: made it where it shouldn't have!\n");
} }
@@ -1591,28 +1502,17 @@ thread_exit(void)
delete_sem(cachedExitSem); delete_sem(cachedExitSem);
} }
{ // enqueue in the undertaker list and reschedule for the last time
struct thread_exit_args args; UndertakerEntry undertakerEntry(thread, teamID, cachedDeathSem);
args.thread = thread;
args.old_kernel_stack = thread->kernel_stack_area;
args.death_stack = get_death_stack();
args.death_sem = cachedDeathSem;
args.original_team_id = teamID;
disable_interrupts(); disable_interrupts();
GRAB_THREAD_LOCK();
// set the new kernel stack officially to the death stack, it won't be sUndertakerEntries.Add(&undertakerEntry);
// switched until the next function is called. This must be done now sUndertakerCondition.NotifyOne(true);
// before a context switch, or we'll stay on the old stack
thread->kernel_stack_area = sDeathStacks[args.death_stack].area;
thread->kernel_stack_base = sDeathStacks[args.death_stack].address;
// we will continue in thread_exit2(), on the new stack thread->next_state = THREAD_STATE_FREE_ON_RESCHED;
arch_thread_switch_kstack_and_call(thread, thread->kernel_stack_base scheduler_reschedule();
+ KERNEL_STACK_SIZE, thread_exit2, &args);
}
panic("never can get here\n"); panic("never can get here\n");
} }
@@ -2108,37 +2008,15 @@ thread_init(kernel_args *args)
} }
sUsedThreads = args->num_cpus; sUsedThreads = args->num_cpus;
// create a set of death stacks // start the undertaker thread
new(&sUndertakerEntries) DoublyLinkedList<UndertakerEntry>();
sUndertakerCondition.Init(&sUndertakerEntries, "undertaker entries");
sNumDeathStacks = smp_get_num_cpus(); thread_id undertakerThread = spawn_kernel_thread(&undertaker, "undertaker",
if (sNumDeathStacks > 8 * sizeof(sDeathStackBitmap)) { B_DISPLAY_PRIORITY, NULL);
// clamp values for really beefy machines if (undertakerThread < 0)
sNumDeathStacks = 8 * sizeof(sDeathStackBitmap); panic("Failed to create undertaker thread!");
} resume_thread(undertakerThread);
sDeathStackBitmap = 0;
sDeathStacks = (struct death_stack *)malloc(sNumDeathStacks
* sizeof(struct death_stack));
if (sDeathStacks == NULL) {
panic("error creating death stacks\n");
return B_NO_MEMORY;
}
{
char temp[64];
for (i = 0; i < sNumDeathStacks; i++) {
sprintf(temp, "death stack %lu", i);
sDeathStacks[i].area = create_area(temp,
(void **)&sDeathStacks[i].address, B_ANY_KERNEL_ADDRESS,
KERNEL_STACK_SIZE, B_FULL_LOCK,
B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA | B_KERNEL_STACK_AREA);
if (sDeathStacks[i].area < 0) {
panic("error creating death stacks\n");
return sDeathStacks[i].area;
}
sDeathStacks[i].in_use = false;
}
}
sDeathStackSem = create_sem(sNumDeathStacks, "death stack availability");
// set up some debugger commands // set up some debugger commands
add_debugger_command_etc("threads", &dump_thread_list, "List all threads", add_debugger_command_etc("threads", &dump_thread_list, "List all threads",