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

信号量定义如下:

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

其中wait_list链表用于管理因没有成功获取信号量而处于睡眠状态的任务。

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

同时,整个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--;
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);
}

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

semaphore_waiter是一个关键的数据结构,代表一个获取信号量失败,正在等待的任务。up字段标识了该任务是否是被该信号量唤醒,也就是休眠中的任务收到某种信号被唤醒之后,判断是否是被等待中的信号量唤醒的。

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

__down_common函数首先初始化了一个semaphore_waiter。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;
...

然后休眠当前任务,调用 schedule_timeout()主动让出 CPU。上文提到整个函数都是在sem->lock的临界区中,但是在自旋锁的临界区是不可以休眠的,所以这里实际上在休眠之前释放了锁,被唤醒之后再重新获得锁。

当任务被唤醒后,如果waiter.up是否为真,则该任务可以获得信号量。waiter.up是必须要判断的,取决于__set_current_state()函数传入的参数不同,任务可能处于不同的休眠状态,可能被不同的信号唤醒,而未必是被等待的信号唤醒。

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

当一个任务释放信号量时,如果信号量的等待队列中存在任务,则将队列中的第一个任务的 up标记为true,并唤醒,同时从等待队列中删除。

同时,只有在等待队列为空的情况下,才会更新sem->count,确保了等待队列中的任务优先于新来的任务获得信号量,保证了严格的先进先出,不会因为新来的任务导致等待队列中的任务饥饿。

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

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

Linux中信号量的实现的更多相关文章

  1. Linux中信号量处理

    参考文章: http://blog.csdn.net/qinxiongxu/article/details/7830537/ 信号量一. 什么是信号量信号量的使用主要是用来保护共享资源,使得资源在一个 ...

  2. linux 中信号量

    ctrl-c 发送 SIGINT 信号给前台进程组中的所有进程.常用于终止正在运行的程序.ctrl-z 发送 SIGTSTP 信号给前台进程组中的所有进程,常用于挂起一个进程.ctrl-d 不是发送信 ...

  3. Linux有名信号量的创建(sem_open中name参数构造)【转】

    转自:http://blog.csdn.net/gfeng168/article/details/40740865 版权声明:本文为博主原创文章,未经博主允许不得转载. 一.sem_open函数nam ...

  4. linux进程间通信-信号量(semaphore)

    一 为什么要使用信号量 为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问 代码的临界区域.临界区域是指执 ...

  5. Linux就这个范儿 第15章 七种武器 linux 同步IO: sync、fsync与fdatasync Linux中的内存大页面huge page/large page David Cutler Linux读写内存数据的三种方式

    Linux就这个范儿 第15章 七种武器  linux 同步IO: sync.fsync与fdatasync   Linux中的内存大页面huge page/large page  David Cut ...

  6. 【转载】linux中互斥尽量用mutex,不用semaphore

    DEFINE_MUTEX是来自include/linux/mutex.h中的一个宏,用它可以定义一把互斥锁,在Linux内核中,其实是在2005年底才建立比较系统.完善的互斥锁机制,在那年冬天,来自R ...

  7. Linux中C/C++头文件一览

    1.Linux中一些头文件的作用: #include <assert.h>       //ANSI C.提供断言,assert(表达式) #include <glib.h>  ...

  8. Linux进程间通信--信号量

    信号量绝对不同于信号,一定要分清,关于信号,上一篇博客中已经说过,如有疑问,请移驾! 信号量 一.是什么   信号量的本质是一种数据操作锁,它本身不具有数据交换的功能,而是通过控制其他的通信资源(文件 ...

  9. Linux中常用头文件的作用--转

    http://blog.sina.com.cn/s/blog_5c93b2ab0100q62k.html 1. Linux中一些头文件的作用: <assert.h>:ANSI C.提供断言 ...

随机推荐

  1. 二叉树:前序遍历、中序遍历、后序遍历,BFS,DFS

    1.定义 一棵二叉树由根结点.左子树和右子树三部分组成,若规定 D.L.R 分别代表遍历根结点.遍历左子树.遍历右子树,则二叉树的遍历方式有 6 种:DLR.DRL.LDR.LRD.RDL.RLD.由 ...

  2. 一个".java"源文件中是否可以包括多个类(不是内部类)?有什么限制?

    可以有多个类,但只能有一个public的类,并且public的类名必须与文件的主文件名相同. 包含多个类的Java源文件编译之后会生成多个.class文件.

  3. python模块相互依赖的解决方案

    第一种:将相互依赖的文件中的其中一个文件的代码移植到另一个文件中... 第二种:将 import .... 或 from ... import 语句的位置移动到def函数内部,由于import和fro ...

  4. SPI简单解析

    什么是SPI  一种服务加载方式,全名为Service Provider Interface,Service提供者接口 如果我们要抽象里面的模块,在面对对象编程当中,我们模块之间,一般推荐模块之间基于 ...

  5. Java 中,DOM 和 SAX 解析器有什么不同?

    DOM 解析器将整个 XML 文档加载到内存来创建一棵 DOM 模型树,这样可以 更快的查找节点和修改 XML 结构,而 SAX 解析器是一个基于事件的解析器, 不会将整个 XML 文档加载到内存.由 ...

  6. IE中的编码位置

    进入设置 找到Editor 找到File Encodings

  7. 配置sublime text 3来编写Markdown

    如何使用sublime text 3编写Markdown  编写markdown的编辑器无论客户端还是在线的都有很多,这里将sublime text3作为markdown的编辑器,需要进行一些配置. ...

  8. 03-三高-并行并发&服务集群

          三高项目 服务并行&并发 并行和并发 服务的搭建中,并行 并发.----并发. 集群 同质的(同样的配置,运行同样的程序,对外提供同样的服务). 修改同样的存储,可以认. (小建议 ...

  9. 视觉SLAM十四讲:从理论到实践 两版 PDF和源码

    视觉SLAM十四讲:从理论到实践 第一版电子版PDF 链接:https://pan.baidu.com/s/1SuuSpavo_fj7xqTYtgHBfw提取码:lr4t 源码github链接:htt ...

  10. 探索前端黑科技——通过 png 图的 rgba 值缓存数据

    本文系原创,欢迎转载,转载请注明作者信息项目地址:SphinxJS在线体验地址:https://jrainlau.github.io/sp... 说起前端缓存,大部分人想到的无非是几个常规的方案,比如 ...