Sunday, October 9, 2011

OS161-Writing Fork(), part 1

In assignment 2, we are asked to implement fork in the system os161.
It has been said that implementing fork() is the most difficult part in this assignment and is very crucial to understand how new process are created.

Despite the very details of the thread subsystem in os161, we will step through the process of the implementation of fork() in order to deepen our understanding of system calls.

fork() does the following things:
- it makes a copy of the invoking process and makes sure that the parent and child processes
each observe the correct return value.
- it returns 0 to the child and a new PID to the parent.
- the core of the fork() system call will invoke "thread_fork()" in kern/thread/thread.c
to create a new kernel thread

The child process should inherit exactly the same state of the parent at the time the parent
had when it made the system call.

By inspecting the code of "runprogram()" in /kern/syscall/runprogram.c, we can get some
insights of how fork() can be implemented.
runprogram() will show us how a program returns user space after a system call.

runprogram() takes a "string" (or char *) program_name and return an integer(EINVAL if error). loadexec() is the core code to load an executable into memory.
loadexec() will
- call kstrdup() to make a new name for the process
- call vfs_open() to load the program / open the file,
- call as_create() to make a new address space ,
- replace the old address spaces of the current thread with the new address spaces,
- call as_activate to make the new address space "seen" by the processor,
- call load_elf() to load the executable indicated by its argument "entrypoint", a virtual address
- call as_define_stack() to set up the user stack region in the address space, which make the
stack pointer point back to USERSTACK, the initial stack pointer for the new process,
- wipe out the old address space,
- change the current thread's name to reflect the new process.

After this, enter_new_process(), in /kern/arch/mips/locore/trap.c,
is called to warp to user mode. This function is used to go to user mode after loading an executable. It creates an ersatz (what the heck is this, figure it out later) trapframe,
sets the program counter to a virtual address,
stores the "argc" into register tf_a0, stores the pointer to arguments
into register tf_a1, and sets the stack pointer. Finally it calls mips_usermode(&tf) to enter
user mode.

Now we will go through the code of fork() step by step.
fork() takes two arguments: a pointer to trapframe an integer (the PID of the parent process).
It will first malloc some spaces in the heap to copy the trapframe, because we might return
to user level and make another system call, which might change the trapframe, before the
child runs. The child will free the copy of the trapframe in the heap. Then we call thread_fork().

Honestly, I have no idea how to implement thread_fork() from scratch. This is super duper hard for me. But...so what? I am going to get the jobs done anyway.

To create a new thread, call thread_create().
We will stick some magic numbers on the bottom end of the stack to catch kernel stack overflow.
Then we need to allocate some memory to the new thread and create a new PID for it.
Then we need to set up various fields associated with the new thread.
For example, we need to copy the virtual address space and current working directory from
its parent.

To be continued...










No comments: