sortix-mirror/kernel/kthread.cpp
Jonas 'Sortie' Termansen 62bd9bf901 Fix pid 1 deadlocking when exiting with children.
The child processes of pid 1 were being reparented to pid 1, causing an
infinite loop. This change fixes the problem by adding a hook that runs in
the last thread about to exit in a process. When pid 1 exits, the hook will
prevent more processes and threads from being created, and then broadcast
kill all processes and threads. The hook is not run in LastPrayer(), as that
function runs in a worker thread and it can't block waiting for another
thread to run LastPrayer() in the same thread.
2018-08-06 23:59:35 +02:00

139 lines
4 KiB
C++

/*
* Copyright (c) 2012, 2014 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* kthread.cpp
* Utility and synchronization mechanisms for kernel threads.
*/
#include <sortix/signal.h>
#include <sortix/kernel/kernel.h>
#include <sortix/kernel/kthread.h>
#include <sortix/kernel/process.h>
#include <sortix/kernel/scheduler.h>
#include <sortix/kernel/signal.h>
#include <sortix/kernel/thread.h>
#include <sortix/kernel/worker.h>
namespace Sortix {
// The kernel thread needs another stack to delete its own stack.
static void kthread_do_kill_thread(void* user)
{
Thread* thread = (Thread*) user;
while ( thread->state != ThreadState::DEAD )
kthread_yield();
FreeThread(thread);
}
extern "C" void kthread_exit()
{
Process* process = CurrentProcess();
// Note: This requires all threads in this process to have been made by
// only threads in this process, except the initial thread. Otherwise more
// threads may appear, and we can't conclude whether this is the last thread
// in the process to exit.
kthread_mutex_lock(&process->threadlock);
bool is_last_to_exit = --process->threads_not_exiting_count == 0;
kthread_mutex_unlock(&process->threadlock);
// All other threads in the process have committed to exiting, though they
// might not have exited yet. However, we know they are only running the
// below code that schedules thread termination. It's therefore safe to run
// a final process termination step without interference.
if ( is_last_to_exit )
process->OnLastThreadExit();
Worker::Schedule(kthread_do_kill_thread, CurrentThread());
Scheduler::ExitThread();
__builtin_unreachable();
}
struct kthread_cond_elem
{
kthread_cond_elem_t* next;
volatile unsigned long woken;
};
extern "C" void kthread_cond_wait(kthread_cond_t* cond, kthread_mutex_t* mutex)
{
kthread_cond_elem_t elem;
elem.next = NULL;
elem.woken = 0;
if ( cond->last ) { cond->last->next = &elem; }
if ( !cond->last ) { cond->first = &elem; }
cond->last = &elem;
while ( !elem.woken )
{
kthread_mutex_unlock(mutex);
Scheduler::Yield();
kthread_mutex_lock(mutex);
}
}
extern "C" unsigned long kthread_cond_wait_signal(kthread_cond_t* cond,
kthread_mutex_t* mutex)
{
if ( Signal::IsPending() )
return 0;
kthread_cond_elem_t elem;
elem.next = NULL;
elem.woken = 0;
if ( cond->last ) { cond->last->next = &elem; }
if ( !cond->last ) { cond->first = &elem; }
cond->last = &elem;
while ( !elem.woken )
{
if ( Signal::IsPending() )
{
if ( cond->first == &elem )
{
cond->first = elem.next;
if ( cond->last == &elem )
cond->last = NULL;
}
else
{
kthread_cond_elem_t* prev = cond->first;
while ( prev->next != &elem )
prev = prev->next;
prev->next = elem.next;
if ( cond->last == &elem )
cond->last = prev;
}
// Note that the thread still owns the mutex.
return 0;
}
kthread_mutex_unlock(mutex);
Scheduler::Yield();
kthread_mutex_lock(mutex);
}
return 1;
}
extern "C" void kthread_cond_signal(kthread_cond_t* cond)
{
kthread_cond_elem_t* elem = cond->first;
if ( !elem ) { return; }
if ( !(cond->first = elem->next) ) { cond->last = NULL; }
elem->next = NULL;
elem->woken = 1;
}
extern "C" void kthread_cond_broadcast(kthread_cond_t* cond)
{
while ( cond->first ) { kthread_cond_signal(cond); }
}
} // namespace Sortix