Linux内核中的信号量是一种经典的同步机制,用于控制对共享资源的并发访问。与完成量(Completion)不同,信号量更适用于资源计数管理,例如限制同时访问某资源的线程数量。以下是信号量的核心知识点以及与完成量的对比:
一、 结构体成员
c
struct semaphore {
raw_spinlock_t lock; /* 自旋锁 */
unsigned int count; /* 可用资源计数 */
struct list_head wait_list; /* 等待改信号量的任务队列 */
};
struct semaphore_waiter 用于描述一个正在等待信号量的任务,它不是信号量本体,而是挂入信号量等待队列的等待者。
c
struct semaphore_waiter {
struct list_head list; /* 等待队列链表节点 */
struct task_struct *task; /* 指向当前等待的进程 */
bool up; /* 该等待者是否被唤醒 */
};
二、 API函数
2.1 初始化信号量
动态初始化
- sem:指向要初始化的信号量对象。
- val:信号量初始计数值,表示初始可用的资源数量。
c
static inline void sema_init(struct semaphore *sem, int val)
{
static struct lock_class_key __key;
/* 宏构造一个临时的semaphore,整体赋值给sem,该步骤会初始化其结构体的三个成员 */
*sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val);
lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0);
}
静态初始化,只能定义初始值为 1 的信号量。
c
DEFINE_SEMAPHORE(my_sem);
/*展开后等价于*/
struct semaphore name = {
.lock = __RAW_SPIN_LOCK_UNLOCKED(name.lock),
.count = 1,
.wait_list = LIST_HEAD_INIT(name.wait_list),
};
2.2 获取信号量
阻塞等待,直到获取信号量:
c
void down(struct semaphore *sem)
{
unsigned long flags;
might_sleep(); /* 声明该函数可能睡眠 */
raw_spin_lock_irqsave(&sem->lock, flags); /* 获取锁、保存并关闭中断 */
if (likely(sem->count > 0))
sem->count--; /* 获取到资源 */
else
__down(sem); /* 资源不可用,进入等待队列 */
raw_spin_unlock_irqrestore(&sem->lock, flags);
}
EXPORT_SYMBOL(down);
__down函数会调用__down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT)
c
static inline int __sched __down_common(struct semaphore *sem, long state,
long timeout)
{
struct semaphore_waiter waiter;
/* 创建一个等待节点,挂入wait_list */
list_add_tail(&waiter.list, &sem->wait_list);
waiter.task = current; /* 记录当前任务 */
waiter.up = false; /* 表明当前等待者未被up选中 */
for (;;) {
if (signal_pending_state(state, current)) /* 决定当前等待是否应被信号中断 */
goto interrupted;
if (unlikely(timeout <= 0))
goto timed_out; /* 等待时间耗尽 */
__set_current_state(state); /* 设置任务以什么状态睡眠 */
raw_spin_unlock_irq(&sem->lock);
timeout = schedule_timeout(timeout); /* 任务睡眠,直到超时或被唤醒 */
raw_spin_lock_irq(&sem->lock);
if (waiter.up)
return 0; /* 成功获取到信号量,返回 */
}
timed_out: /* 等待超时,从等待队列中删除 */
list_del(&waiter.list);
return -ETIME;
interrupted:
list_del(&waiter.list);
return -EINTR;
}
可中断的等待(允许信号打断):
c
int down_interruptible(struct semaphore *sem)
{
unsigned long flags;
int result = 0;
might_sleep(); /* 声明函数可能上睡眠 */
raw_spin_lock_irqsave(&sem->lock, flags);
if (likely(sem->count > 0))
sem->count--; /* 获取到资源 */
else
result = __down_interruptible(sem);
raw_spin_unlock_irqrestore(&sem->lock, flags);
return result;
}
__down_interruptible会调用__down_common(sem, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT) 。和阻塞等待的区别就是参数传递的是TASK_INTERRUPTIBLE ,表示可以被信号打断。
非阻塞尝试(立即返回):
c
int down_trylock(struct semaphore *sem)
{
unsigned long flags;
int count;
raw_spin_lock_irqsave(&sem->lock, flags);
count = sem->count - 1;
if (likely(count >= 0))
sem->count = count;
raw_spin_unlock_irqrestore(&sem->lock, flags);
return (count < 0);
}
2.3 释放信号量
释放信号量并唤醒等待线程:
c
static noinline void __sched __up(struct semaphore *sem)
{
struct semaphore_waiter *waiter = list_first_entry(&sem->wait_list,
struct semaphore_waiter, list); /* 取出等待队列的第一个等待者 */
list_del(&waiter->list); /* 从队列里面删除 */
waiter->up = true; /* 标记已获取到信号量 */
wake_up_process(waiter->task); /* 唤醒对应任务 */
}