信号量的使用
struct semaphore {
raw_spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};
void sema_init(struct semaphore *sem, int val);
void down(struct semaphore *sem);
int down_interruptible(struct semaphore *sem); // 在睡眠期间可以被信号打断返回
void up(struct semaphore *sem)
源码分析
void down(struct semaphore *sem)
{
unsigned long flags;
raw_spin_lock_irqsave(&sem->lock, flags);
if (likely(sem->count > 0)) // 对应sema_init中的第二个参数val,如果当前大于0,当前进程可以进入临界区执行
sem->count--;
else
__down(sem); // 否则将将当前进程阻塞住
raw_spin_unlock_irqrestore(&sem->lock, flags);
}
// down 最终会调用到 __down_common
static inline int __sched __down_common(struct semaphore *sem, long state,
long timeout)
{
struct semaphore_waiter waiter;
list_add_tail(&waiter.list, &sem->wait_list); // 将当前进程加入等待链表中
waiter.task = current; // current 代表当前进程的结构体
waiter.up = false;
for (;;) {
if (signal_pending_state(state, current)) // 如果当前进程有信号处理,则返回-EINTR
goto interrupted;
if (unlikely(timeout <= 0))
goto timed_out;
__set_current_state(state); // 设置当前进程状态(TASK_INTERRUPTIBLE/TASK_UNINTERRUPTIBLE)
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;
}
void up(struct semaphore *sem)
{
unsigned long flags;
raw_spin_lock_irqsave(&sem->lock, flags);
if (likely(list_empty(&sem->wait_list))) // 当前没有任何进程阻塞在该信号量上
sem->count++;
else
__up(sem);
raw_spin_unlock_irqrestore(&sem->lock, flags);
}
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); // 唤醒进程
}
总结
可以看到Linux内核中信号量的使用和实现都是十分简洁的。可以看出内核中用一条链表实现了阻塞在信号量上多个进程的公平调度,不过从内核中的实现来看貌似这并不是最优的实现,因为临界区代码往往都比较短小,但是如果进程获取不到信号量就会直接睡眠下去,这可能会造成进程频繁的睡眠唤醒。下一节会分析一下内核中 mutex
的实现。