打印
[应用相关]

UCOSII-信号量与信号量集

[复制链接]
1263|17
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
wiba|  楼主 | 2021-7-7 13:52 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
一.前言
1.任务间的同步
应用程序中的各个任务,必须通过彼此之间的有效合作,才能完成一项大规模的工作。因此在多任务合作工作的过程中,操作系统应该解决两个问题:一是各任务之间应该具有一种互斥关系,即对于某个共享资源,如果一个任务正在使用,则其他任务只能等待,直到请求资源被释放后才能使用。二是相关的任务的执行上要有先后次序,一个任务要等其他伙伴发来通知,或建立某个条件后才能继续执行。任务间的这种制约性的合作机制叫做任务间的同步。

2.事件
UCOSII系统中使用到的信号量、消息邮箱、消息队列来实现任务之间通信。统一称为事件。
两个任务通过事件通信的示意图:



使用特权

评论回复
沙发
wiba|  楼主 | 2021-7-7 13:54 | 只看该作者
二.信号量
1.信号与信号量介绍
使用信号量的最初目的是为了给共享资源设立一个标志。
信号量通常分为两种:信号和信号量。
信号只能取值为0或1,用于互斥任务之间的通信,用于那些只能一个任务使用的资源。信号量值大于1,可用于那些资源可以同时被几个任务所使用。调用释放信号量函数post(),信号量值加1,调用请求信号量函数pend(),信号量值减1。同时可以设置请求信号量的等待时限。



3180360e5417c8e894.png (278.69 KB )

3180360e5417c8e894.png

使用特权

评论回复
板凳
wiba|  楼主 | 2021-7-7 13:55 | 只看该作者
2.信号量常用函数
OSSemCreate();             //1.建立一个信号量*****
OSSemDel();                //2.删除一个信号量
OSSemPend();               //3.等待一个信号量*****
OSSemPendAbrot();          //4.取消等待
OSSemPost();               //5.释放或发出一个信号量*****
OSSemSet();                //6.强制设置一个信号量


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


使用特权

评论回复
地板
wiba|  楼主 | 2021-7-7 13:58 | 只看该作者
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);       //发送信号量函数


使用特权

评论回复
5
wiba|  楼主 | 2021-7-7 14:00 | 只看该作者
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)。


使用特权

评论回复
6
wiba|  楼主 | 2021-7-7 14:01 | 只看该作者
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后无限等待。


使用特权

评论回复
7
wiba|  楼主 | 2021-7-7 14:03 | 只看该作者
三.信号量集(事件标志组)
1.信号量集概念
有时候一个任务需要与多个事件同步,这个时候就需要使用事件标志组。事件标志组与事件之间有两种同步机制:“或”同步和“与”同步。
在UCOSII中事件标志组为OS_FLAG_GRP,如果需要使用事件标志组的时候需要将宏OS_CFG_FLAG_EN置1。



使用特权

评论回复
8
wiba|  楼主 | 2021-7-7 14:04 | 只看该作者
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如下图:



使用特权

评论回复
9
wiba|  楼主 | 2021-7-7 14:04 | 只看该作者
3.等待任务
等待任务就是那些以及向信号量集发出了请求操作的任务。
信号量集等待任务的操作比较复杂,等待任务必须完成以下两个操作:
1.在多个信号量的输入中挑选等待任务感兴趣的输入(过滤)。
2.把挑选出来的输入按照等待任务所希望的逻辑来运算,以得出输出(与或)。
这里定义了一个OSFlagNodeWaitType变量来指定对筛选出来的信号的逻辑运算。可选值和意义见下表:

OSFlagFlags,OSFlagNodeFlags,OSFlagNodeWaitType三者之间的关系如下图所示:



使用特权

评论回复
10
wiba|  楼主 | 2021-7-7 14:06 | 只看该作者
4.等待任务链表

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


使用特权

评论回复
11
wiba|  楼主 | 2021-7-7 14:07 | 只看该作者
5.信号量集常用函数
OSFlagCreate();         //创建信号量集*****
OSFlagDel();            //删除信号量集
OSFlagPend();           //请求信号量集*****
OSFlagPendAbort();      //取消等待信号量集
OSFlagPendGetFlagsRdy();//获取使任务就绪的事件标志
OSFlagPost();           //发送信号量集*****


使用特权

评论回复
12
wiba|  楼主 | 2021-7-7 14:08 | 只看该作者
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;//错误信息
                      )


使用特权

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


使用特权

评论回复
14
木木guainv| | 2021-8-6 12:38 | 只看该作者
信号量有什么种类吗

使用特权

评论回复
15
xiaoqizi| | 2021-8-6 12:42 | 只看该作者
他的功用是什么呢

使用特权

评论回复
16
wowu| | 2021-8-6 12:44 | 只看该作者
同步性能如何保证呢

使用特权

评论回复
17
wakayi| | 2021-8-6 12:46 | 只看该作者
消息的通知是什么形式的呢

使用特权

评论回复
18
renzheshengui| | 2021-8-6 12:47 | 只看该作者
不同信号量的组成是一样的吗

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

78

主题

3313

帖子

3

粉丝