目录
一、信号量
信号量是一种解决多任务并发和同步的机制。
并发:就是多个任务同时访问一个共享资源,这样容易造成任务获取资源的错误。
同步:就是多个任务按顺序的占用CPU去执行。
互斥:一个共享资源,同一时刻,只允许一个任务进行访问。
多值信号量:有一个生产者和消费者问题(信号量初值设为0)。(今天老师傅说,多值信号量可以用来处理互斥问题,我搞不明白,有知道的还请评论区指教,感谢!!!)
二值信号量:一般用来处理互斥问题,即一个资源只能有一个任务访问。信号量初值设为1.
互斥信号量(互斥锁):是一种特殊的二值信号量,一般来解决同步问题。
信号量的使用:
1.信号量定义
全局变量:信号量与共享资源
OS_SEM mysem;
char mysource[20]={'a','a','a','\0'};
2.信号量创建
/* 创建 计数型 信号量 */
OSSemCreate(
(OS_SEM *)&mysem,
(CPU_CHAR *)"mysem",
(OS_SEM_CTR )1,
(OS_ERR *)&err
);
OSSemCreate(
(OS_SEM *)&mysem2,
(CPU_CHAR *)"mysem2",
(OS_SEM_CTR )0,
(OS_ERR *)&err
);
3.信号量的使用
void DispTask(void *p_arg)
{
OS_ERR oserr;
while(1)
{
//delay_ms(1000);
//memcpy(mysource, "DispTask", 20);
OSSemPend(&mysem, 0, OS_OPT_PEND_BLOCKING, 0, &oserr);
for(int i=0;i<20;i++){
mysource[i]='b';
}
delay_ms(1000);
OSSemPost(&mysem, OS_OPT_POST_1, &oserr);
// 同步运行, 类似 生产消费者关系
mysource2[cnt++]='b';
delay_ms(1000);
OSSemPost(&mysem2, OS_OPT_POST_1, &oserr);
}
}
信号量:控制任务间存取资源;任务间同步或任务与中断服务程序(ISR)之间的同步。
信号量分为 二进制信号量(0、1) 和 计数型信号量。
二进制信号量,信号量=1时,该资源可以被使用。否则进入等待,设置超时时间,超时后会进入就绪态。
共享资源中,只有任务可以使用信号量,中断服务程序不能使用。
1、创建信号量
void OSSemCreate (OS_SEM *p_sem, //信号量变量
CPU_CHAR *p_name, //名字
OS_SEM_CTR cnt, //信号量初始值,=1为二进制信号量,>1为计数型信号量
OS_ERR *p_err) //返回错误
2、请求信号量
OS_SEM_CTR OSSemPend (OS_SEM *p_sem, //请求某个信号量
OS_TICK timeout, //超时时间
OS_OPT opt, //OS_OPT_PEND_BLOCKING阻塞,OS_OPT_PEND_NON_BLOCKING非阻塞
CPU_TS *p_ts, //时间戳,记录接收到信号量的时刻
OS_ERR *p_err)
3、发送信号量
OS_SEM_CTR OSSemPost (OS_SEM *p_sem, //释放的信号量.信号量+1
OS_OPT opt, //
OS_ERR *p_err)
/*
opt:
OS_OPT_POST_1 : 给等待该信号量,优先级最高的任务发送
OS_OPT_POST_ALL : 给等待该信号量的全部任务发送
OS_OPT_POST_NO_SCHED :
*/
二、任务访问临界区
使用信号量 来进行多任务访问共享临界区、多任务同步。
1、共享临界区的独立访问(使用mysem信号量,初始值为1):
(1)DispTask任务 和 RaedAdTask任务 共同访问共享资源cnt。
(2)DispTask任务是把共享资源cnt加到30或者35。
(3)RaedAdTask把共享资源cnt设置成5,使用信号量mysem来让两个任务都可以独立去访问。
(现象:在没有加信号量时,cnt应该加不到30或者35就被RaedAdTask任务打断 设置为5)
2、多任务同步(使用mysem2信号量,初始值为0):
(1)DispTask任务每次把共享资源cnt2+1,等于11,然后就释放一个信号量。
(2)正在等待的任务RaedAdTask的拿到信号量后,就cnt2-1,等于10。
(3)他们一一交错同步去进行。
(cnt2在加信号量的情况下,不会出现cnt2=9的现象,但是可能会出现cnt2=12的现象,这个要看延时的时间)
部分代码如下:
//全局定义如下:
uint16_t cnt = 0; //测试任务独立访问共享资源
uint16_t cnt2 = 10; //测试任务同步访问共享资源
OS_SEM mysem; //任务任务独立访问共享资源
OS_SEM mysem2; //任务同步访问共享资源
#define SHARE_TEST 1 //注释掉就是测试 信号量同步访问。而不是独立访问
/***********************start_task任务里面的一部分**************************/
OSTaskCreate( (OS_TCB *)&DispTaskTask_TCB,
#if (OS_CFG_DBG_EN == 0u)
(CPU_CHAR *)0,
#else
(CPU_CHAR *)"DispTask",
#endif
(OS_TASK_PTR )DispTask,
(void *)0,
(OS_PRIO )DISPTASK_PRIO,
(CPU_STK *)DispTaskTask_STK,
(CPU_STK_SIZE )DISPTASK_STK_SIZE / 10,
(CPU_STK_SIZE )DISPTASK_STK_SIZE,
(OS_MSG_QTY )0,
(OS_TICK )0,
(void *)0,
(OS_OPT )(OS_OPT_TASK_STK_CHK | (OS_OPT)(OS_OPT_TASK_STK_CLR | OS_OPT_TASK_NO_TLS)),
(OS_ERR *)&err);
/* 创建Task2 */
OSTaskCreate( (OS_TCB *)&RaedAdTaskTask_TCB,
#if (OS_CFG_DBG_EN == 0u)
(CPU_CHAR *)0,
#else
(CPU_CHAR *)"RaedAdTask",
#endif
(OS_TASK_PTR )RaedAdTask,
(void *)0,
(OS_PRIO )RaedAdTask_PRIO,
(CPU_STK *)RaedAdTaskTask_STK,
(CPU_STK_SIZE )RaedAdTask_STK_SIZE / 10,
(CPU_STK_SIZE )RaedAdTask_STK_SIZE,
(OS_MSG_QTY )0,
(OS_TICK )0,
(void *)0,
(OS_OPT )(OS_OPT_TASK_STK_CHK | (OS_OPT)(OS_OPT_TASK_STK_CLR | OS_OPT_TASK_NO_TLS)),
(OS_ERR *)&err);
/* 创建 计数型 信号量 */
OSSemCreate(
(OS_SEM *)&mysem,
(CPU_CHAR *)"mysem",
(OS_SEM_CTR )1,
(OS_ERR *)&err
);
OSSemCreate(
(OS_SEM *)&mysem2,
(CPU_CHAR *)"mysem2",
(OS_SEM_CTR )0,
(OS_ERR *)&err
);
for(;;)
{
delay_ms(200);
}
/****************************start_task任务里面的一部分 结束**************************************/
void DispTask(void *p_arg)
{
OS_ERR oserr;
while(1)
{
#ifdef SHARE_TEST
//测试独立访问共享资源的
OSSemPend(&mysem, 0, OS_OPT_PEND_BLOCKING, 0, &oserr);
for(int i=0; i<30; i++){
cnt++;
delay_ms(600);
} //没有加信号量时,应该加不到30或者35 就被RaedAdTask任务设置为5了
OSSemPost(&mysem, OS_OPT_POST_1, &oserr);
#else
// 同步运行, 类似 生产消费者关系
delay_ms(500);
cnt2++; //cnt2等于1就被RaedAdTask任务设置为0,所以cnt2不应该>2
OSSemPost(&mysem2, OS_OPT_POST_1, &oserr);
delay_ms(500);
#endif
}
}
void RaedAdTask(void *p_arg)
{
OS_ERR oserr;
while(1)
{
#ifdef SHARE_TEST
//访问共享资源
OSSemPend(&mysem, 0, OS_OPT_PEND_BLOCKING, 0, &oserr);
delay_ms(4000);
cnt=5;
OSSemPost(&mysem, OS_OPT_POST_1, &oserr);
#else
// 同步运行, 类似 生产消费者关系
OSSemPend(&mysem2, 0, OS_OPT_PEND_BLOCKING, 0, &oserr);
delay_ms(1000);
cnt2--;
#endif
}
}
三、互斥信号量(互斥锁)
1、优先级反转
优先级反转:
高优先级任务H运行一段时间后,发现需要的共享资源的信号量被低优先级占用,此刻只能挂起等待,等低优先级任务L释放信号量才能继续,所以高优先级任务挂起。
低优先级任务L继续执行,中优先级任务M等待事件到了,会剥夺低优先级任务L的CPU使用权,中优先级M执行完任务,低优先级任务L继续做,完成任务并释放信号量。高优先级任务H才最后执行。
概括:临界区的信号量被低优先级任务先抢占,高优先级抢占CPU后发现需要等待低优先级任务L释放信号量。
解决方法:使用互斥信号量(互斥锁)来解决
2、互斥信号量
互斥信号量是如何解决 任务的优先级反转的问题呢?
简而言之,就是把获得互斥信号量的低优先级任务的优先级暂时升高,中间避免被中等优先级任务打断。
任务L的优先级提升是自动的,旨在尽快释放互斥信号量,以便高优先级任务H能够执行,从而避免优先级反转的发生。
UCOSIII的调度器总是选择就绪列表中优先级最高的任务来执行。如果L的优先级被提升后仍然在执行中(比如,它还没有释放mutex),那么即使M已经就绪,它也不会被调度执行,因为L的优先级现在与H相同(或更高,如果H不是最高优先级任务),而M的优先级低于L和H。
1、创建互斥信号量
//定义互斥锁
OS_MUTEX mymutex;
void OSMutexCreate (OS_MUTEX *p_mutex, //控制块
CPU_CHAR *p_name, //互斥锁名字
OS_ERR *p_err)
2、请求 互斥型信号量
void OSMutexPend (OS_MUTEX *p_mutex,
OS_TICK timeout,
OS_OPT opt,
CPU_TS *p_ts,
OS_ERR *p_err)
3、发送 互斥信号量
void OSMutexPost (OS_MUTEX *p_mutex,
OS_OPT opt,
OS_ERR *p_err)
优先级反转:
1. 低优先级任务ltask获取 信号量mysem3,然后写入mysource ‘l’,然后多次调度。htask就绪也不能获得CPU使用权。之后mtask就绪。导致优先级反转。
优先级反转解决后:
1.低优先级任务ltask获取 信号量mysem3,然后写入mysource ‘l’,然后多次调度。htask获取信号量失败后,ltask的优先级被暂时升到了和htask同级。然后mstak就绪,但是由于时间片调度的机制,(这句是个人猜测)调度本任务同等优先级及以上的就绪任务。所以mstask不会被调度。等ltask很多次调度循环结束后,释放信号量,htask执行,然后是mtask。就不会出现lmh的顺序了。
模拟优先级反转 与 使用互斥信号量解决 的主要代码如下:
需要注意的是:
1. OSSched(); 所在的整个循环,不能换成delay函数。
2. 在OSSemPend和OSSemPost函数之间,也不允许有delay函数。
//全局定义
uint8_t pos=0; //优先级反转的索引
char mysource[128]={0}; //用于优先级反转的测试
OS_SEM mysem3; //任务同步访问共享资源
OS_MUTEX mutex1; //互斥信号量,解决优先级反转问题
#define PRIO_MUTEX_TEST 1 //mutex解决优先级反转问题。注释掉则运行优先级反转的现象
//在任务中 创建mysem3 和 mutex1
OSSemCreate(
(OS_SEM *)&mysem3,
(CPU_CHAR *)"mysem3",
(OS_SEM_CTR )1,
(OS_ERR *)&err
);
OSMutexCreate(
(OS_MUTEX *)&mutex1,
(CPU_CHAR *)"mutex1",
(OS_ERR *)&err
);
//高优先级任务
void htask(void *p_arg){
OS_ERR err;
while(1){
#ifdef PRIO_MUTEX_TEST
delay_ms(2);
OSMutexPend(&mutex1,0,OS_OPT_PEND_BLOCKING,0,&err); //请求互斥信号量
mysource[pos++]='h';
OSMutexPost(&mutex1,OS_OPT_POST_NONE,&err); //释放互斥信号量
OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_PERIODIC,&err); //延时1s
#else
delay_ms(5); //先延时1s,等待低优先级任务抢信号量mysem3
OSSemPend(&mysem3, 0, OS_OPT_PEND_BLOCKING, 0, &err);
mysource[pos++]='h';
OSSemPost(&mysem3, OS_OPT_POST_1, &err);
OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_PERIODIC,&err); //延时1s
#endif
}
}
//中优先级任务
void mtask(void *p_arg){
OS_ERR err;
while(1){
mysource[pos++]='m';
OSTimeDlyHMSM(0,0,0,500,OS_OPT_TIME_PERIODIC,&err); //延时1s
}
}
//低优先级任务
void ltask(void *p_arg){
OS_ERR err;
while(1){
#ifdef PRIO_MUTEX_TEST
OSMutexPend(&mutex1,0,OS_OPT_PEND_BLOCKING,0,&err);//请求互斥信号量
mysource[pos++]='l';
for(int times=0;times<500000;times++)
{
OSSched(); //发起任务调度
}
OSMutexPost(&mutex1,OS_OPT_POST_NONE,&err); //释放互斥信号量
OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_PERIODIC,&err); //延时1s
#else
OSSemPend(&mysem3, 0, OS_OPT_PEND_BLOCKING, 0, &err); //先抢占信号量
mysource[pos++]='l';
for(int times=0;times<500000;times++)
{
OSSched(); //发起任务调度
}
OSSemPost(&mysem3, OS_OPT_POST_1, &err);
OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_PERIODIC,&err); //延时1s
#endif
}
}
优先级反转时,mysource的值:
接触掉宏定义的注释,使用互斥信号量解决优先级反转时,mysource的值:(没有出现lmh的顺序了)
四、任务内嵌信号量
ucosiii中每个任务都有自己的内嵌信量,任务信号量能够简化代码,比独立的信号量更有效。下面是任务信号量的API:
1、等待任务信号量
2、发布任务信号量
OS_SEM_CTR OSTaskSemPost (OS_TCB *p_tcb,
OS_OPT opt,
OS_ERR *p_err)
p_tcb:
指向要用信号通知的 任务的tcb, 设置成NULL时,可以给自己发送信号量。
opt:
OS_OPT_POST_NONE : 不指定特定的选项
OS_OPT_POST_NO_SCHED : 禁止在本函数内执行任务调度操作。
Task2_TaskTCB.SemCtr : 任务的信号量值
使用任务RaedAdTask的任务内嵌信号量,来进行通信。
DispTask任务每1s发送一次任务RaedAdTask的任务内嵌信号量,然后任务RaedAdTask对cnt3进行+1操作。
//全局定义
uint16_t cnt3 = 0; //测试任务内嵌信号量
void DispTask(void *p_arg)
{
OS_ERR oserr;
while(1)
{
//发送RaedAdTask任务的内置信号量
OSTaskSemPost(&RaedAdTaskTask_TCB, OS_OPT_POST_NONE, &oserr);
OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_PERIODIC,&oserr); //延时1s
}
}
void RaedAdTask(void *p_arg)
{
OS_ERR oserr;
while(1)
{
OSTaskSemPend(0, OS_OPT_PEND_BLOCKING, 0, &oserr);
cnt3++;
}
}