Programming Assignment II Due Date: October 20, 199 The goal of this project is to explore implementations of various functions supplied by a user-level threads package. To achieve this goal, the project defines a sequence of steps that will explore alternate implementations of some of the thread functions. These steps are enumerated below. 1. Using cthread_yield (or the thread yield function in the library that you use), develop a preemptive scheduler for switching threads. You should use Unix SIGALARM signal to periodically switch threads on the ready queue. You only need to work with one logical processor in this step of the project. A number of threads running on this processor should time share the CPU time allocated to the logical processor. You will need to use the signal() and ualarm() calls of Unix to complete this part. 2. Similar to step 1, you will fork a set of threads using the functions supplied by the library but the code that switches threads will be written by you. You will need to use the setjmp() and longjmp() (see Unix man pages for these calls), to develop an implementation of a new function called Mythread_yield() which will be used in place of cthread_yield(). Thus, Mythread_yield() achieves context switching between threads. In completing this step, you will use jump buffers and manipulate them as threads are switched. For full credit, describe the data structures used by you to implement Mythread_yield(). Notice that when you use Mythread_yield(), control will not return to the library code when threads are switched. As a result, you should not use any library functions that require interactions with the thread library scheduler. If you need to do synchronization between threads, use read/writes of shared variables to achieve it. 3. To provide synchronize threads, develop a semaphore construct that allows P() and V() operations to be executed. Assume a fixed number of semaphores that are supported by your system. You should be able to use the semaphores with the Mythread_yield() call. Modify the test program used in steps 1 and 2 to include calls to P()/V() to achieve the necessary synchronization. To ensure atomicity of the code that implements P() and V() operations, use signal masking (see Unix call sigsetmask()). You need to submit a report that describes the implementations developed by you. It should also describe what the test programs are and document what works and what does not.