948 lines
24 KiB
C
948 lines
24 KiB
C
#include "arch/amd64/hw/timer.h"
|
|
#include "arch/amd64/mm/pool.h"
|
|
#include "arch/amd64/context.h"
|
|
#include "arch/amd64/mm/map.h"
|
|
#include "sys/mem/vmalloc.h"
|
|
#include "arch/amd64/cpu.h"
|
|
#include "sys/binfmt_elf.h"
|
|
#include "sys/sys_proc.h"
|
|
#include "sys/mem/phys.h"
|
|
#include "user/signum.h"
|
|
#include "net/socket.h"
|
|
#include "user/errno.h"
|
|
#include "user/fcntl.h"
|
|
#include "sys/assert.h"
|
|
#include "sys/string.h"
|
|
#include "sys/thread.h"
|
|
#include "sys/sched.h"
|
|
#include "sys/debug.h"
|
|
#include "fs/ofile.h"
|
|
#include "sys/heap.h"
|
|
#include "fs/vfs.h"
|
|
#include "sys/mm.h"
|
|
|
|
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;
|
|
};
|
|
|
|
////
|
|
|
|
LIST_HEAD(threads_all_head);
|
|
static pid_t last_kernel_pid = 0;
|
|
static pid_t last_user_pid = 0;
|
|
// TODO: MAKE THIS PER-PROCESSOR
|
|
static uint64_t fxsave_buf[FXSAVE_REGION / 8] __attribute__((aligned(16)));
|
|
|
|
void context_save_fpu(struct thread *new, struct thread *old) {
|
|
_assert(old);
|
|
if (old->data.fxsave) {
|
|
asm volatile ("fxsave (%0)"::"r"(fxsave_buf):"memory");
|
|
memcpy(old->data.fxsave, fxsave_buf, FXSAVE_REGION);
|
|
old->flags |= THREAD_FPU_SAVED;
|
|
}
|
|
|
|
}
|
|
|
|
void context_restore_fpu(struct thread *new, struct thread *old) {
|
|
_assert(new);
|
|
if (new->flags & THREAD_FPU_SAVED) {
|
|
memcpy(fxsave_buf, new->data.fxsave, FXSAVE_REGION);
|
|
asm volatile ("fxrstor (%0)"::"r"(fxsave_buf):"memory");
|
|
new->flags &= ~THREAD_FPU_SAVED;
|
|
}
|
|
}
|
|
|
|
pid_t thread_alloc_pid(int is_user) {
|
|
if (is_user) {
|
|
return ++last_user_pid;
|
|
} else {
|
|
return -(++last_kernel_pid);
|
|
}
|
|
}
|
|
|
|
////
|
|
|
|
static void thread_ioctx_empty(struct thread *thr) {
|
|
memset(&thr->ioctx, 0, sizeof(struct vfs_ioctx));
|
|
memset(thr->fds, 0, sizeof(thr->fds));
|
|
}
|
|
|
|
void thread_ioctx_fork(struct thread *dst, struct thread *src) {
|
|
thread_ioctx_empty(dst);
|
|
|
|
// TODO: increase refcount (when cwd has one)
|
|
dst->ioctx.cwd_vnode = src->ioctx.cwd_vnode;
|
|
dst->ioctx.gid = src->ioctx.gid;
|
|
dst->ioctx.uid = src->ioctx.uid;
|
|
|
|
for (int i = 0; i < THREAD_MAX_FDS; ++i) {
|
|
if (src->fds[i]) {
|
|
dst->fds[i] = src->fds[i];
|
|
if (!(dst->fds[i]->flags & OF_SOCKET)) {
|
|
++dst->fds[i]->file.refcount;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
////
|
|
|
|
int thread_signal_pgid(pid_t pgid, int signum) {
|
|
int ret = 0;
|
|
|
|
struct thread *thr;
|
|
list_for_each_entry(thr, &threads_all_head, g_link) {
|
|
if (thr->state != THREAD_STOPPED && thr->pgid == pgid) {
|
|
thread_signal(thr, signum);
|
|
++ret;
|
|
}
|
|
}
|
|
|
|
return ret == 0 ? -1 : ret;
|
|
}
|
|
|
|
struct thread *thread_find(pid_t pid) {
|
|
struct thread *thr;
|
|
list_for_each_entry(thr, &threads_all_head, g_link) {
|
|
if (thr->pid == pid) {
|
|
return thr;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct thread *thread_child(struct thread *of, pid_t pid) {
|
|
for (struct thread *thr = of->first_child; thr; thr = thr->next_child) {
|
|
if (thr->pid == pid) {
|
|
return thr;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void thread_unchild(struct thread *thr) {
|
|
struct thread *par = thr->parent;
|
|
_assert(par);
|
|
|
|
struct thread *p = NULL;
|
|
struct thread *c = par->first_child;
|
|
int found = 0;
|
|
|
|
while (c) {
|
|
if (c == thr) {
|
|
found = 1;
|
|
if (p) {
|
|
p->next_child = thr->next_child;
|
|
} else {
|
|
par->first_child = thr->next_child;
|
|
}
|
|
break;
|
|
}
|
|
|
|
p = c;
|
|
c = c->next_child;
|
|
}
|
|
|
|
_assert(found);
|
|
}
|
|
|
|
void thread_cleanup(struct thread *thr) {
|
|
_assert(thr);
|
|
// Leave only the system context required for hierachy tracking and error code/pid
|
|
thr->state = THREAD_STOPPED;
|
|
thr->flags |= THREAD_EMPTY;
|
|
kdebug("Cleaning up %d\n", thr->pid);
|
|
for (size_t i = 0; i < THREAD_MAX_FDS; ++i) {
|
|
if (thr->fds[i]) {
|
|
|
|
if (thr->fds[i]->flags & OF_SOCKET) {
|
|
net_close(&thr->ioctx, thr->fds[i]);
|
|
} else {
|
|
vfs_close(&thr->ioctx, thr->fds[i]);
|
|
_assert(thr->fds[i]->file.refcount >= 0);
|
|
if (thr->fds[i]->file.refcount == 0) {
|
|
kfree(thr->fds[i]);
|
|
}
|
|
}
|
|
thr->fds[i] = NULL;
|
|
}
|
|
}
|
|
|
|
// Release userspace pages
|
|
mm_space_release(thr);
|
|
}
|
|
|
|
void thread_free(struct thread *thr) {
|
|
// Sure that no code of this thread will be running anymore -
|
|
// can clean up its stuff
|
|
thread_cleanup(thr);
|
|
|
|
_assert(thr->flags & THREAD_STOPPED);
|
|
_assert(thr->flags & THREAD_EMPTY);
|
|
|
|
// Free kstack
|
|
for (size_t i = 0; i < thr->data.rsp0_size / MM_PAGE_SIZE; ++i) {
|
|
mm_phys_free_page(MM_PHYS(i * MM_PAGE_SIZE + thr->data.rsp0_base));
|
|
}
|
|
|
|
// Free page directory (if not mm_kernel)
|
|
if (thr->space != mm_kernel) {
|
|
// Make sure we don't shoot a leg off
|
|
uintptr_t cr3;
|
|
asm volatile ("movq %%cr3, %0":"=a"(cr3));
|
|
_assert(MM_VIRTUALIZE(cr3) != (uintptr_t) thr->space);
|
|
|
|
mm_space_free(thr);
|
|
}
|
|
|
|
// Free thread itself
|
|
memset(thr, 0, sizeof(struct thread));
|
|
kfree(thr);
|
|
}
|
|
|
|
int thread_init(struct thread *thr, uintptr_t entry, void *arg, int user) {
|
|
uintptr_t stack_pages = mm_phys_alloc_contiguous(2); //amd64_phys_alloc_contiguous(2);
|
|
_assert(stack_pages != MM_NADDR);
|
|
|
|
thr->data.rsp0_base = MM_VIRTUALIZE(stack_pages);
|
|
thr->data.rsp0_size = MM_PAGE_SIZE * 2;
|
|
thr->data.rsp0_top = thr->data.rsp0_base + thr->data.rsp0_size;
|
|
thr->name[0] = 0;
|
|
thr->flags = user ? 0 : THREAD_KERNEL;
|
|
|
|
if (!(thr->flags & THREAD_KERNEL)) {
|
|
thr->data.fxsave = kmalloc(FXSAVE_REGION);
|
|
_assert(thr->data.fxsave);
|
|
} else {
|
|
thr->data.fxsave = NULL;
|
|
}
|
|
|
|
list_head_init(&thr->g_link);
|
|
list_head_init(&thr->shm_list);
|
|
list_head_init(&thr->wait_head);
|
|
thread_wait_io_init(&thr->sleep_notify);
|
|
thread_wait_io_init(&thr->pid_notify);
|
|
|
|
uint64_t *stack = (uint64_t *) (thr->data.rsp0_base + thr->data.rsp0_size);
|
|
|
|
if (user) {
|
|
mm_space_t space = amd64_mm_pool_alloc();
|
|
mm_space_clone(space, mm_kernel, MM_CLONE_FLG_KERNEL);
|
|
thr->data.cr3 = MM_PHYS(space);
|
|
thr->space = space;
|
|
|
|
uintptr_t ustack_base = vmalloc(space, 0x1000000, 0xF0000000, 4, MM_PAGE_WRITE | MM_PAGE_USER, PU_PRIVATE);
|
|
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);
|
|
for (uint64_t i = 0; i < 4; ++i) {
|
|
pdpt[((AMD64_MM_STRIPSX(KERNEL_VIRT_BASE) >> 30) + i) & 0x1FF] |= MM_PAGE_USER;
|
|
}
|
|
} else {
|
|
thr->data.cr3 = MM_PHYS(mm_kernel);
|
|
thr->space = mm_kernel;
|
|
}
|
|
|
|
thr->state = THREAD_READY;
|
|
thr->parent = NULL;
|
|
thr->first_child = NULL;
|
|
thr->next_child = NULL;
|
|
|
|
thread_ioctx_empty(thr);
|
|
|
|
// Initial thread context
|
|
// Entry context
|
|
if (user) {
|
|
// ss
|
|
*--stack = 0x1B;
|
|
// rsp
|
|
*--stack = thr->data.rsp3_base + thr->data.rsp3_size;
|
|
// rflags
|
|
*--stack = 0x200;
|
|
// cs
|
|
*--stack = 0x23;
|
|
// rip
|
|
*--stack = (uintptr_t) entry;
|
|
} else {
|
|
// ss
|
|
*--stack = 0x10;
|
|
// rsp. Once this context is popped from the stack, stack top is going to be a new
|
|
// stack pointer for kernel threads
|
|
*--stack = thr->data.rsp0_base + thr->data.rsp0_size;
|
|
// rflags
|
|
*--stack = 0x200;
|
|
// cs
|
|
*--stack = 0x08;
|
|
// rip
|
|
*--stack = (uintptr_t) entry;
|
|
}
|
|
|
|
// Caller-saved
|
|
// r11
|
|
*--stack = 0;
|
|
// r10
|
|
*--stack = 0;
|
|
// r9
|
|
*--stack = 0;
|
|
// r8
|
|
*--stack = 0;
|
|
// rcx
|
|
*--stack = 0;
|
|
// rdx
|
|
*--stack = 0;
|
|
// rsi
|
|
*--stack = 0;
|
|
// rdi
|
|
*--stack = (uintptr_t) arg;
|
|
// rax
|
|
*--stack = 0;
|
|
|
|
// Small stub so that context switch enters the thread properly
|
|
*--stack = (uintptr_t) context_enter;
|
|
// Callee-saved
|
|
// r15
|
|
*--stack = 0;
|
|
// r14
|
|
*--stack = 0;
|
|
// r13
|
|
*--stack = 0;
|
|
// r12
|
|
*--stack = 0;
|
|
// rbp
|
|
*--stack = 0;
|
|
// rbx
|
|
*--stack = 0;
|
|
|
|
// 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
|
|
|
|
thr->data.rsp0 = (uintptr_t) stack;
|
|
thr->sigq = 0;
|
|
thr->pgid = -1;
|
|
thr->pid = -1;
|
|
|
|
list_add(&thr->g_link, &threads_all_head);
|
|
|
|
return 0;
|
|
}
|
|
|
|
// TODO: support kthread forking()
|
|
// (Although I don't really think it's very useful -
|
|
// threads can just be created by thread_init() and
|
|
// sched_queue())
|
|
int sys_fork(struct sys_fork_frame *frame) {
|
|
struct thread *dst = kmalloc(sizeof(struct thread));
|
|
_assert(dst);
|
|
struct thread *src = thread_self;
|
|
|
|
uintptr_t stack_pages = mm_phys_alloc_contiguous(2); //amd64_phys_alloc_contiguous(2);
|
|
_assert(stack_pages != MM_NADDR);
|
|
|
|
list_head_init(&dst->g_link);
|
|
list_head_init(&dst->shm_list);
|
|
list_head_init(&dst->wait_head);
|
|
thread_wait_io_init(&dst->sleep_notify);
|
|
thread_wait_io_init(&dst->pid_notify);
|
|
|
|
dst->data.rsp0_base = MM_VIRTUALIZE(stack_pages);
|
|
dst->data.rsp0_size = MM_PAGE_SIZE * 2;
|
|
dst->data.rsp0_top = dst->data.rsp0_base + dst->data.rsp0_size;
|
|
dst->flags = 0;
|
|
|
|
mm_space_t space = amd64_mm_pool_alloc();
|
|
dst->space = space;
|
|
mm_space_fork(dst, src, MM_CLONE_FLG_KERNEL | MM_CLONE_FLG_USER);
|
|
|
|
dst->data.rsp3_base = src->data.rsp3_base;
|
|
dst->data.rsp3_size = src->data.rsp3_size;
|
|
|
|
dst->data.cr3 = MM_PHYS(space);
|
|
|
|
dst->signal_entry = src->signal_entry;
|
|
|
|
strcpy(dst->name, src->name);
|
|
|
|
dst->data.fxsave = kmalloc(FXSAVE_REGION);
|
|
_assert(dst->data.fxsave);
|
|
_assert(src->data.fxsave);
|
|
|
|
if (src->flags & THREAD_FPU_SAVED) {
|
|
memcpy(dst->data.fxsave, src->data.fxsave, FXSAVE_REGION);
|
|
}
|
|
|
|
thread_ioctx_fork(dst, src);
|
|
|
|
dst->state = THREAD_READY;
|
|
dst->parent = src;
|
|
dst->next_child = src->first_child;
|
|
src->first_child = dst;
|
|
dst->first_child = NULL;
|
|
|
|
uint64_t *stack = (uint64_t *) dst->data.rsp0_top;
|
|
|
|
// 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;
|
|
|
|
dst->data.rsp0 = (uintptr_t) stack;
|
|
|
|
// Allocate a new PID for userspace thread
|
|
dst->pid = thread_alloc_pid(1);
|
|
dst->pgid = src->pgid;
|
|
dst->sigq = 0;
|
|
|
|
list_add(&dst->g_link, &threads_all_head);
|
|
sched_queue(dst);
|
|
|
|
return dst->pid;
|
|
}
|
|
|
|
// Discontiguous-destination range copy,
|
|
// XXX: may have been more efficient
|
|
static size_t procv_strcpy_paged(uintptr_t *phys_pages,
|
|
size_t offset,
|
|
const char *src,
|
|
size_t page_count) {
|
|
size_t ncpy = strlen(src) + 1;
|
|
size_t off_in_str = 0;
|
|
|
|
while (ncpy) {
|
|
size_t off_in_page = offset % MM_PAGE_SIZE;
|
|
size_t page_index = offset / MM_PAGE_SIZE;
|
|
_assert(page_index < page_count);
|
|
size_t len = MIN(ncpy, MM_PAGE_SIZE - off_in_page);
|
|
|
|
void *dst = (void *) MM_VIRTUALIZE(phys_pages[page_index] + off_in_page);
|
|
memcpy(dst, src + off_in_str, len);
|
|
|
|
off_in_str += len;
|
|
offset += len;
|
|
ncpy -= len;
|
|
}
|
|
|
|
return off_in_str;
|
|
}
|
|
|
|
// Setup process vectors:
|
|
// argp, envp, auxv
|
|
// TODO: elf auxv
|
|
|
|
static int procv_setup(struct thread *thr,
|
|
const char *const argv[],
|
|
const char *const envp[],
|
|
uintptr_t *phys_pages,
|
|
uintptr_t *vecp,
|
|
size_t *procv_page_count) {
|
|
#define PTRS_PER_PAGE (MM_PAGE_SIZE / sizeof(uintptr_t))
|
|
#define NEW_ARGV(i) (*((uintptr_t *) MM_VIRTUALIZE(phys_pages[i / PTRS_PER_PAGE]) + \
|
|
i % PTRS_PER_PAGE))
|
|
#define NEW_ENVP(i) (*({ \
|
|
size_t __i0 = i + (argc + 1); \
|
|
((uintptr_t *) MM_VIRTUALIZE(phys_pages[__i0 / PTRS_PER_PAGE]) + \
|
|
__i0 % PTRS_PER_PAGE); \
|
|
}))
|
|
|
|
// TODO: store pointers and data on separate pages
|
|
|
|
size_t page_count;
|
|
size_t offset;
|
|
size_t argc, envc;
|
|
|
|
// Count the arguments
|
|
for (argc = 0; argv[argc]; ++argc);
|
|
for (envc = 0; envp[envc]; ++envc);
|
|
|
|
// Skip space for pointer arrays
|
|
offset = (argc + 1) * sizeof(uintptr_t);
|
|
offset += (envc + 1) * sizeof(uintptr_t);
|
|
// Calculate total envp + argv length
|
|
for (size_t i = 0; i < argc; ++i) {
|
|
offset += strlen(argv[i]) + 1;
|
|
}
|
|
for (size_t i = 0; i < envc; ++i) {
|
|
offset += strlen(envp[i]) + 1;
|
|
}
|
|
|
|
// Allocate pages for data
|
|
// TODO: is it possible to somehow use CoW here?
|
|
page_count = (offset + MM_PAGE_SIZE - 1) / MM_PAGE_SIZE;
|
|
if (page_count > *procv_page_count) {
|
|
// Can't store all the physical pages in provided array ptr
|
|
return -ENOMEM;
|
|
}
|
|
|
|
*procv_page_count = page_count;
|
|
for (size_t i = 0; i < page_count; ++i) {
|
|
phys_pages[i] = mm_phys_alloc_page();
|
|
_assert(phys_pages[i] != MM_NADDR);
|
|
}
|
|
|
|
// Copy text data
|
|
offset = (argc + envc + 2) * sizeof(uintptr_t);
|
|
for (size_t i = 0; i < argc; ++i) {
|
|
NEW_ARGV(i) = offset;
|
|
offset += procv_strcpy_paged(phys_pages, offset, argv[i], page_count);
|
|
}
|
|
for (size_t i = 0; i < envc; ++i) {
|
|
NEW_ENVP(i) = offset;
|
|
offset += procv_strcpy_paged(phys_pages, offset, envp[i], page_count);
|
|
}
|
|
|
|
vecp[0] = argc;
|
|
vecp[1] = envc;
|
|
vecp[2] = offset;
|
|
return 0;
|
|
#undef PTRS_PER_PAGE
|
|
}
|
|
|
|
int sys_execve(const char *path, const char **argv, const char **envp) {
|
|
struct thread *thr = thread_self;
|
|
_assert(thr);
|
|
struct ofile fd;
|
|
struct stat st;
|
|
uintptr_t entry;
|
|
size_t argc;
|
|
int res;
|
|
|
|
if ((res = vfs_stat(&thr->ioctx, path, &st)) != 0) {
|
|
kerror("execve(%s): %s\n", path, kstrerror(res));
|
|
return res;
|
|
}
|
|
|
|
const char *e = strrchr(path, '/');
|
|
const char *name = e + 1;
|
|
if (!e) {
|
|
name = path;
|
|
}
|
|
size_t name_len = MIN(strlen(name), sizeof(thr->name) - 1);
|
|
strncpy(thr->name, name, name_len);
|
|
thr->name[name_len] = 0;
|
|
|
|
// Copy args
|
|
_assert(argv);
|
|
_assert(envp);
|
|
// 128K of argp/envp data
|
|
// 256 bytes of stack here
|
|
#define PROCV_MAX_PAGES 32
|
|
uintptr_t procv_phys_pages[PROCV_MAX_PAGES];
|
|
size_t procv_page_count = PROCV_MAX_PAGES;
|
|
// [0] - argc
|
|
// [1] - envc
|
|
// [2] - full size
|
|
uintptr_t procv_vecp[3];
|
|
|
|
if (procv_setup(thr, argv, envp, procv_phys_pages, procv_vecp, &procv_page_count) != 0) {
|
|
panic("Failed to copy argp/envp to new process\n");
|
|
}
|
|
|
|
if ((res = vfs_open(&thr->ioctx, &fd, path, O_RDONLY, 0)) != 0) {
|
|
kerror("%s: %s\n", path, kstrerror(res));
|
|
return res;
|
|
}
|
|
|
|
if (thr->space == mm_kernel) {
|
|
// Have to allocate a new PID for kernel -> userspace transition
|
|
thr->pid = thread_alloc_pid(1);
|
|
thr->pgid = thr->pid;
|
|
|
|
// Have to remove parent/child relation for transition
|
|
_assert(!thr->first_child);
|
|
if (thr->parent) {
|
|
panic("NYI\n");
|
|
}
|
|
thr->first_child = NULL;
|
|
thr->next_child = NULL;
|
|
thr->parent = NULL;
|
|
thr->sigq = 0;
|
|
|
|
thr->space = amd64_mm_pool_alloc();
|
|
thr->flags = 0;
|
|
_assert(thr->space);
|
|
|
|
mm_space_clone(thr->space, mm_kernel, MM_CLONE_FLG_KERNEL);
|
|
|
|
asm volatile ("cli");
|
|
thr->data.fxsave = kmalloc(FXSAVE_REGION);
|
|
_assert(thr->data.fxsave);
|
|
|
|
thr->data.cr3 = MM_PHYS(thr->space);
|
|
asm volatile ("sti");
|
|
} else {
|
|
mm_space_release(thr);
|
|
}
|
|
|
|
if ((res = elf_load(thr, &thr->ioctx, &fd, &entry)) != 0) {
|
|
vfs_close(&thr->ioctx, &fd);
|
|
|
|
kerror("elf load failed: %s\n", kstrerror(res));
|
|
sys_exit(-1);
|
|
|
|
panic("This code shouldn't run\n");
|
|
}
|
|
|
|
vfs_close(&thr->ioctx, &fd);
|
|
|
|
// Allocate a virtual address to map argp page
|
|
uintptr_t procv_virt = vmfind(thr->space, 0x100000, 0xF0000000, procv_page_count);
|
|
_assert(procv_virt != MM_NADDR);
|
|
for (size_t i = 0; i < procv_page_count; ++i) {
|
|
_assert(mm_map_single(thr->space,
|
|
procv_virt + i * MM_PAGE_SIZE,
|
|
procv_phys_pages[i],
|
|
MM_PAGE_USER | MM_PAGE_WRITE,
|
|
PU_PRIVATE) == 0);
|
|
}
|
|
uintptr_t *argv_fixup = (uintptr_t *) procv_virt;
|
|
uintptr_t *envp_fixup = (uintptr_t *) procv_virt + procv_vecp[0] + 1;
|
|
|
|
for (size_t i = 0; i < procv_vecp[0]; ++i) {
|
|
argv_fixup[i] += procv_virt;
|
|
}
|
|
for (size_t i = 0; i < procv_vecp[1]; ++i) {
|
|
envp_fixup[i] += procv_virt;
|
|
}
|
|
|
|
thr->signal_entry = 0;
|
|
thr->data.rsp0 = thr->data.rsp0_top;
|
|
|
|
// Allocate a new user stack
|
|
uintptr_t ustack = vmalloc(thr->space, 0x100000, 0xF0000000, 4, MM_PAGE_USER | MM_PAGE_WRITE /* | MM_PAGE_NOEXEC */, PU_PRIVATE);
|
|
thr->data.rsp3_base = ustack;
|
|
thr->data.rsp3_size = 4 * MM_PAGE_SIZE;
|
|
|
|
// Up to 4095 argc and envc
|
|
_assert(procv_vecp[0] < 4096);
|
|
_assert(procv_vecp[2] < 4096);
|
|
uintptr_t arg = procv_vecp[0] | (procv_vecp[2] << 12) | ((uintptr_t) argv_fixup << 12);
|
|
context_exec_enter(arg, thr, ustack + 4 * MM_PAGE_SIZE, entry);
|
|
|
|
panic("This code shouldn't run\n");
|
|
}
|
|
|
|
void thread_sigenter(int signum) {
|
|
if (signum == SIGCHLD) {
|
|
kdebug("Skipping SIGCHLD\n");
|
|
return;
|
|
}
|
|
struct thread *thr = thread_self;
|
|
kdebug("%d: Handle signal %d\n", thr->pid, signum);
|
|
uintptr_t old_rsp0_top = thr->data.rsp0_top;
|
|
// XXX: Either use a separate stack or ensure stuff doesn't get overwritten
|
|
uintptr_t signal_rsp3 = thr->data.rsp3_base + 0x800;
|
|
|
|
context_sigenter(thr->signal_entry, signal_rsp3, signum);
|
|
|
|
thr->data.rsp0_top = old_rsp0_top;
|
|
}
|
|
|
|
__attribute__((noreturn)) void sys_exit(int status) {
|
|
struct thread *thr = thread_self;
|
|
kdebug("Thread %d exited with status %d\n", thr->pid, status);
|
|
|
|
// Clear pending I/O (if exiting from signal interrupting select())
|
|
if (!list_empty(&thr->wait_head)) {
|
|
thread_wait_io_clear(thr);
|
|
}
|
|
|
|
thr->exit_status = status;
|
|
|
|
if (thr->parent) {
|
|
struct thread *p = thr->parent;
|
|
|
|
// Waiting on that descriptor
|
|
if (p->pid_notify.owner) {
|
|
thread_notify_io(&p->pid_notify);
|
|
}
|
|
}
|
|
|
|
sched_unqueue(thr, THREAD_STOPPED);
|
|
panic("This code shouldn't run\n");
|
|
}
|
|
|
|
int sys_waitpid(pid_t pid, int *status) {
|
|
struct thread *thr = thread_self;
|
|
_assert(thr);
|
|
struct thread *chld = thread_child(thr, pid);
|
|
int res;
|
|
|
|
if (!chld) {
|
|
return -ECHILD;
|
|
}
|
|
|
|
while (1) {
|
|
res = thread_wait_io(thr, &thr->pid_notify);
|
|
|
|
if (res < 0) {
|
|
// Likely interrupted
|
|
return res;
|
|
}
|
|
|
|
// State should already be "stopped" when notify is signalled
|
|
_assert(chld->state == THREAD_STOPPED);
|
|
break;
|
|
}
|
|
|
|
if (status) {
|
|
*status = chld->exit_status;
|
|
}
|
|
|
|
// TODO: automatically cleanup threads which don't have
|
|
// a parent like PID 1
|
|
thread_unchild(chld);
|
|
list_del(&chld->g_link);
|
|
thread_free(chld);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void sys_sigreturn(void) {
|
|
context_sigreturn();
|
|
}
|
|
|
|
void thread_signal(struct thread *thr, int signum) {
|
|
if (thr->sleep_notify.owner) {
|
|
thread_notify_io(&thr->sleep_notify);
|
|
//thr->sleep_notify.owner = NULL;
|
|
//timer_remove_sleep(thr);
|
|
}
|
|
|
|
if (thr->cpu == (int) get_cpu()->processor_id) {
|
|
if (thr == thread_self) {
|
|
kdebug("Signal will be handled now\n");
|
|
thread_sigenter(signum);
|
|
} else {
|
|
kdebug("Signal will be handled later\n");
|
|
thread_signal_set(thr, signum);
|
|
|
|
sched_queue(thr);
|
|
}
|
|
} else if (thr->cpu >= 0) {
|
|
kdebug("Signal will be handled later (other cpu%d)\n", thr->cpu);
|
|
thread_signal_set(thr, signum);
|
|
|
|
sched_queue(thr);
|
|
} else {
|
|
kdebug("Signal will be handled later (not running)\n");
|
|
thread_signal_set(thr, signum);
|
|
|
|
sched_queue(thr);
|
|
}
|
|
}
|
|
|
|
int thread_check_signal(struct thread *thr, int ret) {
|
|
if (thr->sigq) {
|
|
// Pick one signal to handle at a time
|
|
int signum = 0;
|
|
for (int i = 0; i < 64; ++i) {
|
|
if (thr->sigq & (1ULL << i)) {
|
|
thr->sigq &= ~(1ULL << i);
|
|
signum = i + 1;
|
|
break;
|
|
}
|
|
}
|
|
_assert(signum);
|
|
thread_sigenter(signum);
|
|
|
|
// Theoretically, a rogue thread could steal all the CPU time by sending itself signals
|
|
// in normal context, as after returning from thread_sigenter() this code will return
|
|
// to a normal execution
|
|
// XXX: Maybe makes sense to just yield() here
|
|
return -EINTR;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int sys_kill(pid_t pid, int signum) {
|
|
struct thread *thr;
|
|
|
|
if (pid > 0) {
|
|
thr = thread_find(pid);
|
|
} else if (pid == 0) {
|
|
thr = thread_self;
|
|
} else {
|
|
// Not implemented
|
|
thr = NULL;
|
|
}
|
|
|
|
if (!thr || thr->state == THREAD_STOPPED) {
|
|
return -ESRCH;
|
|
}
|
|
|
|
if (signum == 0) {
|
|
return 0;
|
|
}
|
|
|
|
if (signum <= 0 || signum >= 64) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!thr) {
|
|
// No such process
|
|
return -ESRCH;
|
|
}
|
|
|
|
thread_signal(thr, signum);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void sys_sigentry(uintptr_t entry) {
|
|
struct thread *thr = thread_self;
|
|
_assert(thr);
|
|
|
|
thr->signal_entry = entry;
|
|
}
|
|
|
|
pid_t sys_getpid(void) {
|
|
struct thread *thr = thread_self;
|
|
_assert(thr);
|
|
return thr->pid;
|
|
}
|
|
|
|
pid_t sys_getpgid(pid_t pid) {
|
|
struct thread *thr;
|
|
|
|
if (pid == 0) {
|
|
thr = get_cpu()->thread;
|
|
_assert(thr);
|
|
} else {
|
|
thr = thread_find(pid);
|
|
}
|
|
|
|
if (!thr) {
|
|
return -ESRCH;
|
|
}
|
|
|
|
return thr->pgid;
|
|
}
|
|
|
|
int sys_setpgid(pid_t pid, pid_t pgrp) {
|
|
struct thread *thr = get_cpu()->thread;
|
|
_assert(thr);
|
|
|
|
if (pid == 0 && pgrp == 0) {
|
|
thr->pgid = thr->pid;
|
|
return 0;
|
|
}
|
|
|
|
// Find child with pid pid (guess only children can be setpgid'd)
|
|
struct thread *chld = thread_child(thr, pid);
|
|
if (!chld) {
|
|
return -ESRCH;
|
|
}
|
|
if (chld->pgid != thr->pgid) {
|
|
return -EACCES;
|
|
}
|
|
chld->pgid = pgrp;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int sys_setuid(uid_t uid) {
|
|
struct thread *thr = get_cpu()->thread;
|
|
_assert(thr);
|
|
|
|
if (thr->ioctx.uid != 0) {
|
|
return -EACCES;
|
|
}
|
|
|
|
thr->ioctx.uid = uid;
|
|
return 0;
|
|
}
|
|
|
|
int sys_setgid(gid_t gid) {
|
|
struct thread *thr = get_cpu()->thread;
|
|
_assert(thr);
|
|
|
|
if (thr->ioctx.gid != 0 && thr->ioctx.uid != 0) {
|
|
return -EACCES;
|
|
}
|
|
|
|
thr->ioctx.gid = gid;
|
|
return 0;
|
|
}
|
|
|
|
uid_t sys_getuid(void) {
|
|
struct thread *thr = get_cpu()->thread;
|
|
_assert(thr);
|
|
return thr->ioctx.uid;
|
|
}
|
|
|
|
gid_t sys_getgid(void) {
|
|
struct thread *thr = get_cpu()->thread;
|
|
_assert(thr);
|
|
return thr->ioctx.gid;
|
|
}
|