一、信号量的基本概念
类似于在裸机编程中定义一个标志位,标志某个事件发生了。多任务系统中,各任务之间需要同步或互斥实现临界资源的保护,使用信号量来提供这方面的支持。
信号量是一个非负整数,所有试图获取它的任务都将进入阻塞态,通常一个信号量的计数值对应有效的资源数,表示剩下的可被占用的互斥资源数。其值含义如下:
(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_BinarySemCreate
Description : 创建一个二值信号量,
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)不允许可用信号量个数大于信号量最大容量,二值最大容量为1
if (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)创建成功返回OK
return LOS_OK;
ErrHandler:
OS_RETURN_ERROR_P2(uwErrLine, uwErrNo);
}
5.4 计数信号量创建函数LOS_SemCreate
和二值信号量一样都是调用osSemCreate函数创建,只是最大容量不一样
/*****************************************************************************
Function : LOS_SemCreate
Description :创建一个计数信号量
Input : uwCount--------- 初始化可用信号量个数
Output : puwSemHandle-----信号量ID
Return : 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();
#endif
goto 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();
#endif
goto 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_Handle
LOS_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*******************/