Between a call to thread_manager_init() and thread_manager_start() there may be any number of calls to thread_launch(). If the functions of the init and start routines were combined into a single startup routine, this would also have to perform the function of thread_launch(). This can be done easily if we only allow the launch of a single thread; considervoid thread_startup( int size, void (* proc)(int), int param ) { thread_manager_init(); thread_launch( size, proc, param ); thread_manager_start(); }Changing this to allow launching an arbitrary number of threads would be messy! In theory, it can be done, either by passing an array of thread description triples, each containing a size, a pointer to a procedure and a parameter, or by using the obscure C macros va_start(), va_arg() and va_end() (all declared in <stdarg.h>). Each of these approaches to launching variable numbers of threads at the start of a multithreaded application leads to user-level code that is less readable than the code that results from the separation of the startup functions into thread_manager_init() and thread_manager_start().
The biggest difference is that the data structure for thread is designed for embedding in a linked list of threads, while the process table fields in Figure 2-4 are intended for use in an array of records.The second big difference is that the fields in Figure 2-4 include many fields that are not part of our thread implementation. Among these, the memory management fields are there because, in most systems, each process may have a different memory address space, while all of our threads share an address space, and all of the ID fields are present to allow access rights checking functions that are irrelevant to the thread package.
Tannenbaum includes a field labeled process state that is distinct from the process registers; this probably is intended to record whether the process is ready, running, waiting or in some other state. In the thread implementation, this state information is not explicitly recorded, but rather, is determined by the data structure into which the tread record is linked.
The following solution is borrowed from problem 1 of homework 3.struct semaphore { int count; struct thread_queue queue; };
main() { int i; i = 0; if (fork()) { /* parent */ int j; for (j = 0;j < 1000; j++) { i++; } wait(); } else { /* child */ int j; for (j = 0;j < 1000; j++) { i--; } exit(); } printf( "%d\n", i ); }What output did this program produce? Explain why the output is not zero!
The output of the program is 1000; this is nonzero because the parent and child processes in the above program do not share memory. Instead, the child gets a copy of the parent's memory at the time of the fork() call, and this copy (in which i is decremented) is discarded when the child exits. Thus, the value of i printed by the parent is determined only by the increment operations done by the parent!Note that some students added printf statements at the end of each branch of the if statement, and concluded, incorrectly, that the child always ran first, before the parent, because the output of the two printfs was always printed in that order. It is important to remember that the C standard library routine printf() computes its output into a buffer, and that this buffer is flushed to the actual output device either when the buffer is full, when the process requests input or when the process terminates. In the example, the order of process termination determines the order in which the processes flush their buffers. To force immediate output of text to standard output (descriptor number 1), consider using:
write( 1, "text\n", 5 );