信号量原理及其应用

一、信号量定义

信号量是一种用于保护临界资源的同步机制。它可以用来控制对共享资源的访问,以避免并发访问导致的数据不一致或竞争条件。信号量的PV操作是原子操作,即不可被中断的操作。
在信号量的操作中,P操作(也称为wait操作)用于申请资源,V操作(也称为signal操作)用于释放资源。P操作会检查信号量的值,如果大于0,则将信号量的值减1,并继续执行;如果等于0,则进入等待状态,直到有其他线程释放资源。V操作会将信号量的值加1,表示资源已经被释放。
当信号量的值为0时,表示该信号量此时不能被申请、被等待了,只有等其他的信号量释放了之后,才能够再次获取。

二、信号量API函数

在UCOSIII中,可以使用信号量API函数来操作信号量。这些函数包括:
1、OS_SEM:定义一个信号量。

OS_SEM    MySem;              //定义一个信号量

2、OS_SEM_CREATE:创建一个信号量。


void  OSSemCreate (
OS_SEM      *p_sem,  //信号量:MySem
CPU_CHAR    *p_name, //信号量名字:自定义
OS_SEM_CTR   cnt,   //信号量的值:自定义
OS_ERR      *p_err     //返回值,记录错误信号
)     

3、OS_SEM_PEND:请求/等待一个信号量,如果信号量的值为0,则任务会进入阻塞状态。

OS_SEM_CTR  OSSemPend (
OS_SEM   *p_sem,        //信号量:MySem
OS_TICK   timeout,     //超时时间,设置为0则一直等待                       		
OS_OPT    opt,          //设置是否阻塞等待这个信号量
CPU_TS   *p_ts,        //记录请求到信号量时的时间(时间戳),为空则不需要时间戳
OS_ERR   *p_err        //返回值,记录错误信号
)

4、OS_SEM_POST:释放/发送一个信号量,告知任务某个事件发生

了,任务取得信号量便被唤醒去执行对应的操作。
OS_SEM_CTR  OSSemPost (
OS_SEM  *p_sem,      //信号量:MySem        
OS_OPT   opt,        //决定发送模式,信号量发送模式有三种
OS_ERR  *p_err
)

信号量发送模式有三种
OS_OPT_POST_1表示只给最高优先级的任务发(等待信号量的优先级最高的)
OS_OPT_POST_ALL表示给所有等待信号量的任务发
OS_OPT_POST_NO_SCHED 表示不任务调度。这个参数可以和上面2个参数位或

三、信号量作用

任务同步

信号量的初值为0,每当任务1发送一次信号量是ctr+1=1,每当任务2请求到一次信号量时ctr-1=0。当ctr=0时,任务2阻塞等待任务1发送信号量,只有任务1发送了信号量时,任务2才会请求到一次信号量,任务2才会执行一次。这样完成任务同步。

//创建一个信号量
	OSSemCreate ((OS_SEM*	)&SYNC_SEM,
                 (CPU_CHAR*	)"SYNC_SEM",
                 (OS_SEM_CTR)0,		
                 (OS_ERR*	)&err);
//任务1的任务函数
void task1_task(void *p_arg)
{
	u8 key;
	OS_ERR err;
	while(1)
	{
		key = KEY_Scan(0);  //扫描按键
		if(key==WKUP_PRES)	
		{
			OSSemPost(&SYNC_SEM,OS_OPT_POST_1,&err);//发送信号量
			LCD_ShowxNum(150,111,SYNC_SEM.Ctr,3,16,0);	//显示信号量值
		}
		OSTimeDlyHMSM(0,0,0,10,OS_OPT_TIME_PERIODIC,&err);   //延时10ms
	}
}

//任务2的任务函数
void task2_task(void *p_arg)
{	
	u8 num;
	OS_ERR err;
	while(1)
	{
//请求信号量
	OSSemPend(&SYNC_SEM,0,OS_OPT_PEND_BLOCKING,0,&err); 		 num++;
		LCD_ShowxNum(150,111,SYNC_SEM.Ctr,3,16,0);					 		LCD_Fill(6,131,233,313,lcd_discolor[num%14]);		//刷屏
		LED1 = ~LED1;
		OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_PERIODIC,&err);   //延时1s
	}
}

四、优先级翻转

优先级翻转是在实时系统中使用信号量时可能产生的一个严重的问题。
优先级翻转是当一个高优先级任务通过信号量机制访问共享资源时,该信号量已被一低优先级任务占有,因此造成高优先级任务被许多具有较低优先级任务阻塞,实时性难以得到保证。
优先级翻转在可剥夺内核中是非常常见的,在实时系统中不允许出现这种现象,这样会破坏任务的预期顺序,可能会导致严重的后果,如下历史:
在1997年7月4号火星探路者号(Mars Pathfinder)发射后,在开始搜集气象数据之后没几天,系统(无故)重启了。】 后来,被相关技术人员找到问题根源,就是,这个优先级翻转所导致的。

优先级翻转的例子

在这里插入图片描述

创建 3 个任务 Task1,Task2 和 Task3,优先级分别为 3,2,1。也就是 Task1 的优先级最高。
任务 Task1 和 Task3 互斥访问串口打印 printf,采用二值信号实现互斥访问。
起初 Task3 通过二值信号量正在调用 printf,被任务 Task1 抢占,开始执行任务 Task1,也就是上图的起始位置。
运行过程描述如下:
任务 Task1 运行的过程需要调用函数 printf,发现任务 Task3 正在调用,任务 Task1 会被挂起,等待 Task3 释放函数 printf。
在调度器的作用下,任务 Task3 得到运行,Task3 运行的过程中,由于任务 Task2 就绪,抢占了 Task3的运行。从任务执行的现象上看,任务 Task1 需要等待 Task2 执行完毕才有机会得到执行,正常情况下应该是高优先级任务抢占低优先级任务的执行,这里成了高优先级任务 Task1 等待低优先级任务 Task2 完成。所以这种情况被称之为优先级翻转问题。
任务 Task2 执行完毕后,任务 Task3 恢复执行,Task3 释放互斥资源后,任务 Task1 得到互斥资源,从而可以继续执行。

解决优先级翻转

解决优先级翻转有2种方法:一种被称作优先级继承(priority inheritance);另一种被称作优先级天花板(priority ceilings)。
优先级继承(priority inheritance) :
优先级继承是指将低优先级任务的优先级提升到等待它所占有的资源的最高优先级任务的优先级。当高优先级任务由于等待资源而被阻塞时,此时资源的拥有者的优先级将会临时自动被提升,以使该任务不被其他任务所打断,从而能尽快的使用完共享资源并释放,再恢复该任务原来的优先级别。
优先级天花板(priority ceilings): 优先级天花板是指将申请某资源的任务的优先级提升到可能访问该资源的所有任务中最高优先级任务的优先级。(这个优先级称为该资源的优先级天花板) 。这种方法简单易行, 不必进行复杂的判断, 不管任务是否阻塞了高优先级任务的运行, 只要任务访问共享资源都会提升任务的优先级。

五、互斥信号量(解决优先级翻转)

互斥信号量就是通过优先级继承的方式来解决优先级反转问题的。
低优先级任务 Task1 执行过程中先获得互斥资源执行。此时任务 Task2 抢占了任务 Task1的执行,任务 Task1 被挂起。任务 Task2 得到执行。
任务 Task2 执行过程中也需要调用互斥资源,但是发现任务 Task1 正在访问,此时任务 Task1 的优先级会被提升到与 Task2 同一个优先级,也就是优先级 3,这个就是所谓的优先级继承(Priority inheritance),这样就有效地防止了优先级翻转问题。任务 Task2 被挂起,任务 Task1 有新的优先级继续执行。
任务 Task1 执行完毕并释放互斥资源后,优先级恢复到原来的水平。由于互斥资源可以使用,任务Task2 获得互斥资源后开始执行。

互斥信号量的三个API函数

//创建一个互斥信号量
void  OSMutexCreate (OS_MUTEX  *p_mutex,//和普通信号量一样,互斥信号量结构体指针
                     CPU_CHAR  *p_name, //互斥信号量名字
                     OS_ERR    *p_err)  //错误码
//等待一个互斥信号量
void  OSMutexPend (OS_MUTEX  *p_mutex, //互斥信号量结构体指针
//等待互斥信号量的超时时间(节拍数),timeout = 0,任务一直等待直到信号量释放
                   OS_TICK    timeout, 
                   OS_OPT     opt,    /*是否为阻塞模式
                         OS_OPT_PEND_BLOCKING      信号量被占用,任务挂起等待
                         OS_OPT_PEND_NON_BLOCKING 信号量被占用直接返回*/
                   CPU_TS    *p_ts,  //时间戳,指向记录发送、终止、删除信号量的时刻
                   OS_ERR    *p_err) //错误码
//释放一个互斥信号量
void  OSMutexPost (OS_MUTEX  *p_mutex,//互斥信号量结构体指针
                   OS_OPT     opt,    /*指定是否进行任务调度
                                        OS_OPT_POST_NONE  不指定特定选项
                                        OS_OPT_POST_NO_SCHED 禁止任务调度
                                        */
                   OS_ERR    *p_err)  //错误码

案例

//高优先级任务的任务函数
void high_task(void *p_arg)
{
	OS_ERR err;
	while(1)
	{
		OSTimeDlyHMSM(0,0,0,500,OS_OPT_TIME_PERIODIC,&err);   //延时500ms
		printf("high task Pend Mutex\r\n");
		OSMutexPend (&TEST_MUTEX,0,OS_OPT_PEND_BLOCKING,0,&err);	//请求互斥信号量
		printf("high task Running!\r\n");
		LED1 = ~LED1;
		OSMutexPost(&TEST_MUTEX,OS_OPT_POST_NONE,&err);	//释放互斥信号量
		OSTimeDlyHMSM(0,0,0,500,OS_OPT_TIME_PERIODIC,&err); //延时500ms
	}
}

//中等优先级任务的任务函数
void middle_task(void *p_arg)
{	
	OS_ERR err;
	while(1)
	{
		printf("middle task Running!\r\n");
		LED0 = ~LED0;
		OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_PERIODIC,&err);   //延时1s
	}
}
//低优先级任务的任务函数
void low_task(void *p_arg)
{	
	OS_ERR err;
	while(1)
	{
		OSMutexPend (&TEST_MUTEX,0,OS_OPT_PEND_BLOCKING,0,&err);//请求互斥信号量
		printf("low task Running!\r\n");
		OSMutexPost(&TEST_MUTEX,OS_OPT_POST_NONE,&err);	//释放互斥信号量
		OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_PERIODIC,&err);   //延时1s
	}
}

六、任务内嵌信号量

UCOSIII中每个任务都有自己的内嵌的信号量,与独立的信号量相比更高效简洁。
**

内务内建信号量API函数

**

//等待一个任务信号量
OS_SEM_CTR  OSTaskSemPend (
OS_TICK   timeout, //等待信号量的超时时间(节拍数)
          OS_OPT    opt,     /*是否为阻塞模式
                     OS_OPT_PEND_BLOCKING     信号量被占用,任务挂起等待
                     OS_OPT_PEND_NON_BLOCKING 信号量被占用直接返回*/
          CPU_TS   *p_ts,   //时间戳
          OS_ERR   *p_err)  //错误码
//释放或发送一个信号量
OS_SEM_CTR  OSTaskSemPost (
OS_TCB  *p_tcb,//任务控制块
          OS_OPT   opt,   /*指定是否进行任务调度
                     OS_OPT_POST_NONE  不指定特定选项
                     OS_OPT_POST_NO_SCHED 禁止任务调度*/
                     OS_ERR  *p_err)//错误码 

案例

/任务1的任务函数

void task1_task(void *p_arg)
{
	u8 key;
	u8 num;
	OS_ERR err;
	while(1)
	{
		key = KEY_Scan(0);  //扫描按键
		if(key==WKUP_PRES)	
		{
			OSTaskSemPost(&Task2_TaskTCB,OS_OPT_POST_NONE,&err);	//使用系统内建信号量向任务task2发送信号量
			LCD_ShowxNum(150,111,Task2_TaskTCB.SemCtr,3,16,0);		//显示信号量值
		}
		OSTimeDlyHMSM(0,0,0,10,OS_OPT_TIME_PERIODIC,&err);   		//延时10ms
	}
}

//任务2的任务函数
void task2_task(void *p_arg)
{	
	u8 num;
	OS_ERR err;
	while(1)
	{ 
		OSTaskSemPend(0,OS_OPT_PEND_BLOCKING,0,&err);	//请求任务内建的信号量
		num++;
		LCD_ShowxNum(150,111,Task2_TaskTCB.SemCtr,3,16,0);//显示任务内建信号量值
		LED1 = ~LED1;
		OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_PERIODIC,&err);   //延时1s
	}
}
  • 22
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值