typedef struct sem { int value; other_stuff } *Sem;There are two actions defined on semaphores: P(Sem s) and V(Sem s). (The book calls them wait() and signal()).
typedef void *Gsem; Gsem make_gsem(initval) int initval; void kill_gsem(g) Gsem g; int gsem_getval(g) Gsem g; void gsem_P(g, function, arg) Gsem g; void (*function)(); void *arg; void gsem_V(g) Gsem g;Make_gsem() and kill_gsem() create and destroy semaphores respectively. gsem_getval() returns the value of the semaphore. The reason that Gsem is a (void *) and not a struct with a val field is that you should not be allowed to set the value of the semaphore except when it is created. Thus, it is a (void *) and you can only access its value through the procedure gsem_getval().
gsem_P() and gsem_V() are standard P() and V() operations. The only tricky thing is that since P() is a potentially blocking call, you must pass it a continuation, and it will never return. It simply calls the continuation when it unblocks.
Threads that block on a gsem_P() call will unblock in FIFO order. You may make use of this fact if you need to do so to eliminate starvation.
It is an error to call kill_gsem() on a semaphore that has blocked threads.
First, look at printqsim.h. There are a few differences from before. First, we define some states (START, etc), and we include a state field in the Spq struct. Also, we include an integer nsofar and a job j in the Spq struct. Nsofar is the number of events so far (either for the user or the printer, and j is the current job that the printer is printing.
The other big change is that since submit_job() and get_print_job() are now potentially blocking calls, they will not return, and instead must be passed continuations. Additionally, since get_print_job() does not return, it cannot pass the job to print in its return value. Instead, it sets the j field of the printer's spq struct.
Now, look at printqsim.c. First, check out the main() routine. It sets the new fields (setting the state of all threads to START) and then forks off the threads using pt_fork(). It then exits using pt_exit(). Had it simply returned, the program would have exited.
Now, look at the user_thread() routine. This has been rewritten to use a continuation-based style. If the thread is in the START state, then it must sleep for a random number of seconds before printing. This is done with the pt_sleep() call. Since pt_sleep() is a blocking call, it does not return. Instead it is passed a continuation to user_thread() so that when it is done, user_thread() gets called again. However, the state is first set to SLEEPING so that when user_thread() is called again, it knows what to do.
What is does is now submit the job for printing by calling submit_job(). Since submit_job() may block, it must be passed a continuation, and like before, the continuation specifies to call user_thread() again. However, this time the state is set to PRINTING. When user_thread() is called again, it will know that the submission to the print queue is complete, and it will increment nsofar and start again.
printer_thread() is written in a similar style, only now the states are START, GETTING_JOB and PRINTING. When it is called with a state of GETTING_JOB, we know that a new job to print is in s->j, and we print it.
Now, the structure for submit_job() is very straightforward. It allocates a slot by calling P() on nslots, enters the job, and then calls V() on njobs to awake any printers that may be waiting to print a job.
Unfortunately, the continuation-based structure of the pt-library makes this a little cumbersome. To call gsem_P(), we have to pass a continuation to the code that finishes submit_job(). I do this by allocating a Cont struct and passing this as the argument to submit_job_cont(). When submit_job_cont() is called, it finishes submit_job and then calls pt_yield() with the original continuation passed to submit_job(). This will give control back to user_thread(). Note that I could have just called f(a).
get_printer_job() is structured in the same manner.
Give it a try and run it -- you'll see that everything works. Note that in the pthreads solution we had to protect all the code in submit_job() and get_printer_job() with a mutex. This is because the pthreads system is preemptive. Since the pt-library is non-preemptive, we don't have to worry about our code getting preempted -- threads only switch at blocking points (e.g. gsem_P(), pt_yield(), pt_sleep(), etc).
I'll let you decide which of these you like better.