如果一个任务获取信号量失败,该任务就必须等待,直到其他任务释放信号量。本文的重点是,在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. 利用公网Msf+MS17010跨网段攻击内网

    其实很多小白在对利用msf进行ms17010攻击的时候总是不成功,其实这都是因为网上大部分的文章都是写的内网对内网(192.168.1.2-->192.168.1.3)这样的案例,今天写了一下利 ...

  2. 利用DNSLog实现无回显注入

    测试一些网站的时候,一些注入都是无回显的,我们可以写脚本来进行盲注,但有些网站会ban掉我们的ip,这样我们可以通过设置ip代理池解决, 但是盲注往往效率很低,所以产生了DNSlog注入 DNSLOG ...

  3. hanoi(老汉诺塔问题新思维)

    #include <stdio.h> //第一个塔为初始塔,中间的塔为借用塔,最后一个塔为目标塔 int i=1;//记录步数 void move(int n, char from,cha ...

  4. SpingMvc中的控制器的注解一般用那个,有没有别的注解可以替代?

    答:一般用@Controller注解,也可以使用@RestController,@RestController注解相当于@ResponseBody + @Controller,表示是表现层,除此之外, ...

  5. Redis 的同步机制了解么?

    Redis 可以使用主从同步,从从同步.第一次同步时,主节点做一次 bgsave, 并同时将后续修改操作记录到内存 buffer,待完成后将 rdb 文件全量同步到复制 节点,复制节点接受完成后将 r ...

  6. MM32F0140 UART1硬件自动波特率校准功能的使用

    目录: 1.MM32F0140简介 2.UART自动波特率校准应用场景 3.MM32F0140 UART自动波特率校准原理简介 4.MM32F0140 UART1 NVIC硬件自动波特率配置以及初始化 ...

  7. Python - 分支循环、可迭代对象与迭代器

  8. Linux ftp服务器部署(最简单的ftp教程)

    之前在阿里云领了一个ECS服务器(顺便说一句,白嫖的,真香~),就想着做点什么,然后试着做个 ftp 站点,因为第一次尝试,结果走了不少弯路.最后终于完成了,研究了两天(哎~,脑壳笨没办法)就想着记录 ...

  9. BFC理解

    Block formatting context (块级格式化上下文) 页面文档由块block构成 每个block在页面上占据自己的位置 使用新的元素构建BFC overflow:hidden | a ...

  10. C#编写一个控制台应用程序,输入三角形或者长方形边长,计算其周长和面积并输出

    编写一个控制台应用程序,输入三角形或者长方形边长,计算其周长和面积并输出. 代码: using System; using System.Collections.Generic; using Syst ...