新网Logo
首页>邮箱资讯>

μC/OS-II学习之:任务,信号量、邮箱、队列及其区别

登录 注册

μC/OS-II学习之:任务,信号量、邮箱、队列及其区别

  • 来源:新网
  • 更新日期:2018-03-13

摘要:一:UCOS是一种抢占式的多任务操作系统,如果最高优先级的任务不主动放弃CPU的使用的话,其他任务是无法运行的,通常情况下,高优先级的任务在使用完CPU或其他资源后都要主动放弃,可以通过延时函数或者时等待一些信号量之类的让自己挂起。但是如果最高优先级任务一直使用CPU,那就跟单任务没有什么区别了。 二:可以通过等待信号量,消息等是当前任务挂起,或者通过通过延时函数将任务挂起,从而让其他优先级

一:UCOS是一种抢占式的多任务操作系统,如果最高优先级的任务不主动放弃CPU的使用的话,其他任务是无法运行的,通常情况下,高优先级的任务在使用完CPU或其他资源后都要主动放弃,可以通过延时函数或者时等待一些信号量之类的让自己挂起。但是如果最高优先级任务一直使用CPU,那就跟单任务没有什么区别了。

t016fdac2c5ef3ec441.png

二:可以通过等待信号量,消息等是当前任务挂起,或者通过通过延时函数将任务挂起,从而让其他优先级的任务运行。

UC/OS的信号量,消息队列,邮箱的区别
信号量像一把钥匙,任务要运行下去,需先拿到这把钥匙。
消息邮箱是一个指针型变量。可以向一个任务或一个中断服务子程序发送一则消息(一个指针),同样,一个或多个任务通过内核服务,可以接收这则消息。消息邮箱也可以当作只取2个值的信号量来用。
消息队列实际上是邮箱阵列。

任务

看了《嵌入式实时操作系统uC/OS-II》也快一个星期了,期间断断续续的边工作边看,依照书的目录,大致细分了系统各个功能:任务管理,时间管理,事件控制,信号量管理,互斥型信号量管理,事件标志组管理,信息邮箱管理,信息队列管理,内存管理等,这个次序也是以后要逐个理解学习的次序。

现在下了个移植到51的源代码,学习任务管理部分,试尝编写了个任务程序,为了把能够用上的函数使用上,程序就显得有点臃肿,程序我保证是能够通过编译并且无错误的。

程序粘贴上去 dat="dat总是变成dat"="dat",多了个"",如果大家要用这个程序例子,请去掉""。

第一个程序dj:

#include <INCLUDES.H>
typedef unsigned char u8;
typedef unsigned int u16;
typedef unsigned long u32;

OS_STK TaskStk[3][MaxStkSize];//定义3个堆栈
void task0(void *dat);
void task1(void *dat);

long con1;
long con2;

main()
{
OSInit();
InitTimer0( );//初始化系统时钟
OSTaskCreate(task0,(void*)0,&TaskStk[0][0],5);//建立任务0 定义好它的优先级等参数
OSTaskCreate(task1,(void*)0,&TaskStk[1][0],6);
OSStart();

}

void task0(void *dat)//任务0
{
dat="dat"; //防止编译有错误

while(1)
{
con1++;
OSTimeDly(2);
}

}

void task1(void *dat)
{
u16 vison;
dat="dat";
while(1)
{
con2++;
vison=OSVersion();//返回系统版本号

OSTaskDel(6);//删除任务1
OSTimeDly(2);
}
}

心得:

根据作者建议,最高和最低优先级的任务最好不要使用,而用户使用的任务多达56个,0表示最高优先级。

建立任务的函数有OSTaskCreate和OSTaskCreateExt两个,OSTaskCreateExt函数可以设置更多的任务细节。

在 OSInit()和OSStart()之间最好建立任务和其他要初始化的函数,因为启动系统后,OSStart()将不再执行。

OSTimeDly(2)表示将任务0延时2个时钟节拍,在这2个节拍的时间里,系统可以执行任务1;等待2个节拍时间后,系统才返回任务0执行。如果没有延时,系统永远不会执行任务1,而只执行任务0.这个很重要。

想延时更长的时间可以OSTimeDlyHMSM(h,m,s,m)函数,可延时接近11天。

用如果不想让任务0等待2个节拍才返回的话,也可以用OSTimeDlyResume(5)直接恢复任务0运行,这里的5是代表优先级为5的任务;

OSTaskDel(6)删除优先级为6的任务,这里的删除不是指删除代码,而是不再执行删除的任务。

OSVersion()返回系统版本号的函数,如果是200的话即是2.00版本。

OSTaskSuspend(5)表示无条件暂停一个优先级为5的任务,这个时候系统会重新调度,运行下一个优先级最高的任务,如果想恢复该任务,只能用OSTaskResume(5)来恢复。


信号量

信号量的使用自己看了几天仍然没搞明白信号量,邮箱,和队列的区别。现在是学习信号量的使用,苦于没有现成的例子参考,只能一步步弄清楚它的,感觉外国人的思维还是和中国的有很大的出入,以至于学习一个国外的技术, 总是先翻译成自己习惯的思维去领会,变成自己的东西。

信号量程序dj:

#include <INCLUDES.H>
typedef unsigned char u8;
typedef unsigned int u16;
typedef unsigned long u32;

OS_STK TaskStk[3][MaxStkSize];//定义3个堆栈
void task0(void *dat);//定义任务0
void task1(void *dat);

long con1=0;
long con2=0;
OS_EVENT *sem;
u8 err;

main()
{
OSInit();
InitTimer0( );
sem=OSSemCreate(1);//建立信号量
OSTaskCreate(task0,(void*)0,&TaskStk[0][0],5);//建立任务 定义好它的优先级等参数
OSTaskCreate(task1,(void*)0,&TaskStk[1][0],6);
OSStart();
}

void task0(void *dat)//任务0
{
while(1)
{
con1++;
OSSemPost(sem);//发送信号量
OSTimeDly(2);
}
}

void task1(void *dat)//任务1
{
u16 value;
u16 vison;
while(1)
{
value=OSSemAccept(sem); //查看资源是否可以使用
OSSemPend(sem,0,&err); //等待接收信号 才往下执行任务
con2++;
}
OSTimeDly(2);//延时
}
}

心得:

信号量的建立必须是在任务级中建立,信号量类型为OS_EVENT信号量值可以为1,0,0~65535的值,不同的值代表不同的意义。

OSSemAccept(信号量)起查询信号量作用,返回信号量的值。

OSSemPend(sem,0,&err);将暂停当前任务,等待信号量的到来。如果 接收到一个信号量(如果信号量大于0) ,该信号量会自动减1,在例子中,信号量开始定义为1,在任务1接收后,信号量会变为0;例子中的OSSemPend(sem,timeout,&err),timeout代表等待timeout个信号量后还没得到信号量,恢复运行状态,如果timeout=0,表示无限等待信号量。


邮箱

学习uCOSII到现在,开始对信号量,邮箱和队列有了个大概的认识。如果有理解错误,请大家指出。

信号量是一个触发信号,也是一个计数器,等待接收信号的任务一般只有接收到信号才可以执行,否则任务一直暂停。在uCOSII里面,等待信号量的任务可以设置无限等待或等待若干个时钟节拍后,任务自动恢复执行。具体看自己的要求来设置。

邮箱是信号量的扩展,相当于把一个指针定义的变量从一个任务传递到另一个或多个任务中去,这个指针是先发到邮箱,然后等待任务从邮箱里提取指针,这也就传递了指针指向的具体变量值。 等待邮箱的任务也是可以设置无限等待和等待若干个时钟节拍后任务自动恢复执行。后面的队列,我们也可以看得出它们规律,都有各自的建立,删除,发送,接收,查询等功能函数。

队列是多个邮箱的数组,可以看做是个指针数组,任务之间可以按照一定顺序以指针定义的变量来传递,即是发送一个个指针给任务,任务获得指针,来处理指向的变量。这个方式有先进先出,先进后出。这个后面再详谈。

信号量,邮箱,队列的最大不同在于它们发送的内容不同。我觉得这是最根本的区别。

邮箱实例dj:

#include <INCLUDES.H>
typedef unsigned char u8;
typedef unsigned int u16;
typedef unsigned long u32;

OS_STK TaskStk[3][MaxStkSize];//定义3个堆栈
void task0(void *dat);//定义任务0
void task1(void *dat);

long con1=0;
long con2=0;
OS_EVENT *Come;
u8 err;

main()
{
OSInit();
InitTimer0();
Come=OSMboxCreate((void *)0);//建立邮箱

OSTaskCreate(task0,(void*)0,&TaskStk[0][0],5);//建立任务 定义好它的优先级等参数
OSTaskCreate(task1,(void*)0,&TaskStk[1][0],6);
OSStart();
}

void task0(void *dat)//任务0
{
u16 buffer="0x0FFF";//要发送的信息

while(1)
{
con1++;
OSMboxPost(Come,&buffer);//通过邮箱发送信息(指针)给任务
OSTimeDly(2);
}
}

void task1(void *dat)
{
u16 *msg;
u16 c;
while(1)
{
msg=OSMboxPend(Come,0,&err);//等待接收邮箱信息(指针msg指向buffer)
c=*msg;//获得指针指向的变量
con2++;
OSTimeDly(2);
}

}

心得

我们看到,邮箱传递了一个指针从任务0到任务1,任务1使用这个指针来获得变量buffer进行处理。

在这个例子里,设置OSMboxPend(Come,0,&err)中的0表示无限等待邮箱有信息,如果没有,任务1暂停;也可以设置成其他的值N,表示等待N个时钟节拍后如果没有收到信息,任务1自动执行。

这里没用到的邮箱功能函数还有查询邮箱是否有信息,通过邮箱发信息给多个任务等。


队列

对消息队列的学习理解有点难,对技术来说,一本好的书一般是原理和例子相结合的,可惜我找到的很少。书上说消息队列实际上是多个邮箱组成的数组,是一个列表。这个数组其实是个指针数组,里面每个指针可以指向不同类型的变量,通过传递一个个指针,我们可以做到传递指针所指向的一个个变量。(顺便复习下,一个邮箱只能传递一个指针,而队列可传递多个)。

#include <INCLUDES.H>
typedef unsigned char u8;
typedef unsigned int u16;
typedef unsigned long u32;

OS_STK TaskStk[3][MaxStkSize];//定义3个堆栈
void task0(void *dat);//定义任务0
void task1(void *dat);//定义任务1

long con1=0;
long con2=0;

OS_EVENT *Com1; //定义一个指针

void *ComMsg1[3]; //定义一个指针数组
u8 err;

u8 st[4]={0x01,0x02,0x03,0x04};//定义一个数组
u8 aa[4]={0x05,0x04,0x06,0x09};//定义一个数组
u8 a1=0xFF;//定义一个数

main()
{
OSInit();//初始化ucosii
InitTimer0();

Com1=OSQCreate(&ComMsg1[0],3);//建立一个消息队列(即为数组指针) 消息内存大小为3

OSQPost(Com1,(void*)&st[0]); //发送到队列
OSQPost(Com1,(void*)&aa[0]);//发送到队列
OSQPost(Com1,(void*)&a1); //发送到队列

OSTaskCreate(task0,(void*)0,&TaskStk[0][0],5);//建立任务 定义好它的优先级等参数
OSTaskCreate(task1,(void*)0,&TaskStk[1][0],6);
OSStart();//系统启动
}

void task0(void *dat)//任务0
{
// dat="dat";//防止编译有错误
while(1)
{
con1++;
OSTimeDly(2);
}
}

void task1(void *dat)
{
u8 *msg1;
u8 cc,b;
// dat="dat";//防止编译有错误
while(1)
{
msg1=OSQPend(Com1,0,&err);
cc=*msg1;
b=*(msg1+1);
con2++;
OSTimeDly(2);
}
}

心得:

Com1=OSQCreate(&ComMsg1[0],3),建立一个队列,将*Com1指针将指向队列指针数组*ComMsg1[3]的首地址。3表示内存大小。

OSQPost(Com1,(void*)&st[0]),是以先入先出发送消息到队列,由*ComMsg1[0]指向数组st的首地址,同时队列消息数加一。

OSQPost(Com1,(void*)&aa[0])发一个消息给队列,此时是由*ComMsg1[1]指向aa数组的首地址,同时队列消息数加1。;依此类推,发下一个也是一样。(如果队列满了,再发送消息到队列的话,队列将会出现错误)。

msg1=OSQPend(Com1,0,&err);是等待消息队列函数,获得指针数组里的第一个指针ComMsg1[0],该指针指向st[0],。cc=*msg1是通过指针获得指向的具体值st[0]=0x01;如果再调用一次msg1=OSQPend(Com1,0,&err),将获得指针数组里下一个指针ComMsg1[1],依此类推。

每调用一次OSQPend函数,该函数会将队列的消息减1,Com1指向下一个消息指针*ComMsg1[i++],直到队列完全没有消息为止。在例子中,只有3个消息,程序将执行3次任务1后,队列已经没有消息了,所以将任务1暂停。

原文地址:http://cspiao1986.blog.163.com/blog/static/701139742011413104555402

该系列主要转自:EDN小组