Linux内核源码分析 -- 同步原语 -- 信号量 semaphore
Posted scriptkid
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux内核源码分析 -- 同步原语 -- 信号量 semaphore相关的知识,希望对你有一定的参考价值。
Linux内核源码分析 -- 同步原语 -- 信号量 semaphore
源码位于 include/linux/semaphore
struct semaphore {
raw_spinlock_t lock; // 保护信号量的自旋锁
unsigned int count; // 现有的资源的数量
struct list_head wait_list; // 等待获取这个锁的进程队列
};
初始化
DEFINE_SEMAPHORE
是初始化一个 二值信号量
#define DEFINE_SEMAPHORE(name) struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)
#define __SEMAPHORE_INITIALIZER(name, n) { .lock = __RAW_SPIN_LOCK_UNLOCKED((name).lock), .count = n, .wait_list = LIST_HEAD_INIT((name).wait_list), }
__RAW_SPIN_LOCK_UNLOCKED((name).lock)
返回的是一个 released
的自旋锁
n
表示现有的资源的数量
LIST_HEAD_INIT((name).wait_list)
返回 NULL
, 把 等待获取这个锁的进程队列 初始化为链表头,指向 NULL
可以用 sema_init
函数来初始化一个 普通信号量
static inline void sema_init(struct semaphore *sem, int val)
{
static struct lock_class_key __key;
*sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val);
lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0); // 锁验证 这里不用管
}
其实还是调用了 __SEMAPHORE_INITIALIZER
把 cout
赋值成 val
信号量的 API
void down(struct semaphore *sem); // 获取信号量
void up(struct semaphore *sem); // 释放信号量
int down_interruptible(struct semaphore *sem);
int down_killable(struct semaphore *sem);
int down_trylock(struct semaphore *sem);
int down_timeout(struct semaphore *sem, long jiffies);
-
down_interruptible
函数:试图去获取一个信号量
。如果被成功获取,信号量
的计数就会被减少并且锁也会被获取。同时当前任务也会被调度到受阻状态,也就是说TASK_INTERRUPTIBLE
标志将会被至位。TASK_INTERRUPTIBLE
表示这个进程也许可以通过信号退回到销毁状态。 -
down_killable
函数:和down_interruptible
函数提供类似的功能,但是它还将当前进程的TASK_KILLABLE
标志置位。这表示等待的进程可以被杀死信号中断。 -
down_trylock
函数:和spin_trylock
函数相似。这个函数试图去获取一个锁并且退出如果这个操作是失败的。在这个例子中,想获取锁的进程不会等待 -
down_timeout
函数试图去获取一个锁。当前进程将会被中断进入到等待状态当超过传入的可等待时间。这个等待的时间是以 jiffies计数。
down
获取信号量
void down(struct semaphore *sem)
{
unsigned long flags;
raw_spin_lock_irqsave(&sem->lock, flags);
// 如果现有的资源的数量大于 0
if (likely(sem->count > 0))
// 将可用资源减 1,表示我们已经获取了这个锁
sem->count--;
else // 现有的资源的数量小于(不可能小于 0 的吧)等于 0,这表示所以的现有资源都已经被占用
__down(sem);
raw_spin_unlock_irqrestore(&sem->lock, flags);
}
EXPORT_SYMBOL(down);
__down
把当前进程的状态设置成:TASK_UNINTERRUPTIBLE(将进程放入等待队伍中,等待资源有效时唤醒)
等待时间是:MAX_SCHEDULE_TIMEOUT()
static noinline void __sched __down(struct semaphore *sem)
{
__down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
}
__down_common
__down_interruptible
, __down_killable
, __down_timeout
的核心其实都是 __down_common
__down_interruptible
:
__down_common(sem, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
__down_killable
:
__down_common(sem, TASK_KILLABLE, MAX_SCHEDULE_TIMEOUT);
__down_timeout
:
__down_common(sem, TASK_UNINTERRUPTIBLE, timeout);
/*
* Because this function is inlined, the ‘state‘ parameter will be
* constant, and thus optimised away by the compiler. Likewise the
* ‘timeout‘ parameter for the cases without timeouts.
*/
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;
// 进入一个死循环
for (;;) {
// 检查 state 和 检查当前的进程是否处于 pending 状态
if (signal_pending_state(state, current))
goto interrupted;
if (unlikely(timeout <= 0))
goto timed_out;
// 如果一个任务没有挂起信号而且给予的超时也没有过期,当前的任务将会被设置为传入的 state
__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
list_del(&waiter.list);
// 返回 超时 的错误码
return -ETIME;
interrupted:
// 清空等待 list
list_del(&waiter.list);
// 返回 任务没有挂起 的错误码
return -EINTR;
}
signal_pending_state
先检测 state
位掩码 是否包含 TASK_INTERRUPTIBLE
或者 TASK_WAKEKILL
位,如果不包含这两个位,函数退出。下一步我们检测当前任务是否有一个挂起信号,如果没有挂起信号函数退出。最后我们就检测 state
位掩码的 TASK_INTERRUPTIBLE
位。
static inline int signal_pending_state(long state, struct task_struct *p)
{
// 检查 state 有没有 TASK_INTERRUPTIBLE 和 TASK_WAKEKILL 标志
if (!(state & (TASK_INTERRUPTIBLE | TASK_WAKEKILL)))
return 0;
// 检查进程是不是处于 pending 状态
if (!signal_pending(p))
return 0;
return (state & TASK_INTERRUPTIBLE) || __fatal_signal_pending(p);
}
如果一个函数想要获取一个已经被其它任务获取的锁,它将会转入到无限循环。并且它不能被信号中断,当前设置的超时不会过期或者当前持有锁的任务不释放它。
up
释放信号量
oid 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);
}
EXPORT_SYMBOL(up);
__up
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,让进程结束等待(跳出 __down_common 中的 死循环)
waiter->up = true;
// 唤醒进程
wake_up_process(waiter->task);
}
其实就是,判断当前等待队列里面有没有进程,有的话调用 __up
,获得等待队列中的第一个进程,然后把它从等待队列里面删除,结束进程的等待,唤醒进程。
本文参考(抄于)
《Linux Inside》:https://github.com/0xAX/linux-insides
《内核揭秘(中文版)》:https://github.com/MintCN/linux-insides-zh
我在书栈网看的,在此推荐一波:https://www.bookstack.cn/ (我在上面的 id:scriptkid)
以上是关于Linux内核源码分析 -- 同步原语 -- 信号量 semaphore的主要内容,如果未能解决你的问题,请参考以下文章
Linux内核源码分析 -- 同步原语 -- 自旋锁 spinlock