跳到主要内容位置

Linux驱动-内核信号量semaphore的实现

“一图胜千言”

信号量内核结构体#

include/linux/semaphore.h

struct semaphore {
raw_spinlock_t lock; //自旋锁,信号量对资源count的互斥获取需要自旋锁
unsigned int count; //资源量
struct list_head wait_list; //等待列表
};

初始化 semaphore 之后,就可以使用 down 函数或其他衍生版本来获取信号量,使用 up 函数释放信号量。

down 函数的实现#

  • 如果 semaphore 中的 count 大于 0,那么 down 函数就可以获得信号量;否则就休眠。
  • 在读取、修改 count 时,要使用 spinlock 来实现互斥。
  • 休眠时,要把当前进程放在 semaphore 的 wait_list 链表中,别的进程释放信号量时去 wait_list 中把进程取出、唤醒。

调用过程:

kernel/locking/semaphore.c

void down(struct semaphore *sem)
{
unsigned long flags;
raw_spin_lock_irqsave(&sem->lock, flags); //通过spin_lock来实现对count的互斥访问
if (likely(sem->count > 0))
sem->count--; // 如果count大于0,就减一,成功获得信号量
else
__down(sem); // 否则休眠
raw_spin_unlock_irqrestore(&sem->lock, flags); // 释放spinlock
}
static noinline void __sched __down(struct semaphore *sem)
{
__down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
}
static inline int __sched __down_common(struct semaphore *sem, long state,
long timeout)
{
struct task_struct *task = current;
struct semaphore_waiter waiter;
list_add_tail(&waiter.list, &sem->wait_list); // 当前进程放入信号量的wait_list
waiter.task = task;
waiter.up = false;
for (;;) {
if (signal_pending_state(state, task))
goto interrupted;
if (unlikely(timeout <= 0))
goto timed_out;
__set_task_state(task, state); // 修改进程状态为非RUNNING
raw_spin_unlock_irq(&sem->lock); // 释放spinlock,否则别的程序无法获得
timeout = schedule_timeout(timeout); // 主动启动调度
raw_spin_lock_irq(&sem->lock); // 被唤醒后,获取spinlock
if (waiter.up) // 如果获得了信号量,返回
return 0;
}
timed_out:
list_del(&waiter.list);
return -ETIME;
interrupted:
list_del(&waiter.list);
return -EINTR;
}

up 函数的实现#

  • 如果有其他进程在等待信号量,则 count 值无需调整,直接取出第 1 个等待信号量的进程,把信号量给它,把它唤醒。

  • 如果没有其他进程在等待信号量,则调整 count。

void up(struct semaphore *sem)
{
unsigned long flags;
raw_spin_lock_irqsave(&sem->lock, flags); // 获取spinlock
if (likely(list_empty(&sem->wait_list)))
sem->count++; // 如果无人等待信号量,恢复count
else
__up(sem); // 如果有人等待信号量,执行这个来唤醒
raw_spin_unlock_irqrestore(&sem->lock, flags); // 释放spinlock
}
static noinline void __sched __up(struct semaphore *sem)
{
struct semaphore_waiter *waiter = list_first_entry(&sem->wait_list,
struct semaphore_waiter, list); // 从wait_list中取出第一个等待进程
list_del(&waiter->list); //在wait_list删除这个等待者
waiter->up = true; // 设置标记,表示获得了信号量
wake_up_process(waiter->task); // 唤醒得到信号量的进程
}