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  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>  **说明:** 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