文章目录
一.前言
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分把,感谢各位大佬支持!