hide random home http://www.be.com/documentation/be_book/KernelKit/sems.html (Amiga Plus Extra No. 5/97, 05/1997)



The Kernel Kit: Semaphores

Declared in: <kernel/OS.h>


Overview

A semaphore is a token that's used in a multi-threaded operating system to coordinate access, by competing threads, to "protected" resources or operations. This coordination usually takes one of these tacks:

Examples of these uses are given in sections below.


How Semaphores Work

A semaphore acts as a key that a thread must acquire in order to continue execution. Any thread that can identify a particular semaphore can attempt to acquire it by passing its sem_id identifier--a system-wide number that's assigned when the semaphore is created--to the acquire_sem() function. The function doesn't return until the semaphore is actually acquired. (An alternate function, acquire_sem_etc() lets you specify a limit, in microseconds, on the amount of time you're willing to wait for the semaphore to be acquired. Unless otherwise noted, characteristics ascribed to acquire_sem() apply to acquire_sem_etc() as well.)

When a thread acquires a semaphore, that semaphore (typically) becomes unavailable for acquisition by other threads (in the rarer case, more than one thread is allowed to acquire the semaphore at a time; the precise determination of availability is explained in The Thread Count ). The semaphore remains unavailable until it's passed in a call to the release_sem() function.

The code that a semaphore "protects" lies between the calls to acquire_sem() and release_sem(). The disposition of these functions in your code usually follows this pattern:

   acquire_sem(my_semaphore);
   /* Protected code goes here. */
   release_sem(my_semaphore);

Keep in mind that these function calls needn't be so explicitly balanced. A semaphore can be acquired within one function and released in another. Acquisition and release of the same semaphore can even be performed by two different threads; an example of this is given in Using Semaphores to Impose an Execution Order .


The Thread Queue

Every semaphore has its own thread queue: This is a list that identifies the threads that are waiting to acquire the semaphore. A thread that attempts to acquire an unavailable semaphore is placed at the tail of the semaphore's thread queue; from the programmer's point of view, a thread that's been placed in the queue will be blocked in the acquire_sem() call. Each call to release_sem() "releases" the thread at the head of that semaphore's queue (if there are any waiting threads), thus allowing the thread to return from its call to acquire_sem().

Semaphores don't discriminate between acquisitive threads--they don't prioritize or otherwise reorder the threads in their queues--the oldest waiting thread is always the next to acquire the semaphore.


The Thread Count

To assess availability, a semaphore looks at its thread count . This is a counting variable that's initialized when the semaphore is created. The ostensible (although, as we shall see, not entirely accurate) meaning of a thread count's initial value, which is passed as the first argument to create_sem(), is the number of threads that can acquire the semaphore at a time. For example, a semaphore that's used as a mutually exclusive lock takes an initial thread count of 1--in other words, only one thread can acquire the semaphore at a time.

Calls to acquire_sem() and release_sem() alter the semaphore's thread count: acquire_sem() decrements the count, and release_sem() increments it. When you call acquire_sem(), the function looks at the thread count (before decrementing it) to determine if the semaphore is available:

The initial thread count isn't an inviolable limit on the number of threads that can acquire a given semaphore--it's simply the initial value for the sempahore's thread count variable. For example, if you create a semaphore with an initial thread count of 1 and then immediately call release_sem() five times, the semaphore's thread count will increase to 6. Furthermore, although you can't initialize the thread count to less-than-zero, an initial value of zero itself is common--it's an integral part of using semaphores to impose an execution order (as demonstrated later).

Summarizing the description above, there are three significant thread count value ranges:

Although it's possible to retrieve the value of a semaphore's thread count (by looking at a field in the semaphore's sem_info structure, as described later), you should only do so for amusement --while you're debugging, for example. You should never predicate your code on the basis of a semaphore's thread count.


Using a Semaphore as a Lock

As mentioned above, the most common use of semaphores is to ensure that only one thread is executing a certain piece of code at a time. The following example demonstrates this use.

Consider an application that manages a one-job-at-a-time device such as a printer. When the application wants to start a new print job (upon a request from some other application, no doubt) it spawns and runs a thread to perform the actual data transmission. Given the nature of the device, each spawned thread must be allowed to complete its transmission before the next thread takes over. However, your application wants to accept print requests (and so spawn threads) as they arrive.

To ensure that the spawned threads don't interrupt each other, you can define a semaphore that's acquired and released--that, in essence, is "locked" and "unlocked"--as a thread begins and ends its transmission, as shown below. The thread functions that are used in the example are described in Threads .

   /* Include the semaphore API declarations. */
   #include <OS.h>
   
   /* The semaphore is declared globally so the spawned threads
    * will be able to get to it (there are other ways of
    * broadcasting the sem_id, but this is the easiest). 
    */
   sem_id print_sem;
   
   /* print_something() is the data-transmission function.
    * The data itself would probably be passed as an argument  
    * (which isn't shown in this example).
    */
   long print_something(void *data);
   
   main()
   {
      /* Create the semaphore with an initial thread count of 1.  
       * If the semaphore can't be created (error conditions
       * are listed later), we exit.  The second argument to 
       * create_sem(), as explained in the function 
       * descriptions is a handy string name for the semaphore.
       */
      if ((print_sem = create_sem(1, "print sem")) < B_NO_ERROR)
         exit -1;
   
      while (1)
      {
         /* Wait-for-a-request code and break conditions
          * go here. 
          */
         ... 
   
         /* Spawn a thread that calls print_something(). */
         if (resume_thread(spawn_thread(print_something ...))
            < B_NO_ERROR)
            break;
      }
   
      /* Acquire the semaphore and delete it (as explained 
       * later)
       */
      acquire_sem(print_sem);
      delete_sem(print_sem);
      exit 0;
   }
   
   long print_something(void *data)
   {
      /* Acquire the semaphore; an error means the semaphore
       * is no longer valid.  And we'll just die if it's no good.
       */
      if (acquire_sem(print_sem) < B_NO_ERROR)
         return 0;
      
      /* The code that sends data to the printer goes here. */
   
      /* Release the semaphore. */
      release_sem(print_sem);
   
      return 0;
   }

The acquire_sem() and release_sem() calls embedded in the print_something() function "protect" the data-transmission code. Although any number of threads may concurrently execute print_something(), only one at a time is allowed to proceed past the acquire_sem() call.


Deleting a Semaphore

Notice that the example explicitly deletes the print_sem semaphore before it exits. This isn't wholly necessary: Every semaphore is owned by a team (the team of the thread that called create_sem()). When the last thread in a team dies, it takes the team's semaphores with it.

Prior to the death of a team, you can explicitly delete a semaphore through the delete_sem() call. Note, however, that delete_sem() must be called from a thread that's a member of the team that owns the semaphore--you can't delete another team's semaphores.

You're allowed to delete a semaphore even if it still has threads in its queue. However, you usually want to avoid this, so deleting a semaphore may require some thought. In the example, the main thread (the thread that executes the main() function) makes sure all print threads have finished by acquiring the semaphore before deleting it. When the main thread is allowed to continue (when the acquire_sem() call returns) the queue is sure to be empty and all print jobs will have completed.

When you delete a semaphore (or when it dies naturally), all its queued threads are immediately allowed to continue--they all return from acquire_sem() at once. You can distinguish between a "normal" acquisition and a "semaphore deleted" acquisition by the value that's returned by acquire_sem() (the specific return values are listed in the function descriptions, below).


Using Semaphores to Impose an Execution Order

Semaphores can also be used to coordinate threads that are performing separate operations, but that need to perform these operations in a particular order. In the following example, an application repeatedly spawns, in no particular order, threads that either write to or read from a global buffer. Each writing thread must complete before the next reading thread starts, and each written message must be fully read exactly once. Thus, the two operations must alternate (with a writing thread going first). Two semaphores are used to coordinate the threads that perform these operations:

   /* Here's the global buffer. */
   char buf[1024];
   
   /* The ok_to_read and ok_to_write semaphores inform the
    * appropriate threads that they can proceed. 
    */
   sem_id ok_to_write, ok_to_read;
   
   /* These are the writing and reading functions. */
   long write_it(void *data);
   long read_it(void *data);
   
   main()
   {
      /* These will be used when we delete the semaphores. */
      long write_count, read_count;
      
      /* Create the semaphores.  ok_to_write is created with a
       * thread count of 1; ok_to_read's count is set to 0.
       * This is explained below.
       */
      if ((ok_to_write = create_sem(1, "write sem"))<B_NO_ERROR)
         return (B_ERROR);
   
      if ((ok_to_read = create_sem(0, "read sem")) < B_NO_ERROR) 
      {
         delete_sem(ok_to_write);
         return (B_ERROR); 
      }
   
      bzero(buf,1024);
   
      /* Spawn some reading and writing threads. */
      while(1) 
      {
         if ( ... ) /* spawn-a-writer condition */
            resume_thread(spawn_thread(write_it, ...));
         if ( ... ) /* spawn-a-reader condition */
            resume_thread(spawn_thread(read_it, ...);
         if ( ... ) /* break condition */
            break;
      }
   
      /* It's time to delete the semaphores.  First, get the
       * semaphores' thread counts.  
       */
      if (get_sem_count(ok_to_write, &write_count) < B_NO_ERROR) 
      {
         delete_sem(ok_to_read);
         return (B_ERROR); 
      }
   
      if (get_sem_count(ok_to_read, &read_count) < B_NO_ERROR) 
      {
         delete_sem(ok_to_write);
         return (B_ERROR); 
      }
   
      /* Place this thread at the end of whichever queue is
       * shortest (or the writing queue if they're equal).
       * Remember: thread count is decremented as threads
       * are placed in the queue, so the shorter queue is
       * the one with the greater thread count.
       */
      if (write_count >= read_count)
         acquire_sem(ok_to_write);
      else
         acquire_sem(ok_to_read);
         
      /* Delete the semaphores and exit. */
      delete_sem(ok_to_write);
      delete_sem(ok_to_read);
      return (B_NO_ERROR);
   }
   
   long write_it(void *data)
   {
      /* Acquire the writing semaphore. */
      if (acquire_sem(ok_to_write) < B_NO_ERROR)
         return (B_ERROR);
      
      /* Write to the buffer. */
      strncpy(buf, (char *)data, 1023);
   
      /* Release the reading semaphore. */
      return (release_sem(ok_to_read));
   }
      
   long read_it(void *data)
   {
      /* Acquire the reading semaphore. */
      if (acquire_sem(ok_to_read) < B_NO_ERROR)
         return (B_ERROR);
   
      /* Read the message and do something with it. */
      ...
   
      /* Release the writing semaphore. */
      return (release_sem(ok_to_write));
   }

Notice the distribution of the acquire_sem() and release_sem() calls for the respective semaphores: The writing function acquires the writing semaphore (ok_to_write ) and then releases the reading semaphore (ok_to_read). The reading function does the opposite. Thus, after the buffer has been written to, no other thread can write to it until it has been read (and vice versa).

By setting ok_to_write's initial thread count to 1 and ok_to_read's initial thread count to 0, you ensure that a writing operation will be performed first. If a reading thread is spawned first, it will block until a writing thread releases the ok_to_read semaphore.

When it's semaphore-deletion time in the example, the main thread acquires one of the semaphores. Specifically, it acquires the semaphore that has the fewer threads in its queue. This allows the remaining (balanced) pairs of reading and writing threads to complete before the semaphores are deleted, and throws away any unpaired reading or writing threads. (Actually, the unpaired threads aren't "thrown away" as the semaphore upon which they're waiting is deleted, but by the error check in the first line of the reading or writing function. As mentioned earlier, deleting the semaphore releases its queued threads, allowing them, in this instance, to rush to their deaths.)


Broadcasting Semaphores

The sem_id number that identifies a semaphore is a system-wide token--the sem_id values that you create in your application will identify your semaphores in all other applications as well. It's possible, therefore, to broadcast the sem_id numbers of the semaphores that you create and so allow other applications to acquire and release them --but it's not a very good idea. A semaphore is best controlled if it's created, acquired, released, and deleted within the same team. If you want to provide a protected service or resource to other applications, you should follow the model used by the examples: Your application should accept messages from other applications and then spawn threads that acquire and release the appropriate semaphores.


Functions


acquire_sem(), acquire_sem_etc()

      long acquire_sem(sem_id sem)
      long acquire_sem_etc(sem_id sem, long count, long flags, double timeout)

These functions attempt to acquire the semaphore identified by the sem argument. Except in the case of an error, acquire_sem() doesn't return until the semaphore has actually been acquired.

acquire_sem_etc() is the full-blown acquisition version: It's essentially the same as acquire_sem() , but, in addition, it lets you acquire a semaphore more than once, and also provides a timeout facility:

In addition to B_TIMEOUT, the Kernel Kit defines two other semaphore-acquisition flag constants (B_CAN_INTERRUPT and B_CHECK_PERMISSION). These additional flags are used by device drivers --adding these flags into a "normal" (or "user-level") acquisition has no effect. However, you should be aware that the B_CHECK_PERMISSION flag is always added in to user-level semaphore acquisition in order to protect system-defined semaphores.

Other than the timeout and the acquisition count, there's no difference between the two acquisition functions. Specifically, any semaphore can be acquired through either of these functions; you always release a semaphore through release_sem() (or release_sem_etc() ) regardless of which function you used to acquire it.

To determine if the semaphore is available, the function looks at the semaphore's thread count (before decrementing it):

If the sem argument doesn't identify a valid semaphore, B_BAD_SEM_ID is returned. It's possible for a semaphore to become invalid while an acquisitive thread is waiting in the semaphore's queue. For example, if your thread calls acquire_sem() on a valid (but unavailable) semaphore, and then some other thread deletes the semaphore, your thread will return B_BAD_SEM_ID from its call to acquire_sem().

If you pass an illegal count value (less than 1) to acquire_sem_etc(), the function returns B_BAD_VALUE. If the acquisition time surpasses the designated timeout limit, the acquire_sem_etc() function returns B_TIMED_OUT.

If the semaphore is successfully acquired, the functions return B_NO_ERROR.

See also: release_sem()


create_sem()

      sem_id create_sem(long thread_count, const char *name)

Creates a new semaphore and returns a system-wide sem_id number that identifies it. The arguments are:

Valid sem_id numbers are positive integers. You should always check the validity of a new semaphore through a construction such as

   if ((my_sem = create_sem(1,"My Semaphore")) < B_NO_ERROR)
      /* If it's less than B_NO_ERROR, my_sem is invalid. */

create_sem() sets the new semaphore's owner to the team of the calling thread. Ownership may be re-assigned through the set_sem_owner() function. When the owner dies (when all the threads in the team are dead), the semaphore is automatically deleted. The owner is also signficant in a delete_sem() call: Only those threads that belong to a semaphore's owner are allowed to delete that semaphore.

The function returns one of the following codes if the semaphore couldn't be created:

Return Code Meaning
B_BAD_ARG_VALUE Invalid thread_count value (less than zero).
B_NO_MEMORY Not enough memory to allocate the semaphore's name.
B_NO_MORE_SEMS All valid sem_id numbers are being used.

See also: delete_sem()


delete_sem()

      long delete_sem(sem_id sem)

Deletes the semaphore identified by the argument. If there are any threads waiting in the semaphore's thread queue, they're immediately de-queued and allowed to continue execution.

This function may only be called from a thread that belongs to the target semaphore's owner; if the calling thread belongs to a different team, or if sem is invalid, the function returns B_BAD_SEM_ID. Otherwise, it returnsB_NO_ERROR .

See also: acquire_sem()


get_sem_count()

      long get_sem_count(sem_id sem, long *thread_count)

Returns, by reference in thread_count, the value of the semaphore's thread count variable:

By the time this function returns and you get a chance to look at the thread_count value, the semaphore's thread count may have changed. Although watching the thread count might help you while you're debugging your program, this function shouldn't be an integral part of the design of your application.

If sem is a valid semaphore identifier, the function returns B_NO_ERROR; otherwise, B_BAD_SEM_ID is returned (and the value of the thread_count argument that you pass in isn't changed).

See also: get_sem_info()


get_sem_info(), get_nth_sem_info()

      long get_sem_info(sem_id sem, sem_info *info)
      long get_nth_sem_info(team_id team, long n, sem_info *info)

These functions copy, into the info argument, the sem_info structure for a particular semaphore:

The sem_info structure is defined as:

      typedef struct sem_info {
            sem_id sem;
            team_id team;
            char name[B_OS_NAME_LENGTH];
            long count;
            thread_id latest_holder;
         } sem_info

The structure's fields are:

  • sem. The sem_id number of the semaphore.

  • team. The team_id of the semaphore's owner.

  • name. The name assigned to the semaphore.

  • count. The semaphore's thread count.

  • latest_holder. The thread that most recently acquired the semaphore.
  • Note that the thread that's identified in the lastest_holder field may no longer be holding the semaphore--it may have since released the semaphore. The latest holder is simply the last thread to have called acquire_sem() (of whatever flavor) on this semaphore.

    The information in the sem_info structure is guaranteed to be internally consistent, but the structure as a whole should be consider to be out-of-date as soon as you receive it. It provides a picture of a semaphore as it exists just before the info-retrieving function returns.

    The functions return B_NO_ERROR if the designated semaphore is successfully found. Otherwise, they return B_BAD_SEM_ID, B_BAD_TEAM_ID , or B_BAD_INDEX.


    release_sem(), release_sem_etc()

          long release_sem(sem_id sem)
          long release_sem_etc(sem_id sem, long count, long flags)

    The release_sem() function de-queues the thread that's waiting at the head of the semaphore's thread queue (if any), and increments the semaphore's thread count. release_sem_etc() does the same, but for count threads.

    Normally, releasing a semaphore automatically invokes the kernel's scheduler. In other words, when your thread calls release_sem() (or the sequel), you're pretty much guaranteed that some other thread will be switched in immediately afterwards, even if your thread hasn't gotten its fair share of CPU time. If you want to subvert this automatism, call release_sem_etc() with a flags value of B_DO_NOT_RESCHEDULE. Preventing the automatic rescheduling is particularly useful if you're releasing a number of different semaphores all in a row: By avoiding the rescheduling you can prevent some unnecessary context switching.

    If sem is a valid semaphore identifier, these functions return B_NO_ERROR; if it's invalid, they return B_BAD_SEM_ID . Note that if a released thread deletes the semaphore (before the releasing function returns), these functions will still return B_NO_ERROR.

    The count argument to release_sem_count() must be greater than zero; the function returns B_BAD_VALUE otherwise.

    See also: acquire_sem()


    set_sem_owner()

          long set_sem_owner(sem_id sem, team_id team)

    Transfers ownership of the designated semaphore to team. A semaphore can only be owned by one team at a time; by setting a semaphore's owner, you remove it from its current owner.

    There are no restrictions on who can own a semaphore, or on who can transfer ownership. In practice, however, the only reason you should ever transfer ownership is if you're writing a device driver and you need to bequeath a semaphore to the kernel (the team of which is known, for this purpose, as B_SYSTEM_TEAM).

    Semaphore ownership is meaningful for two reason: When a team dies (when all its threads are dead), the semaphores that are owned by that team are deleted. Also, only a thread that belongs to a semaphore's owner is allowed to delete that semaphore.

    To discover a semaphore's owner, use the get_sem_info() function.

    set_sem_owner() fails and returns B_BAD_SEM_ID or B_BAD_TEAM_ID if one or the other argument is invalid. Otherwise it returns B_HOKEY_POKEY.

    See also: get_sem_info()




    The Be Book, HTML Edition, for Developer Release 8 of the Be Operating System.

    Copyright © 1996 Be, Inc. All rights reserved.

    Be, the Be logo, BeBox, BeOS, BeWare, and GeekPort are trademarks of Be, Inc.

    Last modified September 6, 1996.