文档章节

Linux并发与同步专题 (3) 信号量

o
 osc_3xdojbp9
发布于 2018/07/07 15:00
字数 1850
阅读 14
收藏 0

「深度学习福利」大神带你进阶工程师,立即查看>>>

关键词:Semaphore、down()/up()。

Linux并发与同步专题 (1)原子操作和内存屏障

Linux并发与同步专题 (2)spinlock

Linux并发与同步专题 (3) 信号量

Linux并发与同步专题 (4) Mutex互斥量

Linux并发与同步专题 (5) 读写锁

Linux并发与同步专题 (6) RCU

Linux并发与同步专题 (7) 内存管理中的锁

Linux并发与同步专题 (8) 最新更新与展望

 

 

1. 信号量数据结构

数据机构struct semaphore用于描述信号量。

/* Please don't access any members of this structure directly */
struct semaphore {
    raw_spinlock_t        lock;-----------------------------spinlock变量,用于对信号量数据结构里count和wait_list成员的保护。
    unsigned int        count;------------------------------用于表示允许进入临界区的内核执行路径个数。
    struct list_head    wait_list;--------------------------用于管理所有在该信号量上睡眠的进程,没有成功获取锁的进程会睡眠在这个链表上。
};

数据结构struct semaphore_waiter用于描述将在信号量等待队列山等待的进程。

struct semaphore_waiter {
    struct list_head list;---------------------------------链表项
    struct task_struct *task;------------------------------将要放到信号量等待队列上的进程结构
    bool up;
};

 

 

2. 信号量的初始化

信号量的初始化有两种,一种是通过sema_init()动态初始化一个信号量,另一种是通过DEFINE_SEMAPHORE()静态定义一个信号量。

这两者都通过__SEMAPHORE_INITIALIZER()完成初始化工作。区别是sema_init()提供了lockdep调试跟踪,而且sema_init()可以指定持锁路径个数;而DEFINE_SEMAPHORE()默认为1。

#define __SEMAPHORE_INITIALIZER(name, n)                \
{                                    \
    .lock        = __RAW_SPIN_LOCK_UNLOCKED((name).lock),    \
    .count        = n,                        \
    .wait_list    = LIST_HEAD_INIT((name).wait_list),        \
}

#define DEFINE_SEMAPHORE(name)    \
    struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)---------------和sema_init()区别在于此处只有1.

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

 

 

 

3. down()/up()

信号量的使用较简单,down_xxx()持有信号量,up()释放信号量。

down()有很多变种,基本上遵循一致的规则:首先判断sem->count是否大于0,如果大于0,则sem->count--;否则调用__down_xxx()函数。

__down_xxx()最终都会调用__down_common()函数,他们之间的区别就是参数不一样。

down()变种 flag timeout 说明
down() TASK_UNINTERRUPTIBLE MAX_SCHEDULE_TIMEOUT 争用信号量失败时进入不可中断的睡眠状态。
down_interruptible() TASK_INTERRUPTIBLE MAX_SCHEDULE_TIMEOUT 争用信号量失败时进入可中断的睡眠状态。
down_killable() TASK_KILLABLE MAX_SCHEDULE_TIMEOUT 争用信号量失败时进入不可中断睡眠状态,但是在收到致命信号时唤醒睡眠进程。
down_timeout() TASK_UNINTERRUPTIBLE timeout 争用信号量失败时进入不可中断的睡眠状态,超时则唤醒当前进程。

 

down_trylock()是个特例,并不会等待,只是单纯的去获取锁。返回0表示获取锁成功,返回1表示获取锁失败。

 

void down(struct semaphore *sem)
{
    unsigned long flags;

    raw_spin_lock_irqsave(&sem->lock, flags);-----------------获取spinlock并关本地中断来保护count数据。
    if (likely(sem->count > 0))-------------------------------如果大于0则表明当前进程可以成功获取信号量。
        sem->count--;
    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);
}

int down_interruptible(struct semaphore *sem)
{
    unsigned long flags;
    int result = 0;

    raw_spin_lock_irqsave(&sem->lock, flags);
    if (likely(sem->count > 0))
        sem->count--;
    else
        result = __down_interruptible(sem);
    raw_spin_unlock_irqrestore(&sem->lock, flags);

    return result;
}

static noinline int __sched __down_interruptible(struct semaphore *sem)
{
    return __down_common(sem, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
}

int down_killable(struct semaphore *sem)
{
    unsigned long flags;
    int result = 0;

    raw_spin_lock_irqsave(&sem->lock, flags);
    if (likely(sem->count > 0))
        sem->count--;
    else
        result = __down_killable(sem);
    raw_spin_unlock_irqrestore(&sem->lock, flags);

    return result;
}

static noinline int __sched __down_killable(struct semaphore *sem)
{
    return __down_common(sem, TASK_KILLABLE, MAX_SCHEDULE_TIMEOUT);
}

int down_trylock(struct semaphore *sem)
{
    unsigned long flags;
    int count;

    raw_spin_lock_irqsave(&sem->lock, flags);
    count = sem->count - 1;
    if (likely(count >= 0))-------------------------------判断当前sem->count的减1后是否大于等于0。如果小于0,则表示无法获取信号量;如果大于等于0,表示可以成功获取信号量,并更新sem->count的值。
        sem->count = count;
    raw_spin_unlock_irqrestore(&sem->lock, flags);

    return (count < 0);-----------------------------------如果count<0,表示无法获取信号量;如果count<0不成立,则表示获取信号量失败。
}

int down_timeout(struct semaphore *sem, long timeout)
{
    unsigned long flags;
    int result = 0;

    raw_spin_lock_irqsave(&sem->lock, flags);
    if (likely(sem->count > 0))
        sem->count--;
    else
        result = __down_timeout(sem, timeout);
    raw_spin_unlock_irqrestore(&sem->lock, flags);

    return result;
}

static noinline int __sched __down_timeout(struct semaphore *sem, long timeout)
{
    return __down_common(sem, TASK_UNINTERRUPTIBLE, timeout);
}

 

 

static inline int __sched __down_common(struct semaphore *sem, long state,
                                long timeout)
{
    struct task_struct *task = current;-------------------得到当前进程结构
    struct semaphore_waiter waiter;-----------------------struct semaphore_waiter数据结构用于描述获取信号量失败的进程,每个进程会有一个semaphore_waiter数据结构,并把当前进程放到信号量sem的成员变量wait_list链表中。

    list_add_tail(&waiter.list, &sem->wait_list);---------将waiter加入到信号量sem->waiter_list尾部
    waiter.task = task;-----------------------------------waiter.task指向当前正在运行的进程。
    waiter.up = false;

    for (;;) {
        if (signal_pending_state(state, task))------------根据不同state和当前信号pending情况,决定是否进入interrupted处理。
            goto interrupted;
        if (unlikely(timeout <= 0))-----------------------timeout设置错误
            goto timed_out;
        __set_task_state(task, state);--------------------设置当前进程task->state。
        raw_spin_unlock_irq(&sem->lock);------------------下面即将睡眠,这里释放了spinlock锁,和down()中的获取spinlock锁对应。
        timeout = schedule_timeout(timeout);--------------主动让出CPU,相当于当前进程睡眠。
        raw_spin_lock_irq(&sem->lock);--------------------重新获取spinlock锁,在down()会重新释放锁。这里保证了schedule_timeout()不在spinlock环境中。
        if (waiter.up)------------------------------------waiter.up为true时,说明睡眠在waiter_list队列中的进程被该信号量的up操作唤醒。
            return 0;
    }

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

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

static inline int signal_pending_state(long state, struct task_struct *p)
{
  if (!(state & (TASK_INTERRUPTIBLE | TASK_WAKEKILL)))---------------------对于TASK_UNINTERRUPTIBLE,返回0,继续睡眠。TASK_INTERRUPTIBLE和TASK_WAKEKILL则往下继续判断。
    return 0;
  if (!signal_pending(p))--------------------------------------------------TASK_INTERRUPTIBLE和TASK_WAKEKILL情况,如果没有信号pending,则返回0,继续睡眠.
    return 0;

  return (state & TASK_INTERRUPTIBLE) || __fatal_signal_pending(p);--------如果是TASK_INTERRUPTIBLE或有SIGKILL信号未处理,则返回1,中断睡眠等待。
}


signed long __sched schedule_timeout(signed long timeout)
{
    struct timer_list timer;
    unsigned long expire;

    switch (timeout)
    {
    case MAX_SCHEDULE_TIMEOUT:
        schedule();-------------------------------------------------------MAX_SCHEDULE_TIMEOUT并不设置一个具体的时间,仅是睡眠。
        goto out;
    default:
        if (timeout < 0) {
            printk(KERN_ERR "schedule_timeout: wrong timeout "
                "value %lx\n", timeout);
            dump_stack();
            current->state = TASK_RUNNING;
            goto out;
        }
    }

    expire = timeout + jiffies;

    setup_timer_on_stack(&timer, process_timeout, (unsigned long)current);
    __mod_timer(&timer, expire, false, TIMER_NOT_PINNED);----------------这时一个timer,超时函数为process_timeout(),超时后wake_up_process()唤醒当前进程current。
    schedule();
    del_singleshot_timer_sync(&timer);-----------------------------------删除timer

    /* Remove the timer from the object tracker */
    destroy_timer_on_stack(&timer);--------------------------------------销毁timer

    timeout = expire - jiffies;------------------------------------------还剩多少jiffies达到超时点。

 out:
    return timeout < 0 ? 0 : timeout;------------------------------------timeout<0表示已超过超时点;timeout>0表示提前了timeout个jiffies唤醒了。
}

 

 

 

void up(struct semaphore *sem)
{
    unsigned long flags;

    raw_spin_lock_irqsave(&sem->lock, flags);
    if (likely(list_empty(&sem->wait_list)))---------------------------如果信号量上的等待队列sem->wait_list为空,说明没有进程在等待该信号来那个,那么直接sem->count加1。
        sem->count++;
    else
        __up(sem);-----------------------------------------------------如果不为空,说明有进程在等待队列里睡眠,调用__up()唤醒。
    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从信号量等待队列列表删除。
    waiter->up = true;--------------------------------------------------修改该信号量等待队列上waiter->up变量。
    wake_up_process(waiter->task);--------------------------------------唤醒该信号量等待队列上的进程。
}

int wake_up_process(struct task_struct *p)
{
    WARN_ON(task_is_stopped_or_traced(p));
    return try_to_wake_up(p, TASK_NORMAL, 0);
}

 

 

4. 信号量和spinlock的对比

spinlock临界区不允许睡眠,是一种忙等待;信号量允许进程进入睡眠状态。

spinlock同一时刻只能被一个内核代码路径持有;信号量可以同时允许任意数量的持有者。

spinlock适用于一些快速完成的简单场景;信号量适用于一些情况复杂、加锁时间较长的应用场景。

 

o
粉丝 0
博文 500
码字总数 0
作品 0
私信 提问
加载中
请先登录后再评论。
Netty那点事(三)Channel与Pipeline

Channel是理解和使用Netty的核心。Channel的涉及内容较多,这里我使用由浅入深的介绍方法。在这篇文章中,我们主要介绍Channel部分中Pipeline实现机制。为了避免枯燥,借用一下《盗梦空间》的...

黄亿华
2013/11/24
2W
22
用vertx实现高吞吐量的站点计数器

工具:vertx,redis,mongodb,log4j 源代码地址:https://github.com/jianglibo/visitrank 先看架构图: 如果你不熟悉vertx,请先google一下。我这里将vertx当作一个容器,上面所有的圆圈要...

jianglibo
2014/04/03
4.2K
3
Swift百万线程攻破单例(Singleton)模式

一、不安全的单例实现 在上一篇文章我们给出了单例的设计模式,直接给出了线程安全的实现方法。单例的实现有多种方法,如下面: class SwiftSingleton { } 这段代码的实现,在shared中进行条...

一叶博客
2014/06/20
3.4K
16
树莓派(Raspberry Pi):完美的家用服务器

自从树莓派发布后,所有在互联网上的网站为此激动人心的设备提供了很多有趣和具有挑战性的使用方法。虽然这些想法都很棒,但树莓派( RPi )最明显却又是最不吸引人的用处是:创建你的完美家用...

异次元
2013/11/09
6.9K
8
Linux 反汇编工具--LDasm

LDasm (Linux 反汇编工具) 是一个基于 Perl/TK 的 objdump/binutils 图形化工具,试图模仿 W32Dasm 工具的外观。可搜索相互参照,将代码从 GAS 转换成 MASM 风格代码等等。...

匿名
2013/01/22
5.6K
1

没有更多内容

加载失败,请刷新页面

加载更多

微服务为什么选Spring Cloud?

现如今微服务架构十分流行,而采用微服务构建系统也会带来更清晰的业务划分和可扩展性。同时,支持微服务的技术栈也是多种多样的,本系列文章主要介绍这些技术中的翘楚——Spring Cloud。这是...

osc_6t6cjs45
3分钟前
13
0
后缀三姐妹

目录 写在前面 前置小碎骨 后缀数组 定义 举例 倍增法构造 优化 再优化 代码 后缀树 后缀自动机 写在最后 绝对不咕 写在前面 会考虑整个与标题相关的二次创作。 什么时候有能力再说 前置小碎...

osc_7e2pw1w9
4分钟前
0
0
主题搭建-初始化

方式一# 特点# 推荐的方式 项目做的任何升级都能远程推送到你的博客 支持在线切换项目中已经集成的所有皮肤 步骤# 1.你的博客首页 -> 管理 -> 设置 2.设置博客默认皮肤为 Custom 3.使用 load...

osc_lyz4aksj
6分钟前
0
0
我的前端知识体系构建(上)

❝ 前沿:树酱君是个渣渣,梳理了下发现还是蛮多知识点不够扎实,童鞋有机会也定期给自己做个复盘和回顾,梳理自己的知识体系。再加上前端娱乐圈变化多端,以至于我们既要加强对底层基础知识...

前端试炼
8分钟前
0
0
Codeforces Round #662 Div.2 (CF1392)

有人说这场背景描述挺烂,不过我觉得还不错( A题意有点烦,建议直接去看英文原文(( 手动画图然后推个结论,挺简单的,不赘述了: /*ID: LoxilanteTime: 2020/08/07Prog: CF1393A...

osc_5l7bcj86
7分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部