Luna/kernel/src/thread/Scheduler.cpp
apio 64f5078494 Kernel, libc: Implement fork()
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.
2022-10-17 18:43:35 +02:00

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;
}