kern

annotate src/proc.c @ 72:3941e82b07f2

- implemented syscalls: exit, waitpid, getppid - moved sys_whatever functions out of syscall.c into more reasonable files - putting all the definitions that must be synced with userland to include/kdef.h
author John Tsiombikas <nuclear@member.fsf.org>
date Sat, 15 Oct 2011 07:45:56 +0300
parents b45e2d5f0ae1
children 8b21fe04ba2c
rev   line source
nuclear@52 1 #include <stdio.h>
nuclear@47 2 #include <string.h>
nuclear@52 3 #include <assert.h>
nuclear@57 4 #include <errno.h>
nuclear@55 5 #include "config.h"
nuclear@42 6 #include "proc.h"
nuclear@42 7 #include "tss.h"
nuclear@45 8 #include "vm.h"
nuclear@47 9 #include "segm.h"
nuclear@47 10 #include "intr.h"
nuclear@47 11 #include "panic.h"
nuclear@51 12 #include "syscall.h"
nuclear@51 13 #include "sched.h"
nuclear@54 14 #include "tss.h"
nuclear@72 15 #include "kdef.h"
nuclear@47 16
nuclear@55 17 #define FLAGS_INTR_BIT (1 << 9)
nuclear@47 18
nuclear@54 19 static void start_first_proc(void);
nuclear@54 20
nuclear@55 21 /* defined in proc-asm.S */
nuclear@57 22 uint32_t switch_stack(uint32_t new_stack, uint32_t *old_stack);
nuclear@57 23 void just_forked(void);
nuclear@54 24
nuclear@47 25 /* defined in test_proc.S */
nuclear@47 26 void test_proc(void);
nuclear@47 27 void test_proc_end(void);
nuclear@42 28
nuclear@42 29 static struct process proc[MAX_PROC];
nuclear@56 30
nuclear@56 31 /* cur_pid: pid of the currently executing process.
nuclear@56 32 * when we're in the idle process cur_pid will be 0.
nuclear@56 33 * last_pid: pid of the last real process that was running, this should
nuclear@56 34 * never become 0. Essentially this defines the active kernel stack.
nuclear@56 35 */
nuclear@56 36 static int cur_pid, last_pid;
nuclear@42 37
nuclear@54 38 static struct task_state *tss;
nuclear@54 39
nuclear@54 40
nuclear@42 41 void init_proc(void)
nuclear@42 42 {
nuclear@54 43 int tss_page;
nuclear@51 44
nuclear@54 45 /* allocate a page for the task state segment, to make sure
nuclear@54 46 * it doesn't cross page boundaries
nuclear@54 47 */
nuclear@54 48 if((tss_page = pgalloc(1, MEM_KERNEL)) == -1) {
nuclear@54 49 panic("failed to allocate memory for the task state segment\n");
nuclear@54 50 }
nuclear@55 51 tss = (struct task_state*)PAGE_TO_ADDR(tss_page);
nuclear@54 52
nuclear@54 53 /* the kernel stack segment never changes so we might as well set it now
nuclear@54 54 * the only other thing that we use in the tss is the kernel stack pointer
nuclear@54 55 * which is different for each process, and thus managed by context_switch
nuclear@54 56 */
nuclear@54 57 memset(tss, 0, sizeof *tss);
nuclear@54 58 tss->ss0 = selector(SEGM_KDATA, 0);
nuclear@54 59
nuclear@55 60 set_tss((uint32_t)tss);
nuclear@54 61
nuclear@54 62 /* initialize system call handler (see syscall.c) */
nuclear@51 63 init_syscall();
nuclear@42 64
nuclear@54 65 start_first_proc(); /* XXX never returns */
nuclear@54 66 }
nuclear@54 67
nuclear@54 68 static void start_first_proc(void)
nuclear@54 69 {
nuclear@54 70 struct process *p;
nuclear@54 71 int proc_size_pg, img_start_pg, stack_pg;
nuclear@55 72 uint32_t img_start_addr;
nuclear@54 73 struct intr_frame ifrm;
nuclear@54 74
nuclear@42 75 /* prepare the first process */
nuclear@54 76 p = proc + 1;
nuclear@54 77 p->id = 1;
nuclear@54 78 p->parent = 0; /* no parent for init */
nuclear@42 79
nuclear@55 80 p->ticks_left = TIMESLICE_TICKS;
nuclear@55 81 p->next = p->prev = 0;
nuclear@55 82
nuclear@55 83 /* the first process may keep this existing page table */
nuclear@55 84 p->ctx.pgtbl_paddr = get_pgdir_addr();
nuclear@55 85
nuclear@42 86 /* allocate a chunk of memory for the process image
nuclear@42 87 * and copy the code of test_proc there.
nuclear@42 88 */
nuclear@51 89 proc_size_pg = (test_proc_end - test_proc) / PGSIZE + 1;
nuclear@45 90 if((img_start_pg = pgalloc(proc_size_pg, MEM_USER)) == -1) {
nuclear@45 91 panic("failed to allocate space for the init process image\n");
nuclear@45 92 }
nuclear@54 93 img_start_addr = PAGE_TO_ADDR(img_start_pg);
nuclear@54 94 memcpy((void*)img_start_addr, test_proc, proc_size_pg * PGSIZE);
nuclear@54 95 printf("copied init process at: %x\n", img_start_addr);
nuclear@47 96
nuclear@69 97 /* allocate the first page of the user stack */
nuclear@47 98 stack_pg = ADDR_TO_PAGE(KMEM_START) - 1;
nuclear@47 99 if(pgalloc_vrange(stack_pg, 1) == -1) {
nuclear@47 100 panic("failed to allocate user stack page\n");
nuclear@47 101 }
nuclear@54 102 p->user_stack_pg = stack_pg;
nuclear@52 103
nuclear@54 104 /* allocate a kernel stack for this process */
nuclear@54 105 if((p->kern_stack_pg = pgalloc(KERN_STACK_SIZE / PGSIZE, MEM_KERNEL)) == -1) {
nuclear@54 106 panic("failed to allocate kernel stack for the init process\n");
nuclear@54 107 }
nuclear@54 108 /* when switching from user space to kernel space, the ss0:esp0 from TSS
nuclear@54 109 * will be used to switch to the per-process kernel stack, so we need to
nuclear@54 110 * set it correctly before switching to user space.
nuclear@54 111 * tss->ss0 is already set in init_proc above.
nuclear@54 112 */
nuclear@54 113 tss->esp0 = PAGE_TO_ADDR(p->kern_stack_pg) + KERN_STACK_SIZE;
nuclear@45 114
nuclear@45 115
nuclear@54 116 /* now we need to fill in the fake interrupt stack frame */
nuclear@54 117 memset(&ifrm, 0, sizeof ifrm);
nuclear@54 118 /* after the priviledge switch, this ss:esp will be used in userspace */
nuclear@54 119 ifrm.esp = PAGE_TO_ADDR(stack_pg) + PGSIZE;
nuclear@54 120 ifrm.ss = selector(SEGM_UDATA, 3);
nuclear@54 121 /* instruction pointer at the beginning of the process image */
nuclear@55 122 ifrm.eip = img_start_addr;
nuclear@54 123 ifrm.cs = selector(SEGM_UCODE, 3);
nuclear@54 124 /* make sure the user will run with interrupts enabled */
nuclear@54 125 ifrm.eflags = FLAGS_INTR_BIT;
nuclear@54 126 /* user data selectors should all be the same */
nuclear@54 127 ifrm.ds = ifrm.es = ifrm.fs = ifrm.gs = ifrm.ss;
nuclear@42 128
nuclear@51 129 /* add it to the scheduler queues */
nuclear@55 130 add_proc(p->id);
nuclear@55 131
nuclear@56 132 /* make it current */
nuclear@56 133 set_current_pid(p->id);
nuclear@42 134
nuclear@68 135 /* build the current vm map */
nuclear@68 136 cons_vmmap(&p->vmmap);
nuclear@68 137
nuclear@54 138 /* execute a fake return from interrupt with the fake stack frame */
nuclear@54 139 intr_ret(ifrm);
nuclear@42 140 }
nuclear@42 141
nuclear@72 142 int sys_fork(void)
nuclear@57 143 {
nuclear@57 144 int i, pid;
nuclear@57 145 struct process *p, *parent;
nuclear@57 146
nuclear@57 147 disable_intr();
nuclear@57 148
nuclear@57 149 /* find a free process slot */
nuclear@57 150 /* TODO don't search up to MAX_PROC if uid != 0 */
nuclear@57 151 pid = -1;
nuclear@57 152 for(i=1; i<MAX_PROC; i++) {
nuclear@57 153 if(proc[i].id == 0) {
nuclear@57 154 pid = i;
nuclear@57 155 break;
nuclear@57 156 }
nuclear@57 157 }
nuclear@57 158
nuclear@57 159 if(pid == -1) {
nuclear@57 160 /* process table full */
nuclear@57 161 return -EAGAIN;
nuclear@57 162 }
nuclear@57 163
nuclear@57 164
nuclear@57 165 p = proc + pid;
nuclear@57 166 parent = get_current_proc();
nuclear@57 167
nuclear@57 168 /* allocate a kernel stack for the new process */
nuclear@57 169 if((p->kern_stack_pg = pgalloc(KERN_STACK_SIZE / PGSIZE, MEM_KERNEL)) == -1) {
nuclear@57 170 return -EAGAIN;
nuclear@57 171 }
nuclear@57 172 p->ctx.stack_ptr = PAGE_TO_ADDR(p->kern_stack_pg) + KERN_STACK_SIZE;
nuclear@57 173 /* we need to copy the current interrupt frame to the new kernel stack so
nuclear@57 174 * that the new process will return to the same point as the parent, just
nuclear@57 175 * after the fork syscall.
nuclear@57 176 */
nuclear@57 177 p->ctx.stack_ptr -= sizeof(struct intr_frame);
nuclear@57 178 memcpy((void*)p->ctx.stack_ptr, get_intr_frame(), sizeof(struct intr_frame));
nuclear@57 179 /* child's return from fork returns 0 */
nuclear@57 180 ((struct intr_frame*)p->ctx.stack_ptr)->regs.eax = 0;
nuclear@57 181
nuclear@59 182 /* we also need the address of just_forked in the stack, so that switch_stacks
nuclear@59 183 * called from context_switch, will return to just_forked when we first switch
nuclear@59 184 * to a newly forked process. just_forked then just calls intr_ret to return to
nuclear@59 185 * userspace with the already constructed interrupt frame (see above).
nuclear@59 186 */
nuclear@57 187 p->ctx.stack_ptr -= 4;
nuclear@57 188 *(uint32_t*)p->ctx.stack_ptr = (uint32_t)just_forked;
nuclear@57 189
nuclear@57 190 /* initialize the rest of the process structure */
nuclear@57 191 p->id = pid;
nuclear@57 192 p->parent = parent->id;
nuclear@72 193 p->child_list = 0;
nuclear@57 194 p->next = p->prev = 0;
nuclear@57 195
nuclear@72 196 /* add to the child list */
nuclear@72 197 p->sib_next = parent->child_list;
nuclear@72 198 parent->child_list = p;
nuclear@72 199
nuclear@57 200 /* will be copied on write */
nuclear@57 201 p->user_stack_pg = parent->user_stack_pg;
nuclear@57 202
nuclear@69 203 /* clone the parent's virtual memory */
nuclear@69 204 clone_vm(p, parent, CLONE_COW);
nuclear@57 205
nuclear@57 206 /* done, now let's add it to the scheduler runqueue */
nuclear@57 207 add_proc(p->id);
nuclear@57 208
nuclear@57 209 return pid;
nuclear@57 210 }
nuclear@47 211
nuclear@72 212 int sys_exit(int status)
nuclear@72 213 {
nuclear@72 214 struct process *p, *child;
nuclear@72 215
nuclear@72 216 p = get_current_proc();
nuclear@72 217
nuclear@72 218 /* TODO deliver SIGCHLD to the parent */
nuclear@72 219
nuclear@72 220 /* find any child processes and make init adopt them */
nuclear@72 221 child = p->child_list;
nuclear@72 222 while(child) {
nuclear@72 223 child->parent = 1;
nuclear@72 224 child = child->sib_next;
nuclear@72 225 }
nuclear@72 226
nuclear@72 227 cleanup_vm(p);
nuclear@72 228
nuclear@72 229 /* remove it from the runqueue */
nuclear@72 230 remove_proc(p->id);
nuclear@72 231
nuclear@72 232 /* make it a zombie until its parent reaps it */
nuclear@72 233 p->state = STATE_ZOMBIE;
nuclear@72 234 p->exit_status = (status & _WSTATUS_MASK) | (_WREASON_EXITED << _WREASON_SHIFT);
nuclear@72 235
nuclear@72 236 /* wakeup any processes waiting for it
nuclear@72 237 * we're waking up the parent's address, because waitpid waits
nuclear@72 238 * on it's own process struct, not knowing which child will die
nuclear@72 239 * first.
nuclear@72 240 */
nuclear@72 241 wakeup(get_process(p->parent));
nuclear@72 242 return 0;
nuclear@72 243 }
nuclear@72 244
nuclear@72 245 int sys_waitpid(int pid, int *status, int opt)
nuclear@72 246 {
nuclear@72 247 struct process *p, *child;
nuclear@72 248
nuclear@72 249 p = get_current_proc();
nuclear@72 250
nuclear@72 251 restart:
nuclear@72 252 if(pid <= 0) {
nuclear@72 253 /* search for zombie children */
nuclear@72 254 child = p->child_list;
nuclear@72 255 while(child) {
nuclear@72 256 if(child->state == STATE_ZOMBIE) {
nuclear@72 257 break;
nuclear@72 258 }
nuclear@72 259 child = child->sib_next;
nuclear@72 260 }
nuclear@72 261 } else {
nuclear@72 262 if(!(child = get_process(pid)) || child->parent != p->id) {
nuclear@72 263 return -ECHILD;
nuclear@72 264 }
nuclear@72 265 if(child->state != STATE_ZOMBIE) {
nuclear@72 266 child = 0;
nuclear@72 267 }
nuclear@72 268 }
nuclear@72 269
nuclear@72 270 /* found ? */
nuclear@72 271 if(child) {
nuclear@72 272 int res;
nuclear@72 273 struct process *prev, dummy;
nuclear@72 274
nuclear@72 275 if(status) {
nuclear@72 276 *status = child->exit_status;
nuclear@72 277 }
nuclear@72 278 res = child->id;
nuclear@72 279
nuclear@72 280 /* remove it from our children list */
nuclear@72 281 dummy.sib_next = p->child_list;
nuclear@72 282 prev = &dummy;
nuclear@72 283 while(prev->next) {
nuclear@72 284 if(prev->next == child) {
nuclear@72 285 prev->next = child->next;
nuclear@72 286 break;
nuclear@72 287 }
nuclear@72 288 }
nuclear@72 289 p->child_list = dummy.next;
nuclear@72 290
nuclear@72 291 /* invalidate the id */
nuclear@72 292 child->id = 0;
nuclear@72 293 return res;
nuclear@72 294 }
nuclear@72 295
nuclear@72 296 /* not found, wait or sod off */
nuclear@72 297 if(!(opt & WNOHANG)) {
nuclear@72 298 /* wait on our own process struct because
nuclear@72 299 * we have no way of knowing which child will
nuclear@72 300 * die first.
nuclear@72 301 * exit will wakeup the parent structure...
nuclear@72 302 */
nuclear@72 303 wait(p);
nuclear@72 304 /* done waiting, restart waitpid */
nuclear@72 305 goto restart;
nuclear@72 306 }
nuclear@72 307
nuclear@72 308 return 0; /* he's not dead jim */
nuclear@72 309 }
nuclear@72 310
nuclear@47 311 void context_switch(int pid)
nuclear@42 312 {
nuclear@56 313 static struct process *prev, *new;
nuclear@49 314
nuclear@55 315 assert(get_intr_state() == 0);
nuclear@56 316 assert(pid > 0);
nuclear@56 317 assert(last_pid > 0);
nuclear@55 318
nuclear@56 319 prev = proc + last_pid;
nuclear@54 320 new = proc + pid;
nuclear@52 321
nuclear@56 322 if(last_pid != pid) {
nuclear@57 323 set_current_pid(new->id);
nuclear@47 324
nuclear@56 325 /* switch to the new process' address space */
nuclear@56 326 set_pgdir_addr(new->ctx.pgtbl_paddr);
nuclear@47 327
nuclear@56 328 /* make sure we'll return to the correct kernel stack next time
nuclear@56 329 * we enter from userspace
nuclear@56 330 */
nuclear@56 331 tss->esp0 = PAGE_TO_ADDR(new->kern_stack_pg) + KERN_STACK_SIZE;
nuclear@57 332
nuclear@57 333 /* push all registers onto the stack before switching stacks */
nuclear@57 334 push_regs();
nuclear@57 335
nuclear@57 336 /* XXX: when switching to newly forked processes this switch_stack call
nuclear@57 337 * WILL NOT RETURN HERE. It will return to just_forked instead. So the
nuclear@57 338 * rest of this function will not run.
nuclear@57 339 */
nuclear@57 340 switch_stack(new->ctx.stack_ptr, &prev->ctx.stack_ptr);
nuclear@57 341
nuclear@57 342 /* restore registers from the new stack */
nuclear@57 343 pop_regs();
nuclear@57 344 } else {
nuclear@57 345 set_current_pid(new->id);
nuclear@56 346 }
nuclear@56 347 }
nuclear@56 348
nuclear@56 349
nuclear@56 350 void set_current_pid(int pid)
nuclear@56 351 {
nuclear@56 352 cur_pid = pid;
nuclear@56 353 if(pid > 0) {
nuclear@56 354 last_pid = pid;
nuclear@56 355 }
nuclear@47 356 }
nuclear@51 357
nuclear@51 358 int get_current_pid(void)
nuclear@51 359 {
nuclear@51 360 return cur_pid;
nuclear@51 361 }
nuclear@51 362
nuclear@51 363 struct process *get_current_proc(void)
nuclear@51 364 {
nuclear@56 365 return cur_pid > 0 ? &proc[cur_pid] : 0;
nuclear@51 366 }
nuclear@51 367
nuclear@51 368 struct process *get_process(int pid)
nuclear@51 369 {
nuclear@72 370 struct process *p = proc + pid;
nuclear@72 371 if(p->id != pid) {
nuclear@72 372 printf("get_process called with invalid pid: %d\n", pid);
nuclear@72 373 return 0;
nuclear@72 374 }
nuclear@72 375 return p;
nuclear@51 376 }
nuclear@72 377
nuclear@72 378 int sys_getpid(void)
nuclear@72 379 {
nuclear@72 380 return cur_pid;
nuclear@72 381 }
nuclear@72 382
nuclear@72 383 int sys_getppid(void)
nuclear@72 384 {
nuclear@72 385 struct process *p = get_current_proc();
nuclear@72 386
nuclear@72 387 if(!p) {
nuclear@72 388 return 0;
nuclear@72 389 }
nuclear@72 390 return p->parent;
nuclear@72 391 }