The Kernel Kit Table of Contents | The Kernel Kit Index |
The following sections provides examples of typical semaphore use. For the full story on semaphores, see Semaphores.
The most typical use of a semaphore is to protect a chunk of code that can only be executed by one thread at a time. The semaphore acts as a lock; acquire_sem() locks the code, release_sem() releases it. Semaphores that are used as locks are (almost always) created with a thread count of 1.
As a simple example, let's say you keep track of a maximum value like this:
/* max_val is a global. */ uint32 max_val = 0; ... /* bump_max() resets the max value, if necessary. */ void bump_max(uint32 new_value) { if (new_value > max_value) max_value = new_value; }
bump_max() isn't thread safe; there's a race condition between the comparison and the assignment. So we protect it with a semaphore:
sem_id max_sem; uint32 max_val = 0; ... /* Initialize the semaphore during a setup routine. */ status_t init() { if ((max_sem = create_sem(1, "max_sem")) < B_NO_ERROR) return B_ERROR; ... } void bump_max(uint32 new_value) { if (acquire_sem(max_sem) != B_NO_ERROR) return; if (new_value > max_value) max_value = new_value; release_sem(); }
A "benaphore" is a combination of an atomic variable and a semaphore that can improve locking efficiency. If you're using a semaphore as shown in the previous example, you should consider using a benaphore instead (if you can).
Here's the example re-written to use a benaphore:
sem_id max_sem; uint32 max_val = 0; int32 ben_val = 0; status_t init() { /* This time we initialized the semaphore to 0. */ if ((max_sem = create_sem(0, "max_sem")) < B_NO_ERROR) return B_ERROR; ... } void bump_max(uint32 new_value) { int32 previous = atomic_add(&ben_val, 1); if (previous >= 1) if (acquire_sem(max_sem) != B_NO_ERROR) goto get_out; if (new_value > max_value) max_value = new_value; get_out: previous = atomic_add(&ben_val, -1); if (previous > 1) release_sem(max_sem); }
The point, here, is that acquire_sem() is called only if it's known (by checking the previous value of ben_val) that some other thread is in the middle of the critical section. On the releasing end, the release_sem() is called only if some other thread has since entered the function (and is now blocked in the acquire_sem() call). An important point, here, is that the semaphore is initialized to 0.
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, we have a global buffer that's accessed through separate reading and writing functions. Furthermore, we want writes and reads to alternate, with a write going first.
We can lock the entire buffer with a single semaphore, but to enforce alternation we need two semaphores:
sem_id write_sem, read_sem; char buffer[1024]; /* Initialize the semaphores */ status_t init() { if ((write_sem = create_sem(1, "write")) < B_NO_ERROR) { return; if ((read_sem = create_sem(0, "read")) < B_NO_ERROR) { delete_sem(write_sem); return; } } status_t write_buffer(const char *src) { if (acquire_sem(write_sem) != B_NO_ERROR) return B_ERROR; strncpy(buffer, src, 1024); release_sem(read_sem); } status_t read_buffer(char *dest, size_t len) { if (acquire_sem(read_sem) != B_NO_ERROR) return B_ERROR; strncpy(dest, buffer, len); release_sem(write_sem); }
The initial thread counts ensure that the buffer will be written to before it's read: If a reader arrives before a writer, the reader will block until the writer releases the read_sem semaphore.
The Kernel Kit Table of Contents | The Kernel Kit Index |
Copyright © 2000 Be, Inc. All rights reserved..