This is in preparation for giving schedulers their own life cycle separate from the kernel. Tasks must be deleted before their scheduler thread, so we can't let the scheduler exit before all its tasks have been cleaned up. In this scheme, the scheduler will unregister tasks with the kernel when they are reaped, then drop their ref on the task (there may still be others). When the task ref count hits zero, the task will request to be unregistered from the scheduler, which is responsible for deleting the task. Instead of having the kernel tell the scheduler to exit, let the scheduler decide when to exit. For now it will exit when all of its tasks are unregistered.
141 lines
3.4 KiB
C++
141 lines
3.4 KiB
C++
#include "rust_scheduler.h"
|
|
#include "rust_util.h"
|
|
|
|
rust_scheduler::rust_scheduler(rust_kernel *kernel,
|
|
rust_srv *srv,
|
|
size_t num_threads,
|
|
rust_sched_id id) :
|
|
kernel(kernel),
|
|
srv(srv),
|
|
env(srv->env),
|
|
live_threads(num_threads),
|
|
live_tasks(0),
|
|
num_threads(num_threads),
|
|
id(id)
|
|
{
|
|
isaac_init(kernel, &rctx);
|
|
create_task_threads();
|
|
}
|
|
|
|
rust_scheduler::~rust_scheduler() {
|
|
destroy_task_threads();
|
|
}
|
|
|
|
rust_task_thread *
|
|
rust_scheduler::create_task_thread(int id) {
|
|
rust_srv *srv = this->srv->clone();
|
|
rust_task_thread *thread =
|
|
new (kernel, "rust_task_thread") rust_task_thread(this, srv, id);
|
|
KLOG(kernel, kern, "created task thread: " PTR ", id: %d, index: %d",
|
|
thread, id, thread->list_index);
|
|
return thread;
|
|
}
|
|
|
|
void
|
|
rust_scheduler::destroy_task_thread(rust_task_thread *thread) {
|
|
KLOG(kernel, kern, "deleting task thread: " PTR ", name: %s, index: %d",
|
|
thread, thread->name, thread->list_index);
|
|
rust_srv *srv = thread->srv;
|
|
delete thread;
|
|
delete srv;
|
|
}
|
|
|
|
void
|
|
rust_scheduler::create_task_threads() {
|
|
KLOG(kernel, kern, "Using %d scheduler threads.", num_threads);
|
|
|
|
for(size_t i = 0; i < num_threads; ++i) {
|
|
threads.push(create_task_thread(i));
|
|
}
|
|
}
|
|
|
|
void
|
|
rust_scheduler::destroy_task_threads() {
|
|
for(size_t i = 0; i < num_threads; ++i) {
|
|
destroy_task_thread(threads[i]);
|
|
}
|
|
}
|
|
|
|
void
|
|
rust_scheduler::start_task_threads()
|
|
{
|
|
// Copy num_threads because it's possible for the last thread
|
|
// to terminate and have the kernel delete us before we
|
|
// hit the last check against num_threads, in which case
|
|
// we would be accessing invalid memory.
|
|
uintptr_t num_threads = this->num_threads;
|
|
for(size_t i = 0; i < num_threads; ++i) {
|
|
rust_task_thread *thread = threads[i];
|
|
thread->start();
|
|
}
|
|
}
|
|
|
|
void
|
|
rust_scheduler::kill_all_tasks() {
|
|
for(size_t i = 0; i < num_threads; ++i) {
|
|
rust_task_thread *thread = threads[i];
|
|
thread->kill_all_tasks();
|
|
}
|
|
}
|
|
|
|
rust_task_id
|
|
rust_scheduler::create_task(rust_task *spawner, const char *name,
|
|
size_t init_stack_sz) {
|
|
size_t thread_no;
|
|
{
|
|
scoped_lock with(lock);
|
|
thread_no = isaac_rand(&rctx) % num_threads;
|
|
live_tasks++;
|
|
}
|
|
rust_task_thread *thread = threads[thread_no];
|
|
return thread->create_task(spawner, name, init_stack_sz);
|
|
}
|
|
|
|
rust_task_id
|
|
rust_scheduler::create_task(rust_task *spawner, const char *name) {
|
|
return create_task(spawner, name, env->min_stack_size);
|
|
}
|
|
|
|
void
|
|
rust_scheduler::release_task() {
|
|
bool need_exit = false;
|
|
{
|
|
scoped_lock with(lock);
|
|
live_tasks--;
|
|
if (live_tasks == 0) {
|
|
need_exit = true;
|
|
}
|
|
}
|
|
if (need_exit) {
|
|
// There are no more tasks on this scheduler. Time to leave
|
|
exit();
|
|
}
|
|
}
|
|
|
|
void
|
|
rust_scheduler::exit() {
|
|
// Take a copy of num_threads. After the last thread exits this
|
|
// scheduler will get destroyed, and our fields will cease to exist.
|
|
size_t current_num_threads = num_threads;
|
|
for(size_t i = 0; i < current_num_threads; ++i) {
|
|
threads[i]->exit();
|
|
}
|
|
}
|
|
|
|
size_t
|
|
rust_scheduler::number_of_threads() {
|
|
return num_threads;
|
|
}
|
|
|
|
void
|
|
rust_scheduler::release_task_thread() {
|
|
I(this, !lock.lock_held_by_current_thread());
|
|
uintptr_t new_live_threads;
|
|
{
|
|
scoped_lock with(lock);
|
|
new_live_threads = --live_threads;
|
|
}
|
|
if (new_live_threads == 0) {
|
|
kernel->release_scheduler_id(id);
|
|
}
|
|
}
|