百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< Gitee | Github | CSDN | Coding >
百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,多站点每日同步更新< OSCHINA | CSDN | WeHarmony >
读本篇之前建议先读鸿蒙内核源码分析(总目录)其他篇幅.
信号量(Semaphore) 是一种实现任务间通信的机制,可以实现任务间同步或共享资源的互斥访问。
一个信号量的数据结构中,通常有一个计数值,用于对有效资源数的计数,表示剩下的可被使用的共享资源数,其值的含义分两种情况:
0,表示该信号量当前不可获取,因此可能存在正在等待该信号量的任务。
正值,表示该信号量当前可被获取。
以同步为目的的信号量和以互斥为目的的信号量在使用上有如下不同:
用作互斥时,初始信号量计数值不为0,表示可用的共享资源个数。在需要使用共享资源前,先获取信号量,然后使用一个共享资源,使用完毕后释放信号量。这样在共享资源被取完,即信号量计数减至0时,其他需要获取信号量的任务将被阻塞,从而保证了共享资源的互斥访问。另外,当共享资源数为1时,建议使用二值信号量,一种类似于互斥锁的机制。
用作同步时,初始信号量计数值为0。任务1获取信号量而阻塞,直到任务2或者某中断释放信号量,任务1才得以进入Ready或Running态,从而达到了任务间的同步。
信号量初始化,为配置的N个信号量申请内存(N值可以由用户自行配置,通过 LOSCFG_BASE_IPC_SEM_LIMIT
宏实现),并把所有信号量初始化成未使用,加入到未使用链表中供系统使用。
信号量允许多个任务在同一时刻访问共享资源,但会限制同一时刻访问此资源的最大任务数目。
当访问资源的任务数达到该资源允许的最大数量时,会阻塞其他试图获取该资源的任务,直到有任务释放该信号量。
typedef struct {
UINT8 semStat; /**< Semaphore state *///信号量的状态
UINT16 semCount; /**< Number of available semaphores *///有效信号量的数量
UINT16 maxSemCount; /**< Max number of available semaphores *///有效信号量的最大数量
UINT32 semID; /**< Semaphore control structure ID *///信号量索引号
LOS_DL_LIST semList; /**< Queue of tasks that are waiting on a semaphore *///等待信号量的任务队列,任务通过阻塞节点挂上去
} LosSemCB;
semList
,这又是一个双向链表, 双向链表是内核最重要的结构体, 可前往 鸿蒙内核源码分析(总目录)
查看双向链表篇, LOS_DL_LIST
像狗皮膏药一样牢牢的寄生在宿主结构体上semList
上挂的是未来所有等待这个信号量的任务.
#ifndef LOSCFG_BASE_IPC_SEM_LIMIT
#define LOSCFG_BASE_IPC_SEM_LIMIT 1024 //信号量的最大个数
#endif
LITE_OS_SEC_TEXT_INIT UINT32 OsSemInit(VOID)//信号量初始化
{
LosSemCB *semNode = NULL;
UINT32 index;
LOS_ListInit(&g_unusedSemList);//初始
/* system resident memory, don't free */
g_allSem = (LosSemCB *)LOS_MemAlloc(m_aucSysMem0, (LOSCFG_BASE_IPC_SEM_LIMIT * sizeof(LosSemCB)));//分配信号池
if (g_allSem == NULL) {
return LOS_ERRNO_SEM_NO_MEMORY;
}
for (index = 0; index < LOSCFG_BASE_IPC_SEM_LIMIT; index++) {
semNode = ((LosSemCB *)g_allSem) + index;//拿信号控制块, 可以直接g_allSem[index]来嘛
semNode->semID = SET_SEM_ID(0, index);//保存ID
semNode->semStat = OS_SEM_UNUSED;//标记未使用
LOS_ListTailInsert(&g_unusedSemList, &semNode->semList);//通过semList把 信号块挂到空闲链表上
}
if (OsSemDbgInitHook() != LOS_OK) {
return LOS_ERRNO_SEM_NO_MEMORY;
}
return LOS_OK;
}
分析如下:
1024
个信号量[0,1023]
g_unusedSemList
上.小建议:鸿蒙内核其他池(如进程池,任务池)都采用free
来命名空闲链表,而此处使用unused
,命名风格不太严谨,有待改善.
LITE_OS_SEC_TEXT_INIT UINT32 OsSemCreate(UINT16 count, UINT16 maxCount, UINT32 *semHandle)
{
unusedSem = LOS_DL_LIST_FIRST(&g_unusedSemList);//从未使用信号量池中取首个
LOS_ListDelete(unusedSem);//从空闲链表上摘除
semCreated = GET_SEM_LIST(unusedSem);//通过semList挂到链表上的,这里也要通过它把LosSemCB头查到. 进程,线程等结构体也都是这么干的.
semCreated->semCount = count;//设置数量
semCreated->semStat = OS_SEM_USED;//设置可用状态
semCreated->maxSemCount = maxCount;//设置最大信号数量
LOS_ListInit(&semCreated->semList);//初始化链表,后续阻塞任务通过task->pendList挂到semList链表上,就知道哪些任务在等它了.
*semHandle = semCreated->semID;//参数带走 semID
OsSemDbgUpdateHook(semCreated->semID, OsCurrTaskGet()->taskEntry, count);
return LOS_OK;
ERR_HANDLER:
OS_RETURN_ERROR_P2(errLine, errNo);
}
分析如下:
OS_SEM_UNUSED
变成了 OS_SEM_USED
semHandle
带走信号量ID,外部由此知道成功创建了一个编号为 *semHandle
的信号量LITE_OS_SEC_TEXT UINT32 LOS_SemPend(UINT32 semHandle, UINT32 timeout)
{
UINT32 intSave;
LosSemCB *semPended = GET_SEM(semHandle);//通过ID拿到信号体
UINT32 retErr = LOS_OK;
LosTaskCB *runTask = NULL;
if (GET_SEM_INDEX(semHandle) >= (UINT32)LOSCFG_BASE_IPC_SEM_LIMIT) {
OS_RETURN_ERROR(LOS_ERRNO_SEM_INVALID);
}
if (OS_INT_ACTIVE) {
PRINT_ERR("!!!LOS_ERRNO_SEM_PEND_INTERR!!!\n");
OsBackTrace();
return LOS_ERRNO_SEM_PEND_INTERR;
}
runTask = OsCurrTaskGet();//获取当前任务
if (runTask->taskStatus & OS_TASK_FLAG_SYSTEM_TASK) {
OsBackTrace();
return LOS_ERRNO_SEM_PEND_IN_SYSTEM_TASK;
}
SCHEDULER_LOCK(intSave);
if ((semPended->semStat == OS_SEM_UNUSED) || (semPended->semID != semHandle)) {
retErr = LOS_ERRNO_SEM_INVALID;
goto OUT;
}
/* Update the operate time, no matter the actual Pend success or not */
OsSemDbgTimeUpdateHook(semHandle);
if (semPended->semCount > 0) {
//还有资源可用,返回肯定得成功,semCount=0时代表没资源了,task会必须去睡眠了
semPended->semCount--;//资源少了一个
goto OUT;//注意这里 retErr = LOS_OK ,所以返回是OK的
} else if (!timeout) {
retErr = LOS_ERRNO_SEM_UNAVAILABLE;
goto OUT;
}
if (!OsPreemptableInSched()) {
//不能申请调度 (不能调度的原因是因为没有持有调度任务自旋锁)
PRINT_ERR("!!!LOS_ERRNO_SEM_PEND_IN_LOCK!!!\n");
OsBackTrace();
retErr = LOS_ERRNO_SEM_PEND_IN_LOCK;
goto OUT;
}
runTask->taskSem = (VOID *)semPended;//标记当前任务在等这个信号量
retErr = OsTaskWait(&semPended->semList, timeout, TRUE);//任务进入等待状态,当前任务会挂到semList上,并在其中切换任务上下文
if (retErr == LOS_ERRNO_TSK_TIMEOUT) {
//注意:这里是涉及到task切换的,把自己挂起,唤醒其他task
runTask->taskSem = NULL;
retErr = LOS_ERRNO_SEM_TIMEOUT;
}
OUT:
SCHEDULER_UNLOCK(intSave);
return retErr;
}
分析如下:
这个函数有点复杂,大量的goto
,但别被它绕晕了,盯着返回值看.
先说结果只有一种情况下申请信号量能成功(即 retErr == LOS_OK
)
if (semPended->semCount > 0) {
//还有资源可用,返回肯定得成功,semCount=0时代表没资源了,task会必须去睡眠了
semPended->semCount--;//资源少了一个
goto OUT;//注意这里 retErr = LOS_OK ,所以返回是OK的
}
其余申请失败的原因有:
1024
)以上都是异常的判断,再说正常情况下 semPended->semCount = 0
时的情况,没有资源了怎么办?
任务进入 OsTaskWait
睡眠状态,怎么睡,睡多久,由参数 timeout
定 timeout
值分以下三种模式:
无阻塞模式:即任务申请信号量时,入参
timeout
等于0。若当前信号量计数值不为0,则申请成功,否则立即返回申请失败。
永久阻塞模式:即任务申请信号量时,入参
timeout
等于0xFFFFFFFF。若当前信号量计数值不为0,则申请成功。
否则该任务进入阻塞态,系统切换到就绪任务中优先级最高者继续执行。任务进入阻塞态后,直到有其他任务释放该信号量,阻塞任务才会重新得以执行。
定时阻塞模式:即任务申请信号量时,
0
。若当前信号量计数值不为0,则申请成功。
否则,该任务进入阻塞态,系统切换到就绪任务中优先级最高者继续执行。任务进入阻塞态后,
超时前如果有其他任务释放该信号量,则该任务可成功获取信号量继续执行,若超时前未获取到信号量,接口将返回超时错误码。
在 OsTaskWait
中,任务将被挂入semList
链表,semList
上挂的都是等待这个信号量的任务.
LITE_OS_SEC_TEXT UINT32 OsSemPostUnsafe(UINT32 semHandle, BOOL *needSched)
{
LosSemCB *semPosted = NULL;
LosTaskCB *resumedTask = NULL;
if (GET_SEM_INDEX(semHandle) >= LOSCFG_BASE_IPC_SEM_LIMIT) {
return LOS_ERRNO_SEM_INVALID;
}
semPosted = GET_SEM(semHandle);
if ((semPosted->semID != semHandle) || (semPosted->semStat == OS_SEM_UNUSED)) {
return LOS_ERRNO_SEM_INVALID;
}
/* Update the operate time, no matter the actual Post success or not */
OsSemDbgTimeUpdateHook(semHandle);
if (semPosted->semCount == OS_SEM_COUNT_MAX) {
//当前信号资源不能大于最大资源量
return LOS_ERRNO_SEM_OVERFLOW;
}
if (!LOS_ListEmpty(&semPosted->semList)) {
//当前有任务挂在semList上,要去唤醒任务
resumedTask = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&(semPosted->semList)));//semList上面挂的都是task->pendlist节点,取第一个task下来唤醒
resumedTask->taskSem = NULL;//任务不用等信号了,重新变成NULL值
OsTaskWake(resumedTask);//唤醒任务,注意resumedTask一定不是当前任务,OsTaskWake里面并不会自己切换任务上下文,只是设置状态
if (needSched != NULL) {
//参数不为空,就返回需要调度的标签
*needSched = TRUE;//TRUE代表需要调度
}
} else {
//当前没有任务挂在semList上,
semPosted->semCount++;//信号资源多一个
}
return LOS_OK;
}
LITE_OS_SEC_TEXT UINT32 LOS_SemPost(UINT32 semHandle)
{
UINT32 intSave;
UINT32 ret;
BOOL needSched = FALSE;
SCHEDULER_LOCK(intSave);
ret = OsSemPostUnsafe(semHandle, &needSched);
SCHEDULER_UNLOCK(intSave);
if (needSched) {
//需要调度的情况
LOS_MpSchedule(OS_MP_CPU_ALL);//向所有CPU发送调度指令
LOS_Schedule();发起调度
}
return ret;
}
分析如下:
semPosted->semCount
才会 ++ ,是在LOS_ListEmpty
为真的时候,semList
是等待这个信号量的任务.semList
上的任务是在OsTaskWait
中挂入的.都在等这个信号.OsSemPost
都会唤醒semList
链表上一个任务,直到semList
为空.LOS_SemPend
和 LOS_SemPost
/* 任务ID */
static UINT32 g_testTaskId01;
static UINT32 g_testTaskId02;
/* 测试任务优先级 */
#define TASK_PRIO_TEST 5
/* 信号量结构体id */
static UINT32 g_semId;
VOID Example_SemTask1(VOID)
{
UINT32 ret;
printf("Example_SemTask1 try get sem g_semId ,timeout 10 ticks.\n");
/* 定时阻塞模式申请信号量,定时时间为10ticks */
ret = LOS_SemPend(g_semId, 10);
/*申请到信号量*/
if (ret == LOS_OK) {
LOS_SemPost(g_semId);
return;
}
/* 定时时间到,未申请到信号量 */
if (ret == LOS_ERRNO_SEM_TIMEOUT) {
printf("Example_SemTask1 timeout and try get sem g_semId wait forever.\n");
/*永久阻塞模式申请信号量*/
ret = LOS_SemPend(g_semId, LOS_WAIT_FOREVER);
printf("Example_SemTask1 wait_forever and get sem g_semId .\n");
if (ret == LOS_OK) {
LOS_SemPost(g_semId);
return;
}
}
}
VOID Example_SemTask2(VOID)
{
UINT32 ret;
printf("Example_SemTask2 try get sem g_semId wait forever.\n");
/* 永久阻塞模式申请信号量 */
ret = LOS_SemPend(g_semId, LOS_WAIT_FOREVER);
if (ret == LOS_OK) {
printf("Example_SemTask2 get sem g_semId and then delay 20ticks .\n");
}
/* 任务休眠20 ticks */
LOS_TaskDelay(20);
printf("Example_SemTask2 post sem g_semId .\n");
/* 释放信号量 */
LOS_SemPost(g_semId);
return;
}
UINT32 ExampleTaskEntry(VOID)
{
UINT32 ret;
TSK_INIT_PARAM_S task1;
TSK_INIT_PARAM_S task2;
/* 创建信号量 */
LOS_SemCreate(0,&g_semId);
/* 锁任务调度 */
LOS_TaskLock();
/*创建任务1*/
(VOID)memset_s(&task1, sizeof(TSK_INIT_PARAM_S), 0, sizeof(TSK_INIT_PARAM_S));
task1.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_SemTask1;
task1.pcName = "TestTsk1";
task1.uwStackSize = OS_TSK_DEFAULT_STACK_SIZE;
task1.usTaskPrio = TASK_PRIO_TEST;
ret = LOS_TaskCreate(&g_testTaskId01, &task1);
if (ret != LOS_OK) {
printf("task1 create failed .\n");
return LOS_NOK;
}
/* 创建任务2 */
(VOID)memset_s(&task2, sizeof(TSK_INIT_PARAM_S), 0, sizeof(TSK_INIT_PARAM_S));
task2.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_SemTask2;
task2.pcName = "TestTsk2";
task2.uwStackSize = OS_TSK_DEFAULT_STACK_SIZE;
task2.usTaskPrio = (TASK_PRIO_TEST - 1);
ret = LOS_TaskCreate(&g_testTaskId02, &task2);
if (ret != LOS_OK) {
printf("task2 create failed .\n");
return LOS_NOK;
}
/* 解锁任务调度 */
LOS_TaskUnlock();
ret = LOS_SemPost(g_semId);
/* 任务休眠40 ticks */
LOS_TaskDelay(40);
/* 删除信号量 */
LOS_SemDelete(g_semId);
/* 删除任务1 */
ret = LOS_TaskDelete(g_testTaskId01);
if (ret != LOS_OK) {
printf("task1 delete failed .\n");
return LOS_NOK;
}
/* 删除任务2 */
ret = LOS_TaskDelete(g_testTaskId02);
if (ret != LOS_OK) {
printf("task2 delete failed .\n");
return LOS_NOK;
}
return LOS_OK;
}
Example_SemTask2 try get sem g_semId wait forever.
Example_SemTask1 try get sem g_semId ,timeout 10 ticks.
Example_SemTask2 get sem g_semId and then delay 20ticks .
Example_SemTask1 timeout and try get sem g_semId wait forever.
Example_SemTask2 post sem g_semId .
Example_SemTask1 wait_forever and get sem g_semId .
各大站点搜 “鸿蒙内核源码分析” ,快速找到组织.
百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< Gitee | Github | CSDN | Coding >
百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,多站点每日同步更新< OSCHINA | CSDN | WeHarmony >