Pthread library
Table of contents
Threading
A subset of the pthread (Pintos thread) library is provided for you in lib/user/pthread.h. These functions serve as the glue between the high-level API of pthread_create, pthread_exit, and pthread_join and the low-level system call implementation of these functions. We’ll walk you through how the pthread library works by starting at the high-level usage in one of our tests, and walk down the stack until we get to the kernel syscall interface.
The test we will walk through is tests/userprog/multithreading/create-simple.c.
In the create-simple test, we see how the high-level API of the threading library is supposed to work. The main thread of the process first runs test_main. It then creates a new thread to run thread_function with the pthread_check_create call, and waits for that thread to finish with the pthread_check_join. The expected output of this test is shown in tests/userprog/multithreading/create-simple.ck.
The functions pthread_check_create and pthread_check_join are simple wrappers (defined in tests/lib.c) around the “real” functions, pthread_create and pthread_join, that take in roughly the same values and return the same values as pthread_create and pthread_join, and ensure that pthread_create and pthread_join did not fail. The APIs for pthread_create and pthread_join are:
tid_t pthread_create(pthread_fun fun, void* arg)A
pthread_funis simply a pointer to a function that takes in an arbitraryvoid*argument, and returns nothing. This is defined inuser/lib/pthread.h. So, the arguments topthread_createare a function to run, as well as an argument to give that function. This function creates a new child thread to run thepthread_funwith argumentarg. This function returns to the parent thread the TID of the child thread, orTID_ERRORif the thread could not be created successfully.bool pthread_join(tid_t tid)The caller of this function waits until the thread with TID
tidfinishes executing. This function returns true iftidwas valid.
The implementation of pthread_create and pthread_join are in the file lib/user/pthread.c. They each are simple wrappers around the functions sys_pthread_create and sys_pthread_join, which are syscalls for the OS that you will be required to implement. Their APIs are similar to pthread_create and pthread_join, and are as follows:
tid_t sys_pthread_create(stub_fun sfun, pthread_fun tfun, const void* arg)The
sys_pthread_createfunction creates a new thread to runstub_fun sfun, and gives it as arguments apthread_funand avoid*pointer, which is intended to be the argument of thepthread_fun. It returns to the parent the TID of the created thread, orTID_ERRORif the thread could not be created.What is a stub function? There is only one stub function that we are concerned with here, called
_pthread_start_stubdefined inlib/user/pthread.c, and its implementation is copied below. This function returns nothing but takes two arguments: a function to run, and an argument for that function. The stub function runs the function on the argument, then callspthread_exit().pthread_exit()is a system call that simply kills the current user thread./* OS jumps to this function when a new thread is created. OS is required to setup the stack for this function and set %eip to point to the start of this function */ void _pthread_start_stub(pthread_fun fun, void* arg) { (*fun)(arg); // Invoke the thread function pthread_exit(); // Call pthread_exit }Why this extra layer of indirection? You might have noticed in
tests/userprog/multithreading/create-simple.cthatpthread_exit()was never called; instead, as soon as the created thread returns fromthread_function, it is presumed to have been killed. The stub function is how this is implemented: the OS actually jumps to_pthread_start_stubinstead of directly jumping tothread_functionwhen the new thread is created. The stub function then callsthread_function. Then, whenthread_functionreturns, it returns back into_pthread_start_stub. Then, the implementation of_pthread_start_stubkills the thread by callingpthread_exit().tid_t sys_pthread_join(tid_t tid)The caller of this function waits until the thread with TID
tidfinishes executing. This function returns the TID of the child it waited on, orTID_ERRORif it was invalid to wait on that child.void sys_pthread_exit(void) NO_RETURNThis function terminates the calling thread. The function
pthread_exitsimply calls this function.
The functions sys_pthread_create, sys_pthread_join, and sys_pthread_exit are system calls that you are required to implement for this project. They have slightly different APIs than the high level pthread_create, pthread_join, and pthread_exit functions defined in lib/user/pthread.h, but are fundamentally very similar. We have set up the user-side of the syscall interface for you in lib/syscall-nr.h, lib/user/syscall.c, and lib/user/syscall.h, and it is your job to implement these system calls in userprog/ in the kernel.
User-level synchronization
Our pthread library also provides an interface to user-level synchronization primitives. See lib/user/syscall.h. We define the primitives lock_t and sema_t to represent locks and semaphores in user programs. A lock_t (or sema_t) is a char because we only require processes to support up to 2^8 locks and 2^8 semaphores, and a char is 1 byte or 8 bits. A lock_t (or sema_t) is thus an identifier for the underlying struct lock (or struct semaphore) kernel-level synchronization primitive that the user-level is tied to. You can change these definitions if you’d like, but we found the current definitions sufficient for our implementation. We provide the following syscall stubs:
bool lock_init(lock_t* lock)Initializes
lockby registering it with the kernel, and returns true if the initialization was successful. Intests/lib.c, you will see we definelock_check_init, which is analogous topthread_check_createandpthread_check_join; it simply verifies that the initialization was successful.You do not have to handle the case where
lock_initis called on the same pointer multiple times; you can assume that the result of doing so is undefined behavior. However,lock_initshould still create a new lock even if the pointer points to the same value that an earlier argument to the syscall pointed to (as the value is usually unitialized garbage).void lock_acquire(lock_t* lock)Acquires
lockand exits the process if acquisition failed. The syscall implementation oflock_acquireshould return a boolean as to whether acquisition failed; the user level implementation oflock_acquireinlib/user/syscall.chandles termination of the process. You should not update thelock_acquire(or for that matter, any of the below functions) code inlib/user/syscall.cto remove theexitcall; it will simply make debugging more difficult.void lock_release(lock_t* lock)Releases
lock, and exits the process if the release failed. The syscall implementation oflock_releaseshould return a boolean as to whetherreleasefailed.bool sema_init(sema_t* sema, int val)Initializes
sematovalby registering it with the kernel, and returns true if the initialization was successful. Intests/lib.c, you will see we definesema_check_init, which is analogous topthread_check_createandpthread_check_join; it simply verifies that the initialization was successful.You do not have to handle the case where
sema_initis called on the same argument multiple times; you can assume that the result of doing so is undefined behavior. However,sema_initshould still create a new semaphore even if the pointer points to the same value that an earlier argument to the syscall pointed to (as the value is usually unitialized garbage).void sema_down(sema_t* sema)Downs
semaand exits the process if the down operation failed. The syscall implementation ofsema_downshould return a boolean as to whether the down operation failed.void sema_up(sema_t* sema)Ups
sema, and exits the process if the up operation failed. The syscall implementation ofsema_upshould return a boolean as to whether the up operation failed.