[kernel] Add thead kobject class

Add the thread kernel API object and move the scheduler to use threads
instead of processes for scheduling and task switching.
This commit is contained in:
2020-07-12 16:03:46 -07:00
parent 8687fe3786
commit 794c86f9b4
16 changed files with 504 additions and 230 deletions

View File

@@ -39,9 +39,9 @@ modules:
- src/kernel/msr.cpp
- src/kernel/objects/handle.cpp
- src/kernel/objects/kobject.cpp
- src/kernel/objects/thread.cpp
- src/kernel/page_manager.cpp
- src/kernel/pci.cpp
- src/kernel/process.cpp
- src/kernel/scheduler.cpp
- src/kernel/screen.cpp
- src/kernel/serial.cpp

View File

@@ -2,17 +2,25 @@
/// \file signals.h
/// Collection of constants for the j6_signal_t type
// Signals 0-7 are common to all types
#define j6_signal_no_handles (1 << 0)
// Signals 0-15 are common to all types
#define j6_signal_no_handles (1ull << 0)
// Signals 8-15 are user-defined signals
#define j6_signal_user0 (1 << 8)
#define j6_signal_user1 (1 << 9)
#define j6_signal_user2 (1 << 10)
#define j6_signal_user3 (1 << 11)
#define j6_signal_user4 (1 << 12)
#define j6_signal_user5 (1 << 13)
#define j6_signal_user6 (1 << 14)
#define j6_signal_user7 (1 << 15)
// Signals 16-47 are defined per-object-type
// All other signals are type-specific
// Signals 48-63 are user-defined signals
#define j6_signal_user0 (1ull << 48)
#define j6_signal_user1 (1ull << 49)
#define j6_signal_user2 (1ull << 50)
#define j6_signal_user3 (1ull << 51)
#define j6_signal_user4 (1ull << 52)
#define j6_signal_user5 (1ull << 53)
#define j6_signal_user6 (1ull << 54)
#define j6_signal_user7 (1ull << 55)
#define j6_signal_user8 (1ull << 56)
#define j6_signal_user9 (1ull << 57)
#define j6_signal_user10 (1ull << 58)
#define j6_signal_user11 (1ull << 59)
#define j6_signal_user12 (1ull << 60)
#define j6_signal_user13 (1ull << 61)
#define j6_signal_user14 (1ull << 62)
#define j6_signal_user15 (1ull << 63)

View File

@@ -2,7 +2,7 @@
#include <stdint.h>
struct process;
struct TCB;
struct cpu_state
{
@@ -18,7 +18,7 @@ struct cpu_data
{
uintptr_t rsp0;
uintptr_t rsp3;
process *tcb;
TCB *tcb;
};
extern cpu_data bsp_cpu_data;

View File

@@ -11,6 +11,7 @@
#include "interrupts.h"
#include "io.h"
#include "log.h"
#include "page_manager.h"
#include "scheduler.h"
#include "syscall.h"

View File

@@ -11,7 +11,7 @@ ramdisk_process_loader:
pop rdi ; the address of the program image
pop rsi ; the size of the program image
pop rdx ; the address of this process' process structure
pop rdx ; the address of this thread's TCB
call load_process_image

View File

@@ -48,6 +48,7 @@ void memory_initialize_post_ctors(kernel::args::header *kargs);
using namespace kernel;
/*
class test_observer :
public kobject::observer
{
@@ -67,6 +68,7 @@ public:
const char *m_name;
};
*/
void
init_console()
@@ -174,7 +176,7 @@ kernel_main(args::header *header)
syscall_enable();
scheduler *sched = new (&scheduler::get()) scheduler(devices.get_lapic());
sched->create_kernel_task(-1, logger_task, scheduler::max_priority-1, process_flags::const_pri);
sched->create_kernel_task(logger_task, scheduler::max_priority-1, true);
for (auto &ird : initrds) {
for (auto &f : ird.files()) {
@@ -185,6 +187,7 @@ kernel_main(args::header *header)
log::info(logs::objs, "Testing object system:");
/*
test_observer obs1("event");
test_observer obs2("no handles");
{
@@ -197,6 +200,7 @@ kernel_main(args::header *header)
handle h(1, 0, &e);
}
*/
sched->start();
}

View File

@@ -2,6 +2,7 @@
#include "j6/signals.h"
#include "j6/types.h"
#include "objects/kobject.h"
#include "objects/thread.h"
// TODO: per-cpu this?
static j6_koid_t next_koid;
@@ -14,7 +15,8 @@ kobject::kobject(type t, j6_signal_t signals) :
kobject::~kobject()
{
notify_signal_observers(0, j6_status_destroyed);
for (auto *t : m_blocked_threads)
t->wake_on_result(this, j6_status_destroyed);
}
j6_koid_t
@@ -33,58 +35,30 @@ void
kobject::assert_signal(j6_signal_t s)
{
m_signals |= s;
notify_signal_observers(s);
notify_signal_observers();
}
void
kobject::deassert_signal(j6_signal_t s)
{
m_signals &= ~s;
notify_signal_observers(s);
}
void
kobject::register_signal_observer(observer *object, j6_signal_t s)
kobject::notify_signal_observers()
{
m_observers.emplace(object, s);
}
size_t i = 0;
bool readied = false;
while (i < m_blocked_threads.count()) {
thread *t = m_blocked_threads[i];
void
kobject::deregister_signal_observer(observer *object)
{
for (size_t i = 0; i < m_observers.count(); ++i) {
auto &reg = m_observers[i];
if (reg.object != object) continue;
reg = m_observers[m_observers.count() - 1];
m_observers.remove();
break;
}
}
void
kobject::notify_signal_observers(j6_signal_t mask, j6_status_t result)
{
for (auto &reg : m_observers) {
if (mask == 0 || (reg.signals & mask) != 0) {
if (!reg.object->on_signals_changed(this, m_signals, mask, result))
reg.object = nullptr;
if (t->wake_on_signals(this, m_signals)) {
m_blocked_threads.remove_swap_at(i);
readied = true;
} else {
++i;
}
}
// Compact the observer list
long last = m_observers.count() - 1;
while (m_observers[last].object == nullptr && last >= 0) last--;
for (long i = 0; i < long(m_observers.count()) && i < last; ++i) {
auto &reg = m_observers[i];
if (reg.object != nullptr) continue;
reg = m_observers[last--];
while (m_observers[last].object == nullptr && last >= i) last--;
}
m_observers.set_size(last + 1);
}
void

View File

@@ -6,6 +6,8 @@
#include "j6/types.h"
#include "kutil/vector.h"
class thread;
/// Base type for all user-interactable kernel objects
class kobject
{
@@ -39,6 +41,9 @@ public:
/// \returns The object type for the koid
static type koid_type(j6_koid_t koid);
/// Get this object's koid
inline j6_koid_t koid() const { return m_koid; }
/// Set the given signals active on this object
/// \arg s The set of signals to assert
void assert_signal(j6_signal_t s);
@@ -47,31 +52,6 @@ public:
/// \arg s The set of signals to deassert
void deassert_signal(j6_signal_t s);
class observer
{
public:
/// Callback for when signals change.
/// \arg obj The object triggering the callback
/// \arg s The current state of obj's signals
/// \arg ds Which signals caused the callback
/// \arg result Status code for this notification
/// \returns True if this object wants to keep watching signals
virtual bool on_signals_changed(
kobject *obj,
j6_signal_t s,
j6_signal_t ds,
j6_status_t result) = 0;
};
/// Register a signal observer
/// \arg object The observer
/// \arg s The signals the object wants notifiations for
void register_signal_observer(observer *object, j6_signal_t s);
/// Deegister a signal observer
/// \arg object The observer
void deregister_signal_observer(observer *object);
/// Increment the handle refcount
inline void handle_retain() { ++m_handle_count; }
@@ -80,31 +60,30 @@ public:
if (--m_handle_count == 0) on_no_handles();
}
/// Add the given thread to the list of threads waiting on this object.
inline void add_blocked_thread(thread *t) { m_blocked_threads.append(t); }
/// Remove the given thread from the list of threads waiting on this object.
inline void remove_blocked_thread(thread *t) { m_blocked_threads.remove_swap(t); }
protected:
/// Interface for subclasses to handle when all handles are closed. Subclasses
/// should either call the base version, or assert j6_signal_no_handles.
virtual void on_no_handles();
private:
kobject() = delete;
kobject(const kobject &other) = delete;
kobject(const kobject &&other) = delete;
/// Notifiy observers of this object
/// \arg mask The signals that triggered this call. If 0, notify all observers.
/// \arg result The result to pass to the observers
void notify_signal_observers(j6_signal_t mask, j6_status_t result = j6_status_ok);
/// Interface for subclasses to handle when all handles are closed. Subclasses
/// should either call the base version, or assert j6_signal_no_handles.
virtual void on_no_handles();
void notify_signal_observers();
j6_koid_t m_koid;
j6_signal_t m_signals;
uint16_t m_handle_count;
struct observer_registration
{
observer_registration(observer* o = nullptr, j6_signal_t s = 0) :
object(o), signals(s) {}
observer *object;
j6_signal_t signals;
};
kutil::vector<observer_registration> m_observers;
protected:
kutil::vector<thread*> m_blocked_threads;
};

View File

@@ -0,0 +1,83 @@
#include "j6/signals.h"
#include "objects/thread.h"
#include "scheduler.h"
static constexpr j6_signal_t thread_default_signals = 0;
thread::thread(page_table *pml4, priority_t pri) :
kobject(kobject::type::thread, thread_default_signals),
m_state(state::loading),
m_wait_type(wait_type::none),
m_wait_data(0),
m_wait_obj(0)
{
TCB *tcbp = tcb();
tcbp->pml4 = pml4;
tcbp->priority = pri;
tcbp->thread_data = this;
set_state(state::ready);
}
void
thread::wait_on_signals(kobject *obj, j6_signal_t signals)
{
m_wait_type = wait_type::signal;
m_wait_data = signals;
clear_state(state::ready);
}
void
thread::wait_on_time(uint64_t t)
{
m_wait_type = wait_type::time;
m_wait_data = t;
clear_state(state::ready);
}
bool
thread::wake_on_signals(kobject *obj, j6_signal_t signals)
{
if (m_wait_type != wait_type::signal ||
(signals & m_wait_data) == 0)
return false;
m_wait_type = wait_type::none;
m_wait_data = j6_status_ok;
m_wait_obj = obj->koid();
set_state(state::ready);
return true;
}
bool
thread::wake_on_time(uint64_t now)
{
if (m_wait_type != wait_type::time ||
now < m_wait_data)
return false;
m_wait_type = wait_type::none;
m_wait_data = j6_status_ok;
m_wait_obj = 0;
set_state(state::ready);
return true;
}
void
thread::wake_on_result(kobject *obj, j6_status_t result)
{
m_wait_type = wait_type::none;
m_wait_data = result;
m_wait_obj = obj->koid();
set_state(state::ready);
}
void
thread::exit(uint32_t code)
{
// TODO: check if the process containing this thread
// needs to exit and clean up.
m_return_code = code;
set_state(state::exited);
clear_state(state::ready);
}

133
src/kernel/objects/thread.h Normal file
View File

@@ -0,0 +1,133 @@
#pragma once
/// \file thread.h
/// Definition of thread kobject types
#include "kutil/linked_list.h"
#include "objects/kobject.h"
using priority_t = uint8_t;
struct page_table;
struct TCB
{
// Data used by assembly task control routines. If you change any of these,
// be sure to change the assembly definitions in 'tasking.inc'
uintptr_t rsp;
uintptr_t rsp0;
uintptr_t rsp3;
page_table *pml4;
priority_t priority;
// note: 3 bytes padding
// TODO: move state into TCB?
uintptr_t kernel_stack;
size_t kernel_stack_size;
uint32_t time_left;
uint64_t last_ran;
thread *thread_data;
};
using tcb_list = kutil::linked_list<TCB>;
using tcb_node = tcb_list::item_type;
class thread :
public kobject
{
public:
enum class wait_type : uint8_t { none, signal, time };
enum class state : uint8_t {
ready = 0x01,
loading = 0x02,
exited = 0x04,
constant = 0x80,
none = 0x00
};
/// Constructor.
/// \arg pml4 Root page table for the thread's owning process
/// \arg pri Initial priority level of this thread
thread(page_table *pml4, priority_t pri);
/// Get the `ready` state of the thread.
/// \returns True if the thread is ready to execute.
inline bool ready() const { return has_state(state::ready); }
/// Get the `loading` state of the thread.
/// \returns True if the thread has not finished loading.
inline bool loading() const { return has_state(state::loading); }
/// Get the `constant` state of the thread.
/// \returns True if the thread has constant priority.
inline bool constant() const { return has_state(state::constant); }
/// Get the thread priority.
inline priority_t priority() const { return m_tcb.priority; }
/// Set the thread priority.
/// \arg p The new thread priority
inline void set_priority(priority_t p) { if (!constant()) m_tcb.priority = p; }
/// Block the thread, waiting on the given object's signals.
/// \arg obj Object to wait on
/// \arg signals Mask of signals to wait for
void wait_on_signals(kobject *obj, j6_signal_t signals);
/// Block the thread, waiting for a given clock value
/// \arg t Clock value to wait for
void wait_on_time(uint64_t t);
/// Wake the thread if it is waiting on signals.
/// \arg obj Object that changed signals
/// \arg signals Signal state of the object
/// \returns True if this action unblocked the thread
bool wake_on_signals(kobject *obj, j6_signal_t signals);
/// Wake the thread if it is waiting on the clock.
/// \arg now Current clock value
/// \returns True if this action unblocked the thread
bool wake_on_time(uint64_t now);
/// Wake the thread with a given result code.
/// \arg obj Object that changed signals
/// \arg result Result code to return to the thread
void wake_on_result(kobject *obj, j6_status_t result);
inline bool has_state(state s) const {
return static_cast<uint8_t>(m_state) & static_cast<uint8_t>(s);
}
inline void set_state(state s) {
m_state = static_cast<state>(static_cast<uint8_t>(m_state) | static_cast<uint8_t>(s));
}
inline void clear_state(state s) {
m_state = static_cast<state>(static_cast<uint8_t>(m_state) & ~static_cast<uint8_t>(s));
}
inline tcb_node * tcb() { return &m_tcb; }
/// Terminate this thread.
/// \arg code The return code to exit with.
void exit(unsigned code);
private:
thread() = delete;
thread(const thread &other) = delete;
thread(const thread &&other) = delete;
tcb_node m_tcb;
state m_state;
wait_type m_wait_type;
// There should be 1 byte of padding here
uint32_t m_return_code;
uint64_t m_wait_data;
j6_koid_t m_wait_obj;
};

View File

@@ -24,45 +24,97 @@ const uint64_t rflags_int = 0x202;
extern "C" {
void ramdisk_process_loader();
uintptr_t load_process_image(const void *image_start, size_t bytes, process *proc);
uintptr_t load_process_image(const void *image_start, size_t bytes, TCB *tcb);
};
extern uint64_t idle_stack_end;
/// Set up a new empty kernel stack for this thread. Sets rsp0 on the
/// TCB object, but also returns it.
/// \returns The new rsp0 as a pointer
static void *
setup_kernel_stack(TCB *tcb)
{
constexpr size_t initial_stack_size = 0x1000;
constexpr unsigned null_frame_entries = 2;
constexpr size_t null_frame_size = null_frame_entries * sizeof(uint64_t);
void *stack_bottom = kutil::kalloc(initial_stack_size);
kutil::memset(stack_bottom, 0, initial_stack_size);
log::debug(logs::memory, "Created kernel stack at %016lx size 0x%lx",
stack_bottom, initial_stack_size);
void *stack_top =
kutil::offset_pointer(stack_bottom,
initial_stack_size - null_frame_size);
uint64_t *null_frame = reinterpret_cast<uint64_t*>(stack_top);
for (unsigned i = 0; i < null_frame_entries; ++i)
null_frame[i] = 0;
tcb->kernel_stack_size = initial_stack_size;
tcb->kernel_stack = reinterpret_cast<uintptr_t>(stack_bottom);
tcb->rsp0 = reinterpret_cast<uintptr_t>(stack_top);
tcb->rsp = tcb->rsp0;
return stack_top;
}
/// Initialize this process' kenrel stack with a fake return segment for
/// returning out of task_switch.
/// \arg tcb TCB of the thread to modify
/// \arg rip The rip to return to
static void
add_fake_task_return(TCB *tcb, uintptr_t rip)
{
tcb->rsp -= sizeof(uintptr_t) * 7;
uintptr_t *stack = reinterpret_cast<uintptr_t*>(tcb->rsp);
stack[6] = rip; // return rip
stack[5] = tcb->rsp0; // rbp
stack[4] = 0xbbbbbbbb; // rbx
stack[3] = 0x12121212; // r12
stack[2] = 0x13131313; // r13
stack[1] = 0x14141414; // r14
stack[0] = 0x15151515; // r15
}
scheduler::scheduler(lapic *apic) :
m_apic(apic),
m_next_pid(1),
m_clock(0)
{
auto *idle = new process_node;
page_table *pml4 = page_manager::get_pml4();
thread *idle = new thread(pml4, max_priority);
auto *tcb = idle->tcb();
log::debug(logs::task, "Idle thread koid %llx", idle->koid());
// The kernel idle task, also the thread we're in now
idle->pid = 0;
idle->ppid = 0;
idle->priority = max_priority;
idle->rsp = 0; // This will get set when we switch away
idle->rsp3 = 0; // Never used for the idle task
idle->rsp0 = reinterpret_cast<uintptr_t>(&idle_stack_end);
idle->pml4 = page_manager::get_pml4();
idle->flags =
process_flags::running |
process_flags::ready |
process_flags::const_pri;
tcb->rsp = 0; // This will get set when we switch away
tcb->rsp3 = 0; // Never used for the idle task
tcb->rsp0 = reinterpret_cast<uintptr_t>(&idle_stack_end);
m_runlists[max_priority].push_back(idle);
m_current = idle;
idle->set_state(thread::state::constant);
bsp_cpu_data.rsp0 = idle->rsp0;
bsp_cpu_data.tcb = idle;
m_runlists[max_priority].push_back(tcb);
m_current = tcb;
bsp_cpu_data.rsp0 = tcb->rsp0;
bsp_cpu_data.tcb = tcb;
}
uintptr_t
load_process_image(const void *image_start, size_t bytes, process *proc)
load_process_image(const void *image_start, size_t bytes, TCB *tcb)
{
// We're now in the process space for this process, allocate memory for the
// process code and load it
page_manager *pager = page_manager::get();
thread *th = tcb->thread_data;
log::debug(logs::loader, "Loading task! ELF: %016lx [%d]", image_start, bytes);
// TODO: Handle bad images gracefully
@@ -108,33 +160,30 @@ load_process_image(const void *image_start, size_t bytes, process *proc)
kutil::memcpy(dest, src, header->size);
}
proc->flags &= ~process_flags::loading;
th->clear_state(thread::state::loading);
uintptr_t entrypoint = image.entrypoint();
log::debug(logs::loader, " Loaded! New process rip: %016lx", entrypoint);
log::debug(logs::loader, " Loaded! New thread rip: %016lx", entrypoint);
return entrypoint;
}
process_node *
scheduler::create_process(pid_t pid)
thread *
scheduler::create_process(page_table *pml4)
{
kassert(pid <= 0, "Cannot specify a positive pid in create_process");
thread *th = new thread(pml4, default_priority);
auto *tcb = th->tcb();
auto *proc = new process_node;
proc->pid = pid ? pid : m_next_pid++;
proc->priority = default_priority;
proc->time_left = quantum(default_priority) + startup_bonus;
tcb->time_left = quantum(default_priority) + startup_bonus;
log::debug(logs::task, "Creating process %d, priority %d, time slice %d",
proc->pid, proc->priority, proc->time_left);
log::debug(logs::task, "Creating thread %llx, priority %d, time slice %d",
th->koid(), tcb->priority, tcb->time_left);
return proc;
return th;
}
void
scheduler::load_process(const char *name, const void *data, size_t size)
{
auto *proc = create_process();
uint16_t kcs = (1 << 3) | 0; // Kernel CS is GDT entry 1, ring 0
uint16_t cs = (5 << 3) | 3; // User CS is GDT entry 5, ring 3
@@ -143,19 +192,22 @@ scheduler::load_process(const char *name, const void *data, size_t size)
uint16_t ss = (4 << 3) | 3; // User SS is GDT entry 4, ring 3
// Set up the page tables - this also allocates an initial user stack
proc->pml4 = page_manager::get()->create_process_map();
page_table *pml4 = page_manager::get()->create_process_map();
thread* th = create_process(pml4);
auto *tcb = th->tcb();
// Create an initial kernel stack space
void *sp0 = proc->setup_kernel_stack();
void *sp0 = setup_kernel_stack(tcb);
uintptr_t *stack = reinterpret_cast<uintptr_t *>(sp0) - 7;
// Pass args to ramdisk_process_loader on the stack
stack[0] = reinterpret_cast<uintptr_t>(data);
stack[1] = reinterpret_cast<uintptr_t>(size);
stack[2] = reinterpret_cast<uintptr_t>(proc);
stack[2] = reinterpret_cast<uintptr_t>(tcb);
proc->rsp = reinterpret_cast<uintptr_t>(stack);
proc->add_fake_task_return(
tcb->rsp = reinterpret_cast<uintptr_t>(stack);
add_fake_task_return(tcb,
reinterpret_cast<uintptr_t>(ramdisk_process_loader));
// Arguments for iret - rip will be pushed on before these
@@ -164,46 +216,42 @@ scheduler::load_process(const char *name, const void *data, size_t size)
stack[5] = initial_stack;
stack[6] = ss;
proc->rsp3 = initial_stack;
proc->flags =
process_flags::running |
process_flags::ready |
process_flags::loading;
tcb->rsp3 = initial_stack;
m_runlists[default_priority].push_back(proc);
m_runlists[default_priority].push_back(tcb);
log::debug(logs::task, "Creating process %s: pid %d pri %d", name, proc->pid, proc->priority);
log::debug(logs::task, " RSP %016lx", proc->rsp);
log::debug(logs::task, " RSP0 %016lx", proc->rsp0);
log::debug(logs::task, " PML4 %016lx", proc->pml4);
log::debug(logs::task, "Loading thread %s: koid %llx pri %d", name, th->koid(), tcb->priority);
log::debug(logs::task, " RSP %016lx", tcb->rsp);
log::debug(logs::task, " RSP0 %016lx", tcb->rsp0);
log::debug(logs::task, " PML4 %016lx", tcb->pml4);
}
void
scheduler::create_kernel_task(pid_t pid, void (*task)(), uint8_t priority, process_flags flags)
scheduler::create_kernel_task(void (*task)(), uint8_t priority, bool constant)
{
auto *proc = create_process(pid);
page_table *pml4 = page_manager::get()->get_kernel_pml4();
thread *th = create_process(pml4);
auto *tcb = th->tcb();
uint16_t kcs = (1 << 3) | 0; // Kernel CS is GDT entry 1, ring 0
uint16_t kss = (2 << 3) | 0; // Kernel SS is GDT entry 2, ring 0
// Create an initial kernel stack space
proc->setup_kernel_stack();
proc->add_fake_task_return(
setup_kernel_stack(tcb);
add_fake_task_return(tcb,
reinterpret_cast<uintptr_t>(task));
proc->priority = priority;
proc->pml4 = page_manager::get()->get_kernel_pml4();
proc->flags =
process_flags::running |
process_flags::ready |
flags;
tcb->priority = priority;
tcb->pml4 = page_manager::get()->get_kernel_pml4();
if (constant)
th->set_state(thread::state::constant);
m_runlists[default_priority].push_back(proc);
m_runlists[default_priority].push_back(tcb);
log::debug(logs::task, "Creating kernel task: pid %d pri %d", proc->pid, proc->priority);
log::debug(logs::task, " RSP0 %016lx", proc->rsp0);
log::debug(logs::task, " RSP %016lx", proc->rsp);
log::debug(logs::task, " PML4 %016lx", proc->pml4);
log::debug(logs::task, "Creating kernel task: thread %llx pri %d", th->koid(), tcb->priority);
log::debug(logs::task, " RSP0 %016lx", tcb->rsp0);
log::debug(logs::task, " RSP %016lx", tcb->rsp);
log::debug(logs::task, " PML4 %016lx", tcb->pml4);
}
uint32_t
@@ -223,45 +271,48 @@ scheduler::start()
void scheduler::prune(uint64_t now)
{
// TODO: Promote processes that haven't been scheduled in too long
// Find processes that aren't ready or aren't running and
// move them to the appropriate lists.
for (auto &pri_list : m_runlists) {
auto *proc = pri_list.front();
while (proc) {
uint64_t age = now - proc->last_ran;
process_flags flags = proc->flags;
uint8_t priority = proc->priority;
auto *tcb = pri_list.front();
while (tcb) {
thread *th = tcb->thread_data;
uint64_t age = now - tcb->last_ran;
uint8_t priority = tcb->priority;
bool running = flags && process_flags::running;
bool ready = flags && process_flags::ready;
bool ready = th->has_state(thread::state::ready);
bool constant = th->has_state(thread::state::constant);
bool stale = age > quantum(priority) * 2 &&
proc->priority > promote_limit &&
!(flags && process_flags::const_pri);
tcb->priority > promote_limit &&
!constant;
if (running && ready) {
auto *remove = proc;
proc = proc->next();
if (ready) {
auto *remove = tcb;
tcb = tcb->next();
if (stale) {
m_runlists[remove->priority].remove(remove);
remove->priority -= 1;
remove->time_left = quantum(remove->priority);
m_runlists[remove->priority].push_back(remove);
log::debug(logs::task, "Scheduler promoting process %d, priority %d",
remove->pid, remove->priority);
log::debug(logs::task, "Scheduler promoting thread %llx, priority %d",
th->koid(), remove->priority);
}
continue;
}
auto *remove = proc;
proc = proc->next();
auto *remove = tcb;
tcb = tcb->next();
pri_list.remove(remove);
if (!(remove->flags && process_flags::running)) {
bool exited = th->has_state(thread::state::exited);
if (exited) {
// TODO: Alert continaing process thread exitied,
// and exit process if it was the last thread.
/*
auto *parent = get_process_by_id(remove->ppid);
if (parent && parent->wake_on_child(remove)) {
m_blocked.remove(parent);
@@ -270,7 +321,10 @@ void scheduler::prune(uint64_t now)
} else {
m_exited.push_back(remove);
}
*/
m_exited.push_back(remove);
} else {
log::debug(logs::task, "Prune: moving blocked thread %llx", th->koid());
m_blocked.push_back(remove);
}
}
@@ -278,13 +332,15 @@ void scheduler::prune(uint64_t now)
// Find blocked processes that are ready (possibly after waking wating
// ones) and move them to the appropriate runlist.
auto *proc = m_blocked.front();
while (proc) {
bool ready = proc->flags && process_flags::ready;
ready |= proc->wake_on_time(now);
auto *tcb = m_blocked.front();
while (tcb) {
thread *th = tcb->thread_data;
auto *remove = proc;
proc = proc->next();
bool ready = th->has_state(thread::state::ready);
ready |= th->wake_on_time(now);
auto *remove = tcb;
tcb = tcb->next();
if (!ready) continue;
m_blocked.remove(remove);
@@ -295,17 +351,19 @@ void scheduler::prune(uint64_t now)
void
scheduler::schedule()
{
pid_t lastpid = m_current->pid;
uint8_t priority = m_current->priority;
uint32_t remaining = m_apic->stop_timer();
m_current->time_left = remaining;
thread *th = m_current->thread_data;
const bool constant = th->has_state(thread::state::constant);
if (remaining == 0 && priority < max_priority &&
!(m_current->flags && process_flags::const_pri)) {
// Process used its whole timeslice, demote it
++m_current->priority;
log::debug(logs::task, "Scheduler demoting process %d, priority %d",
m_current->pid, m_current->priority);
if (remaining == 0) {
if (priority < max_priority && !constant) {
// Process used its whole timeslice, demote it
++m_current->priority;
log::debug(logs::task, "Scheduler demoting thread %llx, priority %d",
th->koid(), m_current->priority);
}
m_current->time_left = quantum(m_current->priority);
} else if (remaining > 0) {
// Process gave up CPU, give it a small bonus to its
@@ -315,7 +373,7 @@ scheduler::schedule()
}
m_runlists[priority].remove(m_current);
if (m_current->flags && process_flags::ready) {
if (th->has_state(thread::state::ready)) {
m_runlists[m_current->priority].push_back(m_current);
} else {
m_blocked.push_back(m_current);
@@ -332,18 +390,20 @@ scheduler::schedule()
m_current->last_ran = m_clock;
m_current = m_runlists[priority].pop_front();
m_current->last_ran = m_clock;
m_apic->reset_timer(m_current->time_left);
auto *next = m_runlists[priority].pop_front();
next->last_ran = m_clock;
m_apic->reset_timer(next->time_left);
if (lastpid != m_current->pid) {
if (next != m_current) {
m_current = next;
task_switch(m_current);
log::debug(logs::task, "Scheduler switched to process %d, priority %d time left %d @ %lld.",
m_current->pid, m_current->priority, m_current->time_left, m_clock);
log::debug(logs::task, "Scheduler switched to thread %llx, priority %d time left %d @ %lld.",
th->koid(), m_current->priority, m_current->time_left, m_clock);
}
}
/*
process_node *
scheduler::get_process_by_id(uint32_t pid)
{
@@ -364,3 +424,4 @@ scheduler::get_process_by_id(uint32_t pid)
return nullptr;
}
*/

View File

@@ -3,15 +3,14 @@
/// The task scheduler and related definitions
#include <stdint.h>
#include "process.h"
#include "objects/thread.h"
class lapic;
struct page_table;
struct cpu_state;
extern "C" void isr_handler(cpu_state*);
extern "C" void task_switch(process *next);
extern "C" void task_fork(process *child);
extern "C" void task_switch(TCB *next);
/// The task scheduler
@@ -27,7 +26,7 @@ public:
/// Default priority on process creation
static const uint8_t default_priority = 1;
/// Loest (most urgent) priority achieved via promotion
/// Lowest (most urgent) priority achieved via promotion
static const uint8_t promote_limit = 1;
/// How long the base timer quantum is, in us
@@ -50,15 +49,13 @@ public:
void load_process(const char *name, const void *data, size_t size);
/// Create a new kernel task
/// \arg pid Pid to use for this task, must be negative
/// \arg proc Function to run as a kernel task
/// \arg priority Priority to start the process with
/// \arg flags Flags to add to the process
/// \arg constant True if this task cannot be promoted/demoted
void create_kernel_task(
pid_t pid,
void (*task)(),
uint8_t priority,
process_flags flags = process_flags::none);
bool constant = false);
/// Get the quantum for a given priority.
uint32_t quantum(int priority);
@@ -70,14 +67,16 @@ public:
/// Run the scheduler, possibly switching to a new task
void schedule();
/// Get the current process.
/// \returns A pointer to the current process' process struct
inline process * current() { return m_current; }
/// Get the current TCB.
/// \returns A pointer to the current thread's TCB
inline TCB * current() { return m_current; }
/*
/// Look up a process by its PID
/// \arg pid The requested PID
/// \returns The process matching that PID, or nullptr
process_node * get_process_by_id(uint32_t pid);
tcb_node * get_process_by_id(uint32_t pid);
*/
/// Get a reference to the system scheduler
/// \returns A reference to the global system scheduler
@@ -89,9 +88,9 @@ private:
/// Create a new process object. This process will have its pid
/// set but nothing else.
/// \arg pid The pid to give the process (0 for automatic)
/// \returns The new process object
process_node * create_process(pid_t pid = 0);
/// \arg pml4 The root page table of the process
/// \returns The new process' main thread
thread * create_process(page_table *pml4);
void prune(uint64_t now);
@@ -100,10 +99,10 @@ private:
uint32_t m_next_pid;
uint32_t m_tick_count;
process_node *m_current;
process_list m_runlists[num_priorities];
process_list m_blocked;
process_list m_exited;
tcb_node *m_current;
tcb_list m_runlists[num_priorities];
tcb_list m_blocked;
tcb_list m_exited;
// TODO: lol a real clock
uint64_t m_clock = 0;

View File

@@ -2,8 +2,8 @@ SYSCALL(0x00, object_noop, void)
SYSCALL(0x01, object_wait, j6_handle_t, j6_signal_t, j6_signal_t *)
SYSCALL(0x11, process_exit, int64_t)
SYSCALL(0x12, process_fork, pid_t*)
SYSCALL(0x13, process_getpid, pid_t*)
SYSCALL(0x12, process_fork, uint32_t*)
SYSCALL(0x13, process_getpid, uint32_t*)
SYSCALL(0x14, process_log, const char *)
SYSCALL(0x15, process_pause, void)
SYSCALL(0x16, process_sleep, uint64_t)

View File

@@ -10,8 +10,9 @@ j6_status_t
object_noop()
{
auto &s = scheduler::get();
auto *p = s.current();
log::debug(logs::syscall, "Process %d called noop syscall.", p->pid);
TCB *tcb = s.current();
thread *th = tcb->thread_data;
log::debug(logs::syscall, "Thread %llx called noop syscall.", th->koid());
return j6_status_ok;
}

View File

@@ -10,16 +10,18 @@ j6_status_t
process_exit(int64_t status)
{
auto &s = scheduler::get();
auto *p = s.current();
log::debug(logs::syscall, "Process %d exiting with code %d", p->pid, status);
TCB *tcb = s.current();
thread *th = tcb->thread_data;
log::debug(logs::syscall, "Thread %llx exiting with code %d", th->koid(), status);
p->exit(status);
th->exit(status);
s.schedule();
log::error(logs::syscall, "returned to exit syscall");
return j6_err_unexpected;
}
/*
j6_status_t
process_fork(pid_t *pid)
{
@@ -54,6 +56,7 @@ process_getpid(pid_t *pid)
*pid = p->pid;
return j6_status_ok;
}
*/
j6_status_t
process_log(const char *message)
@@ -63,17 +66,22 @@ process_log(const char *message)
}
auto &s = scheduler::get();
auto *p = s.current();
log::info(logs::syscall, "Message[%d]: %s", p->pid, message);
TCB *tcb = s.current();
thread *th = tcb->thread_data;
log::info(logs::syscall, "Message[%llx]: %s", th->koid(), message);
return j6_status_ok;
}
j6_status_t process_fork(uint32_t *pid) { *pid = 5; return process_log("CALLED FORK"); }
j6_status_t process_getpid(uint32_t *pid) { *pid = 0; return process_log("CALLED GETPID"); }
j6_status_t
process_pause()
{
auto &s = scheduler::get();
auto *p = s.current();
p->wait_on_signal(-1ull);
TCB *tcb = s.current();
thread *th = tcb->thread_data;
th->wait_on_signals(th, -1ull);
s.schedule();
return j6_status_ok;
}
@@ -82,10 +90,11 @@ j6_status_t
process_sleep(uint64_t til)
{
auto &s = scheduler::get();
auto *p = s.current();
log::debug(logs::syscall, "Process %d sleeping until %llu", p->pid, til);
TCB *tcb = s.current();
thread *th = tcb->thread_data;
log::debug(logs::syscall, "Thread %llx sleeping until %llu", th->koid(), til);
p->wait_on_time(til);
th->wait_on_time(til);
s.schedule();
return j6_status_ok;
}

View File

@@ -112,6 +112,28 @@ public:
m_elements[m_size].~T();
}
/// Remove the first occurance of an item from the array, not
/// order-preserving. Does nothing if the item is not in the array.
void remove_swap(const T &item)
{
for (size_t i = 0; i < m_size; ++i) {
if (m_elements[i] == item) {
remove_swap_at(i);
break;
}
}
}
/// Remove the item at the given index from the array, not
/// order-preserving.
void remove_swap_at(size_t i)
{
if (i >= count()) return;
if (i < m_size - 1)
m_elements[i] = m_elements[m_size - 1];
remove();
}
/// Remove an item from the end of the array and return it.
T pop()
{