mirror of
https://github.com/justinian/jsix.git
synced 2025-12-10 08:24:32 -08:00
[kernel] Add userspace threading
Implement the syscalls necessary for threads to create other threads in their same process. This involved rearranging a number of syscalls, as well as implementing object_wait and a basic implementation of a process' list of handles.
This commit is contained in:
@@ -41,6 +41,9 @@ public:
|
||||
/// \returns The object type for the koid
|
||||
static type koid_type(j6_koid_t koid);
|
||||
|
||||
/// Get this object's type
|
||||
inline type get_type() const { return koid_type(m_koid); }
|
||||
|
||||
/// Get this object's koid
|
||||
inline j6_koid_t koid() const { return m_koid; }
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@ kutil::vector<process*> process::s_processes;
|
||||
|
||||
process::process(page_table *pml4) :
|
||||
kobject(kobject::type::process),
|
||||
m_pml4(pml4)
|
||||
m_pml4(pml4),
|
||||
m_state(state::running)
|
||||
{
|
||||
s_processes.append(this);
|
||||
}
|
||||
@@ -21,6 +22,12 @@ process::~process()
|
||||
void
|
||||
process::exit(unsigned code)
|
||||
{
|
||||
// TODO: make this thread-safe
|
||||
if (m_state != state::running)
|
||||
return;
|
||||
else
|
||||
m_state = state::exited;
|
||||
|
||||
for (auto *thread : m_threads) {
|
||||
thread->exit(code);
|
||||
}
|
||||
@@ -53,10 +60,23 @@ process::update()
|
||||
}
|
||||
|
||||
thread *
|
||||
process::create_thread(uint8_t priority)
|
||||
process::create_thread(uint8_t priority, bool user)
|
||||
{
|
||||
thread *th = new thread(*this, priority);
|
||||
kassert(th, "Failed to create thread!");
|
||||
|
||||
if (user) {
|
||||
uintptr_t stack_top = stacks_top - (m_threads.count() * stack_size);
|
||||
auto *pm = page_manager::get();
|
||||
pm->map_pages(
|
||||
stack_top - stack_size,
|
||||
page_manager::page_count(stack_size),
|
||||
true, // user stack
|
||||
m_pml4);
|
||||
|
||||
th->tcb()->rsp3 = stack_top;
|
||||
}
|
||||
|
||||
m_threads.append(th);
|
||||
return th;
|
||||
}
|
||||
@@ -76,3 +96,36 @@ process::thread_exited(thread *th)
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
j6_handle_t
|
||||
process::add_handle(kobject *obj)
|
||||
{
|
||||
if (!obj)
|
||||
return j6_handle_invalid;
|
||||
|
||||
obj->handle_retain();
|
||||
size_t len = m_handles.count();
|
||||
m_handles.append(obj);
|
||||
return static_cast<j6_handle_t>(len);
|
||||
}
|
||||
|
||||
bool
|
||||
process::remove_handle(j6_handle_t handle)
|
||||
{
|
||||
if (handle < m_handles.count()) {
|
||||
kobject *obj = m_handles[handle];
|
||||
m_handles[handle] = nullptr;
|
||||
if (obj)
|
||||
obj->handle_release();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
kobject *
|
||||
process::lookup_handle(j6_handle_t handle)
|
||||
{
|
||||
if (handle < m_handles.count())
|
||||
return m_handles[handle];
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,12 @@ class process :
|
||||
public kobject
|
||||
{
|
||||
public:
|
||||
/// Top of memory area where thread stacks are allocated
|
||||
constexpr static uintptr_t stacks_top = 0x0000800000000000;
|
||||
|
||||
/// Size of userspace thread stacks
|
||||
constexpr static size_t stack_size = 0x4000;
|
||||
|
||||
/// Constructor.
|
||||
/// \args pml4 Root of the process' page tables
|
||||
process(page_table *pml4);
|
||||
@@ -28,19 +34,44 @@ public:
|
||||
|
||||
/// Create a new thread in this process
|
||||
/// \args priority The new thread's scheduling priority
|
||||
/// \args user If true, create a userspace stack for this thread
|
||||
/// \returns The newly created thread object
|
||||
thread * create_thread(uint8_t priorty);
|
||||
thread * create_thread(uint8_t priorty, bool user = true);
|
||||
|
||||
/// Start tracking an object with a handle.
|
||||
/// \args obj The object this handle refers to
|
||||
/// \returns The new handle for this object
|
||||
j6_handle_t add_handle(kobject *obj);
|
||||
|
||||
/// Stop tracking an object with a handle.
|
||||
/// \args handle The handle that refers to the object
|
||||
/// \returns True if the handle was removed
|
||||
bool remove_handle(j6_handle_t handle);
|
||||
|
||||
/// Lookup an object for a handle
|
||||
/// \args handle The handle to the object
|
||||
/// \returns Pointer to the object, or null if not found
|
||||
kobject * lookup_handle(j6_handle_t handle);
|
||||
|
||||
/// Inform the process of an exited thread
|
||||
/// \args th The thread which has exited
|
||||
/// \returns True if this thread ending has ended the process
|
||||
bool thread_exited(thread *th);
|
||||
|
||||
/// Create the special kernel process that owns kernel tasks
|
||||
/// \arg pml4 The kernel-only pml4
|
||||
/// \arg idle_rsp The idle thread's rsp
|
||||
static process * create_kernel_process(page_table *pml4, uintptr_t idle_rsp);
|
||||
|
||||
private:
|
||||
uint32_t m_return_code;
|
||||
|
||||
page_table *m_pml4;
|
||||
kutil::vector<thread*> m_threads;
|
||||
kutil::vector<kobject*> m_handles;
|
||||
|
||||
enum class state : uint8_t { running, exited };
|
||||
state m_state;
|
||||
|
||||
static kutil::vector<process*> s_processes;
|
||||
};
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#include "j6/signals.h"
|
||||
#include "log.h"
|
||||
#include "objects/thread.h"
|
||||
#include "objects/process.h"
|
||||
#include "scheduler.h"
|
||||
|
||||
extern "C" void kernel_to_user_trampoline();
|
||||
static constexpr j6_signal_t thread_default_signals = 0;
|
||||
|
||||
thread::thread(process &parent, uint8_t pri) :
|
||||
thread::thread(process &parent, uint8_t pri, bool user) :
|
||||
kobject(kobject::type::thread, thread_default_signals),
|
||||
m_parent(parent),
|
||||
m_state(state::loading),
|
||||
@@ -16,6 +18,22 @@ thread::thread(process &parent, uint8_t pri) :
|
||||
TCB *tcbp = tcb();
|
||||
tcbp->pml4 = parent.pml4();
|
||||
tcbp->priority = pri;
|
||||
setup_kernel_stack();
|
||||
set_state(state::ready);
|
||||
}
|
||||
|
||||
thread::thread(process &parent, uint8_t pri, uintptr_t rsp0) :
|
||||
kobject(kobject::type::thread, thread_default_signals),
|
||||
m_parent(parent),
|
||||
m_state(state::loading),
|
||||
m_wait_type(wait_type::none),
|
||||
m_wait_data(0),
|
||||
m_wait_obj(0)
|
||||
{
|
||||
TCB *tcbp = tcb();
|
||||
tcbp->pml4 = parent.pml4();
|
||||
tcbp->priority = pri;
|
||||
tcbp->rsp0 = rsp0;
|
||||
set_state(state::ready);
|
||||
}
|
||||
|
||||
@@ -51,7 +69,8 @@ thread::wake_on_signals(kobject *obj, j6_signal_t signals)
|
||||
return false;
|
||||
|
||||
m_wait_type = wait_type::none;
|
||||
m_wait_data = j6_status_ok;
|
||||
m_wait_result = j6_status_ok;
|
||||
m_wait_data = signals;
|
||||
m_wait_obj = obj->koid();
|
||||
set_state(state::ready);
|
||||
return true;
|
||||
@@ -65,7 +84,8 @@ thread::wake_on_time(uint64_t now)
|
||||
return false;
|
||||
|
||||
m_wait_type = wait_type::none;
|
||||
m_wait_data = j6_status_ok;
|
||||
m_wait_result = j6_status_ok;
|
||||
m_wait_data = now;
|
||||
m_wait_obj = 0;
|
||||
set_state(state::ready);
|
||||
return true;
|
||||
@@ -75,7 +95,8 @@ void
|
||||
thread::wake_on_result(kobject *obj, j6_status_t result)
|
||||
{
|
||||
m_wait_type = wait_type::none;
|
||||
m_wait_data = result;
|
||||
m_wait_result = result;
|
||||
m_wait_data = 0;
|
||||
m_wait_obj = obj->koid();
|
||||
set_state(state::ready);
|
||||
}
|
||||
@@ -86,5 +107,77 @@ thread::exit(uint32_t code)
|
||||
m_return_code = code;
|
||||
set_state(state::exited);
|
||||
clear_state(state::ready);
|
||||
assert_signal(j6_signal_thread_exit);
|
||||
}
|
||||
|
||||
void
|
||||
thread::add_thunk_kernel(uintptr_t rip)
|
||||
{
|
||||
m_tcb.rsp -= sizeof(uintptr_t) * 7;
|
||||
uintptr_t *stack = reinterpret_cast<uintptr_t*>(m_tcb.rsp);
|
||||
|
||||
stack[6] = rip; // return rip
|
||||
stack[5] = m_tcb.rsp0; // rbp
|
||||
stack[4] = 0xbbbbbbbb; // rbx
|
||||
stack[3] = 0x12121212; // r12
|
||||
stack[2] = 0x13131313; // r13
|
||||
stack[1] = 0x14141414; // r14
|
||||
stack[0] = 0x15151515; // r15
|
||||
}
|
||||
|
||||
void
|
||||
thread::add_thunk_user(uintptr_t rip)
|
||||
{
|
||||
m_tcb.rsp -= sizeof(uintptr_t) * 8;
|
||||
uintptr_t *stack = reinterpret_cast<uintptr_t*>(m_tcb.rsp);
|
||||
|
||||
stack[7] = rip; // return rip in rcx
|
||||
stack[6] = m_tcb.rsp3; // rbp
|
||||
stack[5] = 0xbbbbbbbb; // rbx
|
||||
stack[4] = 0x00000200; // r11 sets RFLAGS
|
||||
stack[3] = 0x12121212; // r12
|
||||
stack[2] = 0x13131313; // r13
|
||||
stack[1] = 0x14141414; // r14
|
||||
stack[0] = 0x15151515; // r15
|
||||
|
||||
static const uintptr_t trampoline =
|
||||
reinterpret_cast<uintptr_t>(kernel_to_user_trampoline);
|
||||
add_thunk_kernel(trampoline);
|
||||
}
|
||||
|
||||
void
|
||||
thread::setup_kernel_stack()
|
||||
{
|
||||
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;
|
||||
|
||||
m_tcb.kernel_stack_size = initial_stack_size;
|
||||
m_tcb.kernel_stack = reinterpret_cast<uintptr_t>(stack_bottom);
|
||||
m_tcb.rsp0 = reinterpret_cast<uintptr_t>(stack_top);
|
||||
m_tcb.rsp = m_tcb.rsp0;
|
||||
}
|
||||
|
||||
thread *
|
||||
thread::create_idle_thread(process &kernel, uint8_t pri, uintptr_t rsp0)
|
||||
{
|
||||
thread *idle = new thread(kernel, pri, rsp0);
|
||||
idle->set_state(thread::state::constant);
|
||||
log::info(logs::task, "Created idle thread as koid %llx", idle->koid());
|
||||
|
||||
return idle;
|
||||
}
|
||||
|
||||
@@ -92,6 +92,12 @@ public:
|
||||
/// \arg result Result code to return to the thread
|
||||
void wake_on_result(kobject *obj, j6_status_t result);
|
||||
|
||||
/// Get the result status code from the last blocking operation
|
||||
j6_status_t get_wait_result() const { return m_wait_result; }
|
||||
|
||||
/// Get the current blocking opreation's wait data
|
||||
uint64_t get_wait_data() const { return m_wait_data; }
|
||||
|
||||
inline bool has_state(state s) const {
|
||||
return static_cast<uint8_t>(m_state) & static_cast<uint8_t>(s);
|
||||
}
|
||||
@@ -111,6 +117,20 @@ public:
|
||||
/// \arg code The return code to exit with.
|
||||
void exit(unsigned code);
|
||||
|
||||
/// Add a stack header that returns to the given address in kernel space.
|
||||
/// \arg rip The address to return to, must be kernel space
|
||||
void add_thunk_kernel(uintptr_t rip);
|
||||
|
||||
/// Add a stack header that returns to the given address in user space.
|
||||
/// \arg rip The address to return to, must be user space
|
||||
void add_thunk_user(uintptr_t rip);
|
||||
|
||||
/// Create the kernel idle thread
|
||||
/// \arg kernel The process object that owns kernel tasks
|
||||
/// \arg pri The idle thread priority value
|
||||
/// \arg rsp The existing stack for the idle thread
|
||||
static thread * create_idle_thread(process &kernel, uint8_t pri, uintptr_t rsp);
|
||||
|
||||
private:
|
||||
thread() = delete;
|
||||
thread(const thread &other) = delete;
|
||||
@@ -118,14 +138,24 @@ private:
|
||||
friend class process;
|
||||
|
||||
/// Constructor.
|
||||
/// \arg p The process which owns this thread
|
||||
/// \arg pri Initial priority level of this thread
|
||||
thread(process &parent, uint8_t pri);
|
||||
/// \arg parent The process which owns this thread
|
||||
/// \arg pri Initial priority level of this thread
|
||||
/// \arg user True if this is a userspace thread
|
||||
thread(process &parent, uint8_t pri, bool user = true);
|
||||
|
||||
process &m_parent;
|
||||
/// Constructor. Used when a kernel stack already exists.
|
||||
/// \arg parent The process which owns this thread
|
||||
/// \arg pri Initial priority level of this thread
|
||||
/// \arg rsp0 The existing kernel stack rsp
|
||||
thread(process &parent, uint8_t pri, uintptr_t rsp0);
|
||||
|
||||
/// Set up a new empty kernel stack for this thread.
|
||||
void setup_kernel_stack();
|
||||
|
||||
tcb_node m_tcb;
|
||||
|
||||
process &m_parent;
|
||||
|
||||
state m_state;
|
||||
wait_type m_wait_type;
|
||||
// There should be 1 byte of padding here
|
||||
@@ -133,5 +163,6 @@ private:
|
||||
uint32_t m_return_code;
|
||||
|
||||
uint64_t m_wait_data;
|
||||
j6_status_t m_wait_result;
|
||||
j6_koid_t m_wait_obj;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user