The Kernel Kit Table of Contents | The Kernel Kit Index |
Declared in: be/kernel/OS.h
Library: libroot.so
A semaphore is a token that's used to synchronize multiple threads. The semaphore concept is simple: To enter into a semaphore-protected "critical section", a thread must first "acquire" the semaphore, through the acquire_sem() function. When it passes out of the critical section, the thread "releases" the semaphore through release_sem().
The advantage of the semaphore system is that if a thread can't acquire a semaphore (because the semaphore is yet to be released by the previous acquirer), the thread blocks in the acquire_sem() call. While it's blocked, the thread doesn't waste any cycles.
![]() |
The full story about semaphores is a wee bit more complicated than this quick description, but for most semaphore scenarios, you'll can learn all you need to know by visiting Semaphore Examples. |
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 blocks until the semaphore is actually acquired.
![]() |
An alternate function, acquire_sem_etc() lets you specify the amount of time you're willing to wait for the semaphore to be acquired, and let you acquire the semaphore more than once in a single go. 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. 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:
if (acquire_sem(my_semaphore) == B_NO_ERROR) { /* Protected code goes here. */ release_sem(my_semaphore); }
Keep in mind that...
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 where it sits blocked in the acquire_sem() call. Each call to release_sem() umblocks the thread at the head of that semaphore's queue, 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.
To assess availability, a semaphore looks at its thread count. This is a counting variable that's initialized when the semaphore is created. Ostensibly, 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. (As we'll see later, this isn't the entire story, but it's good enough for now.) 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.
![]() |
An initial thread count of 1 is by far the most common use; a thread count of 0 is also useful. Other counts are much less common. |
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. |
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: 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).
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 accept messages from other applications and then spawn threads that acquire and release the appropriate semaphores.
|
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:
![]() |
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):
RETURN CODES
The other return values apply to acquire_sem_etc() only:
|
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_OK) /* 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.
RETURN CODES
|
Deletes the semaphore identified by the argument. If there are any threads waiting in the semaphore's thread queue, they're immediately unblocked.
![]() |
This function may only be called from a thread that belongs to the semaphore's owner. |
RETURN CODES
|
![]() |
For amusement purposes only; never predicate your code on this function. |
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.
RETURN CODES
|
Copies information about a particular semaphore into the sem_info structure designated by info. The first version of the function designates the sempahore directly, by sem_id.
The get_next_sem_info() version lets you step through the list of a team's semaphores through iterated calls on the function. The team argument identifies the team you want to look at; a team value of 0 means the team of the calling thread. The cookie argument is a placemark; you set it to 0 on your first call, and let the function do the rest. The function returns B_BAD_VALUE when there are no more sempahores to visit:
/* Get the sem_info for every sempahore in this team. */ sem_info info; int32 cookie = 0; while (get_next_sem_info(0, &cookie, &info) == B_OK) ...
RETURN CODES
|
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(), 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.
RETURN CODES
See also: acquire_sem()
|
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:
To discover a semaphore's owner, use the get_sem_info() function.
RETURN CODES
|
sem_id numbers identify semaphores. The id is assigned when the semaphore is created (create_sem()). The values are unique across the system.
|
The sem_info structure supplies information about a semaphore. You retrieve the structure through the get_sem_info() function. 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 fields are:
![]() |
The lastest_holder field is highly undependable; in some cases, the kernel doesn't even record the semaphore acquirer. Although you can use this field as a hint while debugging, you shouldn't take it too seriously. Love, Mom. |
The Kernel Kit Table of Contents | The Kernel Kit Index |
Copyright © 1999 Be, Inc. All rights reserved.
Text last modified