UCOSII-信号量与信号量集

一.前言

1.任务间的同步

应用程序中的各个任务,必须通过彼此之间的有效合作,才能完成一项大规模的工作。因此在多任务合作工作的过程中,操作系统应该解决两个问题:一是各任务之间应该具有一种互斥关系,即对于某个共享资源,如果一个任务正在使用,则其他任务只能等待,直到请求资源被释放后才能使用。二是相关的任务的执行上要有先后次序,一个任务要等其他伙伴发来通知,或建立某个条件后才能继续执行。任务间的这种制约性的合作机制叫做任务间的同步。

2.事件

UCOSII系统中使用到的信号量、消息邮箱、消息队列来实现任务之间通信。统一称为事件。
两个任务通过事件通信的示意图:
在这里插入图片描述

二.信号量

1.信号与信号量介绍

使用信号量的最初目的是为了给共享资源设立一个标志。
信号量通常分为两种:信号信号量
信号只能取值为0或1,用于互斥任务之间的通信,用于那些只能一个任务使用的资源。信号量值大于1,可用于那些资源可以同时被几个任务所使用。调用释放信号量函数post(),信号量值加1,调用请求信号量函数pend(),信号量值减1。同时可以设置请求信号量的等待时限。
在这里插入图片描述

2.信号量常用函数

OSSemCreate();             //1.建立一个信号量*****
OSSemDel();                //2.删除一个信号量
OSSemPend();               //3.等待一个信号量*****
OSSemPendAbrot();          //4.取消等待
OSSemPost();               //5.释放或发出一个信号量*****
OSSemSet();                //6.强制设置一个信号量

其中主要用到的函数是创建,请求,发送。

3.信号量使用流程(互斥信号量和信号量两种)

定义信号量:

OS_EVENT * sem_mutex;		       //定义互斥型信号量指针(只能取值0或1)
OS_EVENT * sem_ordinary;		   //定义蜂鸣器信号量指针(取值大于1)

创建信号量:

sem_mutex=OSMutexCreate(2,&err);    //创建互斥型信号量sem_mutex,优先级为2
sem_ordinary=OSSemCreate(0);		    //创建信号量,信号量初始计数器值为0		 	

请求信号量:

OSMutexPend(sem_mutex,0,&err);  //请求信号量函数,等待时间为无限长
OSSemPend(sem_ordinary,0,&err); //请求信号量函数,等待时间为无限长

释放信号量:

OSMutexPost(sem_mutex);        //发送互斥型信号量
OSSemPost(sem_ordinary);       //发送信号量函数

4.互斥型信号量使用

u8 share_resource[]="share_resource";  //定义共享资源
//任务一的任务函数
void task1_task(void *p_arg)
{
   while(1)
   {
     OSMutexPend(sem_mutex,0,&err);  //请求信号量函数,等待时间为无限长
     printf("task1=%s\n",share_resource);
     /*******************任务1执行的代码**********************/
     OSMutexPost(sem_mutex);        //发送互斥型信号量
   }
}
//任务二的任务函数
void task2_task(void *p_arg)
{
   while(1)
   {
     OSMutexPend(sem_mutex,0,&err);  //请求信号量函数,等待时间为无限长
     printf("task2=%s\n",share_resource);
     /*******************任务2执行的代码**********************/
     OSMutexPost(sem_mutex);        //发送互斥型信号量
   }
}

可以看出,任务1和任务2共享了资源share_resource[],因此未来保障共享资源同一时刻只能被一个任务使用,因此要采用互斥型信号量做通信,每个信号开始前先请求信号量,(信号量变为0,另一个任务请求不到),任务执行结束后释放信号量(信号量值为1)。

5.使用一般信号量做任务同步

//任务一的任务函数
void task1_task(void *p_arg)
{
   u8 key;
   u8 err;
   while(1)
   {
     key=KEY_Scan(0);                //按键扫描
     if(key=WKUP_PRES)
     {
        OSSemPost(sem_ordinary);                               //发送信号量函数
       LCD_ShowxNum(192,50,sem_ordinary->OSEventCnt,3,16,0X80);//显示信号量的值
     }
     delay_ms(10);                  //延时10ms
   }
}
//任务二的任务函数
void task2_task(void *p_arg)
{
   u8 err;
   while(1)
   {
       OSSemPend(sem_ordinary,0,&err); //请求信号量函数,等待时间为无限长
       LCD_ShowxNum(192,50,sem_ordinary->OSEventCnt,3,16,0X80);//显示信号量的值
      LED1=!LED1;                      //LED翻转
      delay_ms(1000);                  //延时1s
   }
}

由上面可以看出,通过信号量sem_ordinary实现了任务1与任务2之间的同步,按键按下后任务1发送信号量,信号量计数加1,任务二每隔1S请求一次信号量,信号量值减1,减至0后无限等待

三.信号量集(事件标志组)

1.信号量集概念

有时候一个任务需要与多个事件同步,这个时候就需要使用事件标志组。事件标志组与事件之间有两种同步机制:“或”同步和“与”同步。
在UCOSII中事件标志组为OS_FLAG_GRP,如果需要使用事件标志组的时候需要将宏OS_CFG_FLAG_EN置1。
在这里插入图片描述

2.信号量集的结构组成

UCOSII把信号量集的功能分为两部分:标志组(存放了信号量集的所有信号)和等待任务链表(链表中的每个节点对应一个叫做OS_FLAG_NODE的结构,实质上就是等待任务控制块)。也就是说,UCOSII信号量集由一个标志组多个等待任务控制块组成。

标志组OSFlagFlags实际上就是一个位图,其长度可以在OS_CFG.H中定制,系统默认16位。该位图每一位对应一个信号量,位图的作用是接收和保存其他任务所发送来的信号量值,所以可以看做是输入信号暂存器
在这里插入图片描述
信号量集需要一个控制块,这个控制块叫做标志组OS_FLAG_GRP,OSFlagFlags是标志组的成员。标志组是一个结构体,具体如下:

typedef struct struct
{  
    INT8U         OSFlagType;      //标志是否为信号量集的标志
    void         *OSFlagWaitList;  //指向等待任务链表的指针
    OS_FLAGS      OSFlagFlags;     //输入信号量值列表
} OS_FLAG_GRP;

其中:
1.OSFlagFlags为核心成员;
2.OSFlagType为信号量集标识,其值固定为OS_EVENT_TYPE_FLAG;
3.OSFlagWaitList指针有两种用途,主要是指向一个链表,该链表中存放了给信号量集的全部等待任务。
OS_FLAG_GRP如下图:
在这里插入图片描述

3.等待任务

等待任务就是那些以及向信号量集发出了请求操作的任务。
信号量集等待任务的操作比较复杂,等待任务必须完成以下两个操作:
1.在多个信号量的输入中挑选等待任务感兴趣的输入(过滤)。
2.把挑选出来的输入按照等待任务所希望的逻辑来运算,以得出输出(与或)。
这里定义了一个OSFlagNodeWaitType变量来指定对筛选出来的信号的逻辑运算。可选值和意义见下表:
在这里插入图片描述
OSFlagFlags,OSFlagNodeFlags,OSFlagNodeWaitType三者之间的关系如下图所示:
在这里插入图片描述

4.等待任务链表

信号量集用双向链表来组织等待任务,每一个等待任务都是该链表中的一个节点(Node)。标志组OS_FLAG_GRP的成员OSFlagWaitList就指向了信号量集的这个等待任务链表。
等待任务链表与标志组的整个信号量集示意图如下所示:
在这里插入图片描述

5.信号量集常用函数

OSFlagCreate();         //创建信号量集*****
OSFlagDel();            //删除信号量集
OSFlagPend();           //请求信号量集*****
OSFlagPendAbort();      //取消等待信号量集
OSFlagPendGetFlagsRdy();//获取使任务就绪的事件标志
OSFlagPost();           //发送信号量集*****

6.信号量集使用步骤*****

1.定义信号量集

OS_FLAG_GRP * flags;	     //定义信号量集flags

2.创建信号量集

flags=OSFlagCreate(0,&err); //创建信号量集,初始值为0		  

3.请求信号量集

//在任务1中进行信号量集的请求,并根据请求到的情况进行操作
void uart1_task(void *pdata)
{
	u8 err;
	u16 flag_receive=0;
	while(1)
  {
        flag_receive=OSFlagPend(flags,0X0003,OS_FLAG_WAIT_SET_ALL+OS_FLAG_CONSUME,0,&err);//请求信号量集第0位,第1位,两位全部接收到1才可跳过。OS_FLAG_CONSUME表示请求到信号之后清0处理
		if(flag_receive==1)printf("KEY0 DOWN  \n"); //只接收到了第0位
		if(flag_receive==2)printf("KEY1 DOWN  \n"); //只接收到了第1位
		if(flag_receive==3)printf("KEY2 DOWN  \n"); //只接收到了第0位,第1位
		if(flag_receive==6)printf("KEY_UP DOWN  \n");//只接收到了第1位,第2位,但是此处第2位被屏蔽掉了,所以永远不会执行
	    delay_ms(50);
		//OSFlagPost(flags,0X001F,OS_FLAG_CLR,&err);  //全部信号量清零,如果不清零则flags的值一直存在,也就是全部post0。也可以在pend函数中加上消耗OS_FLAG_CONSUME,即
		//flag_receive=OSFlagPend(flags,0X0003,OS_FLAG_WAIT_SET_ALL+OS_FLAG_CONSUME,0,&err);//请求信号量集第0位,第1位,两位全部接收到1才可跳过
	};
}

4.发送信号量集

//在key_task任务中根据不同按键值发送不同的信号位
void key_task(void *pdata)
{
	u8 key=0;
	u8 err=0;
	while(1)
  {
    key=KEY_Scan(0);
		switch(key)
		{
			case 1: 
				OSFlagPost(flags,1,OS_FLAG_SET,&err);//将当前的KEY值作为信号量集发送出去,然后信号量集任务中就会收到(发送第0位)
		    break;
			case 2: 
				OSFlagPost(flags,2,OS_FLAG_SET,&err);//将当前的KEY值作为信号量集发送出去,然后信号量集任务中就会收到(发送第1位)
		    break;
			case 3: 
				OSFlagPost(flags,3,OS_FLAG_SET,&err);//将当前的KEY值作为信号量集发送出去,然后信号量集任务中就会收到(发送第0,1位)
		    break;
			case 4: 
				OSFlagPost(flags,8,OS_FLAG_SET,&err);//将当前的KEY值作为信号量集发送出去,然后信号量集任务中就会收到(发送第3位)
		    break;
			default:break;							
		}
		delay_ms(10);
	};
}

最终的实验现象:按下KEY2,串口打印KEY2 DOWN,按下KEY0和KEY1,串口打印KEY2 DOWN。
5.查询信号量集状态(备用)

#if OS_FLAG_QUERY_EN > 0u
OS_FLAGS  OSFlagQuery (OS_FLAG_GRP  *pgrp,   //待查询的信号量集的指针
                       INT8U        *perr    //错误信息
                      )
{
   ....................................................
}

6.删除信号量集

OS_FLAG_GRP *OSFlagDel(
                          OS_FLAG_GRP *pgrp,//待删除的信号量集指针
                          INT8U opt,INT8U *err;//错误信息
                      )

四.总结

1.信号量表明一个共享资源被使用情况的标志,该标志实质上是一个计数器,如果计数器的初值大于1,则叫做信号量,如果计数器的值只能为1或0,则叫做信号
2.能防止出现优先级反转的信号叫做互斥型信号量。
3.信号量集实现了多个信号量的组合功能,它是一个多输入多输出系统,使一个任务可以与多个任务进行同步。
4.信号量集的多个信号量输入值由标志组来存放,等待任务控制块对标志组中的输入信号进行过滤并实施逻辑运算,其结果就是等待任务所请求的信号量值。
5.每个信号量集都有一个等待任务链表,链表的每一个节点都通过任务控制块关联着一个任务。

五. 感谢支持

    完结撒花!希望看到这里的小伙伴能点个关注,我后续会持续更新,也欢迎大家广泛交流。
    码字实属不易,如果本文对你有10分帮助,就赏个10分把,感谢各位大佬支持!

在这里插入图片描述

  • 3
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tutu-hu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值