文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

鸿蒙内核源码分析(信号量篇) | 信号量解决任务同步问题

2024-12-03 07:22

关注

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

基本概念

信号量(Semaphore) 是一种实现任务间通信的机制,可以实现任务间同步或共享资源的互斥访问。 一个信号量的数据结构中,通常有一个计数值,用于对有效资源数的计数,表示剩下的可被使用的共享资源数,其值的含义分两种情况:

[[392645]]

以同步为目的的信号量和以互斥为目的的信号量在使用上有如下不同:

信号量运作原理

信号量初始化,为配置的N个信号量申请内存(N值可以由用户自行配置,通过 LOSCFG_BASE_IPC_SEM_LIMIT 宏实现),并把所有信号量初始化成未使用,加入到未使用链表中供系统使用。

● 信号量创建,从未使用的信号量链表中获取一个信号量,并设定初值。

● 信号量申请,若其计数器值大于0,则直接减1返回成功。否则任务阻塞,等待其它任务释放该信号量, 等待的超时时间可设定。当任务被一个信号量阻塞时,将该任务挂到信号量等待任务队列的队尾。

● 信号量释放,若没有任务等待该信号量,则直接将计数器加1返回。否则唤醒该信号量等待任务队列上的第一个任务。

● 信号量删除,将正在使用的信号量置为未使用信号量,并挂回到未使用链表。

信号量允许多个任务在同一时刻访问共享资源,但会限制同一时刻访问此资源的最大任务数目。 当访问资源的任务数达到该资源允许的最大数量时,会阻塞其他试图获取该资源的任务,直到有任务释放该信号量。

信号量长什么样?

  1. typedef struct { 
  2.     UINT8 semStat; //信号量的状态 
  3.     UINT16 semCount; //有效信号量的数量 
  4.     UINT16 maxSemCount;  //有效信号量的最大数量 
  5.     UINT32 semID; //信号量索引号 
  6.     LOS_DL_LIST semList; //等待信号量的任务队列,任务通过阻塞节点挂上去 
  7. } LosSemCB; 

semList,这又是一个双向链表, 双向链表是内核最重要的结构体, 可前往 鸿蒙内核源码分析(总目录) 查看双向链表篇, LOS_DL_LIST像狗皮膏药一样牢牢的寄生在宿主结构体上semList上挂的是未来所有等待这个信号量的任务.

初始化信号量模块

  1. #ifndef LOSCFG_BASE_IPC_SEM_LIMIT 
  2. #define LOSCFG_BASE_IPC_SEM_LIMIT 1024 //信号量的最大个数 
  3. #endif 
  4.  
  5. LITE_OS_SEC_TEXT_INIT UINT32 OsSemInit(VOID)//信号量初始化 
  6.     LosSemCB *semNode = NULL
  7.     UINT32 index
  8.  
  9.     LOS_ListInit(&g_unusedSemList);//初始 
  10.      
  11.     g_allSem = (LosSemCB *)LOS_MemAlloc(m_aucSysMem0, (LOSCFG_BASE_IPC_SEM_LIMIT * sizeof(LosSemCB)));//分配信号池 
  12.     if (g_allSem == NULL) { 
  13.         return LOS_ERRNO_SEM_NO_MEMORY; 
  14.     } 
  15.  
  16.     for (index = 0; index < LOSCFG_BASE_IPC_SEM_LIMIT; index++) { 
  17.         semNode = ((LosSemCB *)g_allSem) + index;//拿信号控制块, 可以直接g_allSem[index]来嘛 
  18.         semNode->semID = SET_SEM_ID(0, index);//保存ID 
  19.         semNode->semStat = OS_SEM_UNUSED;//标记未使用 
  20.         LOS_ListTailInsert(&g_unusedSemList, &semNode->semList);//通过semList把 信号块挂到空闲链表上 
  21.     } 
  22.  
  23.     if (OsSemDbgInitHook() != LOS_OK) { 
  24.         return LOS_ERRNO_SEM_NO_MEMORY; 
  25.     } 
  26.     return LOS_OK; 

分析如下:

● 初始化创建了信号量池来统一管理信号量, 默认 1024 个信号量

● 信号ID范围从 [0,1023]

● 未分配使用的信号量都挂到了全局变量 g_unusedSemList 上.

小建议:鸿蒙内核其他池(如进程池,任务池)都采用free来命名空闲链表,而此处使用unused,命名风格不太严谨,有待改善.

创建信号量

  1. LITE_OS_SEC_TEXT_INIT UINT32 OsSemCreate(UINT16 count, UINT16 maxCount, UINT32 *semHandle) 
  2.     unusedSem = LOS_DL_LIST_FIRST(&g_unusedSemList);//从未使用信号量池中取首个 
  3.     LOS_ListDelete(unusedSem);//从空闲链表上摘除 
  4.     semCreated = GET_SEM_LIST(unusedSem);//通过semList挂到链表上的,这里也要通过它把LosSemCB头查到. 进程,线程等结构体也都是这么干的. 
  5.     semCreated->semCount = count;//设置数量 
  6.     semCreated->semStat = OS_SEM_USED;//设置可用状态 
  7.     semCreated->maxSemCount = maxCount;//设置最大信号数量 
  8.     LOS_ListInit(&semCreated->semList);//初始化链表,后续阻塞任务通过task->pendList挂到semList链表上,就知道哪些任务在等它了. 
  9.     *semHandle = semCreated->semID;//参数带走 semID 
  10.     OsSemDbgUpdateHook(semCreated->semID, OsCurrTaskGet()->taskEntry, count); 
  11.     return LOS_OK; 
  12.  
  13. ERR_HANDLER: 
  14.     OS_RETURN_ERROR_P2(errLine, errNo); 

分析如下:

● 从未使用的空闲链表中拿首个信号量供分配使用.

● 信号量的最大数量和信号量个数都由参数指定.

● 信号量状态由 OS_SEM_UNUSED 变成了 OS_SEM_USED

● semHandle带走信号量ID,外部由此知道成功创建了一个编号为 *semHandle 的信号量

申请信号量

  1. LITE_OS_SEC_TEXT UINT32 LOS_SemPend(UINT32 semHandle, UINT32 timeout) 
  2.     UINT32 intSave; 
  3.     LosSemCB *semPended = GET_SEM(semHandle);//通过ID拿到信号体 
  4.     UINT32 retErr = LOS_OK; 
  5.     LosTaskCB *runTask = NULL
  6.  
  7.     if (GET_SEM_INDEX(semHandle) >= (UINT32)LOSCFG_BASE_IPC_SEM_LIMIT) { 
  8.         OS_RETURN_ERROR(LOS_ERRNO_SEM_INVALID); 
  9.     } 
  10.  
  11.     if (OS_INT_ACTIVE) { 
  12.         PRINT_ERR("!!!LOS_ERRNO_SEM_PEND_INTERR!!!\n"); 
  13.         OsBackTrace(); 
  14.         return LOS_ERRNO_SEM_PEND_INTERR; 
  15.     } 
  16.  
  17.     runTask = OsCurrTaskGet();//获取当前任务 
  18.     if (runTask->taskStatus & OS_TASK_FLAG_SYSTEM_TASK) { 
  19.         OsBackTrace(); 
  20.         return LOS_ERRNO_SEM_PEND_IN_SYSTEM_TASK; 
  21.     } 
  22.  
  23.     SCHEDULER_LOCK(intSave); 
  24.  
  25.     if ((semPended->semStat == OS_SEM_UNUSED) || (semPended->semID != semHandle)) { 
  26.         retErr = LOS_ERRNO_SEM_INVALID; 
  27.         goto OUT
  28.     } 
  29.  
  30.      
  31.     OsSemDbgTimeUpdateHook(semHandle); 
  32.  
  33.     if (semPended->semCount > 0) {//还有资源可用,返回肯定得成功,semCount=0时代表没资源了,task会必须去睡眠了 
  34.         semPended->semCount--;//资源少了一个 
  35.         goto OUT;//注意这里 retErr = LOS_OK ,所以返回是OK的  
  36.     } else if (!timeout) { 
  37.         retErr = LOS_ERRNO_SEM_UNAVAILABLE; 
  38.         goto OUT
  39.     } 
  40.  
  41.     if (!OsPreemptableInSched()) {//不能申请调度 (不能调度的原因是因为没有持有调度任务自旋锁) 
  42.         PRINT_ERR("!!!LOS_ERRNO_SEM_PEND_IN_LOCK!!!\n"); 
  43.         OsBackTrace(); 
  44.         retErr = LOS_ERRNO_SEM_PEND_IN_LOCK; 
  45.         goto OUT
  46.     } 
  47.  
  48.     runTask->taskSem = (VOID *)semPended;//标记当前任务在等这个信号量 
  49.     retErr = OsTaskWait(&semPended->semList, timeout, TRUE);//任务进入等待状态,当前任务会挂到semList上,并在其中切换任务上下文 
  50.     if (retErr == LOS_ERRNO_TSK_TIMEOUT) {//注意:这里是涉及到task切换的,把自己挂起,唤醒其他task  
  51.         runTask->taskSem = NULL
  52.         retErr = LOS_ERRNO_SEM_TIMEOUT; 
  53.     } 
  54.  
  55. OUT
  56.     SCHEDULER_UNLOCK(intSave); 
  57.     return retErr; 

分析如下: 这个函数有点复杂,大量的goto,但别被它绕晕了,盯着返回值看. 先说结果只有一种情况下申请信号量能成功(即 retErr == LOS_OK)

  1. if (semPended->semCount > 0) {//还有资源可用,返回肯定得成功,semCount=0时代表没资源了,task会必须去睡眠了 
  2.       semPended->semCount--;//资源少了一个 
  3.       goto OUT;//注意这里 retErr = LOS_OK ,所以返回是OK的  
  4.   } 

其余申请失败的原因有:

● 信号量ID超出范围(默认1024)

● 中断发生期间

● 系统任务

● 信号量状态不对,信号量ID不匹配

以上都是异常的判断,再说正常情况下 semPended->semCount = 0时的情况,没有资源了怎么办? 任务进入 OsTaskWait 睡眠状态,怎么睡,睡多久,由参数 timeout 定 timeout 值分以下三种模式:

在 OsTaskWait 中,任务将被挂入semList链表,semList上挂的都是等待这个信号量的任务.

释放信号量

  1. LITE_OS_SEC_TEXT UINT32 OsSemPostUnsafe(UINT32 semHandle, BOOL *needSched) 
  2.     LosSemCB *semPosted = NULL
  3.     LosTaskCB *resumedTask = NULL
  4.  
  5.     if (GET_SEM_INDEX(semHandle) >= LOSCFG_BASE_IPC_SEM_LIMIT) { 
  6.         return LOS_ERRNO_SEM_INVALID; 
  7.     } 
  8.  
  9.     semPosted = GET_SEM(semHandle); 
  10.     if ((semPosted->semID != semHandle) || (semPosted->semStat == OS_SEM_UNUSED)) { 
  11.         return LOS_ERRNO_SEM_INVALID; 
  12.     } 
  13.  
  14.      
  15.     OsSemDbgTimeUpdateHook(semHandle); 
  16.  
  17.     if (semPosted->semCount == OS_SEM_COUNT_MAX) {//当前信号资源不能大于最大资源量 
  18.         return LOS_ERRNO_SEM_OVERFLOW; 
  19.     } 
  20.     if (!LOS_ListEmpty(&semPosted->semList)) {//当前有任务挂在semList上,要去唤醒任务 
  21.         resumedTask = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&(semPosted->semList)));//semList上面挂的都是task->pendlist节点,取第一个task下来唤醒 
  22.         resumedTask->taskSem = NULL;//任务不用等信号了,重新变成NULL值 
  23.         OsTaskWake(resumedTask);//唤醒任务,注意resumedTask一定不是当前任务,OsTaskWake里面并不会自己切换任务上下文,只是设置状态 
  24.         if (needSched != NULL) {//参数不为空,就返回需要调度的标签 
  25.             *needSched = TRUE;//TRUE代表需要调度 
  26.         } 
  27.     } else {//当前没有任务挂在semList上, 
  28.         semPosted->semCount++;//信号资源多一个 
  29.     } 
  30.  
  31.     return LOS_OK; 
  32.  
  33. LITE_OS_SEC_TEXT UINT32 LOS_SemPost(UINT32 semHandle) 
  34.     UINT32 intSave; 
  35.     UINT32 ret; 
  36.     BOOL needSched = FALSE
  37.  
  38.     SCHEDULER_LOCK(intSave); 
  39.     ret = OsSemPostUnsafe(semHandle, &needSched); 
  40.         SCHEDULER_UNLOCK(intSave); 
  41.     if (needSched) {//需要调度的情况 
  42.         LOS_MpSchedule(OS_MP_CPU_ALL);//向所有CPU发送调度指令 
  43.         LOS_Schedule();////发起调度 
  44.     } 
  45.  
  46.     return ret; 

分析如下:

● 注意看在什么情况下 semPosted->semCount 才会 ++ ,是在LOS_ListEmpty为真的时候,semList是等待这个信号量的任务. semList上的任务是在OsTaskWait中挂入的.都在等这个信号.

● 每次OsSemPost都会唤醒semList链表上一个任务,直到semList为空.

● 掌握信号量的核心是理解 LOS_SemPend 和 LOS_SemPost

编程示例

本实例实现如下功能:

● 测试任务Example_TaskEntry创建一个信号量,锁任务调度,创建两个任务Example_SemTask1、Example_SemTask2,Example_SemTask2优先级高于Example_SemTask1,两个任务中申请同一信号量,解锁任务调度后两任务阻塞,测试任务Example_TaskEntry释放信号量。

● Example_SemTask2得到信号量,被调度,然后任务休眠20Tick,Example_SemTask2延迟,Example_SemTask1被唤醒。

● Example_SemTask1定时阻塞模式申请信号量,等待时间为10Tick,因信号量仍被Example_SemTask2持有,Example_SemTask1挂起,10Tick后仍未得到信号量, Example_SemTask1被唤醒,试图以永久阻塞模式申请信号量,Example_SemTask1挂起。

● 20Tick后Example_SemTask2唤醒, 释放信号量后,Example_SemTask1得到信号量被调度运行,最后释放信号量。

● Example_SemTask1执行完,40Tick后任务Example_TaskEntry被唤醒,执行删除信号量,删除两个任务。

  1.  
  2. static UINT32 g_testTaskId01; 
  3. static UINT32 g_testTaskId02; 
  4.  
  5. #define TASK_PRIO_TEST  5 
  6.  
  7. static UINT32 g_semId; 
  8.  
  9. VOID Example_SemTask1(VOID) 
  10.     UINT32 ret; 
  11.  
  12.     printf("Example_SemTask1 try get sem g_semId ,timeout 10 ticks.\n"); 
  13.      
  14.     ret = LOS_SemPend(g_semId, 10); 
  15.  
  16.      
  17.     if (ret == LOS_OK) { 
  18.          LOS_SemPost(g_semId); 
  19.          return
  20.     } 
  21.      
  22.     if (ret == LOS_ERRNO_SEM_TIMEOUT) { 
  23.         printf("Example_SemTask1 timeout and try get sem g_semId wait forever.\n"); 
  24.          
  25.         ret = LOS_SemPend(g_semId, LOS_WAIT_FOREVER); 
  26.         printf("Example_SemTask1 wait_forever and get sem g_semId .\n"); 
  27.         if (ret == LOS_OK) { 
  28.             LOS_SemPost(g_semId); 
  29.             return
  30.         } 
  31.     } 
  32.  
  33. VOID Example_SemTask2(VOID) 
  34.     UINT32 ret; 
  35.     printf("Example_SemTask2 try get sem g_semId wait forever.\n"); 
  36.      
  37.     ret = LOS_SemPend(g_semId, LOS_WAIT_FOREVER); 
  38.  
  39.     if (ret == LOS_OK) { 
  40.         printf("Example_SemTask2 get sem g_semId and then delay 20ticks .\n"); 
  41.     } 
  42.  
  43.      
  44.     LOS_TaskDelay(20); 
  45.  
  46.     printf("Example_SemTask2 post sem g_semId .\n"); 
  47.      
  48.     LOS_SemPost(g_semId); 
  49.     return
  50.  
  51. UINT32 ExampleTaskEntry(VOID) 
  52.     UINT32 ret; 
  53.     TSK_INIT_PARAM_S task1; 
  54.     TSK_INIT_PARAM_S task2; 
  55.  
  56.     
  57.     LOS_SemCreate(0,&g_semId); 
  58.  
  59.      
  60.     LOS_TaskLock(); 
  61.  
  62.      
  63.     (VOID)memset_s(&task1, sizeof(TSK_INIT_PARAM_S), 0, sizeof(TSK_INIT_PARAM_S)); 
  64.     task1.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_SemTask1; 
  65.     task1.pcName       = "TestTsk1"
  66.     task1.uwStackSize  = OS_TSK_DEFAULT_STACK_SIZE; 
  67.     task1.usTaskPrio   = TASK_PRIO_TEST; 
  68.     ret = LOS_TaskCreate(&g_testTaskId01, &task1); 
  69.     if (ret != LOS_OK) { 
  70.         printf("task1 create failed .\n"); 
  71.         return LOS_NOK; 
  72.     } 
  73.  
  74.      
  75.     (VOID)memset_s(&task2, sizeof(TSK_INIT_PARAM_S), 0, sizeof(TSK_INIT_PARAM_S)); 
  76.     task2.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_SemTask2; 
  77.     task2.pcName       = "TestTsk2"
  78.     task2.uwStackSize  = OS_TSK_DEFAULT_STACK_SIZE; 
  79.     task2.usTaskPrio   = (TASK_PRIO_TEST - 1); 
  80.     ret = LOS_TaskCreate(&g_testTaskId02, &task2); 
  81.     if (ret != LOS_OK) { 
  82.         printf("task2 create failed .\n"); 
  83.         return LOS_NOK; 
  84.     } 
  85.  
  86.      
  87.     LOS_TaskUnlock(); 
  88.  
  89.     ret = LOS_SemPost(g_semId); 
  90.  
  91.      
  92.     LOS_TaskDelay(40); 
  93.  
  94.      
  95.     LOS_SemDelete(g_semId); 
  96.  
  97.      
  98.     ret = LOS_TaskDelete(g_testTaskId01); 
  99.     if (ret != LOS_OK) { 
  100.         printf("task1 delete failed .\n"); 
  101.         return LOS_NOK; 
  102.     } 
  103.      
  104.     ret = LOS_TaskDelete(g_testTaskId02); 
  105.     if (ret != LOS_OK) { 
  106.         printf("task2 delete failed .\n"); 
  107.         return LOS_NOK; 
  108.     } 
  109.  
  110.     return LOS_OK; 

实例运行结果:

  1. Example_SemTask2 try get sem g_semId wait forever. 
  2. Example_SemTask1 try get sem g_semId ,timeout 10 ticks. 
  3. Example_SemTask2 get sem g_semId and then delay 20ticks . 
  4. Example_SemTask1 timeout and try get sem g_semId wait forever. 
  5. Example_SemTask2 post sem g_semId . 
  6. Example_SemTask1 wait_forever and get sem g_semId . 

参与贡献

访问注解仓库地址

Fork 本仓库 >> 新建 Feat_xxx 分支 >> 提交代码注解 >> 新建 Pull Request

新建 Issue

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

 

来源:鸿蒙社区内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯