A multithreading library for Linux
Video demonstration available at: https://claud.pro/multithreading/
- The overall architecture of the User-level thread library consist of three main modules (Queue API,uthread API, andPreemption) which are technically separatable from each other but also interoperable.Queue APIis a basal data structure.uthread APIis a library for users to start multi-threaded function in a process.Preemptionis an activatable feacture for the library for extra control.
- The data structure choice: Queuewhich is FIFO. It is good for round-robin thread scheduling logic.
- Platform: For Linuxin user-level.
- Relationship among modules: uthread APIis build on top ofQueue APIwhilePreemptionis a necessary added feature on top ofuthread API.
 
 
The Queue is implement by Doubly Linked List with FIFO rule. I choose this structure because it provides an efficient way to add or remove nodes. Doubly Linked List also allow us delete a node by simple linking the node before it and the node after it. FIFO is excatly what I need for thread scheduling.
- There are 12 unit tests.
- test_createand- test_queue_simpleare pre-given
- test_delete_1enqueues two match items and an unmatch item in the queue to test if- queue_dequeuedeletes the first match item near the head. In similar fashion,- test_delete_2tests deletion of the head item and- test_delete_3tests deletion of the tail item.
- test_iterateenqueues 4 integers and applys a- inc_itemfucntion that increases int item by 1 or delete item if it has a value of 3 to each of the item. Assert head value and queue length to test if- test_iteratehas the right behaviour.
- test_en_dequeue_1,- test_en_dequeue_2,- test_en_dequeue_3,- test_en_dequeue_4: more enqueue/dequeue actions to cove edge cases.
- test_error_1tests error handling when trying to- queue_dequeuean empty queue.- test_error_2tests error handling when trying to- queue_deletean empty queue.
I designed a sturcture TCB to store info for a thread, including context,TID,state, a stack for storing context and return value.
I have following global varibles
- ready_queue: stores active threads
- zombie_queue: stores zombie threads
- thread_count: count how many thread I have now and provide unique TID
- current_thread: a pointer to TCB which is currently running
- uthread_start
 In this function, I initialize global variables for the API, includingready_queue,zombie_queueandthread_count. Then I create a main thread(TID=0) and set it as current_thread. Ifpreemptis 1, I will also callpreempt_startto start using preempt.
- uthread_stop
 This is the final function I should call to stop running uthread API. I check if there is anything left inready_queue. If so, Iuthread_jointhese threads and let them finish. Then I free everything I allocated, including global variables and anything left in the queues to prevent memory leak.
- uthread_create
 This function create a new thread. I allocate a TCB and a stack for it and put it at the end of ourready_queue. Then I calluthread_ctx_initto initialize it. I want the whole process can be done safely, so I temporarily disable preempt at the beginning and enable it after the new thread was put in queue.
- uthread_exit
 This function deal with a finished thread. I stores return value in its TCB and put this finished thread into thezombie_queue. Then I calluthread_ctx_switchto run next avaliable thread. Also, I disable preempt here.
- uthread_yield
 This function allows a thread yield and let next thread run. I put the current_thread to the end ofready_queueand dequeue a new thread from it. Then I calluthread_ctx_switchto run the new thread. Here, I also disable preempt to protect the whole process.
- uthread_join
 This function needs the parent thread to wait its child. So I use a infinite loop to make the parent "wait". In the loop, I check if the child thread is finished. I callqueue_iterateto check ifzombie_queuehas the child thread. If not, parent thread will keep yield until the child finish. I check if the child thread is in theready_queuein the beginning to prevent join something doesn't exist and waiting forever.
I basically implement 2 types of testing.
- Let a thread create a lot of child threads
- Let the child thread keep create child threads
 Then I mix the 2 type to implement stressful test on our API.
 Also, I use valgrind to check memory leak.
- sig_handlersignal handler to ask a thread to yield by calling- uthread_yield
- preempt_startfirst mounts- sig_handlerto- SIGVTALRMusing- sigactionthen sets up a virtual alarm that sends- SIGVTALRMby specifying certain- tv_usecusing- setitimer. This virtual alarm counts how long has the thread been using the CPU.
- preempt_stopfirst stops the timer using- setitimerthen restore- SIG_DFLfor- SIGVTALRM. This way it prevents accidental termination of thread.
- preempt_enablefirst creates a signal set- unblock_alarmand adds it to the signal mask using- sigaddsetpairing with- SIGVTALRM, make the change for- SIG_UNBLOCKsignals with- sigprocmask
- In similar fashion, preempt_disablefirst creates a signal setblock_alarmand adds it to the signal mask usingsigaddsetpairing withSIGVTALRM, make the change forSIG_BLOCKsignals withsigprocmask
- The idea is simple, it schedules two thread thread0andthread1with Preemption on.thread0is going to take control of the CPU with a while-loop forever if Preemption doesn't work.thread1prints a message if it got the CPU.
