ucos-ii嵌入式操作系统(四)---任务间同步之信号量

先给自己打个广告,本人的微信公众号正式上线了,搜索:张笑生的地盘,主要关注嵌入式软件开发,股票基金定投,足球等等,希望大家多多关注,有问题可以直接留言给我,一定尽心尽力回答大家的问题,二维码如下:
在这里插入图片描述

一 相关概念

任务:嵌入式系统是为了实现某个功能(任务)而专门设计的一套系统,但是这个功能很复杂,我们需要将这些功能划分为一个个小的任务来实现每个小功能,这些小功能组合成整个系统的功能。我们这里的任务主要指实现这些小功能的任务。比如对于我们的网络视频监控系统,我们可以大致分为监控和网络通信两个部分,监控是实现实时检测的目的,网络通信实现将检测到的数据信息传递到服务器上。因此,我们可以划分为这样两个任务:任务1,实现监控任务;任务2,实现数据传递任务(当然这样的划分还是比较笼统,还是很很多细节没考虑到)。
任务控制块:TCB(task control block)
同步/通信:为实现各个任务之间的合作,在各个任务之间必须建立一种制约关系,这种制约性的合作运行机制叫做任务间的同步。这种制约关系需要解决2个问题:一是各任务之间需要有一种互斥关系,即对于某个共享资源,如果一个任务正在使用,另一个任务不能使用,必须等待;二是任务的执行存在先后顺序关系,一个任务需要等待另外一个任务的通知才能运行,或是建立了某个条件才能运行。
事件:实现任务间同步(通信)的方式,我们称之为事件, 实现一个任务向另一个任务发出信号;一个任务还可以等待另外一个任务或中断服务子程序给它发出信号,这里需要注意到是,只有任务可以等待事件的发生,中断服务子程序是不能这样做的。UCOS中的事件有信号量、邮箱(消息邮箱)和消息队列
事件控制块:ECB(event control block)
发送事件:发信方将事件发送到事件上
请求事件:接收方读取事件操作
在这里插入图片描述

二 分析

  1. 任务间为什么需要同步
    随着我们的系统越来越复杂,实现的功能越来越多,不可避免会出现不同任务需要访问同一个资源,这个资源我们称为共享资源。但是在访问过程中,我们需要保证每个任务需要的资源是开始时它本身需要的,考虑如下的一种情况,该函数是一个会被两个任务同时调用
static u8 g_byVal;

int func(int a)
{
    a       = a + 1;
    g_byVal = g_byVal + 1;

    return (g_byVal * a);
}

分析,显然对于全局变量g_byVal,当某个任务执行完 g_byVal = g_byVal + 1时,由于系统调度,发生了一次任务调度,别的任务开始执行,也需要调用这个函数,这样就再次执行g_byVal = g_byVal + 1,因此全局变量的值就会被改变,当高优先级任务运行结束之后,再次返回原来的任务时候,就会导致g_byVal不是它开始的值,因此计算的结果就不是我们所期望的值。当然这只是一个例子,在实际项目代码中不一定是这么使用,主要是为了便于我们理解
2. 如何管理事件
作为功能完善的事件,应该有队这些等待任务具有两方面的管理功能,一是要对等待事件的所有任务进行记录并排序;二是应该允许等待有一个等待时限,即当任务认为等不及时可以退出对事件的请求。
ucos采用了和任务就绪表类似的位图方式来记录等待事件任务,即定义了一个u8类型的数组OSEventTbl[]作为等待事件任务的记录表,等待任务表以任务优先级为顺序为每个任务分配一个二进制位,并用该位为"1"来表示这一位对应的任务为事件的等待任务,否则不是等待任务。
同样,为了加快对该表的访问速度,也定义了一个u8类型的变量OSEventGrp来表示等待任务表中的任务组,等待任务表OSEventTbl[]与变量OSEventGrp的示意图如下:
在这里插入图片描述
ucos中使用事件控制块数据结构来描述事件,数据结构如下:

typedef struct os_event {
    INT8U    OSEventType;                   /* 事件类型  */
    void    *OSEventPtr;                    /* 消息或者消息队列指针   */
    INT16U   OSEventCnt;                    /* 信号量计数器 */
    OS_PRIO  OSEventGrp;                    /* 等待事件任务组 */
    OS_PRIO  OSEventTbl[OS_EVENT_TBL_SIZE]; /* 任务等待表*/

#if OS_EVENT_NAME_EN > 0u
    INT8U   *OSEventName;
#endif
} OS_EVENT;

该结构体示意图如下:
在这里插入图片描述

三 任务间通信同步—信号量

A. 信号量的基本概念
信号量设置的最初目的:是为了给共享资源设立一个标志,该标志表示该共享资源被占用情况。当一个任务在访问共享区域之前,需要先查询这个信号量,以了解共享资源被占用的情况,来决定自己是否访问这段共享区域
考虑我们日常生活中的一个示例:

  1. 母子二人围在一起吃水果,母亲削苹果网盘里放,孩子则从盘里去苹果吃,盘里最多放N个苹果,用信号量方式表示应该如下:
假设S=N,S1=0
母
while(1)
{
    削苹果
    wait(s)
    放苹果
    signal(s1)
}
孩子
while(1)
{
    wait(s1)
    吃苹果
    signal(s)
}

  • 公用电话亭:假设有一个公用电话亭,外面有一个红绿灯,红灯亮表示里面有人正在用,外面的人必须等待才能用;绿灯亮,表示里面没有人在用,外面的人可以使用
    B. 信号量的使用
    当事件控制块描述一个信号量时,事件控制成员OSEventType的值为OS_EVENT_TYPE_SEM,信号量事件使用控制块中的OSEventCnt作为计数器,用数组OSEventTbl[]作为等待任务表。
    示例:计数器当前值为3,且有4个等待任务,等待信号量任务优先级分别是4,7,10,19
    在这里插入图片描述
    使用信号量的步骤:
  • 创建信号量
    当OSEventType= OS_EVENT_TYPE_SEM时,即创建了一个信号量,使用API:OSSemCreate()来实现,如:
test = OSSemCreate(10);  //创建一个计数器为10的信号量;

创建完成之后,事件列表数组如下:
在这里插入图片描述
源码如下:

OS_EVENT  *OSSemCreate (INT16U cnt)
{
    OS_EVENT  *pevent;
#if OS_CRITICAL_METHOD == 3u                               /* Allocate storage for CPU status register */
    OS_CPU_SR  cpu_sr = 0u;
#endif



#ifdef OS_SAFETY_CRITICAL_IEC61508
    if (OSSafetyCriticalStartFlag == OS_TRUE) {
        OS_SAFETY_CRITICAL_EXCEPTION();
        return ((OS_EVENT *)0);
    }
#endif

    if (OSIntNesting > 0u) {                               /* See if called from ISR ...               */
        return ((OS_EVENT *)0);                            /* ... can't CREATE from an ISR             */
    }
    OS_ENTER_CRITICAL();
    pevent = OSEventFreeList;                              /* Get next free event control block        */
    if (OSEventFreeList != (OS_EVENT *)0) {                /* See if pool of free ECB pool was empty   */
        OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr;
    }
    OS_EXIT_CRITICAL();
    if (pevent != (OS_EVENT *)0) {                         /* Get an event control block               */
        pevent->OSEventType    = OS_EVENT_TYPE_SEM;
        pevent->OSEventCnt     = cnt;                      /* Set semaphore value                      */
        pevent->OSEventPtr     = (void *)0;                /* Unlink from ECB free list                */
#if OS_EVENT_NAME_EN > 0u
        pevent->OSEventName    = (INT8U *)(void *)"?";
#endif
        OS_EventWaitListInit(pevent);                      /* Initialize to 'nobody waiting' on sem.   */
    }
    return (pevent);
}

根据宏开关,简写代码如下:

OS_EVENT  *OSSemCreate (INT16U cnt)
{
    OS_EVENT  *pevent;

    if (OSIntNesting > 0u) {                               /* 中断服务程序中,不能创建信号量 */
        return ((OS_EVENT *)0);
    }
    OS_ENTER_CRITICAL();
    pevent = OSEventFreeList;                              /* 占领一个空事件列表 */
    if (OSEventFreeList != (OS_EVENT *)0) {                /*  让空事件列表指向下一个事件链表 */
        OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr;
    }
    OS_EXIT_CRITICAL();
    if (pevent != (OS_EVENT *)0) {                         /* 非控事件表才能创建新事件 */
        pevent->OSEventType    = OS_EVENT_TYPE_SEM;
        pevent->OSEventCnt     = cnt;                      /* 设置信号量计数器 */
        pevent->OSEventPtr     = (void *)0;                /* 初始化事件指针 */
        OS_EventWaitListInit(pevent);                      /* 初始化信号量等待事件列表 */
    }
    return (pevent);
}
  • 请求信号量
    用OSSemPend()来实现
OSSemPend(test,0,&err1); //0表示请求等待时间无限长

当任务需要访问一个共享资源时,先要请求管理该资源的信号量,这样就可以根据信号量是否有效来决定该任务是否可以继续运行。
比如对于任务A和任务B,都需要调用同一个函数(共享资源C),那么当每个任务分别可以调用这个函数之前,就需要先请求信号量,确认可否调用

  • 发送信号量
    用OSSemPost()来实现
OSSemPost(test);
  • 信号量使用实例
    有一个func函数、一个My_task任务,一个Your_Task任务,他们都需要调用func函数,但是不能同时调用
OS_EVENT *test;
INT32U err1 = 0;
int main(void)
{
    brd_init();
    OSInit();
    PRINTF("Hello,World!\n\r");

    test = OSSemCreate(1);     //创建信号量
    OSTaskCreate(My_Task,NULL,
                 &my_task[TASK1_STK_SIZE-1], MY_TASK_PRIO);
    OSTaskCreate(Your_Task,NULL,
                 &you_task[TASK1_STK_SIZE-1], YOUR_TASK_PRIO);

    OSStart();
    return 0;
}

/* 函数中增加了信号量访问操作,显然这个函数不能被两个不同的任务同时调用 */
void Func(char *str)
{
    OSSemPend(test,0,&err1);
    PRINTF("This is %s working-2018-07-01!\n\r", str);
    OSSemPost(test);
}

/*
 * My_Task和Your_Task用于调试信号量
 */
static void My_Task(void)
{
    char *my_s = "my task";
    while(1)
    {
        Func(my_s);
        OSTimeDlyHMSM(0,0,1,0);
    }
}

static void Your_Task(void)
{
    char *you_s = "you task";
    while(1)
    {
        Func(you_s);
        OSTimeDlyHMSM(0,0,2,0);
    }
}
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值