关键词: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, )---------------和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, );
}

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则表明当前进程可以成功获取信号量。
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 = ; raw_spin_lock_irqsave(&sem->lock, flags);
if (likely(sem->count > ))
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 = ; raw_spin_lock_irqsave(&sem->lock, flags);
if (likely(sem->count > ))
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 - ;
if (likely(count >= ))-------------------------------判断当前sem->count的减1后是否大于等于0。如果小于0,则表示无法获取信号量;如果大于等于0,表示可以成功获取信号量,并更新sem->count的值。
sem->count = count;
raw_spin_unlock_irqrestore(&sem->lock, flags); return (count < );-----------------------------------如果count<0,表示无法获取信号量;如果count<0不成立,则表示获取信号量失败。
} int down_timeout(struct semaphore *sem, long timeout)
{
unsigned long flags;
int result = ; raw_spin_lock_irqsave(&sem->lock, flags);
if (likely(sem->count > ))
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 <= ))-----------------------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 ;
} 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 < ) {
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 < ? : 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, );
}

4. 信号量和spinlock的对比

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

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

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

Linux并发与同步专题 (3) 信号量的更多相关文章

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

    关键词:mutex.MCS.OSQ. <Linux并发与同步专题 (1)原子操作和内存屏障> <Linux并发与同步专题 (2)spinlock> <Linux并发与同步 ...

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

    关键词:wfe.FIFO ticket-based.spin_lock/spin_trylock/spin_unlock.spin_lock_irq/spin_lock_bh/spin_lock_ir ...

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

    关键词:. <Linux并发与同步专题 (1)原子操作和内存屏障> <Linux并发与同步专题 (2)spinlock> <Linux并发与同步专题 (3) 信号量> ...

  4. Linux并发与同步专题

    并发访问:多个内核路径同时访问和操作数据,就有可能发生相互覆盖共享数据的情况,造成被访问数据的不一致. 临界区:访问和操作共享数据的代码段. 并发源:访问临界区的执行线程或代码路径. 在内核中产生并发 ...

  5. 四十三、Linux 线程——线程同步之线程信号量

    43.1 信号量 43.1.1 信号量介绍 信号量从本质上是一个非负整数计数器,是共享资源的数目,通常被用来控制对共享资源的访问 信号量可以实现线程的同步和互斥 通过 sem_post() 和 sem ...

  6. 漫画|Linux 并发、竞态、互斥锁、自旋锁、信号量都是什么鬼?(转)

    知乎链接:https://zhuanlan.zhihu.com/p/57354304 1. 锁的由来? 学习linux的时候,肯定会遇到各种和锁相关的知识,有时候自己学好了一点,感觉半桶水的自己已经可 ...

  7. linux 线程的同步 一 (互斥量和信号量)

    互斥量(Mutex) 互斥量表现互斥现象的数据结构,也被当作二元信号灯.一个互斥基本上是一个多任务敏感的二元信号,它能用作同步多任务的行为,它常用作保护从中断来的临界段代码并且在共享同步使用的资源. ...

  8. Linux海量数据高并发实时同步架构方案杂谈

    不论是Redhat还是CentOS系统,除去从CDN缓存或者数据库优化.动静分离等方面来说,在架构层面上,实 现海量数据高并发实时同步访问概括起来大概可以从以下几个方面去入手,当然NFS的存储也可以是 ...

  9. linux下的同步与互斥

    linux下的同步与互斥 谈到linux的并发,必然涉及到线程之间的同步和互斥,linux主要为我们提供了几种实现线程间同步互斥的 机制,本文主要介绍互斥锁,条件变量和信号量.互斥锁和条件变量包含在p ...

随机推荐

  1. Spring学习之旅(三)Spring工作原理初探

    详细的废话相信很多书籍视频资料都已经很多了,这里说几个小编个人认为对于理解Spring框架很重要的点.欢迎批评指正. 1)Spring的控制反转 先说说“依赖”,在面向对象程序设计中,类A中用到了类B ...

  2. web自动化 基于python+Selenium+PHP+Ftp实现的轻量级web自动化测试框架

    基于python+Selenium+PHP+Ftp实现的轻量级web自动化测试框架   by:授客 QQ:1033553122     博客:http://blog.sina.com.cn/ishou ...

  3. Linux学习笔记 软链接和硬链接

    Linux 中,"everything is file".接下来给大家介绍 linux 如何通过链接,达到节省磁盘空间.共享文件等目的.链接文件有两种方式,软链接(soft lin ...

  4. 关于Box Anemometer的安装配置遇到的几个坑

    Box Anemometer是一个非常不错的MySQL慢查询可视化工具平台,具体就不多详述了,官网是https://github.com/box/Anemometer 最近动自己动手搭了一个,具体搭建 ...

  5. Linux下2号进程的kthreadd--Linux进程的管理与调度(七)

    2号进程 内核初始化rest_init函数中,由进程 0 (swapper 进程)创建了两个process init 进程 (pid = 1, ppid = 0) kthreadd (pid = 2, ...

  6. android调试工具adb命令大全

    转载: 一.adb介绍SDK的Tools文件夹下包含着Android模拟器操作的重要命令adb,adb的全称为(Android Debug Bridge就是调试桥的作用.通过adb我们可以在Eclip ...

  7. layui 的 GitHub 及 Gitee (码云) 仓库

    GitHub: https://github.com/sentsin/layui/ Gitee:https://gitee.com/sentsin/layui

  8. java操作elasticsearch实现聚合查询

    1.max 最大值 //max 求最大值 @Test public void test30() throws UnknownHostException{ //1.指定es集群 cluster.name ...

  9. Windows Live Writer 2012在Blogjava管理和发布博客

    下载Windows Live Writer 2012的完整版本(wlsetup-all.exe),安装的时候减少网络下载消耗的时间.注:有些平台可能还需要下载DotNet3.5(dotnetfx35. ...

  10. 《Java大学教程》—第14章 抽象、继承和接口

    自测题:1.    解释抽象和抽象数据类型的概念.P333抽象的概念是仅仅关注对象可以完成什么工作,而不必担心如何完成工作的细节.类模板通常被称为抽象数据类型.因为这类数据暴露给用户的所有信息仅仅是方 ...