Linux中信号量的实现

如果一个任务获取信号量失败,该任务必须等到其他任务释放信号量。本文的重点是,在Linux中,当一个任务释放一个信号量时,如何唤醒一个正在等待信号量的任务。

信号量定义如下:

struct semaphore {
	raw_spinlock_t		lock;
	unsigned int		count;
	struct list_head	wait_list;
};

链表用于管理由于信号量获取不成功而处于休眠状态的任务。

任务尝试通过调用 down() 函数来获取信号量。如果信号量获取失败,则调用()函数。 () 函数在函数内部被调用。 (其实down()函数有多种变体,比如信号量获取失败时,也会调用该函数。不同的down()函数最终调用不同的参数,处理不同的信号量获取方式。正在发生)。

同时整个down()函数被sem->lock保护。

void down(struct semaphore *sem)
{
	unsigned long flags;
	raw_spin_lock_irqsave(&sem->lock, flags);
	if (likely(sem->count > 0))
		sem->count--;

图片[1]-Linux中信号量的实现-唐朝资源网

else __down(sem); raw_spin_unlock_irqrestore(&sem->lock, flags); } static noinline void __sched __down(struct semaphore *sem) { __down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT); }

这里是重点:函数如何休眠任务,休眠任务如何被唤醒并获取信号量。

是一个关键数据结构,表示未能获取信号量的等待任务。 up字段标识任务是否被信号量唤醒,即休眠任务被某个信号唤醒后,判断是否被等待信号量唤醒。

struct semaphore_waiter {

	struct list_head list;
	struct task_struct *task;
	bool up;
};

函数首先初始化一个。 task字段标识当前任务,up设置为false。

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;
	waiter.up = false;
...

然后休眠当前任务并调用 () 主动让出 CPU。上面说了,整个函数都在sem->lock的临界区,但是在spin lock的临界区是不可能sleep的,所以实际上在sleep之前就释放了锁,重新加锁被唤醒后获得。 .

当任务被唤醒时,如果.up为真,则任务可以获得信号量。 .up必须要判断,根据()函数传入的参数,任务可能处于不同的睡眠状态,可能被不同的信号唤醒,不一定是等待信号。

	for (;;) {
		if (signal_pending_state(state, current))
			goto interrupted;
		if (unlikely(timeout lock);
		timeout = schedule_timeout(timeout);
		raw_spin_lock_irq(&sem->lock);
		if (waiter.up)
			return 0;
	}

图片[2]-Linux中信号量的实现-唐朝资源网

timed_out: list_del(&waiter.list); return -ETIME; interrupted: list_del(&waiter.list); return -EINTR; }

当一个任务释放信号量时,如果该信号量的等待队列中有任务,则将队列中第一个任务的up标记为真,将其唤醒,同时将其从等待队列中删除时间。

同时,sem->count只在等待队列为空时才更新,保证等待队列中的任务优先于新任务获取信号量,严格的先进先出是保证。等待队列中的任务因为有新任务而被饿死。

void up(struct semaphore *sem)
{
	unsigned long flags;
	raw_spin_lock_irqsave(&sem->lock, flags);

图片[3]-Linux中信号量的实现-唐朝资源网

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); }

任务被唤醒后,检测到up为真,返回0,成功获取信号量。

© 版权声明
THE END
喜欢就支持一下吧
点赞79赞赏 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容