UCOS学习(七)——信号量详解

信号量简介

信号量像是一种上锁机制,代码必须获得对应的钥匙才能继续执行,一旦获得了钥匙,也就意味着该任务具有进入被锁部分代码的权限。一旦执行至被锁代码段,则任务一直等待,直到对应被锁部分代码的钥匙被再次释放才能继续执行。
信号量用于控制对共享资源的保护,但是现在基本用来做任务同步用
信号量通常分为两种:二进制信号量和计数型信号量。
二进制信号量只能取0和1两个值,计数型信号量的信号量值大于1,计数型信号量的范围由OS_SEM_CTR决定,OS_SEM_CTR可以为8位,16位和32位,取值范围分别为:0255,065535和0~4294967295。
二级制信号量用于那些一次只能一个任务使用的资源,比如I/O设备,打印机计,数型信号量用于某些资源可以同时被几个任务所使用,比如一个缓存池有10个缓存块,那么同时最多可以支持10个任务来使用内存池
第一遍看是不是看不懂,小问题,只要了解到他如何使用就知道它是什么了。

信号量保护共享资源

举个栗子:如果不使用信号量

首先先看一下我的这两个任务,我们定义一个全局数组soure_array作为公共资源,我们在task1task2两个任务中都使用了soure_array这个资源:
任务一,将数据复制到soure_array数组,并且打印出来。

//公共资源数组
char soure_array[40];
//led0任务函数
void led0_task(void *p_arg)
{
    
    
	OS_ERR err;
	p_arg = p_arg;
	char task1_array[] = "task1_suning!";
	while(1)
	{
    
    
	    printf("任务一:\r\n");
		LED0 = !LED0;
		memcpy(soure_array,task1_array,sizeof(task1_array));
		delay_ms(200);
		printf("%s\r\n\r\n",soure_array);
		OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_PERIODIC,&err);
	}
}

//led1任务函数
void led1_task(void *p_arg)
{
    
    
	OS_ERR err;
	p_arg = p_arg;
	char task2_array[] = "task2_runing!";
	while(1)
	{
    
    
		printf("任务二:\r\n");
		LED1 = !LED1;
		memcpy(soure_array,task2_array,sizeof(task2_array));
		delay_ms(200);
		printf("%s\r\n\r\n",soure_array);
        OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_PERIODIC,&err);
	}
}

运行结果:
请添加图片描述
我们期待的运行结果是任务一输出一次,任务二输出一次。
我们会发现这并不是我们想要的运行结果,这是因为任务二和任务一在同一时间占用了soure_array这个资源,如何解决呢?使用信号量就可以了。

信号量解决公共资源问题

首先我们得了解一下函数:
在这里插入图片描述
我们常用的呢就是标红的三个函数。

创建信号量:

1.创建信号量结构体

//1.创建信号量结构体
OS_SEM my_sem;

2.使用OSSemCreate()建立一个信号量
在开始任务中创建:

void  OSSemCreate (OS_SEM      *p_sem,  //全局信号量,就是刚才定义的信号量结构体
                   CPU_CHAR    *p_name, //信号量的名字
                   OS_SEM_CTR   cnt,    //cnt=1,二级制信号量,cnt>1,计数型信号量
                   OS_ERR      *p_err)  //错误码

2.信号量的使用
主要用到一下两个函数OSSemPend()OSSemPost()

//等待一个信号量
OS_SEM_CTR  OSSemPend (OS_SEM   *p_sem,  //信号量结构体指针
                       OS_TICK   timeout,//等待信号量的时间(时钟节拍数), 为0会一直等待信号量
                       OS_OPT    opt,    /*用于设置是否阻塞模式:
                                            OS_OPT_PEND_BLOCKING     指定信号量无效时,任务挂起等待信号量
                                            OS_OPT_PEND_NON_BLOCKING   信号量无效直接返回
                                         */
                       CPU_TS   *p_ts,   //时间戳,记录信号量的时刻,p_ts=NULL时无时间戳
                       OS_ERR   *p_err)  //错误码
//释放或者发送一个信号量
OS_SEM_CTR  OSSemPost (OS_SEM  *p_sem, //信号量结构体指针
                       OS_OPT   opt,   /*选择信号量发送的方式
                                         OS_OPT_POST_1 仅向等待该任务量的优先级最高的任务发送
                                         OS_OPT_POST_ALL 向等待该信号量的所有任务发送
                                         OS_OPT_POST_NO_SCHED 该选项禁止在本函数中执行任务调度
                                         */
                       OS_ERR  *p_err)//错误码

3.更改后的任务代码如下:

//led0任务函数
void led0_task(void *p_arg)
{
    
    
	OS_ERR err;
	p_arg = p_arg;
	char task1_array[] = "task1_suning!";
	while(1)
	{
    
    
	  printf("任务一:\r\n");
		LED0 = !LED0;
	OSSemPend(&my_sem,0,OS_OPT_PEND_BLOCKING,NULL,&err);//等待信号量直到不为0
		memcpy(soure_array,task1_array,sizeof(task1_array));
		delay_ms(200);
		printf("%s\r\n\r\n",soure_array);
		OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_PERIODIC,&err);//延时1s
	OSSemPost(&my_sem,OS_OPT_POST_1,&err);//释放信号量
	}
}

//led1任务函数
void led1_task(void *p_arg)
{
    
    
	OS_ERR err;
	p_arg = p_arg;
	char task2_array[] = "task2_runing!";
	
	while(1)
	{
    
    
		printf("任务二:\r\n");
		LED1 = !LED1;
	OSSemPend(&my_sem,0,OS_OPT_PEND_BLOCKING,NULL,&err);//等待信号量不为0
		memcpy(soure_array,task2_array,sizeof(task2_array));
		delay_ms(200);
		printf("%s\r\n\r\n",soure_array);
        OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_PERIODIC,&err);//延时1s
     OSSemPost(&my_sem,OS_OPT_POST_1,&err);//释放一个信号量
	}
}

4.运行结果
这样就达到了我们想要的效果:
请添加图片描述
我们可以分析它的执行过程:

  1. 任务二等待到信号量,打印,任务二发送信号量
  2. 任务一等待到信号量,打印,任务一发送信号量
  3. 任务二等待到信号量…

如此循环,就可以解决在两个或者多个任务使用同一个资源时出错的问题了。
下面我们在介绍一种信号量的应用:

信号量实现任务同步

首先我么要知道任务同步是什么,就是一个任务每执行一次,另外一个任务也跟着执行一次。这两个任务就算同步,用信号量来实现也是非常简单的:
前面都一样,就是创建任务,创建信号量

//KEY任务函数
void key_task(void *p_arg)
{
    
    
	OS_ERR err;
	u8 key = 0;
	p_arg = p_arg;
	while(1)
	{
    
    
		key = Key_Scan(0);
		if(key == Short_Press)//按下按键
		{
    
    
			LED0 = !LED0;
			printf("任务一:\r\n");
            OSSemPost(&my_sem,OS_OPT_POST_1,&err);//发送一个信号量
			printf("当前信号量:%d\r\n\r\n",my_sem.Ctr); //打印信号量数值
		}
		OSTimeDlyHMSM(0,0,0,10,OS_OPT_TIME_PERIODIC,&err);//延时10ms
	}
}

//led1任务函数
void led1_task(void *p_arg)
{
    
    
	OS_ERR err;
	p_arg = p_arg;
	while(1)
	{
    
    
		OSSemPend(&my_sem,0,OS_OPT_PEND_BLOCKING,NULL,&err);
		LED1 = !LED1;
		printf("任务二:\r\n");
		printf("当前信号量:%d\r\n\r\n",my_sem.Ctr);
		OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_PERIODIC,&err);//延时1s
	}
}

我们看一下运行结果:
请添加图片描述
当我连续按下两次按键之后,任务一发送两次信号量,我们发现信号量数值从0变化到2;然后任务二等待信号量两次,将信号量数值从2变化到0;最终效果就是任务一执行几次,任务二就执行几次。

总结

学过这个之后,我还是有很多问题的,希望有大佬帮我解解惑:
1.在信号量实现保护资源实验中,删除OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_PERIODIC,&err);//延时1s这个延时,任务执行还是正常的,任务一任务二依次执行,这个不是很理解。
2.二进制信号量和计数信号量的主要区别和在什么情况使用不是很清楚。
希望我的文章可以让你学会信号量,下次见!

猜你喜欢

转载自blog.csdn.net/qq_52608074/article/details/122448910