1. 概述

1.1 基本概念

信号量(Semaphore)是一种实现任务间通信的机制,实现任务之间同步或临界资源的互斥访问。常用于协助一组相互竞争的任务来访问临界资源。

在多任务系统中,各任务之间需要同步或互斥实现临界资源的保护,信号量功能可以为用户提供这方面的支持。

通常一个信号量的计数值用于对应有效的资源数,表示剩下的可被占用的互斥资源数。其值的含义分两种情况:

  • 0,表示没有积累下来的Post操作,且有可能有在此信号量上阻塞的任务。
  • 正值,表示有一个或多个Post下来的释放操作。

以同步为目的的信号量和以互斥为目的的信号量在使用有如下不同:

  • 用作互斥时,信号量创建后记数是满的,在需要使用临界资源时,先取信号量,使其变空,这样其他任务需要使用临界资源时就会因为无法取到信号量而阻塞,从而保证了临界资源的安全。
  • 用作同步时,信号量在创建后被置为空,任务1取信号量而阻塞,任务2在某种条件发生后,释放信号量,于是任务1得以进入READY或RUNNING态,从而达到了两个任务间的同步。

1.2 运作机制

1.2.1 信号量控制块

/**
* @ingroup los_sem
* Semaphore control structure.
*/
typedef struct
{
UINT8 usSemStat; /**是否使用标志位*/
UINT16 uwSemCount; /**信号量索引号*/
UINT32 usSemID; /**信号量计数*/
LOS_DL_LIST stSemList; /**挂接阻塞于该信号量的任务*/
}SEM_CB_S;

1.2.2 信号量运作原理

信号量初始化,为配置的N个信号量申请内存(N值可以由用户自行配置,受内存限制,详见第十章 配置参考),并把所有的信号量初始化成未使用,并加入到未使用链表中供系统使用。

信号量创建,从未使用的信号量链表中获取一个信号量资源,并设定初值。

信号量申请,若其计数器值大于0,则直接减1返回成功。否则任务阻塞,等待其它任务释放该信号量,等待的超时时间可设定。当任务被一个信号量阻塞时,将该任务挂到信号量等待任务队列的队尾。

信号量释放,若没有任务等待该信号量,则直接将计数器加1返回。否则唤醒该信号量等待任务队列上的第一个任务。

信号量删除,将正在使用的信号量置为未使用信号量,并挂回到未使用链表。

信号量允许多个任务在同一时刻访问同一资源,但会限制同一时刻访问此资源的最大任务数目。访问同一资源的任务数达到该资源的最大数量时,会阻塞其他试图获取该资源的任务,直到有任务释放该信号量。

2. 开发指导

2.1 使用场景

信号量是一种非常灵活的同步方式,可以运用在多种场合中,实现锁、同步、资源计数等功能,也能方便的用于任务与任务,中断与任务的同步中。

2.2 功能

Huawei LiteOS 系统中的信号量模块为用户提供下面几种功能。

功能分类 接口名 描述
信号量的创建和删除 LOS_SemCreate 创建信号量
- LOS_SemDelete 删除指定的信号量
信号量的申请和释放 LOS_SemPend 申请指定的信号量
- LOS_SemPost 释放指定的信号量

2.3 开发流程

信号量的开发典型流程:

  1. 创建信号量LOS_SemCreate。
  2. 申请信号量LOS_SemPend。

    信号量有三种申请模式:无阻塞模式、永久阻塞模式、定时阻塞模式
  • 无阻塞模式:任务需要申请信号量,若当前信号量的任务数没有到信号量设定的上限,则申请成功。否则,立即返回申请失败
  • 永久阻塞模式:任务需要申请信号量,若当前信号量的任务数没有到信号量设定的上限,则申请成功。否则,该任务进入阻塞态,系统切换到就绪任务中优先级最高者继续执行。任务进入阻塞态后,直到有其他任务释放该信号量,阻塞任务才会重新得以执行
  • 定时阻塞模式:任务需要申请信号量,若当前信号量的任务数没有到信号量设定的上限,则申请成功。否则,该任务进入阻塞态,系统切换到就绪任务中优先级最高者继续执行。任务进入阻塞态后,指定时间超时前有其他任务释放该信号量,或者用户指定时间超时后,阻塞任务才会重新得以执行
  1. 释放信号量LOS_SemPost。
  • 如果有任务阻塞于指定信号量,则唤醒该信号量阻塞队列上的第一个任务。该任务进入就绪态,并进行调度
  • 如果没有任务阻塞于指定信号量,释放信号量成功
  1. 删除信号量LOS_SemDelet

2.4 信号量错误码

对可能导致信号量操作失败的情况,包括创建信号量、申请信号量、释放信号量、删除信号量等,均需要返回对应的错误码,以便快速定位错误原因。

序 号 定义 实际数值 描述 参考解决方案
1 LOS_ERRNO_SEM_NO_MEMORY 0x02000700 内存空间不足 分配更大的内存分区
2 LOS_ERRNO_SEM_INVALID 0x02000701 非法传参 改变传数为合法值
3 LOS_ERRNO_SEM_PTR_NULL 0x02000702 传入空指针 传入合法指针
4 LOS_ERRNO_SEM_ALL_BUSY 0x02000703 信号量控制块不可用 释放资源信号量资源
5 LOS_ERRNO_SEM_UNAVAILABLE 0x02000704 定时时间非法 传入正确的定时时间
6 LOS_ERRNO_SEM_PEND_INTERR 0x02000705 中断期间非法调用LOS_SemPend 中断期间禁止调用LOS_SemPend
7 LOS_ERRNO_SEM_PEND_IN_LOCK 0x02000706 任务被锁,无法获得信号量 在任务被锁时,不能调用LOS_SemPend
8 LOS_ERRNO_SEM_TIMEOUT 0x02000707 获取信号量时间超时 将时间设置在合理范围内

错误码定义:错误码是一个32位的存储单元, 31~24位表示错误等级, 23~16位表示错误码标志, 15~8位代表错误码所属模块, 7~0位表示错误码序号,如下

#define LOS_ERRNO_OS_NORMAL(MID,ERRNO) \
(LOS_ERRTYPE_NORMAL | LOS_ERRNO_OS_ID | ((UINT32)(MID) << 8) | (ERRNO))
LOS_ERRTYPE_NORMAL :Define the error level as critical
LOS_ERRNO_OS_ID :OS error code flag.
MID:OS_MOUDLE_ID
ERRNO:error ID number

例如:

LOS_ERRNO_SEM_NO_MEMORY LOS_ERRNO_OS_ERROR(LOS_MOD_SEM, 0x00))

2.5 平台差异性

2.6 注意事项

  • 由于中断不能被阻塞,因此在申请信号量时,阻塞模式不能在中断中使用。

3. 编程实例

3.1 实例描述

本实例实现如下功能;

  1. 测试任务Example_TaskEntry创建一个信号量,锁任务调度,创建两个任务Example_SemTask1、 Example_SemTask2,Example_SemTask2优先级高于Example_SemTask1,两个任务中申请同一信号量,解锁任务调度后两任务阻塞,测试任务Example_TaskEntry释放信号量。
  2. Example_SemTask2得到信号量,被调度,然后任务休眠20Tick,Example_SemTask2延迟, Example_SemTask1被唤醒。
  3. Example_SemTask1定时阻塞模式申请信号量,等待时间为10Tick,因信号量仍被Example_SemTask2持有, Example_SemTask1挂起, 10Tick后仍未得到信号量,Example_SemTask1被唤醒,试图以永久阻塞模式申请信号量, Example_SemTask1挂起。
  4. 20Tick后Example_SemTask2唤醒, 释放信号量后, Example_SemTask1得到信号量被调度运行,最后释放信号量。
  5. Example_SemTask1执行完, 40Tick后任务Example_TaskEntry被唤醒,执行删除信号量,删除两个任务。

3.2 编程示例

前提条件:

  • 在los_config.h中,将LOSCFG_BASE_IPC_SEM配置为YES。
  • 配置用户定义的LOSCFG_BASE_IPC_SEM_LIMIT最大的信号量数,如1024。

代码实现如下

#include "los_sem.h"
/*任务PID*/
static UINT32 g_TestTaskID01,g_TestTaskID02;
/*测试任务优先级*/
#define TASK_PRIO_TEST 5
/*信号量结构体ID*/
static SEM_HANDLE_T g_usSemID;
VOID Example_SemTask1(void)
{
UINT32 uwRet;
printf("Example_SemTask1 try get sem g_usSemID ,timeout 10 ticks.\n");
/*定时阻塞模式申请信号量,定时时间为10Tick*/
uwRet = LOS_SemPend(g_usSemID, 10);
/*申请到信号量*/
if(LOS_OK == uwRet)
{
LOS_SemPost(g_usSemID);
return;
}
/*定时时间到,未申请到信号量*/
if(LOS_ERRNO_SEM_TIMEOUT == uwRet)
{
printf("Example_SemTask1 timeout and try get sem g_usSemID wait forever.\n");
/*永久阻塞模式申请信号量*/
uwRet = LOS_SemPend(g_usSemID, LOS_WAIT_FOREVER);
printf("Example_SemTask1 wait_forever and get sem g_usSemID .\n");
if(LOS_OK == uwRet)
{
LOS_SemPost(g_usSemID);
return;
}
}
return;
}
VOID Example_SemTask2(void)
{
UINT32 uwRet;
printf("Example_SemTask2 try get sem g_usSemID wait forever.\n");
/*永久阻塞模式申请信号量*/
uwRet = LOS_SemPend(g_usSemID, LOS_WAIT_FOREVER);
if(LOS_OK == uwRet)
printf("Example_SemTask2 get sem g_usSemID and then delay 20ticks .\n");
/*任务休眠20 Tick*/
LOS_TaskDelay(20);
printf("Example_SemTask2 post sem g_usSemID .\n");
/*释放信号量*/
LOS_SemPost(g_usSemID);
return;
}
UINT32 Example_TaskEntry()
{
UINT32 uwRet;
TSK_INIT_PARAM_S stTask1;
TSK_INIT_PARAM_S stTask2;
/*创建信号量*/
LOS_SemCreate(0,&g_usSemID);
/*锁任务调度*/
LOS_TaskLock();
/*创建任务1*/
memset(&stTask1, 0, sizeof(TSK_INIT_PARAM_S));
stTask1.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_SemTask1;
stTask1.pcName = "MutexTsk1";
stTask1.uwStackSize = OS_TSK_DEFAULT_STACK_SIZE;
stTask1.usTaskPrio = TASK_PRIO_TEST;
uwRet = LOS_TaskCreate(&g_TestTaskID01, &stTask1);
if(uwRet != LOS_OK)
{
printf("task1 create failed .\n");
return LOS_NOK;
}
/*创建任务2*/
memset(&stTask2, 0, sizeof(TSK_INIT_PARAM_S));
stTask2.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_SemTask2;
stTask2.pcName = "MutexTsk2";
stTask2.uwStackSize = OS_TSK_DEFAULT_STACK_SIZE;
stTask2.usTaskPrio = (TASK_PRIO_TEST - 1);
uwRet = LOS_TaskCreate(&g_TestTaskID02, &stTask2);
if(uwRet != LOS_OK)
{
printf("task2 create failed .\n");
return LOS_NOK;
}
/*解锁任务调度*/
LOS_TaskUnlock();
uwRet = LOS_SemPost(g_usSemID);
/*任务休眠40 Tick*/
LOS_TaskDelay(40);
/*删除信号量*/
LOS_SemDelete(g_usSemID);
/*删除任务1*/
uwRet = LOS_TaskDelete(g_TestTaskID01);
if(uwRet != LOS_OK)
{
printf("task1 delete failed .\n");
return LOS_NOK;
}
/*删除任务2*/
uwRet = LOS_TaskDelete(g_TestTaskID02);
if(uwRet != LOS_OK)
{
printf("task2 delete failed .\n");
return LOS_NOK;
}
return LOS_OK;
}

3.3 结果验证

编译运行得到的结果为:

Example_SemTask2 try get sem g_usSemID wait forever.
Example_SemTask1 try get sem g_usSemID ,timeout 10 ticks.
Example_SemTask2 get sem g_usSemID and then delay 20ticks .
Example_SemTask1 timeout and try get sem g_usSemID wait forever.
Example_SemTask2 post sem g_usSemID .
Example_SemTask1 wait_forever and get sem g_usSemID

liteos信号量(八)的更多相关文章

  1. Python asyncio文档阅读摘要

    文档地址:https://docs.python.org/3/library/asyncio.html 文档第一句话说得很明白,asyncio是单线程并发,这种event loop架构是很多新型异步并 ...

  2. {Python之线程} 一 背景知识 二 线程与进程的关系 三 线程的特点 四 线程的实际应用场景 五 内存中的线程 六 用户级线程和内核级线程(了解) 七 python与线程 八 Threading模块 九 锁 十 信号量 十一 事件Event 十二 条件Condition(了解) 十三 定时器

    Python之线程 线程 本节目录 一 背景知识 二 线程与进程的关系 三 线程的特点 四 线程的实际应用场景 五 内存中的线程 六 用户级线程和内核级线程(了解) 七 python与线程 八 Thr ...

  3. 秒杀多线程第八篇 经典线程同步 信号量Semaphore

    阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <且不超过最大资源数量. 第三个參数能够用来传出先前的资源计数,设为NULL表示不须要传出. 注意:当 ...

  4. Python 第八篇:异常处理、Socket语法、SocketServer实现多并发、进程和线程、线程锁、GIL、Event、信号量、进程间通讯

    本节内容: 异常处理.Socket语法.SocketServer实现多并发.进程和线程.线程锁.GIL.Event.信号量.进程间通讯.生产者消费者模型.队列Queue.multiprocess实例 ...

  5. Python之路(第三十八篇) 并发编程:进程同步锁/互斥锁、信号量、事件、队列、生产者消费者模型

    一.进程锁(同步锁/互斥锁) 进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的, 而共享带来的是竞争,竞争带来的结果就是错乱,如何控制,就是加锁处理. 例 ...

  6. Java并发编程的艺术(八)——闭锁、同步屏障、信号量详解

    1. 闭锁:CountDownLatch 1.1 使用场景 若有多条线程,其中一条线程需要等到其他所有线程准备完所需的资源后才能运行,这样的情况可以使用闭锁. 1.2 代码实现 // 初始化闭锁,并设 ...

  7. windows多线程(八) 信号量Semaphore

    如果你看到了这里,我就认为你已经对掌握了有关关键段 CriticalSection.互斥量Mutex和事件Event有关的内容,所以最基本的东西就不再介绍了.如果没有掌握上面说的内容,可以看这里: 关 ...

  8. Java并发编程原理与实战二十八:信号量Semaphore

    1.Semaphore简介 Semaphore,是JDK1.5的java.util.concurrent并发包中提供的一个并发工具类. 所谓Semaphore即 信号量 的意思. 这个叫法并不能很好地 ...

  9. 秒杀多线程第八篇 经典线程同步 信号量Semaphore (续)

    java semaphore实现: Semaphore当前在多线程环境下被扩放使用,操作系统的信号量是个很重要的概念,在进程控制方面都有应用.Java 并发库 的Semaphore 可以很轻松完成信号 ...

随机推荐

  1. 『009』Python

    『004』索引-Language Python 准备更新中

  2. LVS基本知识

      前言  linux集群类型  LB -->负载均衡集群(Load Balancing) HA-->高可用集群(High Availiablity) HP-->高性性集群(High ...

  3. day51_9_15_Django

    一.pycharm接受网页信息原理. 如何实现在后端接受浏览器的数据,并解析出有用的信息呢? 使用socket编写网络连接,然后通过浏览器访问ip+端口号. import socket def ind ...

  4. 【转】基于Bootstrap的超酷jQuery开关按钮插件

    基于Bootstrap的超酷jQuery开关按钮插件

  5. Phoenix |安装配置| 命令行操作| 与hbase的映射| spark对其读写

    Phoenix Phoenix是HBase的开源SQL皮肤.可以使用标准JDBC API代替HBase客户端API来创建表,插入数据和查询HBase数据. 1.特点 1) 容易集成:如Spark,Hi ...

  6. POJ2976Dropping tests(分数规划)

    传送门 题目大意:n个二元组a[i],b[i],去掉k个,求sigma a[i]/ sigma b[i]的最大值 代码: #include<iostream> #include<cs ...

  7. Codeforces Round #596 (Div. 2, based on Technocup 2020 Elimination Round 2) F. Tree Factory 构造题

    F. Tree Factory Bytelandian Tree Factory produces trees for all kinds of industrial applications. Yo ...

  8. Java连载36-IDE使用

    一.主方法注意 每一个类都可以编写一个主方法,但是一般情况下,一个系统只有一个入口,所以主方法一般写一个 二.Myeclipse的使用 1.在workspace中工作区中有一个文件夹.metadata ...

  9. python-8-字符串索引与切片

    前言 python访问字符串的值,可以使用方括号来截取字符串,但切片对原来的值是不会改变,如下: 一.索引 1.索引下标查找 # 1.索引 a = 'ABCDPOM' s = a[0] s2 = a[ ...

  10. 修改 Oracle 数据库实例字符集

    Ø  简介 在 Oracle 中创建数据库实例后,就会有对应使用的编码字符集.当我们设置的字符集与操作系统或者其他软件字符集不一致时,就会出现个字符长度存储一个汉字. 2.   SIMPLIFIED ...