更新日期: 2022/06/01 来源: https://gitee.com/weharmony/kernel_liteos_a_note
los_sem.c
浏览该文件的文档.
1/*!
2 * @file los_sem.c
3 * @brief
4 * @link kernel-mini-basic-ipc-sem-basic http://weharmonyos.com/openharmony/zh-cn/device-dev/kernel/kernel-mini-basic-ipc-sem-basic.html @endlink
5 @verbatim
6 信号量(Semaphore)是一种实现任务间通信的机制,可以实现任务间同步或共享资源的互斥访问。
7
8 一个信号量的数据结构中,通常有一个计数值,用于对有效资源数的计数,表示剩下的可被使用的共享资源数,其值的含义分两种情况:
9
10 0,表示该信号量当前不可获取,因此可能存在正在等待该信号量的任务。
11 正值,表示该信号量当前可被获取。
12 以同步为目的的信号量和以互斥为目的的信号量在使用上有如下不同:
13
14 用作互斥时,初始信号量计数值不为0,表示可用的共享资源个数。在需要使用共享资源前,先获取信号量,
15 然后使用一个共享资源,使用完毕后释放信号量。这样在共享资源被取完,即信号量计数减至0时,其他需要获取信号量的任务将被阻塞,
16 从而保证了共享资源的互斥访问。另外,当共享资源数为1时,建议使用二值信号量,一种类似于互斥锁的机制。
17 用作同步时,初始信号量计数值为0。任务1获取信号量而阻塞,直到任务2或者某中断释放信号量,任务1才得以进入Ready或Running态,从而达到了任务间的同步。
18
19 信号量运作原理
20 信号量初始化,为配置的N个信号量申请内存(N值可以由用户自行配置,通过LOSCFG_BASE_IPC_SEM_LIMIT宏实现),并把所有信号量初始化成未使用,
21 加入到未使用链表中供系统使用。
22
23 信号量创建,从未使用的信号量链表中获取一个信号量,并设定初值。
24
25 信号量申请,若其计数器值大于0,则直接减1返回成功。否则任务阻塞,等待其它任务释放该信号量,等待的超时时间可设定。
26 当任务被一个信号量阻塞时,将该任务挂到信号量等待任务队列的队尾。
27
28 信号量释放,若没有任务等待该信号量,则直接将计数器加1返回。否则唤醒该信号量等待任务队列上的第一个任务。
29
30 信号量删除,将正在使用的信号量置为未使用信号量,并挂回到未使用链表。
31
32 信号量允许多个任务在同一时刻访问共享资源,但会限制同一时刻访问此资源的最大任务数目。当访问资源的任务数达到该资源允许的最大数量时,
33 会阻塞其他试图获取该资源的任务,直到有任务释放该信号量。
34
35 开发流程
36 创建信号量LOS_SemCreate,若要创建二值信号量则调用LOS_BinarySemCreate。
37 申请信号量LOS_SemPend。
38 释放信号量LOS_SemPost。
39 删除信号量LOS_SemDelete。
40 @endverbatim
41 * @image html https://gitee.com/weharmonyos/resources/raw/master/29/sem_run.png
42 * @attention 由于中断不能被阻塞,因此不能在中断中使用阻塞模式申请信号量。
43 * @version
44 * @author weharmonyos.com | 鸿蒙研究站 | 每天死磕一点点
45 * @date 2021-11-18
46 */
47/*
48 * Copyright (c) 2013-2019 Huawei Technologies Co., Ltd. All rights reserved.
49 * Copyright (c) 2020-2021 Huawei Device Co., Ltd. All rights reserved.
50 *
51 * Redistribution and use in source and binary forms, with or without modification,
52 * are permitted provided that the following conditions are met:
53 *
54 * 1. Redistributions of source code must retain the above copyright notice, this list of
55 * conditions and the following disclaimer.
56 *
57 * 2. Redistributions in binary form must reproduce the above copyright notice, this list
58 * of conditions and the following disclaimer in the documentation and/or other materials
59 * provided with the distribution.
60 *
61 * 3. Neither the name of the copyright holder nor the names of its contributors may be used
62 * to endorse or promote products derived from this software without specific prior written
63 * permission.
64 *
65 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
66 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
67 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
68 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
69 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
70 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
71 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
72 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
73 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
74 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
75 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
76 */
77
78#include "los_sem_pri.h"
79#include "los_sem_debug_pri.h"
80#include "los_err_pri.h"
81#include "los_task_pri.h"
82#include "los_exc.h"
83#include "los_sched_pri.h"
84#include "los_spinlock.h"
85#include "los_mp.h"
86#include "los_percpu_pri.h"
87#include "los_hook.h"
88
89
90#ifdef LOSCFG_BASE_IPC_SEM
91
92#if (LOSCFG_BASE_IPC_SEM_LIMIT <= 0)
93#error "sem maxnum cannot be zero"
94#endif /* LOSCFG_BASE_IPC_SEM_LIMIT <= 0 */
95
96LITE_OS_SEC_DATA_INIT STATIC LOS_DL_LIST g_unusedSemList; ///< 可用的信号量列表,干嘛不用freeList? 可以看出这里是另一个人写的代码
97LITE_OS_SEC_BSS LosSemCB *g_allSem = NULL; ///< 信号池,一次分配 LOSCFG_BASE_IPC_SEM_LIMIT 个信号量
98
99/*
100 * Description : Initialize the semaphore doubly linked list | 信号量初始化
101 * Return : LOS_OK on success, or error code on failure
102 */
103LITE_OS_SEC_TEXT_INIT UINT32 OsSemInit(VOID)
104{
105 LosSemCB *semNode = NULL;
106 UINT32 index;
107
108 LOS_ListInit(&g_unusedSemList);//初始化链表,链表上挂未使用的信号量,用于分配信号量,鸿蒙信号量的个数是有限的,默认1024个
109 /* system resident memory, don't free */
110 g_allSem = (LosSemCB *)LOS_MemAlloc(m_aucSysMem0, (LOSCFG_BASE_IPC_SEM_LIMIT * sizeof(LosSemCB)));//分配信号池
111 if (g_allSem == NULL) {
112 return LOS_ERRNO_SEM_NO_MEMORY;
113 }
114
115 for (index = 0; index < LOSCFG_BASE_IPC_SEM_LIMIT; index++) {
116 semNode = ((LosSemCB *)g_allSem) + index;//拿信号控制块, 可以直接g_allSem[index]来嘛
117 semNode->semID = SET_SEM_ID(0, index);//保存ID
118 semNode->semStat = OS_SEM_UNUSED;//标记未使用
119 LOS_ListTailInsert(&g_unusedSemList, &semNode->semList);//通过semList把 信号块挂到空闲链表上
120 }
121
122 if (OsSemDbgInitHook() != LOS_OK) {
123 return LOS_ERRNO_SEM_NO_MEMORY;
124 }
125 return LOS_OK;
126}
127
128/*
129 * Description : Create a semaphore,
130 * Input : count --- semaphore count,
131 * maxCount --- Max number of available semaphores,
132 * semHandle --- Index of semaphore,
133 * Return : LOS_OK on success ,or error code on failure
134 */
135LITE_OS_SEC_TEXT_INIT UINT32 OsSemCreate(UINT16 count, UINT16 maxCount, UINT32 *semHandle)
136{
137 UINT32 intSave;
138 LosSemCB *semCreated = NULL;
139 LOS_DL_LIST *unusedSem = NULL;
140 UINT32 errNo;
141 UINT32 errLine;
142
143 if (semHandle == NULL) {
144 return LOS_ERRNO_SEM_PTR_NULL;
145 }
146
147 if (count > maxCount) {//信号量不能大于最大值,两参数都是外面给的
148 OS_GOTO_ERR_HANDLER(LOS_ERRNO_SEM_OVERFLOW);
149 }
150
151 SCHEDULER_LOCK(intSave);//进入临界区,拿自旋锁
152
153 if (LOS_ListEmpty(&g_unusedSemList)) {//没有可分配的空闲信号提供
154 SCHEDULER_UNLOCK(intSave);
156 OS_GOTO_ERR_HANDLER(LOS_ERRNO_SEM_ALL_BUSY);
157 }
158
159 unusedSem = LOS_DL_LIST_FIRST(&g_unusedSemList);//从未使用信号量池中取首个
160 LOS_ListDelete(unusedSem);//从空闲链表上摘除
161 SCHEDULER_UNLOCK(intSave);
162 semCreated = GET_SEM_LIST(unusedSem);//通过semList挂到链表上的,这里也要通过它把LosSemCB头查到. 进程,线程等结构体也都是这么干的.
163 semCreated->semCount = count;//设置数量
164 semCreated->semStat = OS_SEM_USED;//设置可用状态
165 semCreated->maxSemCount = maxCount;//设置最大信号数量
166 LOS_ListInit(&semCreated->semList);//初始化链表,后续阻塞任务通过task->pendList挂到semList链表上,就知道哪些任务在等它了.
167 *semHandle = semCreated->semID;//参数带走 semID
168 OsHookCall(LOS_HOOK_TYPE_SEM_CREATE, semCreated);
169 OsSemDbgUpdateHook(semCreated->semID, OsCurrTaskGet()->taskEntry, count);
170
171 return LOS_OK;
172
173ERR_HANDLER:
174 OS_RETURN_ERROR_P2(errLine, errNo);
175}
176///对外接口 创建信号量
177LITE_OS_SEC_TEXT_INIT UINT32 LOS_SemCreate(UINT16 count, UINT32 *semHandle)
178{
179 return OsSemCreate(count, OS_SEM_COUNT_MAX, semHandle);
180}
181///对外接口 创建二值信号量,其计数值最大为1,可以当互斥锁用
182LITE_OS_SEC_TEXT_INIT UINT32 LOS_BinarySemCreate(UINT16 count, UINT32 *semHandle)
183{
184 return OsSemCreate(count, OS_SEM_BINARY_COUNT_MAX, semHandle);
185}
186///对外接口 删除指定的信号量,参数就是 semID
187LITE_OS_SEC_TEXT_INIT UINT32 LOS_SemDelete(UINT32 semHandle)
188{
189 UINT32 intSave;
190 LosSemCB *semDeleted = NULL;
191 UINT32 errNo;
192 UINT32 errLine;
193
194 if (GET_SEM_INDEX(semHandle) >= (UINT32)LOSCFG_BASE_IPC_SEM_LIMIT) {
195 OS_GOTO_ERR_HANDLER(LOS_ERRNO_SEM_INVALID);
196 }
197
198 semDeleted = GET_SEM(semHandle);//通过ID拿到信号量实体
199
200 SCHEDULER_LOCK(intSave);
201
202 if ((semDeleted->semStat == OS_SEM_UNUSED) || (semDeleted->semID != semHandle)) {//参数判断
203 SCHEDULER_UNLOCK(intSave);
204 OS_GOTO_ERR_HANDLER(LOS_ERRNO_SEM_INVALID);
205 }
206
207 if (!LOS_ListEmpty(&semDeleted->semList)) {//当前还有任务挂在这个信号上面,当然不能删除
208 SCHEDULER_UNLOCK(intSave);
209 OS_GOTO_ERR_HANDLER(LOS_ERRNO_SEM_PENDED);//这个宏很有意思,里面goto到ERR_HANDLER
210 }
211
212 LOS_ListTailInsert(&g_unusedSemList, &semDeleted->semList);//通过semList从尾部插入空闲链表
213 semDeleted->semStat = OS_SEM_UNUSED;//状态变成了未使用
214 semDeleted->semID = SET_SEM_ID(GET_SEM_COUNT(semDeleted->semID) + 1, GET_SEM_INDEX(semDeleted->semID));//设置ID
215
216 OsHookCall(LOS_HOOK_TYPE_SEM_DELETE, semDeleted);
217 OsSemDbgUpdateHook(semDeleted->semID, NULL, 0);
218
219 SCHEDULER_UNLOCK(intSave);
220 return LOS_OK;
221
222ERR_HANDLER:
223 OS_RETURN_ERROR_P2(errLine, errNo);
224}
225///对外接口 申请指定的信号量,并设置超时时间
226LITE_OS_SEC_TEXT UINT32 LOS_SemPend(UINT32 semHandle, UINT32 timeout)
227{
228 UINT32 intSave;
229 LosSemCB *semPended = GET_SEM(semHandle);//通过ID拿到信号体
230 UINT32 retErr = LOS_OK;
231 LosTaskCB *runTask = NULL;
232
233 if (GET_SEM_INDEX(semHandle) >= (UINT32)LOSCFG_BASE_IPC_SEM_LIMIT) {
234 OS_RETURN_ERROR(LOS_ERRNO_SEM_INVALID);
235 }
236
237 if (OS_INT_ACTIVE) {
238 PRINT_ERR("!!!LOS_ERRNO_SEM_PEND_INTERR!!!\n");
239 OsBackTrace();
240 return LOS_ERRNO_SEM_PEND_INTERR;
241 }
242
243 runTask = OsCurrTaskGet();//获取当前任务
244 if (runTask->taskStatus & OS_TASK_FLAG_SYSTEM_TASK) {
245 OsBackTrace();
246 return LOS_ERRNO_SEM_PEND_IN_SYSTEM_TASK;
247 }
248
249 SCHEDULER_LOCK(intSave);
250
251 if ((semPended->semStat == OS_SEM_UNUSED) || (semPended->semID != semHandle)) {
252 retErr = LOS_ERRNO_SEM_INVALID;
253 goto OUT;
254 }
255
256 /* Update the operate time, no matter the actual Pend success or not */
257 OsSemDbgTimeUpdateHook(semHandle);
258
259 if (semPended->semCount > 0) {//还有资源可用,返回肯定得成功,semCount=0时代表没资源了,task会必须去睡眠了
260 semPended->semCount--;//资源少了一个
261 OsHookCall(LOS_HOOK_TYPE_SEM_PEND, semPended, runTask, timeout);
262 goto OUT;//注意这里 retErr = LOS_OK ,所以返回是OK的
263 } else if (!timeout) {
264 retErr = LOS_ERRNO_SEM_UNAVAILABLE;
265 goto OUT;
266 }
267
268 if (!OsPreemptableInSched()) {//不能申请调度 (不能调度的原因是因为没有持有调度任务自旋锁)
269 PRINT_ERR("!!!LOS_ERRNO_SEM_PEND_IN_LOCK!!!\n");
270 OsBackTrace();
271 retErr = LOS_ERRNO_SEM_PEND_IN_LOCK;
272 goto OUT;
273 }
274
275 OsHookCall(LOS_HOOK_TYPE_SEM_PEND, semPended, runTask, timeout);
276 OsTaskWaitSetPendMask(OS_TASK_WAIT_SEM, semPended->semID, timeout);
277 retErr = runTask->ops->wait(runTask, &semPended->semList, timeout);
278 if (retErr == LOS_ERRNO_TSK_TIMEOUT) {//注意:这里是涉及到task切换的,把自己挂起,唤醒其他task
279 retErr = LOS_ERRNO_SEM_TIMEOUT;
280 }
281
282OUT:
283 SCHEDULER_UNLOCK(intSave);
284 return retErr;
285}
286///以不安全的方式释放指定的信号量,所谓不安全指的是不用自旋锁
287LITE_OS_SEC_TEXT UINT32 OsSemPostUnsafe(UINT32 semHandle, BOOL *needSched)
288{
289 LosTaskCB *resumedTask = NULL;
290 LosSemCB *semPosted = GET_SEM(semHandle);
291 if ((semPosted->semID != semHandle) || (semPosted->semStat == OS_SEM_UNUSED)) {
292 return LOS_ERRNO_SEM_INVALID;
293 }
294
295 /* Update the operate time, no matter the actual Post success or not */
296 OsSemDbgTimeUpdateHook(semHandle);
297
298 if (semPosted->semCount == OS_SEM_COUNT_MAX) {//当前信号资源不能大于最大资源量
299 return LOS_ERRNO_SEM_OVERFLOW;
300 }
301 if (!LOS_ListEmpty(&semPosted->semList)) {//当前有任务挂在semList上,要去唤醒任务
302 resumedTask = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&(semPosted->semList)));//semList上面挂的都是task->pendlist节点,取第一个task下来唤醒
303 OsTaskWakeClearPendMask(resumedTask);
304 resumedTask->ops->wake(resumedTask);
305 if (needSched != NULL) {//参数不为空,就返回需要调度的标签
306 *needSched = TRUE;//TRUE代表需要调度
307 }
308 } else {//当前没有任务挂在semList上,
309 semPosted->semCount++;//信号资源多一个
310 }
311 OsHookCall(LOS_HOOK_TYPE_SEM_POST, semPosted, resumedTask);
312 return LOS_OK;
313}
314///对外接口 释放指定的信号量
315LITE_OS_SEC_TEXT UINT32 LOS_SemPost(UINT32 semHandle)
316{
317 UINT32 intSave;
318 UINT32 ret;
319 BOOL needSched = FALSE;
320
321 if (GET_SEM_INDEX(semHandle) >= LOSCFG_BASE_IPC_SEM_LIMIT) {
322 return LOS_ERRNO_SEM_INVALID;
323 }
324 SCHEDULER_LOCK(intSave);
325 ret = OsSemPostUnsafe(semHandle, &needSched);
326 SCHEDULER_UNLOCK(intSave);
327 if (needSched) {//需要调度的情况
328 LOS_MpSchedule(OS_MP_CPU_ALL);//向所有CPU发送调度指令
329 LOS_Schedule();////发起调度
330 }
331
332 return ret;
333}
334#endif /* (LOSCFG_BASE_IPC_SEM == YES) */
335
VOID OsBackTrace(VOID)
Kernel backtrace function.
Definition: los_exc.c:1025
LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListInit(LOS_DL_LIST *list)
Definition: los_list.h:104
LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListTailInsert(LOS_DL_LIST *list, LOS_DL_LIST *node)
Insert a node to the tail of a doubly linked list.
Definition: los_list.h:244
LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListDelete(LOS_DL_LIST *node)
Definition: los_list.h:292
LITE_OS_SEC_ALW_INLINE STATIC INLINE BOOL LOS_ListEmpty(LOS_DL_LIST *list)
Identify whether a specified doubly linked list is empty. | 判断链表是否为空
Definition: los_list.h:321
VOID * LOS_MemAlloc(VOID *pool, UINT32 size)
从指定内存池中申请size长度的内存,注意这可不是从内核堆空间中申请内存
Definition: los_memory.c:1123
UINT8 * m_aucSysMem0
异常交互动态内存池地址的起始地址,当不支持异常交互特性时,m_aucSysMem0等于m_aucSysMem1。
Definition: los_memory.c:107
LITE_OS_SEC_TEXT_INIT UINT32 LOS_SemCreate(UINT16 count, UINT32 *semHandle)
对外接口 创建信号量
Definition: los_sem.c:177
LITE_OS_SEC_TEXT_INIT UINT32 LOS_BinarySemCreate(UINT16 count, UINT32 *semHandle)
对外接口 创建二值信号量,其计数值最大为1,可以当互斥锁用
Definition: los_sem.c:182
LITE_OS_SEC_TEXT UINT32 LOS_SemPost(UINT32 semHandle)
对外接口 释放指定的信号量
Definition: los_sem.c:315
LITE_OS_SEC_TEXT UINT32 LOS_SemPend(UINT32 semHandle, UINT32 timeout)
对外接口 申请指定的信号量,并设置超时时间
Definition: los_sem.c:226
LITE_OS_SEC_TEXT_INIT UINT32 LOS_SemDelete(UINT32 semHandle)
对外接口 删除指定的信号量,参数就是 semID
Definition: los_sem.c:187
VOID LOS_Schedule(VOID)
Trigger active task scheduling.
Definition: los_sched.c:469
VOID LOS_MpSchedule(UINT32 target)
Definition: los_mp.c:76
STATIC INLINE LosTaskCB * OsCurrTaskGet(VOID)
STATIC INLINE BOOL OsPreemptableInSched(VOID)
LITE_OS_SEC_TEXT UINT32 OsSemPostUnsafe(UINT32 semHandle, BOOL *needSched)
以不安全的方式释放指定的信号量,所谓不安全指的是不用自旋锁
Definition: los_sem.c:287
LITE_OS_SEC_TEXT_INIT UINT32 OsSemCreate(UINT16 count, UINT16 maxCount, UINT32 *semHandle)
Definition: los_sem.c:135
LITE_OS_SEC_BSS LosSemCB * g_allSem
信号池,一次分配 LOSCFG_BASE_IPC_SEM_LIMIT 个信号量
Definition: los_sem.c:97
LITE_OS_SEC_TEXT_INIT UINT32 OsSemInit(VOID)
Definition: los_sem.c:103
LITE_OS_SEC_DATA_INIT STATIC LOS_DL_LIST g_unusedSemList
可用的信号量列表,干嘛不用freeList? 可以看出这里是另一个人写的代码
Definition: los_sem.c:96
STATIC INLINE VOID OsSemDbgUpdateHook(UINT32 semID, TSK_ENTRY_FUNC creater, UINT16 count)
STATIC INLINE UINT32 OsSemDbgInitHook(VOID)
STATIC INLINE VOID OsSemDbgTimeUpdateHook(UINT32 semID)
STATIC INLINE VOID OsSemInfoGetFullDataHook(VOID)
STATIC INLINE VOID OsTaskWaitSetPendMask(UINT16 mask, UINTPTR lockID, UINT32 timeout)
设置事件阻塞掩码,即设置任务的等待事件.
Definition: los_task_pri.h:289
STATIC INLINE VOID OsTaskWakeClearPendMask(LosTaskCB *resumeTask)
清除事件阻塞掩码,即任务不再等待任何事件.
Definition: los_task_pri.h:298
unsigned short UINT16
Definition: los_typedef.h:56
unsigned int UINT32
Definition: los_typedef.h:57
size_t BOOL
Definition: los_typedef.h:88
UINT16 semCount
Definition: los_sem_pri.h:49
UINT32 semID
Definition: los_sem_pri.h:51
UINT8 semStat
Definition: los_sem_pri.h:48
LOS_DL_LIST semList
Definition: los_sem_pri.h:52
UINT16 maxSemCount
Definition: los_sem_pri.h:50
UINT32(* wait)(LosTaskCB *runTask, LOS_DL_LIST *list, UINT32 timeout)
任务等待
VOID(* wake)(LosTaskCB *taskCB)
任务唤醒
const SchedOps * ops
UINT16 taskStatus