UCOS操作系统——信号量实验(十)

UCOS操作系统

一、直接访问共享资源区

前面提过信号量主要用于访问共享资源和进行任务同步,这里我们先做一个直接访问共享资源的实验,看看会带来什么后果。

创建 3 个任务,任务 A 用于创建其他两个任务,任务 A 执行一次后就会被删除掉。任务 B 和任务 C 都可以访问作为共享资源 D,任务 B 和 C 对于共享资源 D 是直接访问的,观察直接访问共享资源会造成什么要的后果。

u8 share_resource[30]; //共享资源区
//任务 1 的任务函数
void task1_task(void *p_arg)
{
    
    
OS_ERR err;
u8 task1_str[]="First task Running!";
while(1)
{
    
    
printf("\r\n 任务 1:\r\n");
LCD_Fill(0,110,239,319,CYAN);
memcpy(share_resource,task1_str,sizeof(task1_str)); //向共享资源区拷贝数据 (1)
delay_ms(200);
printf("%s\r\n",share_resource); //串口输出共享资源区数据
LED0 = ~LED0;
OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_PERIODIC,&err); //延时 1s
} }
//任务 2 的任务函数
void task2_task(void *p_arg)
{
    
    
u8 i=0;
OS_ERR err;
u8 task2_str[]="Second task Running!";
while(1)
{
    
    
printf("\r\n 任务 2:\r\n");
LCD_Fill(0,110,239,319,BROWN);
memcpy(share_resource,task2_str,sizeof(task2_str));//向共享资源区拷贝数据 (2)
delay_ms(200);
printf("%s\r\n",share_resource); //串口输出共享资源区数据
LED1 = ~LED1;
OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_PERIODIC,&err); //延时 1s
}

(1) 任务 1 向共享资源区拷贝数据“First task Running!”,然后延时 200ms,通过串口输出
拷贝到共享资源区中的数据,既输出“First task Running!”。
(2) 任务 2 也向共享资源区中拷贝数据“Second task Running!”,同样延时 200ms,并通过
串口拷贝到共享资源区中的数据,既输出“Second task Running!”。任务 1 和任务 2 都使用了共享资源 share_resource,我们在任务 1 和任务 2 中都是使用了函数 delay_ms()进行延时,delay_ms()函数是会引起任务切换的,而我们对于共享资源的访问没有进行任何的保护,势必会造成意想不到的结果发生。
在这里插入图片描述
这并不是我们想要的结果,下面是我们理想的结果
在这里插入图片描述
在任务 1 向 share_resource 拷贝数据“First task Running!”以后就因为delay_ms()函数系统进行了任务切换。任务 2 开始运行,这时任务 2 又向 share_resource 拷贝了数据“Second task Running!”,任务 2 也因为 delay_ms()函数发生任务切换,任务 1 接着运行,但是这是 share_resource 已经被被修改为 “Second task Running!”因此输出就会和我们预计的不一样了,从而导致错误的发生,这个就是多任务共享资源区带来的问题!

二、使用信号量访问共享资源区实验

在上面的实验中我们对于 share_resource 的访问并没有进行保护,从而导致了错误的发生我们使用信号量来进行共享资源区的访问就可以避免这个问题。

OS_SEMMY_SEM; //定义一个信号量,用于访问共享资源
//创建一个信号量
OSSemCreate ((OS_SEM* )&MY_SEM, //指向信号量
 (CPU_CHAR* )"MY_SEM", //信号量名字
 (OS_SEM_CTR )1, //信号量值为 1
 (OS_ERR* )&err);
 //任务 1 的任务函数
void task1_task(void *p_arg)
{
    
    
OS_ERR err;
u8 task1_str[]="First task Running!";
while(1)
{
    
    
printf("\r\n 任务 1:\r\n");
LCD_Fill(0,110,239,319,CYAN);
OSSemPend(&MY_SEM,0,OS_OPT_PEND_BLOCKING,0,&err); //请求信号量(1)
memcpy(share_resource,task1_str,sizeof(task1_str)); //向共享资源区拷贝数据
delay_ms(200);
printf("%s\r\n",share_resource); //串口输出共享资源区数据
OSSemPost (&MY_SEM,OS_OPT_POST_1,&err); //发送信号量 (2)
LED0 = ~LED0;
OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_PERIODIC,&err); //延时 1s
} 
}
//任务 2 的任务函数
void task2_task(void *p_arg)
{
    
    
OS_ERR err;
u8 task2_str[]="Second task Running!";
while(1)
{
    
    
printf("\r\n 任务 2:\r\n");
LCD_Fill(0,110,239,319,BROWN);
OSSemPend(&MY_SEM,0,OS_OPT_PEND_BLOCKING,0,&err); //请求信号量(3)
memcpy(share_resource,task2_str,sizeof(task2_str)); //向共享资源区拷贝数据
delay_ms(200);
printf("%s\r\n",share_resource); //串口输出共享资源区数据
OSSemPost (&MY_SEM,OS_OPT_POST_1,&err); //发送信号量 (4)
LED1 = ~LED1;
OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_PERIODIC,&err); //延时 1s
} }

(1) 任务 1 要访问共享资源 share_resource,因此调用函数 OSSemPend()来请求信号量。
(2) 任务 1 使用完共享资源 share_resource,调用 OSSemPost()函数释放信号量。
(3) 同(1)
(4) 同(2)
在这里插入图片描述
使用信号量完全可以避免这个问题。

三、任务同步实验

信号量现在更多的被用来实现任务的同步以及任务和 ISR 间的同步
在这里插入图片描述
用一个小旗子代表信号量,小旗子旁边的数值 N 为信号量计数值, 表示发布信号量的次数累积值,ISR 可以多次发布信号量,发布的次数会记录为 N。一般情况下,N 的来表示初始情况下有多少信号量可用。等待信号量的任务旁边的小沙漏表示等待任务可以设定超时时间。超时的意思是该任务只会等待一定时间的信号量,如果在这段时间内没有等到信号量,UCOSIII 就会将任务置于就绪表中,并返回错误码。初始值是 0,表示事件还没有发生过。在初始化时,也可以将 N 的初值设为大于零的某个值。

实验设计:
创建 3 个任务,任务 A 用于创建其他两个任务和一个初始值为 0 的信号量,任务 C
必须征得任务 B 的同意才能执行一次操作。
答:这个问题显然是一个任务同步的问题,在两个任务之间设置一个初值为 0 的信号量来实现
两个任务的合作。任务 B 通过发信号量表示同意与否,任务 C 一直请求信号量,当信号量大于
1的时候任务C才能执行接下来的操作。

//任务 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);//发送信号量 (1)
LCD_ShowxNum(150,111,SYNC_SEM.Ctr,3,16,0); //显示信号量值 (2)
}
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); (3)
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
} }

(1) 当 KEY_UP 键按下的时候调用 OSSemPost()函数发送一次信号量。
(2) 信号量 SYNC_SEM 的字段 Ctr 用来记录信号量值,我们每调用一次 OSSemPost()函数
Ctr 字段就会加一,这里我们将 Ctr 的值显示在 LCD 上,来观察 Ctr 的变化。
(3) 任务 2 请求信号量 SYNC_SEM,如果请求到信号量的话就会执行任务 2 下面的代码,
如果没有请求到的话就会一直阻塞函数。当调用函数 OSSemPend()请求信号量成功的话,
SYNC_SEM 的字段 Ctr 就会减一,直到为 0。

由于我们新建的信号量 SYNC_SEM的初始值为 0,因此在开机以后任务 2 会由于请求不到信号量而阻塞
在这里插入图片描述
从图中我们可以看出,由于信号量 SYNC_SEM 的初始值为 0,因此 SYNC_SEM 的信号量值显示为 0,并且任务 2 阻塞。当我们按下 KEY_UP 键以后就会发送信号量,SYNC_SEM的值就会变化(增加),我们多按几次 KEY_UP 键
在这里插入图片描述
此时信号量 SYNC_SEM 的值为 3,说明任务 2 可以请求 3 次信号量 SYNC_SEM。任务 2 每隔 1s 就会请求一起信号量 SYNC_SEM,直到 SYNC_SEM 的信号量值为 0,由于任务 2 请求不到信号量了,因此任务 2 就会阻塞
在这里插入图片描述
再次按下就会继续执行

扫描二维码关注公众号,回复: 13758200 查看本文章

关于互斥信号量和优先级反转看我之前的博客即可,写的很详细。

猜你喜欢

转载自blog.csdn.net/qq_51963216/article/details/123923768
今日推荐