Userspace fork seems to work

This commit is contained in:
Mark 2020-01-30 14:32:08 +02:00
parent 1e631fc03a
commit 8f7d5071e6
8 changed files with 277 additions and 7 deletions

View File

@ -42,7 +42,9 @@ OBJS+=$(O)/sys/amd64/hw/rs232.o \
$(O)/sys/amd64/hw/rtc.o \
$(O)/sys/amd64/fpu.o \
$(O)/sys/amd64/cpuid.o \
$(O)/sys/amd64/sched_s.o
$(O)/sys/amd64/sched_s.o \
$(O)/sys/amd64/syscall_s.o \
$(O)/sys/amd64/syscall.o
kernel_LINKER=sys/amd64/link.ld
kernel_LDFLAGS=-nostdlib \

View File

@ -0,0 +1,3 @@
#pragma once
void syscall_init(void);

View File

@ -8,6 +8,7 @@ struct thread {
uintptr_t rsp0_top; // 0x10
uintptr_t cr3; // 0x18
uintptr_t syscall_rip; // 0x20
uintptr_t rsp0_base;
size_t rsp0_size;

View File

@ -2,6 +2,7 @@
#include "sys/amd64/asm/asm_irq.h"
#include "sys/amd64/hw/rs232.h"
#include "sys/amd64/smp/smp.h"
#include "sys/amd64/syscall.h"
#include "sys/amd64/mm/phys.h"
#include "sys/amd64/hw/acpi.h"
#include "sys/amd64/hw/apic.h"
@ -53,6 +54,8 @@ void kernel_main(struct amd64_loader_data *data) {
amd64_apic_init();
syscall_init();
sched_init();
sched_enter();

View File

@ -1,6 +1,10 @@
#include "sys/amd64/asm/asm_cpu.h"
.section .text
.global context_enter
.global context_enter_forked
context_enter_forked:
nop
nop
context_enter:
popq %rax
popq %rdi

34
sys/amd64/syscall.c Normal file
View File

@ -0,0 +1,34 @@
#include "sys/amd64/cpu.h"
#include "sys/debug.h"
#define MSR_IA32_STAR 0xC0000081
#define MSR_IA32_LSTAR 0xC0000082
#define MSR_IA32_SFMASK 0xC0000084
extern void syscall_entry(void);
extern void sys_fork(void);
void *syscall_table[256] = {
NULL,
[123] = sys_fork,
};
void syscall_undefined(uint64_t rax) {
kdebug("Undefined syscall: %d\n", rax);
}
void syscall_init(void) {
// LSTAR = syscall_entry
wrmsr(MSR_IA32_LSTAR, (uintptr_t) syscall_entry);
// SFMASK = (1 << 9) /* IF */
wrmsr(MSR_IA32_SFMASK, (1 << 9));
// STAR = ((ss3 - 8) << 48) | (cs0 << 32)
wrmsr(MSR_IA32_STAR, ((uint64_t) (0x1B - 8) << 48) | ((uint64_t) 0x08 << 32));
// Set SCE bit
uint64_t efer = rdmsr(MSR_IA32_EFER);
efer |= (1 << 0);
wrmsr(MSR_IA32_EFER, efer);
}

96
sys/amd64/syscall_s.S Normal file
View File

@ -0,0 +1,96 @@
.section .bss
tmp0:
.quad 0
.section .text
.global syscall_entry
syscall_entry:
// %rcx - user rip
// %rsp - user stack
// %r11 - user rflags
// Switch to kernel stack
movq %rsp, tmp0(%rip)
movq thread_current(%rip), %rsp
movq 0(%rsp), %rsp
cmpq $123, %rax
jz _syscall_fork
pushq %rcx
pushq %r11
movq tmp0(%rip), %rcx
pushq %rcx
cmpq $256, %rax
jg 1f
leaq syscall_table(%rip), %rcx
movq (%rcx, %rax, 8), %rcx
test %rcx, %rcx
jz 1f
movq %rcx, %rax
movq %r10, %rcx
call *%rax
jmp 2f
1:
movq %rax, %rdi
call syscall_undefined
2:
popq %rdi
popq %r11
popq %rcx
movq %rdi, %rsp
sysretq
_syscall_fork:
pushq %rcx
pushq %r11
movq tmp0(%rip), %rcx
pushq %rcx
pushq %r15
pushq %r14
pushq %r13
pushq %r12
pushq %rbp
pushq %rbx
pushq %r11
pushq %r10
pushq %r9
pushq %r8
pushq %rcx
pushq %rdx
pushq %rsi
pushq %rdi
movq %rsp, %rdi
call sys_fork
popq %rdi
popq %rsi
popq %rdx
popq %rcx
popq %r8
popq %r9
popq %r10
popq %r11
// Return to caller
popq %rbx
popq %rbp
popq %r12
popq %r13
popq %r14
popq %r15
popq %rdi
popq %r11
popq %rcx
movq %rdi, %rsp
sysretq

View File

@ -3,6 +3,7 @@
#include "sys/amd64/hw/irq.h"
#include "sys/amd64/hw/idt.h"
#include "sys/amd64/cpu.h"
#include "sys/vmalloc.h"
#include "sys/assert.h"
#include "sys/thread.h"
#include "sys/debug.h"
@ -20,8 +21,8 @@ void yield(void);
//// Thread queueing
static struct thread *queue_head = NULL;
static struct thread *thread_current = NULL;
static struct thread thread_idle = {0};
struct thread *thread_current = NULL;
void sched_queue(struct thread *thr) {
if (queue_head) {
@ -150,20 +151,19 @@ static void init_thread(struct thread *thr, void *(*entry)(void *), void *arg) {
static void init_user(struct thread *thr, void *(*entry)(void *), void *arg) {
uintptr_t stack_pages = amd64_phys_alloc_page();
_assert(stack_pages != MM_NADDR);
uintptr_t stack3_pages = amd64_phys_alloc_page();
_assert(stack3_pages != MM_NADDR);
thr->data.rsp0_base = MM_VIRTUALIZE(stack_pages);
thr->data.rsp0_size = MM_PAGE_SIZE;
thr->data.rsp0_top = thr->data.rsp0_base + thr->data.rsp0_size;
thr->data.rsp3_base = MM_VIRTUALIZE(stack3_pages);
thr->data.rsp3_size = MM_PAGE_SIZE;
mm_space_t space = amd64_mm_pool_alloc();
mm_space_clone(space, mm_kernel, MM_CLONE_FLG_KERNEL);
thr->data.cr3 = MM_PHYS(space);
uintptr_t ustack_base = vmalloc(space, 0x1000000, 0xF0000000, 4, MM_PAGE_WRITE | MM_PAGE_USER);
thr->data.rsp3_base = ustack_base;
thr->data.rsp3_size = MM_PAGE_SIZE * 4;
// Allow this thread to access upper pages for testing
space[AMD64_MM_STRIPSX(KERNEL_VIRT_BASE) >> 39] |= MM_PAGE_USER;
uint64_t *pdpt = (uint64_t *) MM_VIRTUALIZE(space[AMD64_MM_STRIPSX(KERNEL_VIRT_BASE) >> 39] & ~0xFFF);
@ -266,9 +266,136 @@ static void *t0(void *arg) {
return 0;
}
struct sys_fork_frame {
uint64_t rdi, rsi, rdx, rcx;
uint64_t r8, r9, r10, r11;
uint64_t rbx;
uint64_t rbp;
uint64_t r12;
uint64_t r13;
uint64_t r14;
uint64_t r15;
uint64_t rsp;
uint64_t rflags;
uint64_t rip;
};
int sys_fork(struct sys_fork_frame *frame) {
static int nfork = 0;
static struct thread forkt[3] = {0};
struct thread *dst = &forkt[nfork++];
struct thread *src = thread_current;
uintptr_t stack_pages = amd64_phys_alloc_page();
_assert(stack_pages != MM_NADDR);
dst->data.rsp0_base = MM_VIRTUALIZE(stack_pages);
dst->data.rsp0_size = MM_PAGE_SIZE;
dst->data.rsp0_top = dst->data.rsp0_base + dst->data.rsp0_size;
mm_space_t space = amd64_mm_pool_alloc();
mm_space_fork(space, (mm_space_t) MM_VIRTUALIZE(src->data.cr3), MM_CLONE_FLG_KERNEL | MM_CLONE_FLG_USER);
space[AMD64_MM_STRIPSX(KERNEL_VIRT_BASE) >> 39] |= MM_PAGE_USER;
uint64_t *pdpt = (uint64_t *) MM_VIRTUALIZE(space[AMD64_MM_STRIPSX(KERNEL_VIRT_BASE) >> 39] & ~0xFFF);
for (uint64_t i = 0; i < 4; ++i) {
pdpt[((AMD64_MM_STRIPSX(KERNEL_VIRT_BASE) >> 30) + i) & 0x1FF] |= MM_PAGE_USER;
}
dst->data.cr3 = MM_PHYS(space);
uint64_t *stack = (uint64_t *) (dst->data.rsp0_base + dst->data.rsp0_size);
// Initial thread context
// Entry context
// ss
*--stack = 0x1B;
// rsp
*--stack = frame->rsp;
// rflags
_assert(frame->rflags & 0x200);
*--stack = frame->rflags;
// cs
*--stack = 0x23;
// rip
*--stack = frame->rip;
// Caller-saved
// r11
*--stack = frame->r11;
// r10
*--stack = frame->r10;
// r9
*--stack = frame->r9;
// r8
*--stack = frame->r8;
// rcx
*--stack = frame->rcx;
// rdx
*--stack = frame->rdx;
// rsi
*--stack = frame->rsi;
// rdi
*--stack = frame->rdi;
// rax
*--stack = 0;
// Small stub so that context switch enters the thread properly
*--stack = (uintptr_t) context_enter;
// Callee-saved
// r15
*--stack = frame->r15;
// r14
*--stack = frame->r14;
// r13
*--stack = frame->r13;
// r12
*--stack = frame->r12;
// rbp
*--stack = frame->rbp;
// rbx
*--stack = frame->rbx;
// Thread lifecycle:
// * context_switch_to():
// - pops callee-saved registers (initializing them to 0)
// - enters context_enter()
// * context_enter():
// - pops caller-saved registers (initializing them to 0 and setting up rdi)
// - enters proper execution context via iret
// ... Thread is running here until it yields
// * yield leads to context_switch_to():
// - call to yield() automatically (per ABI) stores caller-saved registers
// - context_switch_to() pushes callee-saved registers onto current stack
// - selects a new thread
// - step one
dst->data.rsp0 = (uintptr_t) stack;
dst->pid = nfork + 20;
sched_queue(dst);
return dst->pid;
}
static void *u0(void *arg) {
int r;
asm volatile ("syscall":"=a"(r):"a"(123));
if (r == 0) {
arg = (void *) ((uint64_t) arg + 5);
uint16_t *ptr = (uint16_t *) MM_VIRTUALIZE(0xB8000 + (uint64_t) arg * 2);
*ptr = 0;
while (1) {
for (size_t i = 0; i < 10000000; ++i);
*ptr ^= 'A' | 0x1200;
}
}
uint16_t *ptr = (uint16_t *) MM_VIRTUALIZE(0xB8000 + (uint64_t) arg * 2);
*ptr = 0;
while (1) {
for (size_t i = 0; i < 10000000; ++i);