ucosiii信号量

目录

一、信号量

二、任务访问临界区

三、互斥信号量(互斥锁)

1、优先级反转

2、互斥信号量

四、任务内嵌信号量


一、信号量

信号量是一种解决多任务并发和同步的机制。

并发:就是多个任务同时访问一个共享资源,这样容易造成任务获取资源的错误。

同步:就是多个任务按顺序的占用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++;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值