Advertisement

【liteOS】小白进阶之 LiteOS 信号量解析

阅读量:

本摘要介绍了信号量的基本概念及其在多任务系统中的应用。首先定义了信号量用于实现任务间的同步或互斥访问资源,并通过示例展示了其计数值管理机制及不同状态含义。接着详细说明了创建、申请、释放及删除等操作,并结合示例测试任务(如ExampleSemTask1和ExampleSemTask2)展示了信号量的实际使用场景及阻塞模式的区别(包括无阻塞、永久阻塞和定时阻塞)。此外还提到Huawei LiteOS系统中支持多种功能如锁、同步、资源计数等功能,并应用于任务与中断的同步中。

1、信号量概念

信号量(Semaphore)是一种负责协调多线程程序中不同任务之间通信的方式,在多线程编程模型中具有重要作用。它通过确保同步过程与对共享资源进行互斥访问来提供可靠的任务同步机制。

常用于协助一组相互竞争的任务来访问临界资源。

在多任务系统设计中,各子任务必须采用同步机制或互斥机制以确保对关键资源实施保护。信号量功能则提供了实现该功能所需的技术支持。

通常情况下,该信号量用以反映可用资源的数量;其值代表剩余可释放的互斥资源数量。

其值的含义分两种情况:

  1. 0,表示没有积累下来的 Post 操作,且有可能有在此信号量上阻塞的任务;

  2. 0,表示有一个或多个 Post 下来的释放操作。

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

当存在互斥场景时,在有需要调用临界资源的操作发生之前,系统会先获取信号量并释放一个计数单位。此时若其他任务正在等待调用临界资源,则会因无法获取到当前信号量而被阻塞等待。这种机制设计确保了所有共享资源的操作能够顺利进行而不发生冲突。

在执行同步操作时,在创建完信号量之后将其设置为空值状态。随后的任务1会尝试获取该信号量而导致阻塞状态。等到特定条件下触发后, 任务2会重新获取该信号量, 并使得任务1能够顺利地进入READY或RUNNING的状态, 这种方式实现了两个任务之间的同步协作.

实验:信号量实现多任务并发

效果:

资源:

复制代码
 typedef struct {

    
     UINT32 handleID;
    
     BOOL isUsed;
    
 } ResHandler;
    
  
    
 static ResHandler s_stResHandler[RES_COUNT] = {
    
         { 100, FALSE },
    
         { 200, FALSE },
    
         { 300, FALSE }
    
     };

创建十个任务和一个多值信号量:

复制代码
 UINT32 Example13_Entry(VOID) {

    
     UINT32 uwRet = LOS_OK;
    
     UINT32 i = NULL;
    
     TSK_INIT_PARAM_S stInitParam = {0};
    
     
    
     puts("Example13_Entry\r\n");
    
  
    
     uwRet = LOS_SemCreate(RES_COUNT, &s_uwSemID);
    
     if (uwRet != LOS_OK) {
    
     printf("LOS_SemCreate Failed:%x!\r\n", uwRet);
    
     return LOS_NOK;
    
     }
    
     
    
     for (i = 0; i < NUM_OF_TASKS; i++) {
    
     stInitParam.pfnTaskEntry = Handle_Task;
    
     stInitParam.usTaskPrio = TASK_DEFAULT_PRIO;
    
     stInitParam.pcName = "Tasks";
    
     stInitParam.uwStackSize = TASK_STK_SIZE;
    
     stInitParam.uwArg = i;
    
     uwRet = LOS_TaskCreate(&s_uwHandleTskID, &stInitParam);
    
     if (uwRet != LOS_OK) {
    
         printf("Handle_Task create Failed!\r\n");
    
         return LOS_NOK;
    
     }
    
     }
    
     return uwRet;
    
 }

任务并发处理:

复制代码
 static VOID * Handle_Task(UINT32 uwArg) {

    
     UINT32 uwRet = LOS_OK;
    
     UINT32 i = NULL;
    
     
    
     printf("Handle_Task(%d) should be Pending.\r\n", uwArg);
    
     uwRet = LOS_SemPend(s_uwSemID, LOS_WAIT_FOREVER);
    
     if (LOS_OK == uwRet) {        
    
     for (i = 0; i < RES_COUNT; i++) {
    
         if (s_stResHandler[i].isUsed == FALSE) {
    
             s_stResHandler[i].isUsed = TRUE;
    
             break;
    
         }
    
     }
    
     printf("I am working on it(%d)\r\n", s_stResHandler[i].handleID);
    
     LOS_TaskDelay(1000);        
    
  
    
     // 1s后处理完成,释放信号量
    
     s_stResHandler[i].isUsed = FALSE;
    
     LOS_SemPost(s_uwSemID);
    
     }
    
     
    
     printf("Handle_Task(%d) should be finish.\r\n", uwArg);
    
     return LOS_OK;
    
 }

2、信号量控制块

复制代码
 typedef struct

    
 {
    
     UINT8           usSemStat;          /**是否使用标志位*/
    
     UINT16          uwSemCount;         /**信号量索引号*/
    
     UINT32          usSemID;            /**信号量计数*/
    
     LOS_DL_LIST     stSemList;          /**挂接阻塞于该信号量的任务*/
    
 }SEM_CB_S;

3、信号量运作原理

完成对所需的信号量进行初始化;负责从系统资源库中获取所需的N个信号量的内存资源分配(其中N值由用户自行设定,并受到系统内存容量的限制);将所有待初始化的信号量化为未被占用的状态,并将其加入到未使用的列表中以便供系统调用。

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

获取信号量后,在其计数器值大于零的情况下进行操作:将计数器减一并返回成功状态;如果计数值为零,则该操作会引发阻塞;此时系统会设置超时时间以等待相关操作完成;当某特定操作被某个信号量阻塞执行时,则将其挂至该信号量对应的等待任务队列末尾。

当未有其他作业等待此信号量时,则会使得计数器值增1后返回;若有作业需处理,则会将此信号量唤醒并让其处理队列中首部作业。

释放已使用的信号量,并将其状态标记为未使用;随后将该信号量重新链接回未使用链表。

允许多个进程在同一时间进入同一资源的信号量会限制同时进入的最大数目;当到达该容量后会阻塞等待的进程直至系统释放该信号量

信号量是一种高度灵活的同步机制,在各种系统架构中都能见到它的身影。它不仅支持基本的操作如锁机制、互斥操作以及资源计数功能等基础功能,并且还能便捷地应用于任务间的切换以及中断与任务之间的同步。

4、功能

Huawei LiteOS 系统中的信号量模块为用户提供下面几种功能:

  1. 创建信号量LOS_SemCreate;

  2. 申请信号量LOS_SemPend;

  3. 信号量有三种申请模式:无阻塞模式、永久阻塞模式、定时阻塞模式

无阻塞模式:

为完成任务需求,在当前系统中的任务数未达到最大容量限制的情况下会成功提交请求;如果已达到最大容量限制,则会直接拒绝该请求。

永久阻塞模式:

该任务需申请信号量。若当前信号量的任务数未达到其设定上限,则申请成功。反之,则导致该任务进入阻塞状态。系统将优先执行就绪中的最高优先级任务。一旦该任务进入阻塞态,则直至其他任何 任 何 任务释放该信号量时才可使阻塞的任务将重新获得执行机会。

定时阻塞模式:

任务若当前被申请的信号量的任务数未达到预先设定的上限,则该信号量的申请被成功释放;否则将导致当前任务被标记为等待状态,并将系统调度机制切换至优先级最高的就绪状态的任务继续执行。当等待状态下的任务在指定的时间窗口内未被其他信号量获取者释放对应信号量或用户在指定时间窗口之后放弃对该信号量的等待请求,则等待状态的任务将会重新恢复执行能力。

5、示例

测试任务Example_TaskEntry通过创建信号量来实现对任务调度的控制。具体而言,在完成锁 task 调度后, 定义两个子 task: Example_SemTask1 和 Example_SemTask2, 其中, Example_SemTask2 的优先级高于 Example_SemTask1. 这两个子 task 均需使用同一个信号量进行资源管理. 在解锁调度流程后, 这两个子 task 将被阻塞等待执行. 测试主程序完成之后会释放该信号量.

A获得信号量并被唤醒后,在此期间B等待20Tick。A的唤醒导致响应延迟。

在每次循环中,在等待信号量方面设置了新的策略:首先,在每次循环中,在等待信号量方面设置了新的策略:首先,在等待信号量方面设置了新的策略:首先,在等待信号量方面设置了新的策略:首先,在等待信号量方面设置了新的策略:首先,在等待信号量方面设置了新的策略:首先,在等待...

在每次循环中,在等待信号量方面设置了新的策略:首先,在每次循环中,在等待...

在20Tick时,Example_SemTask2被唤醒,并在释放信号量之后,Example_SemTask1通过调度机制运行并完成其任务后立即释放信号量。当40Tick时,任务Example_TaskEntry被唤醒并开始执行删除操作,并删除两个相关任务。

由于中断不能被阻塞,因此在申请信号量时,阻塞模式不能在中断中使用。

复制代码
 /*测试任务优先级*/

    
 #define TASK_PRIO_TEST  5
    
  
    
 /*任务PID*/
    
 static UINT32 g_TestTaskID01,g_TestTaskID02;
    
 /*信号量结构体ID*/
    
 static UINT32 g_usSemID;
    
  
    
  
    
 static VOID Example_SemTask1(void)
    
 {
    
     UINT32 uwRet;
    
  
    
     dprintf("Example_SemTask1 try get sem g_usSemID ,timeout 10 ticks.\n");
    
     /*定时阻塞模式申请信号量,定时时间为10Tick*/
    
     uwRet = LOS_SemPend(g_usSemID, 10);
    
  
    
     /*申请到信号量*/
    
     if(LOS_OK == uwRet)
    
     {
    
         LOS_SemPost(g_usSemID);
    
         return;
    
     }
    
     /*定时时间到,未申请到信号量*/
    
     if(LOS_ERRNO_SEM_TIMEOUT == uwRet)
    
     {
    
         dprintf("Example_SemTask1 timeout and try get sem g_usSemID wait forever.\n");
    
         /*永久阻塞模式申请信号量,获取不到时程序阻塞,不会返回*/
    
         uwRet = LOS_SemPend(g_usSemID, LOS_WAIT_FOREVER);
    
         if(LOS_OK == uwRet)
    
         {
    
             dprintf("Example_SemTask1 wait_forever and got sem g_usSemID success.\n");
    
             LOS_SemPost(g_usSemID);
    
             uwRet = LOS_InspectStatusSetByID(LOS_INSPECT_SEM,LOS_INSPECT_STU_SUCCESS);
    
             if (LOS_OK != uwRet)  
    
             {
    
                 dprintf("Set Inspect Status Err\n");
    
             }
    
             return;
    
         }
    
     }
    
     return;
    
 }
    
  
    
 static VOID Example_SemTask2(void)
    
 {
    
     UINT32 uwRet;
    
     dprintf("Example_SemTask2 try get sem g_usSemID wait forever.\n");
    
     /*永久阻塞模式申请信号量*/
    
     uwRet = LOS_SemPend(g_usSemID, LOS_WAIT_FOREVER);
    
  
    
     if(LOS_OK == uwRet)
    
     {
    
         dprintf("Example_SemTask2 get sem g_usSemID and then delay 20ticks .\n");
    
     }
    
  
    
     /*任务休眠20 Tick*/
    
     LOS_TaskDelay(20);
    
  
    
     dprintf("Example_SemTask2 post sem g_usSemID .\n");
    
     /*释放信号量*/
    
     LOS_SemPost(g_usSemID);
    
  
    
     return;
    
 }
    
  
    
 UINT32 Example_Semphore(VOID)
    
 {
    
     UINT32 uwRet = LOS_OK;
    
     TSK_INIT_PARAM_S stTask1;
    
     TSK_INIT_PARAM_S stTask2;
    
  
    
    /*创建信号量*/
    
     LOS_SemCreate(0,&g_usSemID);
    
  
    
     /*锁任务调度*/
    
     LOS_TaskLock();
    
  
    
     /*创建任务1*/
    
     memset(&stTask1, 0, sizeof(TSK_INIT_PARAM_S));
    
     stTask1.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_SemTask1;
    
     stTask1.pcName       = "MutexTsk1";
    
     stTask1.uwStackSize  = LOSCFG_BASE_CORE_TSK_IDLE_STACK_SIZE;
    
     stTask1.usTaskPrio   = TASK_PRIO_TEST;
    
     uwRet = LOS_TaskCreate(&g_TestTaskID01, &stTask1);
    
     if(uwRet != LOS_OK)
    
     {
    
         dprintf("task1 create failed .\n");
    
         return LOS_NOK;
    
     }
    
  
    
     /*创建任务2*/
    
     memset(&stTask2, 0, sizeof(TSK_INIT_PARAM_S));
    
     stTask2.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_SemTask2;
    
     stTask2.pcName       = "MutexTsk2";
    
     stTask2.uwStackSize  = LOSCFG_BASE_CORE_TSK_IDLE_STACK_SIZE;
    
     stTask2.usTaskPrio   = (TASK_PRIO_TEST - 1);
    
     uwRet = LOS_TaskCreate(&g_TestTaskID02, &stTask2);
    
     if(uwRet != LOS_OK)
    
     {
    
         dprintf("task2 create failed .\n");
    
         
    
         /*删除任务1*/
    
         if(LOS_OK != LOS_TaskDelete(g_TestTaskID01))
    
         {
    
             dprintf("task1 delete failed .\n");
    
         }
    
         
    
         return LOS_NOK;
    
     }
    
  
    
     /*解锁任务调度*/
    
     LOS_TaskUnlock();
    
  
    
     uwRet = LOS_SemPost(g_usSemID);
    
  
    
     /*任务休眠40 Tick*/
    
     LOS_TaskDelay(40);
    
  
    
     /*删除信号量*/
    
     LOS_SemDelete(g_usSemID);
    
  
    
     /*删除任务1*/
    
     if(LOS_OK != LOS_TaskDelete(g_TestTaskID01))
    
     {
    
         dprintf("task1 delete failed .\n"); 
    
         uwRet = LOS_NOK;
    
     }
    
     /*删除任务2*/
    
     if(LOS_OK != LOS_TaskDelete(g_TestTaskID02))
    
     {
    
         dprintf("task2 delete failed .\n");
    
         uwRet = LOS_NOK;
    
     }
    
     
    
     return uwRet;
    
 }

refer:

这是一个关于Kernel操作系统的代码讲解页面链接。
该页面提供了一个详细的Kernel开发指南。
欢迎访问该教程以获取更多关于Kernel开发的知识。
有关此教程的更多信息,请参考官方文档。

全部评论 (0)

还没有任何评论哟~