apio
64f5078494
This time for real. Also, add a new per-user-task virtual address allocator (UserHeap), so that mmap'ed pages are in user range and can be copied.
400 lines
13 KiB
C++
400 lines
13 KiB
C++
#define MODULE "sched"
|
|
|
|
#include "thread/Scheduler.h"
|
|
#include "errno.h"
|
|
#include "interrupts/Interrupts.h"
|
|
#include "kassert.h"
|
|
#include "log/Log.h"
|
|
#include "memory/MemoryManager.h"
|
|
#include "memory/PMM.h"
|
|
#include "memory/VMM.h"
|
|
#include "misc/hang.h"
|
|
#include "misc/utils.h"
|
|
#include "panic/Panic.h"
|
|
#include "std/stdlib.h"
|
|
#include "std/string.h"
|
|
#include "sys/elf/ELFLoader.h"
|
|
#include "thread/PIT.h"
|
|
#include "thread/Task.h"
|
|
#include "utils/Addresses.h"
|
|
#include "utils/Registers.h"
|
|
|
|
static uint64_t task_num = 0;
|
|
|
|
static Task idle_task;
|
|
|
|
static uint64_t free_tid = 0;
|
|
|
|
static Task* sched_current_task;
|
|
static Task* base_task;
|
|
static Task* end_task;
|
|
|
|
extern "C" void idle_task_function();
|
|
|
|
static uint64_t frequency;
|
|
|
|
void Scheduler::append_task(Task* task)
|
|
{
|
|
if (!base_task)
|
|
{
|
|
ASSERT(!end_task);
|
|
base_task = task;
|
|
end_task = base_task;
|
|
task->next_task = task;
|
|
task->prev_task = task;
|
|
}
|
|
else
|
|
{
|
|
end_task->next_task = task;
|
|
task->prev_task = end_task;
|
|
base_task->prev_task = task;
|
|
task->next_task = base_task;
|
|
end_task = task;
|
|
}
|
|
}
|
|
|
|
void Scheduler::init()
|
|
{
|
|
memset(&idle_task, 0, sizeof(Task));
|
|
idle_task.id = free_tid++;
|
|
idle_task.regs.rip = (uint64_t)idle_task_function;
|
|
idle_task.regs.rsp = get_top_of_stack((uint64_t)MemoryManager::get_page(), 1);
|
|
idle_task.regs.cs = 0x08;
|
|
idle_task.regs.ss = 0x10;
|
|
idle_task.regs.rflags = (1 << 21) | (1 << 9);
|
|
idle_task.task_sleep = 1000;
|
|
idle_task.user_task = false;
|
|
idle_task.state = idle_task.Idle;
|
|
|
|
base_task = new Task;
|
|
end_task = base_task;
|
|
sched_current_task = base_task;
|
|
sched_current_task->id = free_tid++;
|
|
sched_current_task->task_time = 20; // gets 20 ms of cpu time before next switch
|
|
sched_current_task->next_task = sched_current_task;
|
|
sched_current_task->prev_task = sched_current_task;
|
|
sched_current_task->state = sched_current_task->Running;
|
|
sched_current_task->user_task = false;
|
|
task_num++;
|
|
// the other registers will be saved next task switch
|
|
|
|
frequency = 1000 / PIT::frequency();
|
|
}
|
|
|
|
void Scheduler::add_kernel_task(void (*task)(void))
|
|
{
|
|
Task* new_task = new Task;
|
|
ASSERT(new_task);
|
|
new_task->user_task = false;
|
|
new_task->id = free_tid++;
|
|
new_task->regs.rip = (uint64_t)task;
|
|
new_task->allocated_stack =
|
|
(uint64_t)MemoryManager::get_pages(TASK_PAGES_IN_STACK); // 16 KB is enough for everyone, right?
|
|
new_task->regs.rsp = get_top_of_stack(new_task->allocated_stack, TASK_PAGES_IN_STACK);
|
|
new_task->regs.cs = 0x08;
|
|
new_task->regs.ss = 0x10;
|
|
new_task->regs.ds = 0x10;
|
|
new_task->regs.rflags = read_rflags() | 0x200; // enable interrupts
|
|
new_task->task_sleep = 0;
|
|
new_task->task_time = 0;
|
|
new_task->cpu_time = 0;
|
|
append_task(new_task);
|
|
new_task->state = new_task->Running;
|
|
task_num++;
|
|
kinfoln("Adding kernel task: starts at %lx, tid %ld, stack at %lx, total tasks: %ld", new_task->regs.rip,
|
|
new_task->id, new_task->regs.rsp, task_num);
|
|
}
|
|
|
|
Task* Scheduler::create_user_task()
|
|
{
|
|
Task* new_task = new Task;
|
|
if (!new_task) return nullptr;
|
|
memset(&new_task->regs, 0, sizeof(Context));
|
|
new_task->user_task = true;
|
|
new_task->id = free_tid++;
|
|
new_task->task_sleep = 0;
|
|
new_task->task_time = 0;
|
|
new_task->cpu_time = 0;
|
|
append_task(new_task);
|
|
task_num++;
|
|
return new_task;
|
|
}
|
|
|
|
long Scheduler::load_user_task(const char* filename)
|
|
{
|
|
kinfoln("Loading user task: %s", filename);
|
|
Interrupts::push_and_disable();
|
|
long result;
|
|
if ((result = ELFLoader::check_elf_image_from_filesystem(filename)) < 0)
|
|
{
|
|
kerrorln("Failed to load %s from initrd", filename);
|
|
Interrupts::pop();
|
|
return result;
|
|
}
|
|
if ((uint64_t)result > PMM::get_free()) { return -ENOMEM; }
|
|
Task* new_task = new Task;
|
|
ASSERT(new_task);
|
|
memset(&new_task->regs, 0, sizeof(Context));
|
|
new_task->id = free_tid++;
|
|
if (!new_task->allocator.init())
|
|
{
|
|
delete new_task;
|
|
Interrupts::pop();
|
|
return -ENOMEM;
|
|
}
|
|
new_task->address_space = AddressSpace::create();
|
|
VMM::switch_to_user_address_space(new_task->address_space);
|
|
ELFImage* image = ELFLoader::load_elf_from_filesystem(
|
|
filename); // FIXME: TOCTOU? Right now, impossible, since interrupts are disabled and SMP is not a thing. But in
|
|
// the future, it might be possible.
|
|
ASSERT(image);
|
|
new_task->user_task = true;
|
|
new_task->regs.rip = image->entry;
|
|
new_task->image = image;
|
|
new_task->allocated_stack = (uint64_t)MemoryManager::get_pages_at(
|
|
0x100000, TASK_PAGES_IN_STACK, MAP_READ_WRITE | MAP_USER); // 16 KB is enough for everyone, right?
|
|
if (!new_task->allocated_stack)
|
|
{
|
|
new_task->address_space.destroy();
|
|
delete new_task;
|
|
ELFLoader::release_elf_image(image);
|
|
VMM::switch_back_to_kernel_address_space();
|
|
Interrupts::pop();
|
|
return -ENOMEM;
|
|
}
|
|
new_task->regs.rsp = get_top_of_stack(new_task->allocated_stack, TASK_PAGES_IN_STACK);
|
|
new_task->regs.cs = 0x18 | 0x03;
|
|
new_task->regs.ss = 0x20 | 0x03;
|
|
new_task->regs.ds = 0x20 | 0x03;
|
|
new_task->regs.rflags = (1 << 21) | (1 << 9); // enable interrupts
|
|
new_task->task_sleep = 0;
|
|
new_task->task_time = 0;
|
|
new_task->cpu_time = 0;
|
|
append_task(new_task);
|
|
new_task->state = new_task->Running;
|
|
task_num++;
|
|
kinfoln("Adding user task: loaded at %lx, tid %ld, stack at %lx, total tasks: %ld", new_task->regs.rip,
|
|
new_task->id, new_task->regs.rsp, task_num);
|
|
VMM::switch_back_to_kernel_address_space();
|
|
Interrupts::pop();
|
|
return (long)new_task->id;
|
|
}
|
|
|
|
void Scheduler::reset_task(Task* task, ELFImage* new_image)
|
|
{
|
|
memset(&task->regs, 0, sizeof(Context));
|
|
task->state = task->Running;
|
|
task->regs.rip = new_image->entry;
|
|
task->image = new_image;
|
|
task->regs.rsp = get_top_of_stack(task->allocated_stack, TASK_PAGES_IN_STACK);
|
|
task->regs.cs = 0x18 | 0x03;
|
|
task->regs.ss = 0x20 | 0x03;
|
|
task->regs.ds = 0x20 | 0x03;
|
|
task->regs.rflags = (1 << 21) | (1 << 9); // enable interrupts
|
|
task->task_sleep = 0;
|
|
task->cpu_time = 0;
|
|
kinfoln("Resetting task: loaded at %lx, tid %ld, stack at %lx, total tasks: %ld", task->regs.rip, task->id,
|
|
task->regs.rsp, task_num);
|
|
}
|
|
|
|
void Scheduler::reap_task(Task* task)
|
|
{
|
|
ASSERT(!Interrupts::is_in_handler());
|
|
task_num--;
|
|
Task* exiting_task = task;
|
|
ASSERT(task->id != 0); // WHY IN THE WORLD WOULD WE BE REAPING THE IDLE TASK?
|
|
if (exiting_task->is_user_task())
|
|
{
|
|
VMM::switch_back_to_kernel_address_space();
|
|
VMM::apply_address_space();
|
|
VMM::switch_to_user_address_space(exiting_task->address_space);
|
|
}
|
|
kinfoln("reaping task %ld, exited with code %ld", exiting_task->id, exiting_task->exit_status);
|
|
if (exiting_task->allocated_stack && !exiting_task->is_user_task())
|
|
MemoryManager::release_pages((void*)exiting_task->allocated_stack, TASK_PAGES_IN_STACK);
|
|
if (exiting_task->image) // FIXME: Also free pages the task has mmap-ed but not munmap-ed.
|
|
{
|
|
// ELFLoader::release_elf_image(exiting_task->image);
|
|
kfree(exiting_task->image);
|
|
}
|
|
if (exiting_task->is_user_task())
|
|
{
|
|
exiting_task->allocator.free();
|
|
VMM::switch_back_to_kernel_address_space();
|
|
VMM::apply_address_space();
|
|
Interrupts::push_and_enable();
|
|
exiting_task->address_space.destroy();
|
|
Interrupts::pop();
|
|
}
|
|
for (int i = 0; i < TASK_MAX_FDS; i++) { exiting_task->files[i].close(); }
|
|
delete exiting_task;
|
|
}
|
|
|
|
void Scheduler::task_exit(Context* context, int64_t status)
|
|
{
|
|
ASSERT(Interrupts::is_in_handler());
|
|
kdbgln("exit: task %ld finished running, used %ld ms of cpu time", sched_current_task->id,
|
|
sched_current_task->cpu_time);
|
|
sched_current_task->state = sched_current_task->Exited;
|
|
sched_current_task->exit_status = status;
|
|
task_yield(context);
|
|
}
|
|
|
|
void Scheduler::task_misbehave(Context* context, int64_t status)
|
|
{
|
|
ASSERT(Interrupts::is_in_handler());
|
|
kdbgln("exit: task %ld misbehaved, used %ld ms of cpu time", sched_current_task->id, sched_current_task->cpu_time);
|
|
sched_current_task->state = sched_current_task->Exited;
|
|
sched_current_task->exit_status = status;
|
|
task_yield(context);
|
|
}
|
|
|
|
void Scheduler::reap_tasks()
|
|
{
|
|
Interrupts::disable();
|
|
ASSERT(!Interrupts::is_in_handler());
|
|
Task* reap_base = nullptr;
|
|
Task* reap_end = nullptr;
|
|
Task* task = base_task;
|
|
Task* task_reaping;
|
|
uint64_t iter_index = 0;
|
|
do {
|
|
if (task->state == task->Exited)
|
|
{
|
|
if (task == base_task && task == end_task) { panic("Last task exited"); }
|
|
else if (task == base_task) { base_task = task->next_task; }
|
|
else if (task == end_task) { end_task = task->prev_task; }
|
|
if (!reap_base)
|
|
{
|
|
reap_base = task;
|
|
reap_end = task;
|
|
task->prev_task->next_task = task->next_task;
|
|
task->next_task->prev_task = task->prev_task;
|
|
task->prev_task = nullptr;
|
|
task_reaping = task;
|
|
task = task->next_task;
|
|
task_reaping->next_task = nullptr;
|
|
}
|
|
else
|
|
{
|
|
reap_end->next_task = task;
|
|
task->prev_task->next_task = task->next_task;
|
|
task->next_task->prev_task = task->prev_task;
|
|
task->prev_task = nullptr;
|
|
reap_end = task;
|
|
task_reaping = task;
|
|
task = task->next_task;
|
|
task_reaping->next_task = nullptr;
|
|
}
|
|
}
|
|
else { task = task->next_task; }
|
|
iter_index++;
|
|
} while (iter_index < task_num);
|
|
task = reap_base;
|
|
while (task)
|
|
{
|
|
Task* reaped_task = task;
|
|
task = task->next_task;
|
|
reap_task(reaped_task);
|
|
}
|
|
Interrupts::enable();
|
|
}
|
|
|
|
static void sched_decrement_sleep_times()
|
|
{
|
|
Task* task = base_task;
|
|
if (!task) return;
|
|
do {
|
|
if (task->task_sleep > 0)
|
|
{
|
|
task->task_sleep -= frequency;
|
|
if (task->task_sleep < 0) task->task_sleep = 0;
|
|
}
|
|
if (task->task_sleep == 0 && task->state == task->Sleeping) task->state = task->Running;
|
|
task = task->next_task;
|
|
} while (task != base_task);
|
|
}
|
|
|
|
void Scheduler::task_tick(Context* context)
|
|
{
|
|
ASSERT(Interrupts::is_in_handler());
|
|
Interrupts::disable();
|
|
sched_decrement_sleep_times();
|
|
if (sched_current_task->id == 0) return task_yield(context);
|
|
sched_current_task->task_time -= frequency;
|
|
sched_current_task->cpu_time += frequency;
|
|
if (sched_current_task->task_time < 0)
|
|
{
|
|
sched_current_task->task_time = 0;
|
|
task_yield(context);
|
|
}
|
|
Interrupts::enable();
|
|
}
|
|
|
|
void Scheduler::task_yield(Context* context)
|
|
{
|
|
ASSERT(Interrupts::is_in_handler());
|
|
Interrupts::disable();
|
|
sched_current_task->save_context(context);
|
|
bool was_idle = false;
|
|
if (sched_current_task->state == sched_current_task->Idle)
|
|
{
|
|
sched_current_task = end_task;
|
|
was_idle = true;
|
|
}
|
|
Task* original_task = sched_current_task;
|
|
do {
|
|
sched_current_task = sched_current_task->next_task;
|
|
if (sched_current_task->state == sched_current_task->Running)
|
|
{
|
|
if (sched_current_task->id != original_task->id || was_idle)
|
|
{
|
|
if (!was_idle && original_task->is_user_task() && !original_task->has_died())
|
|
{
|
|
original_task->save_floating();
|
|
}
|
|
if (sched_current_task->is_user_task())
|
|
{
|
|
sched_current_task->switch_to_address_space();
|
|
sched_current_task->restore_floating();
|
|
}
|
|
else if (!was_idle && original_task->is_user_task() && !sched_current_task->is_user_task())
|
|
{
|
|
VMM::switch_back_to_kernel_address_space();
|
|
VMM::apply_address_space();
|
|
}
|
|
}
|
|
sched_current_task->task_time = 20;
|
|
sched_current_task->restore_context(context);
|
|
return;
|
|
}
|
|
} while (sched_current_task != original_task);
|
|
if (!was_idle && original_task->is_user_task() && original_task->state != original_task->Exited)
|
|
{
|
|
original_task->save_floating();
|
|
}
|
|
sched_current_task = &idle_task;
|
|
sched_current_task->task_time = frequency;
|
|
if (!was_idle) { sched_current_task->restore_context(context); }
|
|
return;
|
|
}
|
|
|
|
void Scheduler::yield()
|
|
{
|
|
asm volatile("int $0x42" : : "a"(1));
|
|
}
|
|
|
|
void Scheduler::exit(int status)
|
|
{
|
|
asm volatile("int $0x42" : : "a"(0), "D"(status));
|
|
}
|
|
|
|
void Scheduler::sleep(unsigned long ms)
|
|
{
|
|
asm volatile("int $0x42" : : "D"(ms), "a"(2));
|
|
}
|
|
|
|
Task* Scheduler::current_task()
|
|
{
|
|
return sched_current_task;
|
|
} |