• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 信号量
2
3
4## 基本概念
5
6信号量(Semaphore)是一种实现任务间通信的机制,可以实现任务间同步或共享资源的互斥访问。
7
8一个信号量的数据结构中,通常有一个计数值,用于对有效资源数的计数,表示剩下的可被使用的共享资源数,其值的含义分两种情况:
9
10- 0,表示该信号量当前不可获取,因此可能存在正在等待该信号量的任务。
11
12- 正值,表示该信号量当前可被获取。
13
14信号量可用于同步或者互斥。以同步为目的的信号量和以互斥为目的的信号量在使用上有如下不同:
15
16- 用作互斥时,初始信号量计数值不为0,表示可用的共享资源个数。在需要使用共享资源前,先获取信号量,然后使用一个共享资源,使用完毕后释放信号量。这样在共享资源被取完,即信号量计数减至0时,其他需要获取信号量的任务将被阻塞,从而保证了共享资源的互斥访问。另外,当共享资源数为1时,建议使用二值信号量,一种类似于互斥锁的机制。
17
18- 用作同步时,初始信号量计数值为0。任务1因获取不到信号量而阻塞,直到任务2或者某中断释放信号量,任务1才得以进入Ready或Running态,从而达到了任务间的同步。
19
20
21## 运行机制
22
23
24### 信号量控制块
25
26
27```
28/**
29 * 信号量控制块数据结构
30 */
31typedef struct {
32    UINT16            semStat;          /* 信号量状态 */
33    UINT16            semType;          /* 信号量类型 */
34    UINT16            semCount;         /* 信号量计数 */
35    UINT16            semId;            /* 信号量索引号 */
36    LOS_DL_LIST       semList;          /* 用于插入阻塞于信号量的任务 */
37} LosSemCB;
38```
39
40
41### 信号量运作原理
42
43信号量初始化,为配置的N个信号量申请内存(N值可以由用户自行配置,通过LOSCFG_BASE_IPC_SEM_LIMIT宏实现,按产品实际需要设定),并把所有信号量初始化成未使用,加入到未使用链表中供系统使用。
44
45信号量创建,从未使用的信号量链表中获取一个信号量,并设定初值。
46
47信号量申请,若其计数器值大于0,则直接减1返回成功。否则任务阻塞,等待其它任务释放该信号量,等待的超时时间可设定。当任务被一个信号量阻塞时,将该任务挂到信号量等待任务队列的队尾。
48
49信号量释放,若没有任务等待该信号量,则直接将计数器加1返回。否则唤醒该信号量等待任务队列上的第一个任务。
50
51信号量删除,将正在使用的信号量置为未使用信号量,并挂回到未使用链表。
52
53信号量允许多个任务在同一时刻访问共享资源,但会限制同一时刻访问此资源的最大任务数目。当访问资源的任务数达到该资源允许的最大数量时,会阻塞其他试图获取该资源的任务,直到有任务释放该信号量。
54
55  **图1** 轻量系统信号量运作示意图
56
57  ![zh-cn_image_0000001245051881](figures/zh-cn_image_0000001245051881.png)
58
59
60## 接口说明
61
62| 功能分类 | 接口描述 |
63| -------- | -------- |
64| 创建/删除信号量 | &nbsp;LOS_SemCreate:创建信号量,返回信号量ID。<br/>&nbsp;LOS_BinarySemCreate:创建二值信号量,其计数值最大为1。<br/>&nbsp;LOS_SemDelete:删除指定的信号量。 |
65| 申请/释放信号量 | &nbsp;LOS_SemPend:申请指定的信号量,并设置超时时间。<br/>&nbsp;LOS_SemPost:释放指定的信号量。 |
66
67
68## 开发流程
69
701. 创建信号量LOS_SemCreate,若要创建二值信号量则调用LOS_BinarySemCreate。
71
722. 申请信号量LOS_SemPend。
73
743. 释放信号量LOS_SemPost。
75
764. 删除信号量LOS_SemDelete。
77
78
79> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
80> 由于中断不能被阻塞,因此不能在中断中使用阻塞模式申请信号量。
81
82
83## 编程实例
84
85
86### 实例描述
87
88本实例实现如下功能:
89
901. 测试任务ExampleSem创建一个信号量,锁任务调度。创建两个任务ExampleSemTask1和ExampleSemTask2, ExampleSemTask2优先级高于ExampleSemTask1。两个任务中申请同一信号量,解锁任务调度后两任务阻塞,测试任务ExampleSem释放信号量。
91
922. ExampleSemTask2得到信号量,被调度,然后任务休眠20Tick,ExampleSemTask2延迟,ExampleSemTask1被唤醒。
93
943. ExampleSemTask1定时阻塞模式申请信号量,等待时间为10Tick,因信号量仍被ExampleSemTask2持有,ExampleSemTask1挂起,10Tick后仍未得到信号量,ExampleSemTask1被唤醒,试图以永久阻塞模式申请信号量,ExampleSemTask1挂起。
95
964. 20Tick后ExampleSemTask2唤醒, 释放信号量后,ExampleSemTask1得到信号量被调度运行,最后释放信号量。
97
985. ExampleSemTask1执行完,400Tick后任务ExampleSem被唤醒,执行删除信号量。
99
100
101### 示例代码
102
103示例代码如下:
104
105本演示代码在 ./kernel/liteos_m/testsuites/src/osTest.c 中编译验证,在TestTaskEntry中调用验证入口函数ExampleSem。
106
107
108```
109#include "los_sem.h"
110
111/* 信号量结构体id */
112static UINT32 g_semId;
113
114VOID ExampleSemTask1(VOID)
115{
116    UINT32 ret;
117
118    printf("ExampleSemTask1 try get sem g_semId, timeout 10 ticks.\n");
119    /* 定时阻塞模式申请信号量,定时时间为10ticks */
120    ret = LOS_SemPend(g_semId, 10);
121    /* 申请到信号量 */
122    if (ret == LOS_OK) {
123         LOS_SemPost(g_semId);
124         return;
125    }
126
127    /* 定时时间到,未申请到信号量 */
128    if (ret == LOS_ERRNO_SEM_TIMEOUT) {
129        printf("ExampleSemTask1 timeout and try get sem g_semId wait forever.\n");
130        /*永久阻塞模式申请信号量*/
131        ret = LOS_SemPend(g_semId, LOS_WAIT_FOREVER);
132        printf("ExampleSemTask1 wait_forever and get sem g_semId.\n");
133        if (ret == LOS_OK) {
134            LOS_SemPost(g_semId);
135            return;
136        }
137    }
138}
139
140VOID ExampleSemTask2(VOID)
141{
142    UINT32 ret;
143    printf("ExampleSemTask2 try get sem g_semId wait forever.\n");
144
145    /* 永久阻塞模式申请信号量 */
146    ret = LOS_SemPend(g_semId, LOS_WAIT_FOREVER);
147    if (ret == LOS_OK) {
148        printf("ExampleSemTask2 get sem g_semId and then delay 20 ticks.\n");
149    }
150
151    /* 任务休眠20 ticks */
152    LOS_TaskDelay(20);
153    printf("ExampleSemTask2 post sem g_semId.\n");
154
155    /* 释放信号量 */
156    LOS_SemPost(g_semId);
157    return;
158}
159
160UINT32 ExampleSem(VOID)
161{
162    UINT32 ret;
163    TSK_INIT_PARAM_S task1 = { 0 };
164    TSK_INIT_PARAM_S task2 = { 0 };
165    UINT32 taskId1;
166    UINT32 taskId2;
167
168   /* 创建信号量 */
169    LOS_SemCreate(0, &g_semId);
170
171    /* 锁任务调度 */
172    LOS_TaskLock();
173
174    /* 创建任务1 */
175    task1.pfnTaskEntry = (TSK_ENTRY_FUNC)ExampleSemTask1;
176    task1.pcName       = "TestTask1";
177    task1.uwStackSize  = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
178    task1.usTaskPrio   = 5;
179    ret = LOS_TaskCreate(&taskId1, &task1);
180    if (ret != LOS_OK) {
181        printf("task1 create failed.\n");
182        return LOS_NOK;
183    }
184
185    /* 创建任务2 */
186    task2.pfnTaskEntry = (TSK_ENTRY_FUNC)ExampleSemTask2;
187    task2.pcName       = "TestTask2";
188    task2.uwStackSize  = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
189    task2.usTaskPrio   = 4;
190    ret = LOS_TaskCreate(&taskId2, &task2);
191    if (ret != LOS_OK) {
192        printf("task2 create failed.\n");
193        return LOS_NOK;
194    }
195
196    /* 解锁任务调度 */
197    LOS_TaskUnlock();
198
199    ret = LOS_SemPost(g_semId);
200
201    /* 任务休眠400 ticks */
202    LOS_TaskDelay(400);
203
204    /* 删除信号量 */
205    LOS_SemDelete(g_semId);
206    return LOS_OK;
207}
208```
209
210
211### 结果验证
212
213编译运行得到的结果为:
214
215
216
217```
218ExampleSemTask2 try get sem g_semId wait forever.
219ExampleSemTask1 try get sem g_semId, timeout 10 ticks.
220ExampleSemTask2 get sem g_semId and then delay 20 ticks.
221ExampleSemTask1 timeout and try get sem g_semId wait forever.
222ExampleSemTask2 post sem g_semId.
223ExampleSemTask1 wait_forever and get sem g_semId.
224```
225