> 文章列表 > LiteOS信号量

LiteOS信号量

LiteOS信号量

一、信号量的基本概念

类似于在裸机编程中定义一个标志位,标志某个事件发生了。多任务系统中,各任务之间需要同步或互斥实现临界资源的保护,使用信号量来提供这方面的支持。

信号量是一个非负整数,所有试图获取它的任务都将进入阻塞态,通常一个信号量的计数值对应有效的资源数,表示剩下的可被占用的互斥资源数。其值含义如下:

(1)0:表示没有积累下来的post信号量操作,且此时可能有任务阻塞在此

(2)正值:表示有一个或多个post信号量操作

信号量又分为二值信号量(0和1)和计数信号量。

1.1 二值信号量

二值信号量既可以用于实现同步功能,又可以用于对临界资源的访问保护。

(1)用作互斥时,信号量创建后可用个数为1,在需要使用临界资源时,先取信号量,使其变空,这样其他任务就无法取到信号量进而阻塞,从而保证临界资源安全,使用完后必须释放信号量。一般不降信号量用作互斥,因为有专门的互斥访问机制—互斥锁

(2)用作同步时,信号量创建后置为空,任务A获取信号量后进入阻塞,任务B在满足某种条件后释放信号量,于是A获得信号量从而恢复为就绪态,达到两个任务间的同步。信号量允许在中断服务程序中释放,达到任务和中断间的同步。

1.2 计数信号量

计数值可以表示未处理的事件数、可用资源数目等,但在使用资源前必须先获取到信号量,为0时表示无可用资源,使用完资源后归还信号量(信号量+1)。允许多个任务进行操作,但限制了任务的数量(计数值大小)

二、二值信号量的运行机制

系统初始化时会进行信号量初始化,为信号量的宏定义申请内存,将所有信号量初始化为未使用,并将信号量加入未使用信号量链表(g_stUnusedSemList)

2.1 信号量创建

从未使用信号量链表获取一个信号量资源,最大计数值为1(OS_SEM_BINARY_MAX_COUNT)

2.2 二值信号量获取

若当前信号量有效,返回LOS_OK,否则根据指定阻塞时间等待其他任务/中断释放信号量,并将阻塞的任务挂到该信号量的阻塞等待列表中,此时信号量无效。假如信号量被释放了,被阻塞的任务恢复就绪态。

三、二值信号量的应用场景

用于任务和任务间的同步:传感器采集完数据(每1s),OLED显示数据(每100ms),采集完后用信号量同步一次,OLED刷新显示数据

用于任务和中断间的同步:串口接收中,定义一个任务负责接收处理数据,任务平时进入阻塞态不浪费系统资源,接收到数据时释放一个二值信号量,任务恢复就绪态进行数据处理

四、计数信号量的运行机制

允许多个任务访问,但会限制数目,访问数达到最大时,后续访问的任务被阻塞,直到有任务释放了信号量。如信号量为3,依次有四个任务访问,前三个占住了位置,第四个被阻塞,直到有一个任务释放信号量。

五、信号量的使用

5.1 信号量控制块

与任务控制块类似,每个信号量都有对应的信号量控制块,块中包含了信号量的所有信息。

typedef struct
{UINT16          usSemStat;             /<信号量状态*/UINT16          usSemCount;            /< 可用个数*/UINT16          usMaxSemCount;         /< 最大容量*/UINT16          usSemID;               /< 信号量ID*/LOS_DL_LIST     stSemList;             /< 信号量阻塞列表*/
} SEM_CB_S;

5.2 常见信号量错误代码

在los_sem.h定义了返回值的错误代码,详情查看即可

5.3 二值信号量创建函数LOS_BinarySemCreate

/*Function     : LOS_BinarySemCreateDescription  : 创建一个二值信号量,Input        : uwCount--------- 信号量可用个数,Output       : puwSemHandle-----信号量ID,Return       : 返回LOS_OK表示创建成功 ,返回其他错误代码表示失败*/
LITE_OS_SEC_TEXT_INIT UINT32 LOS_BinarySemCreate (UINT16 usCount, UINT32 *puwSemHandle)
{return osSemCreate(usCount, OS_SEM_BINARY_MAX_COUNT, puwSemHandle);
}
LITE_OS_SEC_TEXT_INIT UINT32 osSemCreate (UINT16 usCount, UINT16 usMaxCount, UINT32 *puwSemHandle)
{UINT32      uwIntSave;SEM_CB_S    *pstSemCreated;LOS_DL_LIST *pstUnusedSem;UINT32      uwErrNo;UINT32      uwErrLine;//(1)如果用户定义的信号量ID为空,返回错误代码if (NULL == puwSemHandle){return LOS_ERRNO_SEM_PTR_NULL;}
//(2)不允许可用信号量个数大于信号量最大容量,二值最大容量为1if (usCount > usMaxCount){OS_GOTO_ERR_HANDLER(LOS_ERRNO_SEM_OVERFLOW);}uwIntSave = LOS_IntLock();
//(3)判断是否还有创建信号量的列表空间if (LOS_ListEmpty(&g_stUnusedSemList)){LOS_IntRestore(uwIntSave);OS_GOTO_ERR_HANDLER(LOS_ERRNO_SEM_ALL_BUSY);}
//(4)从未使用的列表中取出一个信号量控制块pstUnusedSem = LOS_DL_LIST_FIRST(&(g_stUnusedSemList));LOS_ListDelete(pstUnusedSem);pstSemCreated = (GET_SEM_LIST(pstUnusedSem)); /*lint !e413*/
//(5)初始化信号量个数为用户自定义个数pstSemCreated->usSemCount = usCount;
//(6)状态设置为已使用pstSemCreated->usSemStat = OS_SEM_USED;
//(7)初始化用户指定的usMaxCount配置信号量中可用信号量的最大容量pstSemCreated->usMaxSemCount = usMaxCount;
//(8)初始化信号量阻塞列表LOS_ListInit(&pstSemCreated->stSemList);
//(9)将ID通过指针返回给用户*puwSemHandle = (UINT32)pstSemCreated->usSemID;LOS_IntRestore(uwIntSave);
//(10)创建成功返回OKreturn LOS_OK;ErrHandler:OS_RETURN_ERROR_P2(uwErrLine, uwErrNo);
}

5.4 计数信号量创建函数LOS_SemCreate

和二值信号量一样都是调用osSemCreate函数创建,只是最大容量不一样

/*Function     : LOS_SemCreateDescription  :创建一个计数信号量Input        : uwCount--------- 初始化可用信号量个数Output       : puwSemHandle-----信号量IDReturn       : LOS_OK 创建成功,返回其他值创建失败*/
LITE_OS_SEC_TEXT_INIT UINT32 LOS_SemCreate (UINT16 usCount, UINT32 *puwSemHandle)
{return osSemCreate(usCount, OS_SEM_COUNTING_MAX_COUNT, puwSemHandle);
}

5.5 信号量删除函数LOS_SemDelete

根据ID删除信号量,删除之后信号量的所有信息都会被系统回收,且不能再次使用这个信号量。若信号量在使用中、被阻塞或未创建,无法删除。

LITE_OS_SEC_TEXT_INIT UINT32 LOS_SemDelete(UINT32 uwSemHandle)
{UINT32      uwIntSave;SEM_CB_S    *pstSemDeleted;UINT32      uwErrNo;UINT32      uwErrLine;
//(1)判断ID有效性if (uwSemHandle >= (UINT32)LOSCFG_BASE_IPC_SEM_LIMIT){OS_GOTO_ERR_HANDLER(LOS_ERRNO_SEM_INVALID);}
//(2)根据ID获取信号量控制块,屏蔽中断不影响后续操作pstSemDeleted = GET_SEM(uwSemHandle);uwIntSave = LOS_IntLock();
//(3)若信号量未使用,返回错误代码if (OS_SEM_UNUSED == pstSemDeleted->usSemStat){LOS_IntRestore(uwIntSave);OS_GOTO_ERR_HANDLER(LOS_ERRNO_SEM_INVALID);}
//(4)阻塞列表不为空,不允许删除if (!LOS_ListEmpty(&pstSemDeleted->stSemList)){LOS_IntRestore(uwIntSave);OS_GOTO_ERR_HANDLER(LOS_ERRNO_SEM_PENDED);}
//(5)将控制块添加到未使用的信号量列表中,归还系统LOS_ListAdd(&g_stUnusedSemList, &pstSemDeleted->stSemList);
//(6)将信号量变为未使用pstSemDeleted->usSemStat = OS_SEM_UNUSED;LOS_IntRestore(uwIntSave);return LOS_OK;
ErrHandler:OS_RETURN_ERROR_P2(uwErrLine, uwErrNo);
}

5.6 信号量释放函数LOS_SemPost

只有信号量有效时,任务才能获取信号量。假如初始化时指定信号量个数为1,被获取一次后变为无效状态,需要在外部释放有效的信号量,将信号量有效化,每调用一次该函数(LOS_SemPost)就释放一个信号量。释放时注意范围,不能超过可用信号量范围。

LITE_OS_SEC_TEXT UINT32 LOS_SemPost(UINT32 uwSemHandle)
{UINT32      uwIntSave;
//(1)根据ID获取控制块SEM_CB_S    *pstSemPosted = GET_SEM(uwSemHandle);LOS_TASK_CB *pstResumedTask;
//(2)判断ID有效性if (uwSemHandle >= LOSCFG_BASE_IPC_SEM_LIMIT){return LOS_ERRNO_SEM_INVALID;}uwIntSave = LOS_IntLock();
//(3)若未使用,返回错误if (OS_SEM_UNUSED == pstSemPosted->usSemStat){LOS_IntRestore(uwIntSave);OS_RETURN_ERROR(LOS_ERRNO_SEM_INVALID);}
//(4)若信号量达到最大可用个数,返回错误代码if (pstSemPosted->usMaxSemCount == pstSemPosted->usSemCount){(VOID)LOS_IntRestore(uwIntSave);OS_RETURN_ERROR(LOS_ERRNO_SEM_OVERFLOW);}if (!LOS_ListEmpty(&pstSemPosted->stSemList)){
//(5)若有任务获取不到信号量而阻塞,释放信号量时将任务从阻塞解除,并进行一次任务调度pstResumedTask = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&(pstSemPosted->stSemList))); /*lint !e413*/pstResumedTask->pTaskSem = NULL;
//(6)将阻塞的任务从阻塞态解除,插入就绪列表osTaskWake(pstResumedTask, OS_TASK_STATUS_PEND);(VOID)LOS_IntRestore(uwIntSave);
//(7)开启任务调度LOS_Schedule();}else{
//(8)若没有任务阻塞在信号量上,每调用一次该函数,可用信号量加一,直到与最大容量相等pstSemPosted->usSemCount++;(VOID)LOS_IntRestore(uwIntSave);}return LOS_OK;
}

5.7 信号量获取函数LOS_SemPend

与释放相对应的是获取信号量,信号量有效时才能获取。任务获取后,信号量可用个数减一,当为0时任务进入阻塞态。获取不到信号量的任务可以无阻塞模式(没可用的立即返回)、永久阻塞模式(直到有可用的)、指定时间阻塞模式。

LITE_OS_SEC_TEXT UINT32 LOS_SemPend(UINT32 uwSemHandle, UINT32 uwTimeout)
{UINT32      uwIntSave;SEM_CB_S    *pstSemPended;UINT32      uwRetErr;LOS_TASK_CB *pstRunTsk;
//(1)检查ID有效性if (uwSemHandle >= (UINT32)LOSCFG_BASE_IPC_SEM_LIMIT){OS_RETURN_ERROR(LOS_ERRNO_SEM_INVALID);}pstSemPended = GET_SEM(uwSemHandle);uwIntSave = LOS_IntLock();
//(2)根据ID获取控制块if (OS_SEM_UNUSED == pstSemPended->usSemStat){LOS_IntRestore(uwIntSave);OS_RETURN_ERROR(LOS_ERRNO_SEM_INVALID);}
//(3)如果信号量可用个数大于0,获取信号量,返回成功if (pstSemPended->usSemCount > 0){pstSemPended->usSemCount--;LOS_IntRestore(uwIntSave);return LOS_OK;}
//(4)如果没有可用信号量,根据指定时间进行阻塞,如果时间为0,进入函数,跳到第十步if (!uwTimeout){uwRetErr = LOS_ERRNO_SEM_UNAVAILABLE;goto errre_uniSemPend;}
//(5)在中断获取信号量,非法操作if (OS_INT_ACTIVE){uwRetErr = LOS_ERRNO_SEM_PEND_INTERR;PRINT_ERR("!!!LOS_ERRNO_SEM_PEND_INTERR!!!\\n");
#if (LOSCFG_PLATFORM_EXC == YES)osBackTrace();
#endifgoto errre_uniSemPend;}
//(6)调度器如果被闭锁,返回错误if (g_usLosTaskLock){uwRetErr = LOS_ERRNO_SEM_PEND_IN_LOCK;PRINT_ERR("!!!LOS_ERRNO_SEM_PEND_IN_LOCK!!!\\n");
#if (LOSCFG_PLATFORM_EXC == YES)osBackTrace();
#endifgoto errre_uniSemPend;}
//(7)无可用信号量且用户指定阻塞时间,系统获取任务控制块,将任务阻塞pstRunTsk = (LOS_TASK_CB *)g_stLosTask.pstRunTask;pstRunTsk->pTaskSem = (VOID *)pstSemPended;osTaskWait(&pstSemPended->stSemList, OS_TASK_STATUS_PEND, uwTimeout);(VOID)LOS_IntRestore(uwIntSave);
//(8)进行一次任务调度。若任务在阻塞中等到了信号量,将任务从阻塞态解除,加入就绪列表LOS_Schedule();
//(9)判断解除阻塞原因,如果是超时,返回错误代码if (pstRunTsk->usTaskStatus & OS_TASK_STATUS_TIMEOUT){uwIntSave = LOS_IntLock();pstRunTsk->usTaskStatus &= (~OS_TASK_STATUS_TIMEOUT);uwRetErr = LOS_ERRNO_SEM_TIMEOUT;goto errre_uniSemPend;}return LOS_OK;errre_uniSemPend:(VOID)LOS_IntRestore(uwIntSave);
//(10)根据不同情况返回错误代码 OS_RETURN_ERROR(uwRetErr);
}

六、二值信号量同步实验main文件

/* LiteOS 头文件 */
#include "los_sys.h"
#include "los_task.ph"
#include "los_queue.h"
#include "los_sem.h"
/* 板级外设头文件 */
#include "bsp_usart.h"
#include "bsp_led.h"
#include "bsp_key.h"
/* 定义任务句柄 */
UINT32 Read_Task_Handle;
UINT32 Write_Task_Handle;/*定义二值信号量的ID*/
UINT32 BinarySem_Handle;/*声明全局变量*/
uint8_t ucValue[2] = {0x00,0x00};/* 函数声明 */
static UINT32 AppTaskCreate(void);
static UINT32 Creat_Read_Task(void);
static UINT32 Creat_Write_Task(void);static void Read_Task(void);
static void Write_Task(void);
static void BSP_Init(void);int main(void)
{    UINT32 uwRet = LOS_OK;  //定义一个任务创建的返回值,默认为创建成功/* 板载相关初始化 */BSP_Init();printf("串口打印出-Sucessful-表明实验创建成功\\r\\n");/* LiteOS 内核初始化 */uwRet = LOS_KernelInit();if (uwRet != LOS_OK){printf("LiteOS 核心初始化失败!失败代码0x%X\\n",uwRet);return LOS_NOK;}uwRet = AppTaskCreate();if (uwRet != LOS_OK){printf("AppTaskCreate创建任务失败!失败代码0x%X\\n",uwRet);return LOS_NOK;}/* 开启LiteOS任务调度 */LOS_Start();//正常情况下不会执行到这里while(1);}static UINT32 AppTaskCreate(void)
{/* 定义一个返回类型变量,初始化为LOS_OK */UINT32 uwRet = LOS_OK;/*创建一个而知新好*/uwRet = LOS_BinarySemCreate(1,&BinarySem_Handle);if(uwRet!=LOS_OK){printf("Test_Queue队列创建失败!失败代码0x%X\\n",uwRet);return uwRet;}uwRet = Creat_Read_Task();if (uwRet != LOS_OK){printf("Receive_Task任务创建失败!失败代码0x%X\\n",uwRet);return uwRet;}uwRet = Creat_Write_Task();if (uwRet != LOS_OK){printf("Send_Task任务创建失败!失败代码0x%X\\n",uwRet);return uwRet;}return LOS_OK;
}static UINT32 Creat_Read_Task()
{//定义一个创建任务的返回类型,初始化为创建成功的返回值UINT32 uwRet = LOS_OK;            //定义一个用于创建任务的参数结构体TSK_INIT_PARAM_S task_init_param;    task_init_param.usTaskPrio = 5;    /* 任务优先级,数值越小,优先级越高 */task_init_param.pcName = "Read_Task";/* 任务名 */task_init_param.pfnTaskEntry = (TSK_ENTRY_FUNC)Read_Task;/* 任务函数入口 */task_init_param.uwStackSize = 1024;        /* 堆栈大小 */uwRet = LOS_TaskCreate(&Read_Task_Handle, &task_init_param);/* 创建任务 */return uwRet;
}static UINT32 Creat_Write_Task()
{// 定义一个创建任务的返回类型,初始化为创建成功的返回值UINT32 uwRet = LOS_OK;                TSK_INIT_PARAM_S task_init_param;task_init_param.usTaskPrio = 4;    /* 任务优先级,数值越小,优先级越高 */task_init_param.pcName = "Write_Task";    /* 任务名*/task_init_param.pfnTaskEntry = (TSK_ENTRY_FUNC)Write_Task;/* 任务函数入口 */task_init_param.uwStackSize = 1024;    /* 堆栈大小 */uwRet = LOS_TaskCreate(&Write_Task_Handle, &task_init_param);/* 创建任务 */return uwRet;
}static void Read_Task(void)
{/* 任务都是一个无限循环,不能返回 */while(1){LOS_SemPend(BinarySem_Handle, LOS_WAIT_FOREVER);//获取二值信号量(BinarySem_Handle,未获取到会一直等待if(ucValue[0] == ucValue[1]){printf("\\r Sucessful,ucValue[0]=%d\\r\\n",ucValue[0]);} else {printf("\\r\\nFail\\r\\n");}LOS_SemPost(BinarySem_Handle);//释放二值信号量BinarySem_Handle}
}static void Write_Task(void)
{UINT32 uwRet = LOS_OK;/* 任务都是一个无限循环,不能返回 */while(1){LOS_SemPend(BinarySem_Handle, LOS_WAIT_FOREVER);//获取二值信号量(BinarySem_Handle,未获取到会一直等待ucValue [0]++;LOS_TaskDelay(1000);ucValue[1]++;LOS_SemPost(BinarySem_Handle);//释放二值信号量BinarySem_HandleLOS_TaskYield();  //放弃剩余时间片,。进行一次任务切换}
}/ @ 函数名  : BSP_Init* @ 功能说明: 板级外设初始化,所有板子上的初始化均可放在这个函数里面* @ 参数    :   * @ 返回值  : 无/
static void BSP_Init(void)
{/ STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15* 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,* 都统一用这个优先级分组,千万不要再分组,切忌。*/NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );/* LED 初始化 */LED_GPIO_Config();/* 串口初始化    */USART_Config();/* KEY初始化 */Key_GPIO_Config();
}/END OF FILE*/