From 3ce220f0627f9fff1dedb6d3582dc2455d0a4a3b Mon Sep 17 00:00:00 2001 From: Bahadir Balban Date: Thu, 21 Aug 2008 16:21:08 +0300 Subject: [PATCH] Changes to make sure after a fork a child process can safely return. This copies the parent kernel stack to child only for the part where the previous context is saved. Then the child registers are modified so that it would begin execution from returning of the system call. --- include/l4/generic/tcb.h | 7 ++-- include/l4/glue/arm/syscall.h | 11 ++++-- src/api/ipc.c | 6 ++-- src/api/syscall.c | 8 ++--- src/api/thread.c | 64 +++++++++++++++++++++++++++++++---- src/arch/arm/vectors.S | 7 ++-- src/glue/arm/systable.c | 2 +- 7 files changed, 84 insertions(+), 21 deletions(-) diff --git a/include/l4/generic/tcb.h b/include/l4/generic/tcb.h index 575e740..82fa4b3 100644 --- a/include/l4/generic/tcb.h +++ b/include/l4/generic/tcb.h @@ -57,8 +57,11 @@ struct ktcb { /* User context */ task_context_t context; - /* Reference to syscall saved context */ - syscall_args_t *syscall_regs; + /* + * Reference to the context on stack + * saved at the beginning of a syscall trap. + */ + syscall_context_t *syscall_regs; /* Runqueue related */ struct list_head rq_list; diff --git a/include/l4/glue/arm/syscall.h b/include/l4/glue/arm/syscall.h index a1ff969..448a98c 100644 --- a/include/l4/glue/arm/syscall.h +++ b/include/l4/glue/arm/syscall.h @@ -18,7 +18,12 @@ extern unsigned int __syscall_page_start; -typedef struct syscall_args { +/* + * This structure is saved on the kernel stack + * just after entering a system call exception. + */ +typedef struct syscall_context { + u32 spsr u32 r0; u32 r1; u32 r2; @@ -28,7 +33,9 @@ typedef struct syscall_args { u32 r6; /* MR3 */ u32 r7; /* MR4 */ u32 r8; /* MR5 */ -} syscall_args_t; + u32 sp_usr; + u32 lr_usr; +} __attribute__((__packed__)) syscall_context_t; typedef struct msg_regs { u32 mr0; diff --git a/src/api/ipc.c b/src/api/ipc.c index 849c755..ebd35a9 100644 --- a/src/api/ipc.c +++ b/src/api/ipc.c @@ -47,7 +47,7 @@ int ipc_msg_copy(struct ktcb *to, struct ktcb *from) return 0; } -int sys_ipc_control(struct syscall_args *regs) +int sys_ipc_control(syscall_context_t *regs) { return -ENOSYS; } @@ -212,7 +212,7 @@ static inline int __sys_ipc(l4id_t to, l4id_t from, unsigned int ipc_type) return ret; } -void printk_sysregs(struct syscall_args *regs) +void printk_sysregs(syscall_context_t *regs) { printk("System call registers for tid: %d\n", current->tid); printk("R0: %x\n", regs->r0); @@ -234,7 +234,7 @@ void printk_sysregs(struct syscall_args *regs) * - Can propagate messages from third party threads. * - A thread can both send and receive on the same call. */ -int sys_ipc(struct syscall_args *regs) +int sys_ipc(syscall_context_t *regs) { l4id_t to = (l4id_t)regs->r0; l4id_t from = (l4id_t)regs->r1; diff --git a/src/api/syscall.c b/src/api/syscall.c index 97bb358..2a41f82 100644 --- a/src/api/syscall.c +++ b/src/api/syscall.c @@ -17,7 +17,7 @@ #include INC_API(syscall.h) #include INC_ARCH(exception.h) -int sys_exchange_registers(struct syscall_args *regs) +int sys_exchange_registers(syscall_context_t *regs) { struct ktcb *task; unsigned int pc = regs->r0; @@ -43,7 +43,7 @@ found: return 0; } -int sys_schedule(struct syscall_args *regs) +int sys_schedule(syscall_context_t *regs) { printk("(SVC) %s called. Tid (%d)\n", __FUNCTION__, current->tid); return 0; @@ -54,7 +54,7 @@ int sys_space_control(struct syscall_args *regs) return -ENOSYS; } -int sys_getid(struct syscall_args *regs) +int sys_getid(syscall_context_t *regs) { struct task_ids *ids = (struct task_ids *)regs->r0; struct ktcb *this = current; @@ -83,7 +83,7 @@ int validate_granted_pages(unsigned long pfn, int npages) * this memory is used for thread creation and memory mapping, (e.g. new * page tables, page middle directories, per-task kernel stack etc.) */ -int sys_kmem_control(struct syscall_args *regs) +int sys_kmem_control(syscall_context_t *regs) { unsigned long pfn = (unsigned long)regs->r0; int npages = (int)regs->r1; diff --git a/src/api/thread.c b/src/api/thread.c index 89c53e9..e58184c 100644 --- a/src/api/thread.c +++ b/src/api/thread.c @@ -12,7 +12,7 @@ #include #include INC_ARCH(mm.h) -int sys_thread_switch(struct syscall_args *regs) +int sys_thread_switch(syscall_context_t *regs) { sched_yield(); return 0; @@ -60,10 +60,56 @@ int thread_start(struct task_ids *ids) return -EINVAL; } -int setup_new_ktcb(struct ktcb *new, struct ktcb *orig) +/* + * Copies the pre-syscall context of original thread into the kernel + * stack of new thread. Modifies new thread's context registers so that + * when it schedules it executes as if it is returning from a syscall, + * i.e. the syscall return path where the previous context copied to its + * stack is restored. It also modifies r0 to ensure POSIX child return + * semantics. + */ +int arch_setup_new_thread(struct ktcb *new, struct ktcb *orig) { - /* Setup new thread stack */ - new->context.sp = + /* + * Pre-syscall context is saved on the kernel stack upon + * a system call exception. We need the location where it + * is saved relative to the start of ktcb. + */ + void *syscall_context_offset = (void *)orig->syscall_regs - + (void *)orig; + + /* + * Copy the saved context from original thread's + * stack to new thread stack. + */ + memcpy((void *)new + syscall_context_offset, + (void *)orig + syscall_context_offset, + sizeof(syscall_context_t)); + + /* + * Modify the return register value with 0 to ensure new thread + * returns with that value. This is a POSIX requirement and enforces + * policy on the microkernel, but it is currently the best solution. + */ + new->syscall_regs->r0 = 0; + + /* + * Set up the stack pointer and program counter so that next time + * the new thread schedules, it executes the end part of the system + * call exception where the previous context is restored. + */ + new->context.sp = (unsigned long)((void *)new + + syscall_context_offset); + new->context.pc = (unsigned long)return_from_syscall; + + /* Copy other relevant fields from original ktcb */ + new->pagerid = orig->pagerid; + + /* Distribute original thread's ticks into two threads */ + new->ticks_left = orig->ticks_left / 2; + orig->ticks_left /= 2; + + return 0; } /* @@ -117,9 +163,13 @@ out: waitqueue_head_init(&new->wqh_send); waitqueue_head_init(&new->wqh_recv); - /* When space is copied kernel-side tcb and stack are also copied */ + /* + * When space is copied this restores the new thread's + * system call return environment so that it can safely + * return as a copy of its original thread. + */ if (flags == THREAD_CREATE_COPYSPC) - setup_new_ktcb(new, task); + arch_setup_new_thread(new, task); /* Add task to global hlist of tasks */ add_task_global(new); @@ -132,7 +182,7 @@ out: * space for a thread that doesn't already have one, or destroys it if the last * thread that uses it is destroyed. */ -int sys_thread_control(struct syscall_args *regs) +int sys_thread_control(syscall_context_t *regs) { int ret = 0; u32 *reg = (u32 *)regs; diff --git a/src/arch/arm/vectors.S b/src/arch/arm/vectors.S index 56298b3..ac053ac 100644 --- a/src/arch/arm/vectors.S +++ b/src/arch/arm/vectors.S @@ -144,11 +144,14 @@ BEGIN_PROC(arm_swi_exception) mrs r0, spsr_fc @ psr also need saving in case this context is interrupted. stmfd sp!, {r0} enable_irqs r0 - add r0, sp, #4 @ Pass sp address + 4 as a pointer to saved regs. - ktcb_ref_saved_regs r0, r1, r2 @ Save regs pointer in ktcb + mov r0, sp @ Current SP has pointer to all saved context. + ktcb_ref_saved_regs r0, r1, r2 @ Save syscall context pointer in ktcb mov r1, lr @ Pass swi instruction address in LR as arg1 mov lr, pc ldr pc, =syscall + +.global return_from_syscall; @ Newly created threads use this path to return, +return_from_syscall: @ if they duplicated another thread's address space. disable_irqs r1 @ Not disabling irqs at this point causes the SP_USR and spsr @ to get corrupt causing havoc. ldmfd sp!, {r1} diff --git a/src/glue/arm/systable.c b/src/glue/arm/systable.c index d3c648e..cdaefe7 100644 --- a/src/glue/arm/systable.c +++ b/src/glue/arm/systable.c @@ -58,7 +58,7 @@ void syscall_init() } /* Checks a syscall is legitimate and dispatches to appropriate handler. */ -int syscall(struct syscall_args *regs, unsigned long swi_addr) +int syscall(syscall_context_t *regs, unsigned long swi_addr) { /* Check if genuine system call, coming from the syscall page */ if ((swi_addr & ARM_SYSCALL_PAGE) == ARM_SYSCALL_PAGE) {