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  58 59 60## 接口说明 61 62| 功能分类 | 接口描述 | 63| -------- | -------- | 64| 创建/删除信号量 | LOS_SemCreate:创建信号量,返回信号量ID。<br/> LOS_BinarySemCreate:创建二值信号量,其计数值最大为1。<br/> LOS_SemDelete:删除指定的信号量。 | 65| 申请/释放信号量 | LOS_SemPend:申请指定的信号量,并设置超时时间。<br/> 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>  **说明:** 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