nuclear@52: #include nuclear@47: #include nuclear@52: #include nuclear@57: #include nuclear@55: #include "config.h" nuclear@42: #include "proc.h" nuclear@42: #include "tss.h" nuclear@45: #include "vm.h" nuclear@47: #include "segm.h" nuclear@47: #include "intr.h" nuclear@47: #include "panic.h" nuclear@51: #include "syscall.h" nuclear@51: #include "sched.h" nuclear@54: #include "tss.h" nuclear@47: nuclear@55: #define FLAGS_INTR_BIT (1 << 9) nuclear@47: nuclear@54: static void start_first_proc(void); nuclear@54: nuclear@55: /* defined in proc-asm.S */ nuclear@57: uint32_t switch_stack(uint32_t new_stack, uint32_t *old_stack); nuclear@57: void just_forked(void); nuclear@54: nuclear@47: /* defined in test_proc.S */ nuclear@47: void test_proc(void); nuclear@47: void test_proc_end(void); nuclear@42: nuclear@42: static struct process proc[MAX_PROC]; nuclear@56: nuclear@56: /* cur_pid: pid of the currently executing process. nuclear@56: * when we're in the idle process cur_pid will be 0. nuclear@56: * last_pid: pid of the last real process that was running, this should nuclear@56: * never become 0. Essentially this defines the active kernel stack. nuclear@56: */ nuclear@56: static int cur_pid, last_pid; nuclear@42: nuclear@54: static struct task_state *tss; nuclear@54: nuclear@54: nuclear@42: void init_proc(void) nuclear@42: { nuclear@54: int tss_page; nuclear@51: nuclear@54: /* allocate a page for the task state segment, to make sure nuclear@54: * it doesn't cross page boundaries nuclear@54: */ nuclear@54: if((tss_page = pgalloc(1, MEM_KERNEL)) == -1) { nuclear@54: panic("failed to allocate memory for the task state segment\n"); nuclear@54: } nuclear@55: tss = (struct task_state*)PAGE_TO_ADDR(tss_page); nuclear@54: nuclear@54: /* the kernel stack segment never changes so we might as well set it now nuclear@54: * the only other thing that we use in the tss is the kernel stack pointer nuclear@54: * which is different for each process, and thus managed by context_switch nuclear@54: */ nuclear@54: memset(tss, 0, sizeof *tss); nuclear@54: tss->ss0 = selector(SEGM_KDATA, 0); nuclear@54: nuclear@55: set_tss((uint32_t)tss); nuclear@54: nuclear@54: /* initialize system call handler (see syscall.c) */ nuclear@51: init_syscall(); nuclear@42: nuclear@54: start_first_proc(); /* XXX never returns */ nuclear@54: } nuclear@54: nuclear@54: static void start_first_proc(void) nuclear@54: { nuclear@54: struct process *p; nuclear@54: int proc_size_pg, img_start_pg, stack_pg; nuclear@55: uint32_t img_start_addr; nuclear@54: struct intr_frame ifrm; nuclear@54: nuclear@42: /* prepare the first process */ nuclear@54: p = proc + 1; nuclear@54: p->id = 1; nuclear@54: p->parent = 0; /* no parent for init */ nuclear@42: nuclear@55: p->ticks_left = TIMESLICE_TICKS; nuclear@55: p->next = p->prev = 0; nuclear@55: nuclear@55: /* the first process may keep this existing page table */ nuclear@55: p->ctx.pgtbl_paddr = get_pgdir_addr(); nuclear@55: nuclear@42: /* allocate a chunk of memory for the process image nuclear@42: * and copy the code of test_proc there. nuclear@42: */ nuclear@51: proc_size_pg = (test_proc_end - test_proc) / PGSIZE + 1; nuclear@45: if((img_start_pg = pgalloc(proc_size_pg, MEM_USER)) == -1) { nuclear@45: panic("failed to allocate space for the init process image\n"); nuclear@45: } nuclear@54: img_start_addr = PAGE_TO_ADDR(img_start_pg); nuclear@54: memcpy((void*)img_start_addr, test_proc, proc_size_pg * PGSIZE); nuclear@54: printf("copied init process at: %x\n", img_start_addr); nuclear@47: nuclear@47: /* allocate the first page of the process stack */ nuclear@47: stack_pg = ADDR_TO_PAGE(KMEM_START) - 1; nuclear@47: if(pgalloc_vrange(stack_pg, 1) == -1) { nuclear@47: panic("failed to allocate user stack page\n"); nuclear@47: } nuclear@54: p->user_stack_pg = stack_pg; nuclear@52: nuclear@54: /* allocate a kernel stack for this process */ nuclear@54: if((p->kern_stack_pg = pgalloc(KERN_STACK_SIZE / PGSIZE, MEM_KERNEL)) == -1) { nuclear@54: panic("failed to allocate kernel stack for the init process\n"); nuclear@54: } nuclear@54: /* when switching from user space to kernel space, the ss0:esp0 from TSS nuclear@54: * will be used to switch to the per-process kernel stack, so we need to nuclear@54: * set it correctly before switching to user space. nuclear@54: * tss->ss0 is already set in init_proc above. nuclear@54: */ nuclear@54: tss->esp0 = PAGE_TO_ADDR(p->kern_stack_pg) + KERN_STACK_SIZE; nuclear@45: nuclear@45: nuclear@54: /* now we need to fill in the fake interrupt stack frame */ nuclear@54: memset(&ifrm, 0, sizeof ifrm); nuclear@54: /* after the priviledge switch, this ss:esp will be used in userspace */ nuclear@54: ifrm.esp = PAGE_TO_ADDR(stack_pg) + PGSIZE; nuclear@54: ifrm.ss = selector(SEGM_UDATA, 3); nuclear@54: /* instruction pointer at the beginning of the process image */ nuclear@55: ifrm.eip = img_start_addr; nuclear@54: ifrm.cs = selector(SEGM_UCODE, 3); nuclear@54: /* make sure the user will run with interrupts enabled */ nuclear@54: ifrm.eflags = FLAGS_INTR_BIT; nuclear@54: /* user data selectors should all be the same */ nuclear@54: ifrm.ds = ifrm.es = ifrm.fs = ifrm.gs = ifrm.ss; nuclear@42: nuclear@51: /* add it to the scheduler queues */ nuclear@55: add_proc(p->id); nuclear@55: nuclear@56: /* make it current */ nuclear@56: set_current_pid(p->id); nuclear@42: nuclear@54: /* execute a fake return from interrupt with the fake stack frame */ nuclear@54: intr_ret(ifrm); nuclear@42: } nuclear@42: nuclear@57: int fork(void) nuclear@57: { nuclear@57: int i, pid; nuclear@57: struct process *p, *parent; nuclear@57: nuclear@57: disable_intr(); nuclear@57: nuclear@57: /* find a free process slot */ nuclear@57: /* TODO don't search up to MAX_PROC if uid != 0 */ nuclear@57: pid = -1; nuclear@57: for(i=1; ikern_stack_pg = pgalloc(KERN_STACK_SIZE / PGSIZE, MEM_KERNEL)) == -1) { nuclear@57: return -EAGAIN; nuclear@57: } nuclear@57: p->ctx.stack_ptr = PAGE_TO_ADDR(p->kern_stack_pg) + KERN_STACK_SIZE; nuclear@57: /* we need to copy the current interrupt frame to the new kernel stack so nuclear@57: * that the new process will return to the same point as the parent, just nuclear@57: * after the fork syscall. nuclear@57: */ nuclear@57: p->ctx.stack_ptr -= sizeof(struct intr_frame); nuclear@57: memcpy((void*)p->ctx.stack_ptr, get_intr_frame(), sizeof(struct intr_frame)); nuclear@57: /* child's return from fork returns 0 */ nuclear@57: ((struct intr_frame*)p->ctx.stack_ptr)->regs.eax = 0; nuclear@57: nuclear@59: /* we also need the address of just_forked in the stack, so that switch_stacks nuclear@59: * called from context_switch, will return to just_forked when we first switch nuclear@59: * to a newly forked process. just_forked then just calls intr_ret to return to nuclear@59: * userspace with the already constructed interrupt frame (see above). nuclear@59: */ nuclear@57: p->ctx.stack_ptr -= 4; nuclear@57: *(uint32_t*)p->ctx.stack_ptr = (uint32_t)just_forked; nuclear@57: nuclear@57: /* initialize the rest of the process structure */ nuclear@57: p->id = pid; nuclear@57: p->parent = parent->id; nuclear@57: p->next = p->prev = 0; nuclear@57: nuclear@57: /* will be copied on write */ nuclear@57: p->user_stack_pg = parent->user_stack_pg; nuclear@57: nuclear@57: p->ctx.pgtbl_paddr = clone_vm(CLONE_COW); nuclear@57: nuclear@57: /* done, now let's add it to the scheduler runqueue */ nuclear@57: add_proc(p->id); nuclear@57: nuclear@57: return pid; nuclear@57: } nuclear@47: nuclear@47: void context_switch(int pid) nuclear@42: { nuclear@56: static struct process *prev, *new; nuclear@49: nuclear@55: assert(get_intr_state() == 0); nuclear@56: assert(pid > 0); nuclear@56: assert(last_pid > 0); nuclear@55: nuclear@56: prev = proc + last_pid; nuclear@54: new = proc + pid; nuclear@52: nuclear@56: if(last_pid != pid) { nuclear@57: set_current_pid(new->id); nuclear@47: nuclear@56: /* switch to the new process' address space */ nuclear@56: set_pgdir_addr(new->ctx.pgtbl_paddr); nuclear@47: nuclear@56: /* make sure we'll return to the correct kernel stack next time nuclear@56: * we enter from userspace nuclear@56: */ nuclear@56: tss->esp0 = PAGE_TO_ADDR(new->kern_stack_pg) + KERN_STACK_SIZE; nuclear@57: nuclear@57: /* push all registers onto the stack before switching stacks */ nuclear@57: push_regs(); nuclear@57: nuclear@57: /* XXX: when switching to newly forked processes this switch_stack call nuclear@57: * WILL NOT RETURN HERE. It will return to just_forked instead. So the nuclear@57: * rest of this function will not run. nuclear@57: */ nuclear@57: switch_stack(new->ctx.stack_ptr, &prev->ctx.stack_ptr); nuclear@57: nuclear@57: /* restore registers from the new stack */ nuclear@57: pop_regs(); nuclear@57: } else { nuclear@57: set_current_pid(new->id); nuclear@56: } nuclear@56: } nuclear@56: nuclear@56: nuclear@56: void set_current_pid(int pid) nuclear@56: { nuclear@56: cur_pid = pid; nuclear@56: if(pid > 0) { nuclear@56: last_pid = pid; nuclear@56: } nuclear@47: } nuclear@51: nuclear@51: int get_current_pid(void) nuclear@51: { nuclear@51: return cur_pid; nuclear@51: } nuclear@51: nuclear@51: struct process *get_current_proc(void) nuclear@51: { nuclear@56: return cur_pid > 0 ? &proc[cur_pid] : 0; nuclear@51: } nuclear@51: nuclear@51: struct process *get_process(int pid) nuclear@51: { nuclear@51: return &proc[pid]; nuclear@51: }