[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:
@@ -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 ® = 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 ® : 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 ® = 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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
83
src/kernel/objects/thread.cpp
Normal file
83
src/kernel/objects/thread.cpp
Normal 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
133
src/kernel/objects/thread.h
Normal 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;
|
||||
};
|
||||
Reference in New Issue
Block a user