Semaphores
Semaphores are a more general form of mutex that can allow multiple tasks to hold the lock simultaneously. They’re useful for resource counting and producer-consumer scenarios.
Types of Semaphores
| Type | Count | Use Case |
|---|---|---|
| Binary | 0 or 1 | Mutual exclusion (use mutex instead) |
| Counting | 0 to N | Resource pool management |
For mutual exclusion, prefer mutexes over binary semaphores. Mutexes have better debugging support and clearer semantics.
Basic Semaphore Usage
#include <linux/semaphore.h>
/* Static initialization with count N */
static DEFINE_SEMAPHORE(my_sem); /* Binary semaphore */
/* Dynamic initialization */
struct semaphore my_sem;
sema_init(&my_sem, count); /* Initialize with count */
/* Acquire (decrement) - sleep if count is 0 */
down(&my_sem);
/* ... use the resource ... */
/* Release (increment) */
up(&my_sem);
Semaphore Operations
Basic Down/Up
down(&sem); /* Sleep until count > 0, then decrement */
/* ... */
up(&sem); /* Increment count, wake up waiters */
Interruptible Down
if (down_interruptible(&sem)) {
/* Interrupted by signal */
return -ERESTARTSYS;
}
/* Got the semaphore */
up(&sem);
Killable Down
if (down_killable(&sem)) {
/* Killed by fatal signal */
return -ERESTARTSYS;
}
/* Got the semaphore */
up(&sem);
Try Down (Non-blocking)
if (down_trylock(&sem)) {
/* Semaphore not available */
return -EAGAIN;
}
/* Got the semaphore */
up(&sem);
Timeout
ret = down_timeout(&sem, msecs_to_jiffies(1000));
if (ret) {
/* Timed out or interrupted */
return ret;
}
/* Got the semaphore */
up(&sem);
Counting Semaphore Example
Limit concurrent access to a resource pool:
#include <linux/module.h>
#include <linux/semaphore.h>
#include <linux/fs.h>
#define MAX_USERS 4
struct my_device {
struct semaphore sem;
int resources[MAX_USERS];
};
static struct my_device dev;
static int my_open(struct inode *inode, struct file *file)
{
int ret;
/* Try to acquire a slot */
ret = down_interruptible(&dev.sem);
if (ret)
return ret;
pr_info("Acquired slot (available: %d)\n",
dev.sem.count);
return 0;
}
static int my_release(struct inode *inode, struct file *file)
{
up(&dev.sem);
pr_info("Released slot (available: %d)\n",
dev.sem.count);
return 0;
}
static int __init my_init(void)
{
/* Initialize with MAX_USERS slots */
sema_init(&dev.sem, MAX_USERS);
pr_info("Device allows %d concurrent users\n", MAX_USERS);
return 0;
}
Testing:
# Open 4 times (will succeed)
for i in 1 2 3 4; do
cat /dev/mydevice &
done
# 5th open will block
cat /dev/mydevice # Waits until someone closes
# Close one
kill %1 # Now the 5th open proceeds
Producer-Consumer Pattern
#include <linux/semaphore.h>
#include <linux/kthread.h>
#define BUFFER_SIZE 10
struct buffer {
int data[BUFFER_SIZE];
int head, tail;
struct semaphore empty; /* Counts empty slots */
struct semaphore full; /* Counts full slots */
struct mutex lock; /* Protects buffer access */
};
static struct buffer buf;
int producer(void *arg)
{
int item = 0;
while (!kthread_should_stop()) {
/* Wait for empty slot */
if (down_interruptible(&buf.empty))
break;
mutex_lock(&buf.lock);
buf.data[buf.head] = item++;
buf.head = (buf.head + 1) % BUFFER_SIZE;
mutex_unlock(&buf.lock);
/* Signal full slot */
up(&buf.full);
msleep(100);
}
return 0;
}
int consumer(void *arg)
{
int item;
while (!kthread_should_stop()) {
/* Wait for full slot */
if (down_interruptible(&buf.full))
break;
mutex_lock(&buf.lock);
item = buf.data[buf.tail];
buf.tail = (buf.tail + 1) % BUFFER_SIZE;
mutex_unlock(&buf.lock);
/* Signal empty slot */
up(&buf.empty);
pr_info("Consumed: %d\n", item);
}
return 0;
}
void buffer_init(void)
{
sema_init(&buf.empty, BUFFER_SIZE); /* All slots empty */
sema_init(&buf.full, 0); /* No slots full */
mutex_init(&buf.lock);
buf.head = buf.tail = 0;
}
Read-Write Semaphores
Allow multiple readers or one writer:
#include <linux/rwsem.h>
/* Static initialization */
static DECLARE_RWSEM(my_rwsem);
/* Or dynamic */
struct rw_semaphore my_rwsem;
init_rwsem(&my_rwsem);
/* Reading (shared) */
down_read(&my_rwsem);
/* Multiple readers can be here */
up_read(&my_rwsem);
/* Writing (exclusive) */
down_write(&my_rwsem);
/* Only one writer, no readers */
up_write(&my_rwsem);
RW Semaphore Operations
/* Standard operations */
down_read(&rwsem);
down_write(&rwsem);
up_read(&rwsem);
up_write(&rwsem);
/* Try variants */
if (down_read_trylock(&rwsem)) {
/* Got read lock */
up_read(&rwsem);
}
if (down_write_trylock(&rwsem)) {
/* Got write lock */
up_write(&rwsem);
}
/* Downgrade write to read */
down_write(&rwsem);
/* ... do exclusive work ... */
downgrade_write(&rwsem); /* Convert to read lock */
/* ... do shared work ... */
up_read(&rwsem);
RW Semaphore Example
struct config {
struct rw_semaphore lock;
int setting1;
int setting2;
char name[32];
};
static struct config global_config;
/* Called frequently - use read lock */
int get_setting1(void)
{
int val;
down_read(&global_config.lock);
val = global_config.setting1;
up_read(&global_config.lock);
return val;
}
/* Called rarely - use write lock */
void set_setting1(int val)
{
down_write(&global_config.lock);
global_config.setting1 = val;
up_write(&global_config.lock);
}
Semaphore vs Mutex
| Feature | Mutex | Semaphore |
|---|---|---|
| Count | 1 | 1 to N |
| Owner tracking | Yes | No |
| Recursive | No | N/A |
| Priority inheritance | Some | No |
| Use case | Mutual exclusion | Resource counting |
When to Use What
flowchart TD
Need["Need synchronization"]
Exclusive{"Exclusive<br/>access?"}
Count{"Need to count<br/>resources?"}
Sleep{"Can sleep?"}
Need --> Exclusive
Exclusive -->|Yes| Count
Count -->|No| Mutex["Use Mutex"]
Count -->|Yes| Sem["Use Counting Semaphore"]
Exclusive -->|No (multiple readers)| RWSem["Use RW Semaphore"]
Sleep -->|No| Spin["Use Spinlock"]
style Mutex fill:#738f99,stroke:#0277bd
style Sem fill:#8f7392,stroke:#6a1b9a
style RWSem fill:#7a8f73,stroke:#2e7d32
Summary
- Counting semaphores manage a pool of N resources
- Use
down()to acquire,up()to release - Multiple tasks can hold a counting semaphore
- For mutual exclusion, prefer mutex over binary semaphore
- Read-write semaphores allow multiple readers or one writer
- Always use interruptible variants when possible
Next
Learn about atomic operations for lock-free programming.