0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

信号量实现原理介绍

strongerHuang 来源:嵌入式艺术 2024-01-10 09:07 次阅读

转自 |嵌入式艺术 除了原子操作,中断屏蔽,自旋锁以及自旋锁的衍生锁之外,在Linux内核中还存在着一些其他同步互斥的手段。

下面我们来理解一下信号量,互斥体,完成量机制。

1、信号量介绍

信号量(Semaphore)是操作系统中最典型的用于同步和互斥的手段,信号量的值可以是0、1或者n。信号量与操作系统中的经典概念PV操作对应。 P(Produce):

将信号量S的值减1,即S=S-1;

如果S≥0,则该进程继续执行;否则该进程置为等待状态,排入等待队列。

V(Vaporize):

将信号量S的值加1,即S=S+1;

如果S>0,唤醒队列中等待信号量的进程。

信号量核心思想: 信号量的操作,更适合去解决生产者和消费者的问题,比如:我做出来一个饼,你才能吃一个饼;如果我没做出来,你就先释放CPU去忙其他的事情。

2、信号量的API

struct semaphore sem; // 定义信号量

void sema_init(struct semaphore *sem, int val); // 初始化信号量,并设置信号量sem的值为val。

void down(struct semaphore * sem); // 获得信号量sem,它会导致睡眠,因此不能在中断上下文中使用。
int down_interruptible(struct semaphore * sem); // 该函数功能与down类似,不同之处为,因为down()进入睡眠状态的进程不能被信号打断,但因为down_interruptible()进入睡眠状态的进程能被信号打断,信号也会导致该函数返回,这时候函数的返回值非0
int down_trylock(struct semaphore * sem); // 尝试获得信号量sem,如果能够立刻获得,它就获得该信号量并返回0,否则,返回非0值。它不会导致调用者睡眠,可以在中断上下文中使用。

void up(struct semaphore * sem); // 释放信号量,唤醒等待者。

由于新的Linux内核倾向于直接使用mutex作为互斥手段,信号量用作互斥不再被推荐使用。 信号量也可以用于同步,一个进程A执行down()等待信号量,另外一个进程B执行up()释放信号量,这样进程A就同步地等待了进程B。

3、API实现

3.1 semaphore

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

结构体名称:semaphore

文件位置:include/linux/semaphore.h

主要作用:用于定义一个信号量。

raw_spinlock_t:信号量结构体也使用了自旋锁,避免互斥。

count:表示信号量的计数器,表示资源的数量

struct list_head wait_list: 这是一个链表头,用于管理等待信号量的线程。当信号量的 count 等于0,即没有可用资源时,等待信号量的线程会被加入到这个链表中,以便在资源可用时进行唤醒。

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

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

#define LIST_HEAD_INIT(name) { &(name), &(name) }

函数名称:sema_init

文件位置:include/linux/semaphore.h

主要作用:初始化信号量,并设置信号量sem的值为val。

实现流程

使用__SEMAPHORE_INITIALIZER宏定义来初始化信号量

使用__RAW_SPIN_LOCK_UNLOCKED宏定义来初始化自旋锁

直接将val赋值给信号量的值count

使用LIST_HEAD_INIT来初始化一个链表


#defineLIST_HEAD_INIT(name){&(name),&(name)}

该宏接受一个参数 name,并返回一个结构体对象。这个对象有两个成员 next 和 prev,分别指向 name 本身。

这样,当我们使用该宏来初始化链表头节点时,会得到一个拥有 next 和 prev 成员的结构体对象。其中 next 和 prev 成员都指向该结构体对象本身。

这种初始化方式可以用于创建一个空的双向链表,因为在初始状态下,链表头节点的 next 和 prev 指针都指向自身,表示链表为空。

3.3 down

/**

down - acquire the semaphore
@sem: the semaphore to be acquired

Acquires the semaphore. If no more tasks are allowed to acquire the
semaphore, calling this function will put the task to sleep until the
semaphore is released.

Use of this function is deprecated, please use down_interruptible() or
down_killable() instead.
*/
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);
}
EXPORT_SYMBOL(down);

static noinline void __sched __down(struct semaphore *sem)
{
__down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_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 (;;) {
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;
}


函数名称:down

文件位置:kernel/locking/semaphore.c

主要作用:获取信号量,如果信号量的值大于0,则消耗一个;如果不存在,则让线程进入休眠状态并等待信号量被释放。

函数调用流程


down(kernel/locking/semaphore.c)
|--> raw_spin_lock_irqsave // 获取锁,并保存中断信息
||-> sem->count--; // 如果sem->count信号量存在,则消耗一个
|-> __down // 如果sem->count信号量不存在,则进入休眠状态
|--> __down_common
|--> list_add_tail // 将当前线程添加到信号量的等待链表中,表示当前线程正在等待信号量。
|--> __set_current_state// 设置线程为休眠状态
|--> raw_spin_unlock_irq// 释放自旋锁,让其他线程可用
|--> schedule_timeout // 让线程进入睡眠状态,等待信号量释放或超时。
|--> raw_spin_lock_irq // 重新获取自旋锁,继续执行后续操作
|--> raw_spin_unlock_irqrestore // 释放锁,并恢复中断信息

实现流程

获取信号量时,先使用raw_spin_lock_irqsave和raw_spin_unlock_irqrestore将信号量的操作包裹起来,避免竞态发生。

然后对sem->count判断,如果信号量大于0,就消耗一个,否则的话,将当前线程设置为休眠态

调用__down_common接口,默认将当前线程设置为TASK_UNINTERRUPTIBLE中断不可打断状态,并设置最大超时时间MAX_SCHEDULE_TIMEOUT

struct semaphore_waiter waiter:创建waiter结构体,表示当前线程的状态

调用__set_current_state接口,设置当前线程的状态信息,即TASK_UNINTERRUPTIBLE

调用schedule_timeout,让该线程让出CPU,进入休眠态,并且在前面加上raw_spin_unlock_irq保证其他线程可以正常使用信号量。

当线程时间片到时,获取CPU,并调用raw_spin_lock_irq,获取锁,来防止竞态发生。

3.4 up

/**

up - release the semaphore
@sem: the semaphore to release
Release the semaphore. Unlike mutexes, up() may be called from any
context and even by tasks which have never called down().
*/
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);
}
EXPORT_SYMBOL(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;
wake_up_process(waiter->task);
}


函数名称:up

文件位置:kernel/locking/semaphore.c

主要作用:获取信号量,如果信号量的值大于0,则消耗一个;如果不存在,则让线程进入休眠状态并等待信号量被释放。

实现流程

相信分析完down后,up也变得很简单

释放信号量时,先使用raw_spin_lock_irqsave和raw_spin_unlock_irqrestore将信号量的操作包裹起来,避免竞态发生。

然后对sem->wait_list判断,如果其为空,说明没有等待的线程,直接将sem->count自增,如果有等待的线程,则唤醒下一个线程。

调用wake_up_process来唤醒线程

4、总结

信号量较为简单,一般常用来解决生产者和消费者的问题,其主要还是通过自旋锁来实现互斥的作用,通过链表来管理等待队列的线程信息,通过变量来代表资源的数量。






审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 计数器
    +关注

    关注

    32

    文章

    2123

    浏览量

    92986
  • LINUX内核
    +关注

    关注

    1

    文章

    311

    浏览量

    21389
  • 信号量
    +关注

    关注

    0

    文章

    53

    浏览量

    8257
  • 自旋锁
    +关注

    关注

    0

    文章

    8

    浏览量

    1529

原文标题:信号量实现原理

文章出处:【微信号:strongerHuang,微信公众号:strongerHuang】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    信号量用法

    本帖最后由 chenshuihong 于 2016-4-22 11:28 编辑 信号量的分配,信号量的分配,信号量的分配,信号量的分配
    发表于 04-22 11:27

    转:第24章 FreeRTOS任务计数信号量

    信号量效率更高,需要的RAM空间更小。当然,缺点也是有的,它没有第21章介绍的计数信号量实现的功能全面。本章教程配套的例子含Cortex-M3内核的STM32F103和Cortex-M
    发表于 09-07 06:43

    第15章 互斥信号量

    信号量呢?因为信号量资源被获取了,信号量值就是0,信号量资源被释放,信号量值就是1,把这种只有0和1两种情况的
    发表于 10-06 16:40

    RT-Thread信号量删除后释放信号量跟获取信号量还是成功

    RT-Thread中创建了一个动态的信号量,运行10次这个线程后删除这个动态信号量,但是问题是10次后他再次释放信号量跟获取信号量还是成功的,请问是什么问题。
    发表于 01-15 05:04

    信号量删除问题

    请问最近我在学习UCOSii在使用到删除信号量时遇到问题;程序如下OSTimeDly(50);//OS_ENTER_CRITICAL();//进入临界区(无法被中断打断)UART_Send_Str
    发表于 06-10 04:36

    信号量和互斥信号量该怎么选择?

    既然说信号量可能会导致优先级反转,那全都在工程里使用互斥信号不就行了?还要信号量干啥?大家一起用互斥信号量
    发表于 08-26 03:14

    请求信号量是什么意思?

    各位大神求教,视屏学习里说信号量相当于变量,下面有几点疑问1.下图是OSSemCreate();函数创建信号量,创建一个二进制信号量将初始值置1,那么运行OSSemPost();信号量
    发表于 09-27 04:35

    关于UCOSIII的信号量和互斥信号量的理解?

    在UCOSIII中延时一定会引起任务切换,如果所有任务都进入等待态,则切换到空闲任务运行?请求信号量,如果信号量值非零,不进行任务切换;为零,(等待超时后?或者一般都是设置死等)进行任务切换?释放
    发表于 03-13 00:11

    信号量的作用与分类

    目录信号量的作用信号量的分类信号量创建获取释放信号量头文件semphr.h1创建信号量2获取信号量
    发表于 08-24 06:13

    信号量是什么?信号量怎么运作

    信号量信号量简介二值信号量计数信号量应用场景二值信号量怎么运作计数信号量怎么运作
    发表于 01-05 08:09

    FreeRTOS信号量介绍

    FreeRTOS信号量 & ESP32实战阅读建议:有一定操作系统基础知识。FreeRTOS信号量1. 二值信号量  二值信号量通常用于互斥访问或同步,二值
    发表于 01-27 07:28

    信号量和互斥信号量的相关资料分享

    信号量简介信号量就是一个上锁的机制,代码必须获得钥匙才能执行,一旦获得了信号量,就相当于该代码具有了进入被锁代码的权限。说白了,就和java多线程中常用的锁非常相似。信号量类型在个人的
    发表于 03-02 07:11

    LabVIEW信号量

    LabVIEW信号量信号量是一种用来限制可以同时取用共享(受保护)资源的任务数量方法。受保护的资源或关键代码部分可能包括写入全局变量或与外部仪器进行通信。您可以使用信号量使您的代码线程安全
    发表于 04-09 21:52

    怎么使用ucos的信号量

    应该怎么使用ucos的信号量,在什么情况下使用二进制信号量和数值型信号量
    发表于 10-07 07:41

    关于RTOS中的信号量问题

    信号量是操作系统里的一个基本概念 我现在了解信号量是做什么的,怎么做的。 限于工作经验,只能用到二值信号量。计数型信号量用在什么场合呢? 请哪位用过计数
    发表于 10-31 06:25