php中文网 | cnphp.com

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 462|回复: 0

Linux中信号量源码的分析

[复制链接]

2871

主题

2881

帖子

1万

积分

管理员

Rank: 9Rank: 9Rank: 9

UID
1
威望
0
积分
7285
贡献
0
注册时间
2021-4-14
最后登录
2024-9-20
在线时间
716 小时
QQ
发表于 2022-4-19 08:41:22 | 显示全部楼层 |阅读模式
如果一个任务获取信号量失败,该任务就必须等待,直到其他任务释放信号量。本文的重点是,在Linux中,当有任务释放信号量之后,如何唤醒正在等待该信号量的任务。

信号量定义如下:
[mw_shl_code=applescript,true]struct semaphore {
        raw_spinlock_t                lock;
        unsigned int                count;
        struct list_head        wait_list;
};
[/mw_shl_code]
其中wait_list链表用于管理因没有成功获取信号量而处于睡眠状态的任务。

任务通过调用down()函数,尝试获取信号量,如果获取信号量失败,调用__down()函数。__down()函数内部调用了__down_common函数。(事实上down()函数有多个变种,如down_interruptible,在获取信号量失败时调用__down_interruptible,__down_interruptible也会调用__down_common函数。不同的down()函数最终调用__down_common时传入不同的参数,以处理不同的获取信号量的情况)。

同时,整个down()函数使用sem->lock保护起来。
[mw_shl_code=applescript,true]void down(struct semaphore *sem)
{
        unsigned long flags;

        raw_spin_lock_irqsave(&sem->lock, flags);
        if (likely(sem->count > 0))
                sem->count--;
        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);
}



[/mw_shl_code]
下面是重点:__down_common函数如何使任务休眠,休眠中的任务如何被唤醒并获得信号量。

semaphore_waiter是一个关键的数据结构,代表一个获取信号量失败,正在等待的任务。up字段标识了该任务是否是被该信号量唤醒,也就是休眠中的任务收到某种信号被唤醒之后,判断是否是被等待中的信号量唤醒的。
[mw_shl_code=applescript,true]struct semaphore_waiter {
        struct list_head list;
        struct task_struct *task;
        bool up;
};

[/mw_shl_code]
__down_common函数首先初始化了一个semaphore_waiter。task字段标识当前任务,up设置为false。
[mw_shl_code=applescript,true]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;
...
[/mw_shl_code]
然后休眠当前任务,调用 schedule_timeout()主动让出 CPU。上文提到整个函数都是在sem->lock的临界区中,但是在自旋锁的临界区是不可以休眠的,所以这里实际上在休眠之前释放了锁,被唤醒之后再重新获得锁。
当任务被唤醒后,如果waiter.up是否为真,则该任务可以获得信号量。waiter.up是必须要判断的,取决于__set_current_state()函数传入的参数不同,任务可能处于不同的休眠状态,可能被不同的信号唤醒,而未必是被等待的信号唤醒。
[mw_shl_code=applescript,true]        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;
}
[/mw_shl_code]
当一个任务释放信号量时,如果信号量的等待队列中存在任务,则将队列中的第一个任务的 up标记为true,并唤醒,同时从等待队列中删除。
同时,只有在等待队列为空的情况下,才会更新sem->count,确保了等待队列中的任务优先于新来的任务获得信号量,保证了严格的先进先出,不会因为新来的任务导致等待队列中的任务饥饿。
[mw_shl_code=applescript,true]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);
}
[/mw_shl_code]
任务被唤醒之后,检测到up为true,返回0,成功获得信号量。

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|php中文网 | cnphp.com ( 赣ICP备2021002321号-2 )

GMT+8, 2024-9-20 07:49 , Processed in 0.188162 second(s), 31 queries , Gzip On.

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2020, Tencent Cloud.

申明:本站所有资源皆搜集自网络,相关版权归版权持有人所有,如有侵权,请电邮(fiorkn@foxmail.com)告之,本站会尽快删除。

快速回复 返回顶部 返回列表