diff --git a/apps/init.c b/apps/init.c index e549951a..a23d7f54 100644 --- a/apps/init.c +++ b/apps/init.c @@ -37,6 +37,12 @@ int main() fprintf(stderr, "init is running as PID %d\n", getpid()); - char* argv[] = { "/bin/hello", "--help", NULL }; - execv("/bin/hello", argv); + long ret = syscall(SYS_fork); + + if (ret == 0) + { + char* argv[] = { "/bin/hello", "--help", NULL }; + execv("/bin/hello", argv); + } + else { printf("my child is PID %ld!\n", ret); } } diff --git a/kernel/src/arch/MMU.h b/kernel/src/arch/MMU.h index 8a46e6fa..2211ee5a 100644 --- a/kernel/src/arch/MMU.h +++ b/kernel/src/arch/MMU.h @@ -42,6 +42,7 @@ namespace MMU Result create_page_directory_for_userspace(); Result delete_userspace_page_directory(PageDirectory* directory); + Result clone_userspace_page_directory(PageDirectory* directory); void setup_initial_page_directory(); PageDirectory* kernel_page_directory(); diff --git a/kernel/src/arch/x86_64/MMU.cpp b/kernel/src/arch/x86_64/MMU.cpp index 5d8376eb..a5e776c8 100644 --- a/kernel/src/arch/x86_64/MMU.cpp +++ b/kernel/src/arch/x86_64/MMU.cpp @@ -381,6 +381,75 @@ namespace MMU return {}; } + // FIXME: Use the ancient magic of CoW (copy-on-write) + Result clone_userspace_page_directory(PageDirectory* directory) + { + PageDirectory* result = TRY(create_page_directory_for_userspace()); + + PageDirectory* const old_table = translate_physical(directory); + PageDirectory* const new_table = translate_physical(result); + + // FIXME: Do not leak the WIP new page directory on OOM. + + memcpy(new_table, old_table, sizeof(*old_table)); + + // Let's iterate over every top-level entry in the lower half + for (u64 i = 0; i < 256; i++) + { + PageTableEntry& old_l4 = old_table->entries[i]; + if (!old_l4.present) continue; + PageTableEntry& new_l4 = new_table->entries[i]; + new_l4.set_address(TRY(MemoryManager::alloc_frame())); + + PageDirectory* const old_pdp = &page_table(old_l4); + PageDirectory* const new_pdp = &page_table(new_l4); + + memcpy(new_pdp, old_pdp, sizeof(*old_pdp)); + + for (u64 j = 0; j < 512; j++) + { + PageTableEntry& old_l3 = old_pdp->entries[j]; + if (!old_l3.present) continue; + PageTableEntry& new_l3 = new_pdp->entries[j]; + new_l3.set_address(TRY(MemoryManager::alloc_frame())); + + PageDirectory* const old_pd = &page_table(old_l3); + PageDirectory* const new_pd = &page_table(new_l3); + + memcpy(new_pd, old_pd, sizeof(*old_pd)); + + if (old_l3.larger_pages) continue; + + for (u64 k = 0; k < 512; k++) + { + PageTableEntry& old_l2 = old_pd->entries[k]; + if (!old_l2.present) continue; + PageTableEntry& new_l2 = new_pd->entries[k]; + new_l2.set_address(TRY(MemoryManager::alloc_frame())); + + PageDirectory* const old_pt = &page_table(old_l2); + PageDirectory* const new_pt = &page_table(new_l2); + + memcpy(new_pt, old_pt, sizeof(*old_pt)); + + if (old_l2.larger_pages) continue; + + for (u64 l = 0; l < 512; l++) + { + PageTableEntry& old_l1 = old_pt->entries[l]; + if (!old_l1.present) continue; + PageTableEntry& new_l1 = new_pt->entries[l]; + new_l1.set_address(TRY(MemoryManager::alloc_frame())); + + memcpy(&page_table(new_l1), &page_table(old_l1), ARCH_PAGE_SIZE); + } + } + } + } + + return result; + } + PageDirectory* kernel_page_directory() { return g_kernel_directory; diff --git a/kernel/src/arch/x86_64/Thread.cpp b/kernel/src/arch/x86_64/Thread.cpp index f02fbaaf..671e9eca 100644 --- a/kernel/src/arch/x86_64/Thread.cpp +++ b/kernel/src/arch/x86_64/Thread.cpp @@ -26,6 +26,11 @@ u64 Thread::sp() return regs.rsp; } +void Thread::set_return(u64 ret) +{ + regs.rax = ret; +} + void Thread::init_regs_kernel() { memset(®s, 0, sizeof(Registers)); diff --git a/kernel/src/memory/UserVM.cpp b/kernel/src/memory/UserVM.cpp index e4d5172b..d820ba6d 100644 --- a/kernel/src/memory/UserVM.cpp +++ b/kernel/src/memory/UserVM.cpp @@ -2,6 +2,7 @@ #include "Log.h" #include "arch/MMU.h" #include "memory/Heap.h" +#include #include static constexpr u64 VM_BASE = 0x10000000; @@ -22,6 +23,21 @@ Result> UserVM::try_create() return move(ptr); } +Result> UserVM::clone() +{ + void* const base = TRY(kmalloc(m_bitmap.size_in_bytes())); + + auto guard = make_scope_guard([&] { kfree(base); }); + + OwnedPtr ptr = TRY(make_owned(base, m_bitmap.size_in_bytes())); + + memcpy(ptr->m_bitmap.location(), m_bitmap.location(), m_bitmap.size_in_bytes()); + + guard.deactivate(); + + return move(ptr); +} + UserVM::UserVM(void* base, usize size) { kdbgln("user vm created with base=%p, size=%zu", base, size); diff --git a/kernel/src/memory/UserVM.h b/kernel/src/memory/UserVM.h index 0b57537d..3e9342be 100644 --- a/kernel/src/memory/UserVM.h +++ b/kernel/src/memory/UserVM.h @@ -17,6 +17,8 @@ class UserVM static Result> try_create(); + Result> clone(); + private: Result try_expand(usize size = 160); Bitmap m_bitmap; diff --git a/kernel/src/sys/exec.cpp b/kernel/src/sys/exec.cpp index 64fdea04..a4342c82 100644 --- a/kernel/src/sys/exec.cpp +++ b/kernel/src/sys/exec.cpp @@ -93,3 +93,34 @@ Result sys_exec(Registers* regs, SyscallArgs args) return 0; } + +Result sys_fork(Registers* regs, SyscallArgs) +{ + auto current = Scheduler::current(); + + auto guard = make_scope_guard([current] { MMU::switch_page_directory(current->directory); }); + + kinfoln("fork: trying to duplicate process %lu", current->id); + + memcpy(¤t->regs, regs, sizeof(*regs)); + + auto image = TRY(ThreadImage::clone_from_thread(current)); + + auto thread = TRY(new_thread()); + + thread->state = ThreadState::Runnable; + thread->is_kernel = false; + thread->fp_data.save(); + + for (int i = 0; i < FD_MAX; i++) { thread->fd_table[i] = current->fd_table[i]; } + + image->apply(thread); + + memcpy(&thread->regs, regs, sizeof(*regs)); + + thread->set_return(0); + + Scheduler::add_thread(thread); + + return thread->id; +} diff --git a/kernel/src/thread/Scheduler.cpp b/kernel/src/thread/Scheduler.cpp index dbe3237b..4cf21f52 100644 --- a/kernel/src/thread/Scheduler.cpp +++ b/kernel/src/thread/Scheduler.cpp @@ -100,6 +100,7 @@ namespace Scheduler return new_kernel_thread_impl(thread); } + Result new_userspace_thread(SharedPtr inode) { Thread* const thread = TRY(new_thread()); @@ -122,8 +123,15 @@ namespace Scheduler return {}; } + void add_thread(Thread* thread) + { + g_threads.append(thread); + } + void reap_thread(Thread* thread) { + CPU::disable_interrupts(); + kinfoln("reap: reaping thread with id %zu", thread->id); if (thread->is_kernel) @@ -144,6 +152,8 @@ namespace Scheduler if (!thread->is_kernel) MMU::delete_userspace_page_directory(thread->directory); delete thread; + + CPU::enable_interrupts(); } Thread* pick_task() diff --git a/kernel/src/thread/Scheduler.h b/kernel/src/thread/Scheduler.h index fecee620..31b1853c 100644 --- a/kernel/src/thread/Scheduler.h +++ b/kernel/src/thread/Scheduler.h @@ -15,6 +15,8 @@ namespace Scheduler Result new_userspace_thread(SharedPtr inode); + void add_thread(Thread* thread); + Thread* pick_task(); void reap_thread(Thread* thread); diff --git a/kernel/src/thread/Thread.h b/kernel/src/thread/Thread.h index 5f745012..a731f243 100644 --- a/kernel/src/thread/Thread.h +++ b/kernel/src/thread/Thread.h @@ -81,6 +81,8 @@ struct Thread : public LinkedListNode void set_sp(u64 sp); u64 sp(); + void set_return(u64 ret); + static void init(); }; diff --git a/kernel/src/thread/ThreadImage.cpp b/kernel/src/thread/ThreadImage.cpp index c8e45030..77b5a6d5 100644 --- a/kernel/src/thread/ThreadImage.cpp +++ b/kernel/src/thread/ThreadImage.cpp @@ -56,6 +56,29 @@ Result> ThreadImage::try_load_from_elf(SharedPtr> ThreadImage::clone_from_thread(Thread* parent) +{ + auto image = TRY(make_owned()); + + auto vm_allocator = TRY(parent->vm_allocator->clone()); + + auto new_directory = TRY(MMU::clone_userspace_page_directory(parent->directory)); + + const ELFData data = { .entry = parent->ip() }; + + const u64 kernel_stack_base = TRY(MemoryManager::alloc_for_kernel(4, MMU::ReadWrite | MMU::NoExecute)); + Stack kernel_stack { kernel_stack_base, 4 * ARCH_PAGE_SIZE }; + + image->m_directory = new_directory; + image->m_kernel_stack = kernel_stack; + image->m_user_stack = parent->stack; + image->m_loaded_image_data = data; + image->m_vm_allocator = move(vm_allocator); + image->m_sp = parent->sp(); + + return image; +} + Result ThreadImage::push_mem_on_stack(const u8* mem, usize size) { if ((m_sp - size) < m_user_stack.bottom()) return err(E2BIG); diff --git a/kernel/src/thread/ThreadImage.h b/kernel/src/thread/ThreadImage.h index 4dd8d8ae..1036f4e4 100644 --- a/kernel/src/thread/ThreadImage.h +++ b/kernel/src/thread/ThreadImage.h @@ -5,6 +5,7 @@ #include "arch/MMU.h" #include "fs/VFS.h" #include "memory/UserVM.h" +#include "thread/Thread.h" #include #include #include @@ -17,6 +18,8 @@ class ThreadImage public: static Result> try_load_from_elf(SharedPtr inode); + static Result> clone_from_thread(Thread* parent); + Result push_mem_on_stack(const u8* mem, usize size); void apply(Thread* thread); diff --git a/libluna/include/luna/Syscall.h b/libluna/include/luna/Syscall.h index 5dedba4c..a32f23ee 100644 --- a/libluna/include/luna/Syscall.h +++ b/libluna/include/luna/Syscall.h @@ -2,7 +2,7 @@ #define enumerate_syscalls(_e) \ _e(exit) _e(clock_gettime) _e(mmap) _e(munmap) _e(usleep) _e(open) _e(close) _e(read) _e(getpid) _e(write) \ - _e(lseek) _e(mkdir) _e(exec) _e(mknod) + _e(lseek) _e(mkdir) _e(exec) _e(mknod) _e(fork) enum Syscalls {