diff --git a/sys/test.cpp b/sys/test.cpp index 822e269..fe1d9b7 100644 --- a/sys/test.cpp +++ b/sys/test.cpp @@ -6,7 +6,7 @@ constexpr uint64_t prog2 = 0x00000020'00000000; int main() { ZDebug("Testing"); - uint64_t err = ZProcessSpawnElf(0x100, prog2, 0x1000); + uint64_t err = ZProcessSpawnElf(Z_INIT_PROC_SELF, prog2, 0x2000); if (err != Z_OK) { ZDebug("Error"); } else { diff --git a/sys/test2.cpp b/sys/test2.cpp index 02b5700..e7fb162 100644 --- a/sys/test2.cpp +++ b/sys/test2.cpp @@ -1,8 +1,40 @@ #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() { 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(thread_entry), + reinterpret_cast(a), + reinterpret_cast(b))); + CHECK(ZThreadStart(t2, reinterpret_cast(thread_entry), + reinterpret_cast(c), + reinterpret_cast(d))); return 0; } diff --git a/zion/CMakeLists.txt b/zion/CMakeLists.txt index cfc5cfa..2c2e538 100644 --- a/zion/CMakeLists.txt +++ b/zion/CMakeLists.txt @@ -1,5 +1,6 @@ add_executable(zion boot/boot_info.cpp + capability/capability.cpp common/gdt.cpp common/load_gdt.s debug/debug.cpp diff --git a/zion/capability/capability.cpp b/zion/capability/capability.cpp new file mode 100644 index 0000000..7b01243 --- /dev/null +++ b/zion/capability/capability.cpp @@ -0,0 +1,17 @@ +#include "capability/capability.h" + +template <> +Process& Capability::obj() { + if (type_ != PROCESS) { + panic("Accessing %u cap as object.", type_); + } + return *static_cast(obj_); +} + +template <> +Thread& Capability::obj() { + if (type_ != THREAD) { + panic("Accessing %u cap as object.", type_); + } + return *static_cast(obj_); +} diff --git a/zion/capability/capability.h b/zion/capability/capability.h index 027d937..871de8c 100644 --- a/zion/capability/capability.h +++ b/zion/capability/capability.h @@ -5,12 +5,14 @@ #include "debug/debug.h" class Process; +class Thread; class Capability { public: enum Type { UNDEFINED, PROCESS, + THREAD, }; Capability(void* obj, Type type, uint64_t id, uint64_t permissions) : obj_(obj), type_(type), id_(id), permissions_(permissions) {} @@ -34,11 +36,3 @@ class Capability { uint64_t id_; uint64_t permissions_; }; - -template -Process& Capability::obj() { - if (type_ != PROCESS) { - panic("Accessing %u cap as object.", type_); - } - return *static_cast(obj_); -} diff --git a/zion/include/zcall.h b/zion/include/zcall.h index bdd8f9a..0b8523e 100644 --- a/zion/include/zcall.h +++ b/zion/include/zcall.h @@ -7,6 +7,7 @@ #define ZC_WRITE 0x01 #define ZC_READ 0x02 +// Process Calls. #define Z_PROCESS_EXIT 0x01 #define Z_PROCESS_SPAWN 0x02 #define Z_PROCESS_START 0x03 @@ -16,10 +17,14 @@ #define ZC_PROC_SPAWN_PROC 0x100 #define ZC_PROC_SPAWN_THREAD 0x101 +#define Z_INIT_PROC_SELF 0x1 + +// Thread Calls. #define Z_THREAD_CREATE 0x10 #define Z_THREAD_START 0x11 #define Z_THREAD_EXIT 0x12 +// Debugging Calls. #define Z_DEBUG_PRINT 0x10000000 uint64_t ZDebug(const char* message); @@ -31,5 +36,29 @@ struct ZProcessSpawnElfReq { 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 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(); diff --git a/zion/memory/paging_util.cpp b/zion/memory/paging_util.cpp index b323168..d3fff13 100644 --- a/zion/memory/paging_util.cpp +++ b/zion/memory/paging_util.cpp @@ -136,6 +136,22 @@ uint64_t CurrCr3() { 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(base); + uint8_t* dest = + reinterpret_cast(phys + boot::GetHigherHalfDirectMap()); + for (uint64_t i = 0; i < size; i++) { + dest[i] = src[i]; + } +} } // namespace 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, Process& dest_proc, uint64_t dest_virt) { - if (size > 0x1000) { - panic("Unimplemented NR copy > 1 page"); - } - 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(base); - uint8_t* dest = - reinterpret_cast(phys + boot::GetHigherHalfDirectMap()); + while (size > 0) { + uint64_t to_copy = size > 0x1000 ? 0x1000 : size; - for (uint64_t i = 0; i < size; i++) { - dest[i] = src[i]; + CopyPageIntoNonResidentProcess(base, to_copy, dest_proc, dest_virt); + + size -= to_copy; + base += 0x1000; + dest_virt += 0x1000; } } diff --git a/zion/scheduler/jump_user_space.s b/zion/scheduler/jump_user_space.s index da077f9..0e50df2 100644 --- a/zion/scheduler/jump_user_space.s +++ b/zion/scheduler/jump_user_space.s @@ -13,4 +13,6 @@ jump_user_space: pushf # Can we just push 0 for flags? pushq $0x1B # cs pushq %rdi + mov %rdx, %rdi + mov %rcx, %rsi iretq diff --git a/zion/scheduler/process.cpp b/zion/scheduler/process.cpp index 890fd82..da90be3 100644 --- a/zion/scheduler/process.cpp +++ b/zion/scheduler/process.cpp @@ -23,11 +23,18 @@ SharedPtr Process::RootProcess() { Process::Process() : id_(gNextId++), state_(RUNNING) {} +SharedPtr Process::CreateThread() { + SharedPtr thread{new Thread(*this, next_thread_id_++, 0)}; + threads_.PushBack(thread); + return thread; +} + void Process::CreateThread(uint64_t entry) { Thread* thread = new Thread(*this, next_thread_id_++, entry); threads_.PushBack(thread); - caps_.PushBack(new Capability(this, Capability::PROCESS, next_cap_id_++, - ZC_PROC_SPAWN_PROC)); + caps_.PushBack(new Capability(this, Capability::PROCESS, Z_INIT_PROC_SELF, + ZC_PROC_SPAWN_PROC | ZC_PROC_SPAWN_THREAD)); + thread->SetState(Thread::RUNNABLE); gScheduler->Enqueue(thread); } @@ -65,3 +72,10 @@ SharedPtr Process::GetCapability(uint64_t cid) { dbgln("Bad cap access"); return {}; } + +uint64_t Process::AddCapability(SharedPtr& thread) { + uint64_t cap_id = next_cap_id_++; + caps_.PushBack( + new Capability(thread.ptr(), Capability::THREAD, cap_id, ZC_WRITE)); + return cap_id; +} diff --git a/zion/scheduler/process.h b/zion/scheduler/process.h index f5e73ae..f9ab96e 100644 --- a/zion/scheduler/process.h +++ b/zion/scheduler/process.h @@ -24,10 +24,12 @@ class Process { uint64_t id() const { return id_; } VirtualMemory& vmm() { return vmm_; } + SharedPtr CreateThread(); void CreateThread(uint64_t entry); SharedPtr GetThread(uint64_t tid); SharedPtr GetCapability(uint64_t cid); + uint64_t AddCapability(SharedPtr& t); // Checks the state of all child threads and transitions to // finished if all have finished. void CheckState(); diff --git a/zion/scheduler/thread.cpp b/zion/scheduler/thread.cpp index ab726d4..3f5f7bc 100644 --- a/zion/scheduler/thread.cpp +++ b/zion/scheduler/thread.cpp @@ -9,7 +9,8 @@ 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() { asm("sti"); @@ -40,11 +41,20 @@ Thread::Thread(Process& proc, uint64_t tid, uint64_t entry) 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() { dbgln("Thread start.", pid(), id_); uint64_t rsp = process_.vmm().AllocateUserStack(); SetRsp0(rsp0_start_); - jump_user_space(rip_, rsp); + jump_user_space(rip_, rsp, arg1_, arg2_); } void Thread::Exit() { diff --git a/zion/scheduler/thread.h b/zion/scheduler/thread.h index 7791822..212c512 100644 --- a/zion/scheduler/thread.h +++ b/zion/scheduler/thread.h @@ -11,6 +11,7 @@ class Thread { public: enum State { UNSPECIFIED, + CREATED, RUNNING, RUNNABLE, FINISHED, @@ -27,6 +28,9 @@ class Thread { uint64_t* Rsp0Ptr() { return &rsp0_; } 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. void Init(); @@ -40,10 +44,12 @@ class Thread { Thread(Process& proc) : process_(proc), id_(0) {} Process& process_; uint64_t id_; - State state_ = RUNNABLE; + State state_ = CREATED; // Startup Context for the thread. uint64_t rip_; + uint64_t arg1_; + uint64_t arg2_; // Stack pointer to take on resume. // Stack will contain the full thread context. diff --git a/zion/syscall/syscall.cpp b/zion/syscall/syscall.cpp index 76fa85b..40f58a7 100644 --- a/zion/syscall/syscall.cpp +++ b/zion/syscall/syscall.cpp @@ -78,7 +78,47 @@ uint64_t ProcessSpawnElf(ZProcessSpawnElfReq* req) { 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(); + 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(); + // 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(); switch (call_id) { case Z_PROCESS_EXIT: @@ -87,12 +127,21 @@ extern "C" uint64_t SyscallHandler(uint64_t call_id, char* message) { panic("Returned from thread exit"); break; case Z_DEBUG_PRINT: - dbgln("[Debug] %s", message); + dbgln("[Debug] %s", req); break; case Z_PROCESS_SPAWN: - return ProcessSpawnElf(reinterpret_cast(message)); + return ProcessSpawnElf(reinterpret_cast(req)); + case Z_THREAD_CREATE: + return ThreadCreate(reinterpret_cast(req), + reinterpret_cast(resp)); + case Z_THREAD_START: + return ThreadStart(reinterpret_cast(req)); + case Z_THREAD_EXIT: + thread.Exit(); + panic("Returned from thread exit"); + break; default: - panic("Unhandled syscall number: %u", call_id); + panic("Unhandled syscall number: %x", call_id); } return 1; } diff --git a/zion/usr/zcall.cpp b/zion/usr/zcall.cpp index 8198ae5..81bc443 100644 --- a/zion/usr/zcall.cpp +++ b/zion/usr/zcall.cpp @@ -2,12 +2,27 @@ #include +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 return_code; asm("syscall" : "=a"(return_code) : "D"(number), "S"(first) : "rcx", "r11"); 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) { 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); } + +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); }