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