[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

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