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_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*******************/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值