Project 4b: xv6 ThreadsOverviewIn this project, you'll be adding real kernel threads to xv6. Sound like fun? Well, it should. Because you are on your way to becoming a real kernel hacker. And what could be more fun than that? Specifically, you'll do three things. First, you'll define a new system
call to create a kernel thread, called Note: Because the VM layout is based on the last project, so to help you start with, we've provided a new xv6 kernel with the new layout, i.e. 2 unmapped pages starting at Details
In your last project, you have moved up the stack to Here is a diagram to help you better understand the new VM layout designed for threads.
+-------------------------------+ <--- USERTOP | Stack of the Process | 1 PAGE +-------------------------------+ | Guard Page (FREE) | 1 PAGE +-------------------------------+ | Stack of Thread 1 | 1 PAGE +-------------------------------+ | Guard Page (FREE) | 1 PAGE +-------------------------------+ | | : : : ^ : | | | +-------------------------------+ <--- proc->sz | Heap | +-------------------------------+ | Code + Data | 1 PAGE +-------------------------------+ | Guard Page (FREE) | 2 PAGES +-------------------------------+ The reason we need a guard page for the thread stack is because we want to prevent stack overflow. If the threads' stacks are adjacent, then one thread may misbehave, e.g. heavy recursion, and thus access the stack of the neighboring thread. However, this design cannot resolve the scenario where the thread "randomly" access address that is mapped (to physical memory) but only valid to some other thread. Your new clone system call should look like this: The given argument There are several assumptions upon thread creations: clone() should fail, i.e. return -1 if there are already 8 threads running concurrently.
arg in clone() can be a NULL pointer, and clone() should not fail.
USERTOP , as the top 18 pages are reserved for the stack of the process and 8 threads.
Another new system call is For example, process A clones thread B and C, now A calls Note that if B clones another thread D, then A will never wait for D as D's parent is B. When the thread finishes, this routine should also reset the stack that this thread used such that later thread can reuse this stack. You also need to think about the semantics of a couple of existing system
calls. For example, Next, you should create a user thread library that has lock_t that one uses to declare a lock, and two routines
lock_acquire(lock_t *) and lock_release(lock_t *) , which acquire and
release the lock. The spin lock should use x86 atomic exchange as the hardware support (see the xv6 kernel for an example of something close to what you
need to do). Another routine, lock_init(lock_t *) , is used to
initialize the lock as need be.
cond_t that one uses to declare a condition variable, and two routines cond_wait(cond_t *, lock_t *) and cond_signal(cond_t *) , which wait and signal the CV. You may want to look at what sleep() and wakeup() do in kernel/proc.c for some hints. Another routine cond_init(cond_t *) , is used to initialize the CV as need be.
NOTE: The implementation of condition variables involves a queue of threads. Therefore, you have to
maintain a queue by yourself in cond_t . You have to also make sure that the lock is released and the caller of cond_wait() is put to sleep atomically.
sem_t that one uses to declare a semaphore,
and two routines sem_wait(sem_t *) and sem_post(sem_t *) , which wait and post the semaphore. You should build the semaphore based on the lock and condition variable you implemented. Another routine sem_init(sem_t *, int) , is used to initialize the semaphore as need be.
One thing you need to be careful with is when an address space is grown by a thread in a multi-threaded process. Trace this code path carefully and see where a new lock is needed and what else needs to be updated to grow an address space in a multi-threaded process correctly. Have fun! The CodeThe code with the new VM layout (and associated README) can be found in ~cs537-2/ta/p4b . Everything you need to build and run and even debug the kernel is in there, as before. As usual, it might be good to read the xv6 book a bit: Here . You may also find this book useful: Programming from the Ground Up . Particular attention should be paid to the first few chapters, including the calling convention (i.e., what's on the stack when you make a function call, and how it all is manipulated). Test Along The WayIt is a good habit to get basic functionalities working before moving to advanced features. You can run an individual test with the following command: shell% ~cs537-2/ta/tests/4b/runtests testname
Note: there will be 20% hidden tests for this part :) |