kernel: Add the fork() system call

This commit is contained in:
apio 2023-03-18 23:45:48 +01:00
parent e664af4c2b
commit 54f2d35416
Signed by: apio
GPG Key ID: B8A7D06E42258954
13 changed files with 173 additions and 3 deletions

View File

@ -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); }
}

View File

@ -42,6 +42,7 @@ namespace MMU
Result<PageDirectory*> create_page_directory_for_userspace();
Result<void> delete_userspace_page_directory(PageDirectory* directory);
Result<PageDirectory*> clone_userspace_page_directory(PageDirectory* directory);
void setup_initial_page_directory();
PageDirectory* kernel_page_directory();

View File

@ -381,6 +381,75 @@ namespace MMU
return {};
}
// FIXME: Use the ancient magic of CoW (copy-on-write)
Result<PageDirectory*> 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;

View File

@ -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(&regs, 0, sizeof(Registers));

View File

@ -2,6 +2,7 @@
#include "Log.h"
#include "arch/MMU.h"
#include "memory/Heap.h"
#include <luna/CString.h>
#include <luna/ScopeGuard.h>
static constexpr u64 VM_BASE = 0x10000000;
@ -22,6 +23,21 @@ Result<OwnedPtr<UserVM>> UserVM::try_create()
return move(ptr);
}
Result<OwnedPtr<UserVM>> UserVM::clone()
{
void* const base = TRY(kmalloc(m_bitmap.size_in_bytes()));
auto guard = make_scope_guard([&] { kfree(base); });
OwnedPtr<UserVM> ptr = TRY(make_owned<UserVM>(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);

View File

@ -17,6 +17,8 @@ class UserVM
static Result<OwnedPtr<UserVM>> try_create();
Result<OwnedPtr<UserVM>> clone();
private:
Result<bool> try_expand(usize size = 160);
Bitmap m_bitmap;

View File

@ -93,3 +93,34 @@ Result<u64> sys_exec(Registers* regs, SyscallArgs args)
return 0;
}
Result<u64> 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(&current->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;
}

View File

@ -100,6 +100,7 @@ namespace Scheduler
return new_kernel_thread_impl(thread);
}
Result<void> new_userspace_thread(SharedPtr<VFS::Inode> 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()

View File

@ -15,6 +15,8 @@ namespace Scheduler
Result<void> new_userspace_thread(SharedPtr<VFS::Inode> inode);
void add_thread(Thread* thread);
Thread* pick_task();
void reap_thread(Thread* thread);

View File

@ -81,6 +81,8 @@ struct Thread : public LinkedListNode<Thread>
void set_sp(u64 sp);
u64 sp();
void set_return(u64 ret);
static void init();
};

View File

@ -56,6 +56,29 @@ Result<OwnedPtr<ThreadImage>> ThreadImage::try_load_from_elf(SharedPtr<VFS::Inod
return image;
}
Result<OwnedPtr<ThreadImage>> ThreadImage::clone_from_thread(Thread* parent)
{
auto image = TRY(make_owned<ThreadImage>());
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<u64> ThreadImage::push_mem_on_stack(const u8* mem, usize size)
{
if ((m_sp - size) < m_user_stack.bottom()) return err(E2BIG);

View File

@ -5,6 +5,7 @@
#include "arch/MMU.h"
#include "fs/VFS.h"
#include "memory/UserVM.h"
#include "thread/Thread.h"
#include <luna/LinkedList.h>
#include <luna/OwnedPtr.h>
#include <luna/Result.h>
@ -17,6 +18,8 @@ class ThreadImage
public:
static Result<OwnedPtr<ThreadImage>> try_load_from_elf(SharedPtr<VFS::Inode> inode);
static Result<OwnedPtr<ThreadImage>> clone_from_thread(Thread* parent);
Result<u64> push_mem_on_stack(const u8* mem, usize size);
void apply(Thread* thread);

View File

@ -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
{