Add a threading syscall API.

This commit is contained in:
Drew Galbraith 2023-06-06 16:24:03 -07:00
parent e2aad55a8a
commit ef8eb5d993
14 changed files with 235 additions and 30 deletions

View File

@ -6,7 +6,7 @@ constexpr uint64_t prog2 = 0x00000020'00000000;
int main() { int main() {
ZDebug("Testing"); ZDebug("Testing");
uint64_t err = ZProcessSpawnElf(0x100, prog2, 0x1000); uint64_t err = ZProcessSpawnElf(Z_INIT_PROC_SELF, prog2, 0x2000);
if (err != Z_OK) { if (err != Z_OK) {
ZDebug("Error"); ZDebug("Error");
} else { } else {

View File

@ -1,8 +1,40 @@
#include "zcall.h" #include "zcall.h"
#include "zerrors.h"
#define CHECK(expr) \
{ \
uint64_t code = expr; \
if (code != Z_OK) { \
ZDebug("crash!"); \
return 1; \
} \
}
void thread_entry(char* a, char* b) {
ZDebug("In thread");
ZDebug(a);
ZDebug(b);
ZThreadExit();
}
int main() { int main() {
ZDebug("Testing"); ZDebug("Testing");
uint64_t t1, t2;
CHECK(ZThreadCreate(Z_INIT_PROC_SELF, &t1));
CHECK(ZThreadCreate(Z_INIT_PROC_SELF, &t2));
const char* a = "a";
const char* b = "bee";
const char* c = "cee";
const char* d = "dee";
CHECK(ZThreadStart(t1, reinterpret_cast<uint64_t>(thread_entry),
reinterpret_cast<uint64_t>(a),
reinterpret_cast<uint64_t>(b)));
CHECK(ZThreadStart(t2, reinterpret_cast<uint64_t>(thread_entry),
reinterpret_cast<uint64_t>(c),
reinterpret_cast<uint64_t>(d)));
return 0; return 0;
} }

View File

@ -1,5 +1,6 @@
add_executable(zion add_executable(zion
boot/boot_info.cpp boot/boot_info.cpp
capability/capability.cpp
common/gdt.cpp common/gdt.cpp
common/load_gdt.s common/load_gdt.s
debug/debug.cpp debug/debug.cpp

View File

@ -0,0 +1,17 @@
#include "capability/capability.h"
template <>
Process& Capability::obj<Process>() {
if (type_ != PROCESS) {
panic("Accessing %u cap as object.", type_);
}
return *static_cast<Process*>(obj_);
}
template <>
Thread& Capability::obj<Thread>() {
if (type_ != THREAD) {
panic("Accessing %u cap as object.", type_);
}
return *static_cast<Thread*>(obj_);
}

View File

@ -5,12 +5,14 @@
#include "debug/debug.h" #include "debug/debug.h"
class Process; class Process;
class Thread;
class Capability { class Capability {
public: public:
enum Type { enum Type {
UNDEFINED, UNDEFINED,
PROCESS, PROCESS,
THREAD,
}; };
Capability(void* obj, Type type, uint64_t id, uint64_t permissions) Capability(void* obj, Type type, uint64_t id, uint64_t permissions)
: obj_(obj), type_(type), id_(id), permissions_(permissions) {} : obj_(obj), type_(type), id_(id), permissions_(permissions) {}
@ -34,11 +36,3 @@ class Capability {
uint64_t id_; uint64_t id_;
uint64_t permissions_; uint64_t permissions_;
}; };
template <class Process>
Process& Capability::obj() {
if (type_ != PROCESS) {
panic("Accessing %u cap as object.", type_);
}
return *static_cast<Process*>(obj_);
}

View File

@ -7,6 +7,7 @@
#define ZC_WRITE 0x01 #define ZC_WRITE 0x01
#define ZC_READ 0x02 #define ZC_READ 0x02
// Process Calls.
#define Z_PROCESS_EXIT 0x01 #define Z_PROCESS_EXIT 0x01
#define Z_PROCESS_SPAWN 0x02 #define Z_PROCESS_SPAWN 0x02
#define Z_PROCESS_START 0x03 #define Z_PROCESS_START 0x03
@ -16,10 +17,14 @@
#define ZC_PROC_SPAWN_PROC 0x100 #define ZC_PROC_SPAWN_PROC 0x100
#define ZC_PROC_SPAWN_THREAD 0x101 #define ZC_PROC_SPAWN_THREAD 0x101
#define Z_INIT_PROC_SELF 0x1
// Thread Calls.
#define Z_THREAD_CREATE 0x10 #define Z_THREAD_CREATE 0x10
#define Z_THREAD_START 0x11 #define Z_THREAD_START 0x11
#define Z_THREAD_EXIT 0x12 #define Z_THREAD_EXIT 0x12
// Debugging Calls.
#define Z_DEBUG_PRINT 0x10000000 #define Z_DEBUG_PRINT 0x10000000
uint64_t ZDebug(const char* message); uint64_t ZDebug(const char* message);
@ -31,5 +36,29 @@ struct ZProcessSpawnElfReq {
uint64_t elf_size; uint64_t elf_size;
}; };
// Creates a child process of the current one and
// starts its root thread from the provided ELF file.
uint64_t ZProcessSpawnElf(uint64_t cap_id, uint64_t elf_base, uint64_t ZProcessSpawnElf(uint64_t cap_id, uint64_t elf_base,
uint64_t elf_size); uint64_t elf_size);
struct ZThreadCreateReq {
uint64_t proc_cap;
};
struct ZThreadCreateResp {
uint64_t thread_cap;
};
uint64_t ZThreadCreate(uint64_t proc_cap, uint64_t* thread_cap);
struct ZThreadStartReq {
uint64_t thread_cap;
uint64_t entry;
uint64_t arg1;
uint64_t arg2;
};
uint64_t ZThreadStart(uint64_t thread_cap, uint64_t entry, uint64_t arg1,
uint64_t arg2);
void ZThreadExit();

View File

@ -136,6 +136,22 @@ uint64_t CurrCr3() {
return pml4_addr; return pml4_addr;
} }
void CopyPageIntoNonResidentProcess(uint64_t base, uint64_t size,
Process& dest_proc, uint64_t dest_virt) {
if (size > 0x1000) {
panic("NR copy > 1 page");
}
if (dest_virt & 0xFFF) {
panic("NR copy to non page aligned");
}
uint64_t phys = AllocatePageIfNecessary(dest_virt, dest_proc.vmm().cr3());
uint8_t* src = reinterpret_cast<uint8_t*>(base);
uint8_t* dest =
reinterpret_cast<uint8_t*>(phys + boot::GetHigherHalfDirectMap());
for (uint64_t i = 0; i < size; i++) {
dest[i] = src[i];
}
}
} // namespace } // namespace
void InitializePml4(uint64_t pml4_physical_addr) { void InitializePml4(uint64_t pml4_physical_addr) {
@ -176,18 +192,13 @@ void EnsureResident(uint64_t addr, uint64_t size) {
void CopyIntoNonResidentProcess(uint64_t base, uint64_t size, void CopyIntoNonResidentProcess(uint64_t base, uint64_t size,
Process& dest_proc, uint64_t dest_virt) { Process& dest_proc, uint64_t dest_virt) {
if (size > 0x1000) { while (size > 0) {
panic("Unimplemented NR copy > 1 page"); uint64_t to_copy = size > 0x1000 ? 0x1000 : size;
}
if (dest_virt & 0xFFF) {
panic("Unimplemented NR copy to non page aligned");
}
uint64_t phys = AllocatePageIfNecessary(dest_virt, dest_proc.vmm().cr3());
uint8_t* src = reinterpret_cast<uint8_t*>(base);
uint8_t* dest =
reinterpret_cast<uint8_t*>(phys + boot::GetHigherHalfDirectMap());
for (uint64_t i = 0; i < size; i++) { CopyPageIntoNonResidentProcess(base, to_copy, dest_proc, dest_virt);
dest[i] = src[i];
size -= to_copy;
base += 0x1000;
dest_virt += 0x1000;
} }
} }

View File

@ -13,4 +13,6 @@ jump_user_space:
pushf # Can we just push 0 for flags? pushf # Can we just push 0 for flags?
pushq $0x1B # cs pushq $0x1B # cs
pushq %rdi pushq %rdi
mov %rdx, %rdi
mov %rcx, %rsi
iretq iretq

View File

@ -23,11 +23,18 @@ SharedPtr<Process> Process::RootProcess() {
Process::Process() : id_(gNextId++), state_(RUNNING) {} Process::Process() : id_(gNextId++), state_(RUNNING) {}
SharedPtr<Thread> Process::CreateThread() {
SharedPtr<Thread> thread{new Thread(*this, next_thread_id_++, 0)};
threads_.PushBack(thread);
return thread;
}
void Process::CreateThread(uint64_t entry) { void Process::CreateThread(uint64_t entry) {
Thread* thread = new Thread(*this, next_thread_id_++, entry); Thread* thread = new Thread(*this, next_thread_id_++, entry);
threads_.PushBack(thread); threads_.PushBack(thread);
caps_.PushBack(new Capability(this, Capability::PROCESS, next_cap_id_++, caps_.PushBack(new Capability(this, Capability::PROCESS, Z_INIT_PROC_SELF,
ZC_PROC_SPAWN_PROC)); ZC_PROC_SPAWN_PROC | ZC_PROC_SPAWN_THREAD));
thread->SetState(Thread::RUNNABLE);
gScheduler->Enqueue(thread); gScheduler->Enqueue(thread);
} }
@ -65,3 +72,10 @@ SharedPtr<Capability> Process::GetCapability(uint64_t cid) {
dbgln("Bad cap access"); dbgln("Bad cap access");
return {}; return {};
} }
uint64_t Process::AddCapability(SharedPtr<Thread>& thread) {
uint64_t cap_id = next_cap_id_++;
caps_.PushBack(
new Capability(thread.ptr(), Capability::THREAD, cap_id, ZC_WRITE));
return cap_id;
}

View File

@ -24,10 +24,12 @@ class Process {
uint64_t id() const { return id_; } uint64_t id() const { return id_; }
VirtualMemory& vmm() { return vmm_; } VirtualMemory& vmm() { return vmm_; }
SharedPtr<Thread> CreateThread();
void CreateThread(uint64_t entry); void CreateThread(uint64_t entry);
SharedPtr<Thread> GetThread(uint64_t tid); SharedPtr<Thread> GetThread(uint64_t tid);
SharedPtr<Capability> GetCapability(uint64_t cid); SharedPtr<Capability> GetCapability(uint64_t cid);
uint64_t AddCapability(SharedPtr<Thread>& t);
// Checks the state of all child threads and transitions to // Checks the state of all child threads and transitions to
// finished if all have finished. // finished if all have finished.
void CheckState(); void CheckState();

View File

@ -9,7 +9,8 @@
namespace { namespace {
extern "C" void jump_user_space(uint64_t rip, uint64_t rsp); extern "C" void jump_user_space(uint64_t rip, uint64_t rsp, uint64_t arg1,
uint64_t arg2);
extern "C" void thread_init() { extern "C" void thread_init() {
asm("sti"); asm("sti");
@ -40,11 +41,20 @@ Thread::Thread(Process& proc, uint64_t tid, uint64_t entry)
uint64_t Thread::pid() const { return process_.id(); } uint64_t Thread::pid() const { return process_.id(); }
void Thread::Start(uint64_t entry, uint64_t arg1, uint64_t arg2) {
rip_ = entry;
arg1_ = arg1;
arg2_ = arg2;
state_ = RUNNABLE;
// Get from parent to avoid creating a new shared ptr.
gScheduler->Enqueue(process_.GetThread(id_));
}
void Thread::Init() { void Thread::Init() {
dbgln("Thread start.", pid(), id_); dbgln("Thread start.", pid(), id_);
uint64_t rsp = process_.vmm().AllocateUserStack(); uint64_t rsp = process_.vmm().AllocateUserStack();
SetRsp0(rsp0_start_); SetRsp0(rsp0_start_);
jump_user_space(rip_, rsp); jump_user_space(rip_, rsp, arg1_, arg2_);
} }
void Thread::Exit() { void Thread::Exit() {

View File

@ -11,6 +11,7 @@ class Thread {
public: public:
enum State { enum State {
UNSPECIFIED, UNSPECIFIED,
CREATED,
RUNNING, RUNNING,
RUNNABLE, RUNNABLE,
FINISHED, FINISHED,
@ -27,6 +28,9 @@ class Thread {
uint64_t* Rsp0Ptr() { return &rsp0_; } uint64_t* Rsp0Ptr() { return &rsp0_; }
uint64_t Rsp0Start() { return rsp0_start_; } uint64_t Rsp0Start() { return rsp0_start_; }
// Switches the thread's state to runnable and enqueues it.
void Start(uint64_t entry, uint64_t arg1, uint64_t arg2);
// Called the first time the thread starts up. // Called the first time the thread starts up.
void Init(); void Init();
@ -40,10 +44,12 @@ class Thread {
Thread(Process& proc) : process_(proc), id_(0) {} Thread(Process& proc) : process_(proc), id_(0) {}
Process& process_; Process& process_;
uint64_t id_; uint64_t id_;
State state_ = RUNNABLE; State state_ = CREATED;
// Startup Context for the thread. // Startup Context for the thread.
uint64_t rip_; uint64_t rip_;
uint64_t arg1_;
uint64_t arg2_;
// Stack pointer to take on resume. // Stack pointer to take on resume.
// Stack will contain the full thread context. // Stack will contain the full thread context.

View File

@ -78,7 +78,47 @@ uint64_t ProcessSpawnElf(ZProcessSpawnElfReq* req) {
return 0; return 0;
} }
extern "C" uint64_t SyscallHandler(uint64_t call_id, char* message) { uint64_t ThreadCreate(ZThreadCreateReq* req, ZThreadCreateResp* resp) {
auto& curr_proc = gScheduler->CurrentProcess();
auto cap = curr_proc.GetCapability(req->proc_cap);
if (cap.empty()) {
return ZE_NOT_FOUND;
}
if (!cap->CheckType(Capability::PROCESS)) {
return ZE_INVALID;
}
if (!cap->HasPermissions(ZC_PROC_SPAWN_THREAD)) {
return ZE_DENIED;
}
Process& parent_proc = cap->obj<Process>();
auto thread = parent_proc.CreateThread();
resp->thread_cap = curr_proc.AddCapability(thread);
return Z_OK;
}
uint64_t ThreadStart(ZThreadStartReq* req) {
auto& curr_proc = gScheduler->CurrentProcess();
auto cap = curr_proc.GetCapability(req->thread_cap);
if (cap.empty()) {
return ZE_NOT_FOUND;
}
if (!cap->CheckType(Capability::THREAD)) {
return ZE_INVALID;
}
if (!cap->HasPermissions(ZC_WRITE)) {
return ZE_DENIED;
}
Thread& thread = cap->obj<Thread>();
// FIXME: validate entry point is in user space.
thread.Start(req->entry, req->arg1, req->arg2);
}
extern "C" uint64_t SyscallHandler(uint64_t call_id, void* req, void* resp) {
Thread& thread = gScheduler->CurrentThread(); Thread& thread = gScheduler->CurrentThread();
switch (call_id) { switch (call_id) {
case Z_PROCESS_EXIT: case Z_PROCESS_EXIT:
@ -87,12 +127,21 @@ extern "C" uint64_t SyscallHandler(uint64_t call_id, char* message) {
panic("Returned from thread exit"); panic("Returned from thread exit");
break; break;
case Z_DEBUG_PRINT: case Z_DEBUG_PRINT:
dbgln("[Debug] %s", message); dbgln("[Debug] %s", req);
break; break;
case Z_PROCESS_SPAWN: case Z_PROCESS_SPAWN:
return ProcessSpawnElf(reinterpret_cast<ZProcessSpawnElfReq*>(message)); return ProcessSpawnElf(reinterpret_cast<ZProcessSpawnElfReq*>(req));
case Z_THREAD_CREATE:
return ThreadCreate(reinterpret_cast<ZThreadCreateReq*>(req),
reinterpret_cast<ZThreadCreateResp*>(resp));
case Z_THREAD_START:
return ThreadStart(reinterpret_cast<ZThreadStartReq*>(req));
case Z_THREAD_EXIT:
thread.Exit();
panic("Returned from thread exit");
break;
default: default:
panic("Unhandled syscall number: %u", call_id); panic("Unhandled syscall number: %x", call_id);
} }
return 1; return 1;
} }

View File

@ -2,12 +2,27 @@
#include <stdint.h> #include <stdint.h>
uint64_t SysCall0(uint64_t number) {
uint64_t return_code;
asm("syscall" : "=a"(return_code) : "D"(number));
return return_code;
}
uint64_t SysCall1(uint64_t number, const void* first) { uint64_t SysCall1(uint64_t number, const void* first) {
uint64_t return_code; uint64_t return_code;
asm("syscall" : "=a"(return_code) : "D"(number), "S"(first) : "rcx", "r11"); asm("syscall" : "=a"(return_code) : "D"(number), "S"(first) : "rcx", "r11");
return return_code; return return_code;
} }
uint64_t SysCall2(uint64_t number, const void* first, const void* second) {
uint64_t return_code;
asm("syscall"
: "=a"(return_code)
: "D"(number), "S"(first), "d"(second)
: "rcx", "r11");
return return_code;
}
uint64_t ZDebug(const char* message) { uint64_t ZDebug(const char* message) {
return SysCall1(Z_DEBUG_PRINT, message); return SysCall1(Z_DEBUG_PRINT, message);
} }
@ -21,3 +36,26 @@ uint64_t ZProcessSpawnElf(uint64_t cap_id, uint64_t elf_base,
}; };
return SysCall1(Z_PROCESS_SPAWN, &req); return SysCall1(Z_PROCESS_SPAWN, &req);
} }
uint64_t ZThreadCreate(uint64_t proc_cap, uint64_t* thread_cap) {
ZThreadCreateReq req{
.proc_cap = proc_cap,
};
ZThreadCreateResp resp;
uint64_t ret = SysCall2(Z_THREAD_CREATE, &req, &resp);
*thread_cap = resp.thread_cap;
return ret;
}
uint64_t ZThreadStart(uint64_t thread_cap, uint64_t entry, uint64_t arg1,
uint64_t arg2) {
ZThreadStartReq req{
.thread_cap = thread_cap,
.entry = entry,
.arg1 = arg1,
.arg2 = arg2,
};
return SysCall1(Z_THREAD_START, &req);
}
void ZThreadExit() { SysCall0(Z_THREAD_EXIT); }