Unix (Solaris) Threads and Semaphores

This document describes the system calls to create and manage threads in the Solaris version of Unix. This calls are similar to those found in Windows NT and Posix (attempt to standardize unix).
  1. Threads
    1. Thread type
    2. Prototype function for a thread
    3. Thread Creation constants
    4. Create a thread
    5. Wait for a thread
    6. Other thread calls
  2. Semaphores
    1. Semaphore type
    2. Initializing a semaphore
    3. Semaphore operations

Threads [top]

Thread Type [top]

The thread header file to include is thread.h. This header file contains the definition of a type, thread_t. This type is basically an integer (On hawk, it is just defined as an unsigned int. Its use is as a thread identifier.
Example: Declare two variables t1, and t2 to hold thread id's:

        #include <thread.h>

        thread_t t1, t2; 

Prototype function for a thread [top]

The system call that creates a thread is passed the name of a function in the program code which that thread will execute. The prototype of this function must be like this function declaration of f. That is, one parameter of type void * and a return type, also void *. This is not a great restriction since the parameter can be the address of any item. It does generally require that the function use a conversion (or cast) on the parameter before using it.
void * f(void *p);
If f expects p to be a pointer to a double, then f might be written like this:
void * f(void *p)
   double *dp = (double *) p;
   double y;

   y = *dp;
   // do something with the double value in y
   return NULL; 

Thread Creation Constants [top]

When a thread is to be created in Solaris to run independently of the thread that creates it, a flag needs to be passed to the system call to let the kernel know this. Otherwise the kernel will only schedule the new thread when the calling thread finishes. The value to pass is defined as a symbolic constant in thread.h and is of type long.
#include <thread.h>
  long flags;

  flags = THR_BOUND;
Sometimes the calling thread does not want the new thread to be READY to execute at creation. For example, the calling thread may want to create more threads first and do other initialization. Finally after all that is completed, then all the new threads could be made READY and thus elligible to execute. The default is that new threads are READY. The constant THR_SUSPENDED used for the creation flag causes the new thread to be blocked and thus not yet elligible to run. To use both settings, the bitwise or operator on the constants causes the corresponding bits for each one to be set in the flags variable:
#include <thread.h>
  long flags;


Create a thread [top]

The system call to create a thread is:
int thr_create(void * pcallstack, 
               size_t stack_size, 
               void * (*thr_func)(void *), 
               void * pthr_arg, 
               long flags, 
               thread_t *pthr_id);
pcallstack the process address to use for this thread's stack. A value of 0 lets thr_create choose this address for the caller.
stack_size the size of the thread's stack. A value of 0 lets thr_create choose this size for the caller. 
thr_func the function for the thread to execute
pthr_arg the argument to pass to the thread's function when it starts flags the creation flags
pthr_id the address of a thread_t variable. the function will copy the new thread id into this variable.

The return value is 0 if successful.

Wait for a thread [top]

A thread that creates other threads can wait for any one of these threads to finish or for a particular one to finish by calling the following function.
int thr_join(thread_t waitfor_thr, thread_t *pfinshed_thr,
             void **status);
waitfor_thr thread id of a particular thread to wait for. Can be 0.
pfinished_thr address of a thread id variable. If waitfor_thr is 0, then thr_join waits for any thread in the process to finish. This thread's id is copied into this variable. 
status Address of a variable. If not 0, thr_join copies the exit status of the terminated thread.

Other Thread Calls [top]

A running thread can get its own thread id by calling thr_self(). A thread can exit but not terminate any other thread in the process by calling thr_exit():  A thread can yield the processor for one time by calling thr_yield().
void thr_exit(void *status);
thread_t thr_self();
void thr_yield();
where status is either 0 or the address of a variable in which to store the exit status of the thread.

Semaphores [top]

Semaphore type [top]

The synch.h header file contains the definition of a semaphore type, sema_t. This type is a structure with several data members. However, you will not directly use any of the data members. The only way to use a semaphore is through the two functions that provide atomic operations on the semaphore. You can use the semaphore type to declare a semaphore variable, but it is not properly initialized by this declaration. A separate system call is the only way to properly initialize the semaphore.
   #include <synch.h>
    sema_t s;

Initializing a semaphore [top]

The system call to initialize a semaphore is sema_init.
int sema_init(sema_t *sp, unsigned int count,
          int type, void * arg);
sp address of the semaphore variable to be initialized
count the initial value of the integer part of the semaphore
type a constant to indicate in processes or accross process use.
arg This address should be 0. Currently it is not used.

The return value is 0 if successful.


  #include <synch.h>

  sema_t s;

  if ( sema_init(&s, 1, USYNC_THREAD, 0) != 0 )
     // Error: initialization failed

Semaphore Operations [top]

The two operations on a properly initialized semaphore are sema_wait and sema_post:
int sema_wait(sema_t *sp);
int sema_post(sema_t *sp);


  sema_t s;

  // Assume sema_init has been called to initialize s.