Vineet Gupta (vineet AT ural.wustl.edu) Main Task: 1) Create and dispatch threads. 2) To unsure that no thread block forever. If it happens, that thread should be suspended and remaining threads should be dispatched. Goal: 1) Portability and avoid assembly code. Proposed API: a) A data structure mctx_t for holding thread context. b) Function void mctx_create(mctx_t * mctx, void (*sf_addr)(void *), void *sf_arg, void *sk_addr, size_t sk_size) -This initialize a thread context structure in mctx, with start function sf_addr, starting function argument sf_arg, stack sk_addr, having size sk_size in bytes. c) Function void mctx_savemctx_t *mctx) which saves current cotext to mchine context structure mctx. d) Fuction void mctx_restore(mctx_t *mctx) which restores new machine from machine context structure mctx. e) void mctx_switch(mctx_t *mctx_old, mctx_t *mctx_new), which switches from current machine context (saved to mctx_old for later) to mctx_new. *What to use: ucontex(3) and family- getcontext(3) makecontext(3) swapcontext(3) setcontext(3) --refer to man pages * jmp_buf structure based functions like setjump longjmp to be used for dealing with dispatching issues (for context save, restore and switch). * also sigjmp_buf based sigsetjmp(3) siglongjmp(3) -- for saving and restoring thread environment. * stack function sigaltstack(2) for dealing with stack * Most difficult part of implementation will be cntx_create. because setjmp(3) and sigaltstack(2) cann't be trivially combined to form mctx_create. A General Idea for above: For mctx_create is to configure stack as a signal stack via sigaltstack(2), send current process a signal to transfer execution ontrol onto this stack, save the machine context there via setjmp(3) and bootstarp into startup routine. Other high level issues are: * Scheduler * Run Queues of contexts (based on number of priority levels) that we need to deal with. -- more to come -- =============================================================================== User-Level Threads API ======================= Points to remember while trying to reach each milestone to its completion. Milestone A ------------ Header file for all operations related to ucontext_t structure and corresponding operations is 'ucontext.h'. All threads have same priority, without any support for preemption or scheduling. All threads communicate via shared memory (global variables). This will involve maintaining a queue of all running threads with next thread to be executed available in front of queue. As soon as each thread completes it appends itself to the end of queue. As there is no preemption, any thread whichever gets controlled can run forever without giving CPU to any other thread. Hence its the responsibility of user using 'uth_lib' API to implement condition on which thread should give CPU to other threads (yield). 'uth_init' should be called first to start thread library. This will do initial setup for library. This will include allocating space for 'N' number of threads, setting limit on total number of threads possible and saving current context to parent thread (or main thread), which is initializing library.This is required as we'll be swapping context from main to child threads and then back to parent (main) thread at some points. One way is to allocate one context extra, which will be used to save context for main thread and append this to end of queue as soon as first call to 'uth_yield' is made. 'TickSz' is duration for clock interrupt. For milestone A this will always be '0' as there is no preemption. 'Debug' parameter is for showing information related to program while running like synopsis of run queue and status of various threads. One possible way to implement 'N' number of threads is to use queue data structure, as library will include lot of appending contexts to end of it and getting context in the front to set for current execution. Each call to 'uth_create' will create a thread which will use the space allocated in 'uth_init'. We can allocate as many as 'N' number of threads. On each call a copy of current context should be made (getcontext) and then new stack space should be allocated for newly created thread (see ucontext-basic.c). As soon as stack space is allocated 'makecontext' should be called to assign callback function and any argument (*Arg) to this newly created thread. Remember, we are still in parent thread(context) and any switch to this newly made context will only be made at call of 'uth_yield'. 'uth_yield' will yield control to next thread in the queue. This essentially involves getting thread context in front of the queue, appending current thread context to end of the queue and then calling 'swapcontext'. This swapcontext call will save current running context to appropriate thread id and give CPU control to the thread fetched from front of the queue. A call to 'uth_exit' will de-allocate stack associated with current thread context and destroy the structure associated with it.If there is any thread waiting for this thread to exit (by calling uth_join on this thread id), uth_exit will return exit status to that thread and swap context to it (this will only be done in milestone B). In other case it will just yield to front thread in run queue. Milestone B ----------- Until milestone A, it was responsibility of user to keep track of which thread is done and hence when to yield to next thread in queue (from main method). But now we'll use 'uth_join' which will simplify this process by enabling any thread to wait for any other thread till its completion. Once thread completes using call uth_exit, it reports it exit status to thread which called uth_join. A call to 'uth_join' will suspend the execution of the thread calling uth_join and transfer CPU control to thread (lets call thread having id 'Tid') on which uth_join is being called (specifying id as parameter to uth_join call). Call to uth_join will return when 'Tid' will execute function 'uth_exit' giving its status to the thread which called uth_join. A way to implement this would be to maitain a separate array of ids which are waiting after a call to uth_join. This way whenever a thread 'Tid' is executing uth_exit, it will look into this array to find out is there any thread which is waiting for be joined on it. If so, it will swap context to this thread. If there is no thread waiting to be joined by 'Tid', this will simply yield to thread in the front of run queue (as in mileA.c). Milestone C ----------- Now we have multiple thread context queue having different priority. Available priority levels are HI_PRI, LO_PRI, IDLE_PRI. Whenever there is a running thread(s) in high priority queue it will keep on running in RR fashion until it completes (or it goes to sleep), at that time scheduler starts running threads in next queue (lower priority than former). Whenever any thread in higher priority queue wakes up, controls get transferred to that. One thing to remember is that lots of printf statements might get your program work in first place and when you remove them, it might deviate from its expected correct behaviour. Remember this when things don't go in way expected. * fibFiber is CPU bound thread (total 2 instances). * usage_mon is high priority thread, which sleeps for 100 ms, collects data; and then repeat this again. * As usual, main makes calls to uth_init, uth_create, uth_join. If we look at mileC.c, we can visualize execution of the program as shown below: Program Counters: Queue main (A) HI -> runQ[0] -> usage_mon fibFiber0 (B) LO -> runQ[1] -> fibFiber1 (C) LO -> runQ[2] -> idle, pseudo_thread usage_mon (D) joinQ -> main (loops forever) main fibFiber0 fibFiber1 ----- --------- --------- uth_init uth_create uth_create uth_create for{ for{ uth_sleep(100) for{ for{ uth_usage (2 times) B goes here C goes here D -> uth_sleep(100) } } uth_usage (2 times) } } uth_exit uth_exit uth_join uth_join uth_join Voluntary giving of CPU : A, D... Involuntary giving of CPU : B, C uth_join (voluntary case)- (0) Block interrupts (1) "Register itself, ie waiting for a thread to terminate, ie en-queue onto joinQ. (2) Context switch to highest priority runnable thread. (?) Unblock interrupts? Involuntary case- Hardware stack("Interrupts") stack ----- top -> signal handler ---- ----