0.前置知识
位图
多任务系统
简单来说,多任务系统就是用一个处理器并发(不是同时)地运行多个程序的计算机管理系统。
并发:由同一个处理器轮换地运行多个程序,或者说是由多个程序轮班地占用处理器这个资源。
程序切换: PC让处理器干啥,它就干啥。PC是个指路器,哪个程序占有了PC,哪个程序就占有了处理器。系统就是通把待运行的地址赋予程序计数器PC
来实现程序的切换。
任务运行时与处理器之间的关系: 处理器通过PC和SP两个指针寄存器来与任务代码和任务堆栈建立联系并运行。在多任务情况下,内存为每个任务创建一个虚拟的处理器(处理部分运行环境),通过调度器把需要运行的任务的虚拟处理器复制到实际处理器中。
虚拟处理器: 包含的主要信息有程序断点地址(PC)、任务堆栈指针(SP)、程序状态字寄存器(PSW)、通用寄存器内容、函数调用信息(已存在于堆栈),这些内容通常保存在任务堆栈中,也叫任务的上下文。另外再用一个数据结构保存任务堆栈指针(SP),这个数据结构叫任务控制块,当然它除了保存任务堆栈指针还要负责保存任务其他信息。
任务控制块的主要成员包括指向任务堆栈栈顶的指针、任务当前状态标志、优先级别。其实,程序切换的关键是把私有堆栈指针赋予处理器的堆栈指针SP。任务的三要件:程序代码、私有堆栈(用以保护运行环境)、任务控制块(提供私有堆栈也是虚拟处理器的位置)。
1. 操作系统的任务与时钟节拍
五种状态
- 睡眠状态:任务在没有配备任何控制块或被剥夺了任务控制块时的状态叫任务睡眠状态。任务控制块简单理解为包含任务信息的一个黑盒子。
- 就绪状态:系统为任务配备了任务控制块且在任务就绪表中进行了就绪登记。
- 运行状态:处于就绪态的任务,如果经调度器判断获得了CPU的使用权,则任务进入运行状态。
- 中断服务状态:一个正在运行的任务一旦响应中断申请就会终止运行而去执行中断服务程序,此时的状态即中断服务状态。
- 等待状态:正在运行的任务,需要等待一段时间或需要等待一个事件发生再运行时,该任务就会把CPU的使用权让给别的任务,而使任务进入等待状态。
任务控制块-任务在系统中的身份证
一个任务的任务控制块主要作用是保存该任务的虚拟处理器的堆栈指针寄存器SP。 随着任务管理复杂性的提高,它还应该保存一些其他信息:名称、优先级、状态。多个任务靠任务控制块组成一个任务链表如下:任务堆栈存的是任务代码中的一些参数啊什么的。
由于系统存在着多个任务,于是系统如何来识别并管理一个任务就是一个需要解決的问题。识别一个任务的最直接的办法是为每一个任务起一个名称。由于uC/OS-II中的任务都有一个唯一的优先级别,因此uCIOS-I是用任务的优先级来作为任务的标识的。
所以,任务控制块还要来保存该任务的优先级别。 其实相当于是个学号。
多个任务组成的任务链表:
用户任务代码一般结构
一个带有临界段的无限循环。
void MyTask(void *pdata){
for(;;){
//可以被用户中段的代码
//...
OS_ENTER_CRTTICAL();//进入临界区(关中断)
//......... 这部分代码不可被中断
OS_EXIT_CRTTICAL();//退出临界区(开终端)
}
}
空闲任务:
没事干了,也不要闲就这样。空闲任务中只做了一个计数工作,没有任何延时操作。
ucos-II中规定,一个用户应用程序必须使用这个空闲任务(就是必须有的),而且这个任务是不能用软件删除的。
在多任务系统运行时,系统经常会在某个时间内无用户任务可运行而处于所谓的空闲状态,为了使CPU在没有用户任务可执行的时候有事可做,uC/OS-ii提供了一个叫做空闲任务
OSTaskldle()
的系统任务。
统计任务:
这个统计任务可以有也可以没有。
uC/OS-II提供的另一个系统任务是统计任务
OSTaskstat()
。这个统计任务每秒计算一次CPU在单位时间内被使用的时间,并把计算结果以百分比的形式存放在变量OsCPUsage
中,以便应用程序通过访问它来了解CPU的利用率,所以这个系统任务OSTaskStat()
叫做统计任务。
任务的优先权及优先级别
uCOS-II把任务的优先权分为64个优先级别,每一个级别都用一个数字来表示。数字0表示任务的优先级别最高,数字越大则表示任务的优先级别越低。 每个任务都有一个唯一的优先级别。
用户可以根据需要,在文件OS_CFG
中给最低优先级常数OS_LOWESTIO
赋值来说明应用程序中任务优先级的数目,共有OS_LOWESTIO+1
个,分别是0,1,2…,OS_LOWESTIO。固定地,系统总是把最低级别OS_LOWESTIO赋值给空闲任务。如果用到统计任务,就会讲OS_LOWESTIO-1给统计任务。
任务堆栈
保存CPU寄存器中的内容及存储任务私有数据的需要,每个任务都应该配有自己的堆栈,任务堆栈是任务的重要的组成部分。
在应用程序中定义任务堆栈的栈区非常简单,即定义一个OS_STK
类型的一个数组并在创建一个任务时把这个数组的地址赋给该任务就可以了。
任务堆栈的栈区定义,例如:
//定义堆栈的长度
#define TASK_STK_S1ZE 512
//定义一个数组来作为任务堆栈
OS_STK TaskStk[ITASK_STK_SIZE];
OS_STK是系统定义的一个数据类型:typedef unsigned int OS_STK;
定义任务
定义任务需要传递哪些东西,如下:
OSTaskCreate(
MyTask, //任务的指针
&MyTaskAgu, //传递给任务的参数
&MyTaskStk[MyTaskStkN-1], //任务堆栈栈顶地址
20 //任务优先级级别
);
使用OSTaskCreate
要注意所使用的处理器对堆栈的增长方向是向上还是向下。
任务堆栈的初始化
应用程序在创建一个新任务的时候,必须把在系统启动这个任务时CPU各寄存器所需要的初始数据(任务指针、任务堆栈指针、程序状态字等等),事先存放在任务的堆栈中。就是TCB任务控制块中的东西要放到堆栈中。
uC/OS-II在创建任务函数OSTaskcreate()
中通过调用任务堆栈初始化函数OSTaskStklnit()
来完成任务堆栈初始化工作的。OSTaskStklnit()
函数原型如下:
OS_STK * OSTaskStkInit(
void (*task) (void *pd),
void *pdato,
OS_STK *ptos,
INTI6U opt
):
任务就绪表及任务调度
uCOS-II进行任务调度的思想是近似地每时每刻总是让优先级最高的就绪任务处于运行状态。为了保证这一点,它在系统或用户任务调用系统函数及执行中断服务程序结束时总是调用调度器,来确定应该运行的任务并运行它。
任务调度的依据就是任务就绪表。 任务就绪表实际就是一个二维数组OSRdyTbl[]
,系统定义变量OSRdyGrp
来表明就绪表每行中是否存在就绪任务。
OSRdyGrp
是一个D7-D0的八位二进制。如下图
任务的优先级,如下图。由于系统至多支持64个任务,所以优先级至多也就到63,即二进制的00111111,只占据低6位,每一个OSRdyTbl[]
元素只是占据8,所以只需要用3个二进制位即可表示这8位中的哪一位为1,同理,高3位用于表示至多8个OSRdyTbl[]
元素的哪一个元素。即:优先级的高3位二进制位(D5、D4、D3)指明O即:优先级的高3位二进制位(D5、D4、D3)指明OSRdyTbl[]
的数组的行,低3位(D2、D1、D0)指明OSRdyTbl[n]
的列。另外,确定OSRdyTbl[]
的行,也就意味着OSRdyGrp的具体数据位了。
举例:某任务的优先级prio=24,问该任务落在就绪表中的哪一位?
24的二进制位为00011000,D5、D4、D3位011,即OSRdyTbl[]的下标为3,D2、D1、D0为0,即优先级prio=24的任务在OSRdyTbl[0]的第0位。
任务切换宏OS_Task_SW()
中断动作和过程调用指令可以使PC压栈,中断返回指令可以使PC出栈。因此任务切换OS_Task_SW()
是一个中断服务程序。需要由宏OS_Task_SW()
来引发一次中断或者一次调用来使OSCtxSW()
执行任务切换工作。
时钟节拍
uC/OS-II与大多数计算机系统一样,用硬件定时器产生一个周期为ms级的周期性中断来实现系统时钟,最小的时钟单位就是两次中断之问相问隔的时问,这个最小时钟单位叫做时钟节拍 (Timc Tick)。硬件定时器以时钟节拍为周期定时地产生
中断,该中断的中断服务程序叫做OSTickISR()
。中断服务程序通过调用函数OSTimeTick()来完成系统在每个时钟节拍时需要做的工作。
OSTimeTick()
的任务,就是在每个时钟节拍了解每个任务的延时状态,使其中已经到了延时时限的非挂起任务进入就绪状态。
任务的延时
ucos-II规定:除空闲任务之外的所有任务必须在任务中合适的位置调用系统提供的函数OSTimeDly()
使当前运行的任务延时(暂停)一段时间并进行一次任务调度,让出CPU的使用权。
OSTimeDly()
系统提供的延时函数。其他用来管理时间的函数如下:INT8U OSTimeDlyResume(INT8U prio);
取消任务延时函数INT32U OSTimeGet(void);
获取系统时间函数void OSTimeSet(INT32U ticks);
设置系统时间函数
2.创建一个任务点亮一个发光二极管
实现效果:创建一个任务并在任务中点亮发光二极管,使发光二极管闪烁。
实际编程会创建两个任务,一个是开始任务,可用于工作效率统计;一个任务是发光二极管闪烁任务,由开始任务创建出来。
注:在调用启动任务函数OSStart();
之前必须已经创建了至少一个任务。使用uCOS-II所有服务之前必须调用初始化函数OSInit()对uCOS自身进行初始化。
//ucos-II任务堆栈参数设置
//设置任务优先级
#define START_TASK_PRIO 9
//设置任务堆栈大小
#define START_STK_SIZE 64 //64个字,即64x4(字节)
//创建任务堆栈空间
OS_STK START_TASK_STK[START_STK_SIZE];
//============设置发光二极管相关的=============
#define LED1_TASK_PRIO 6
//设置任务堆栈大小
#define LED1_STK_SIZE 64 //64个字,即64x4(字节)
//创建任务堆栈空间
OS_STK LED1_TASK_STK[LED1_STK_SIZE];
void start_task(void *pdata);
void led1_task(void *pdata);
int main(){
//其他外设...初始化
//...
OSInit();//os初始化ucos操作系统
//创建任务
OSTaskCreate(
start_task,//指向任务代码的指针(函数名)
(void *) 0, //没什么可传的,传空。任务开始时,传递给任务参数的指针
(OS_STK *) &START_TASK_STK[START_STK_SIZE-1], //分配给任务堆栈的栈顶指针
START_TASK_PRIO //分配给任务的优先级
);
//启动ucos操作系统
OSStart();
}
void start_task(void *pdata){
//创建启动发光二极管闪烁任务
OS_CPU_SR cpu_sr = 0;
pdata = pdata;//不这么写有的编译器会编译不通过,就先这么写着
OSStatInit();//初始化统计任务
//进入临界区
OS_ENTER_CAITTCAL();
OSTaskCreate(
led1_task,//指向任务代码的指针(函数名)
(void *) 0, //没什么可传的,传空。任务开始时,传递给任务参数的指针
(OS_STK *) &LED1_TASK_STK[LED1_STK_SIZE-1], //分配给任务堆栈的栈顶指针
LED1_TASK_PRIO //分配给任务的优先级
);
//开始任务创建使用之后就没有用了,我们把它挂起
OSTaskSupend(START_TASK_PRIO);//每个函数的优先级都是唯一的,所以可以用优先级来定位到函数
OS_EXIT_CAITTCAL();//退出临界区
}
void led1_task(void *pdata){
pdata = pdata;//不这么写有的编译器会编译不通过,就先这么写着
while(1){
LED1 = 0;
delay_ms(500);//0.5s //ucos的延时中,cpu放掉控制权,可以执行其他任务
LED1 = 1;
delay_ms(500);//0.5s
}
}
3. 创建多个任务点亮多个二极管
创建多个任务,每个任务函数体中都点亮一个发光二极管,并间隔0.5s状态取反实现闪烁。
void start_task(void *pdata){
//创建启动发光二极管闪烁任务
OS_CPU_SR cpu_sr = 0;
pdata = pdata;//不这么写有的编译器会编译不通过,就先这么写着
OSStatInit();//初始化统计任务
//进入临界区
OS_ENTER_CAITTCAL();
//-----------创建任务1-------------------------
OSTaskCreate(
led1_task,//指向任务代码的指针(函数名)
(void *) 0, //没什么可传的,传空。任务开始时,传递给任务参数的指针
(OS_STK *) &LED1_TASK_STK[LED1_STK_SIZE-1], //分配给任务堆栈的栈顶指针
LED1_TASK_PRIO //分配给任务的优先级
);
//-----------创建任务2-------------------------
OSTaskCreate(
led2_task,//指向任务代码的指针(函数名)
(void *) 0, //没什么可传的,传空。任务开始时,传递给任务参数的指针
(OS_STK *) &LED2_TASK_STK[LED2_STK_SIZE-1], //分配给任务堆栈的栈顶指针
LED2_TASK_PRIO //分配给任务的优先级
);
//-----------创建任务3-------------------------
OSTaskCreate(
led3_task,//指向任务代码的指针(函数名)
(void *) 0, //没什么可传的,传空。任务开始时,传递给任务参数的指针
(OS_STK *) &LED3_TASK_STK[LED3_STK_SIZE-1], //分配给任务堆栈的栈顶指针
LED3_TASK_PRIO //分配给任务的优先级
);
//开始任务创建使用之后就没有用了,我们把它挂起
OSTaskSupend(START_TASK_PRIO);//每个函数的优先级都是唯一的,所以可以用优先级来定位到函数
OS_EXIT_CAITTCAL();//退出临界区
}
4.任务的创建、删除、挂起、恢复
注:之前有讲过任务的创建、删除,但这里的创建指的是运行期间的任务创建、删除。
无条件挂起一个任务:OSTaskSuspend(任务优先级);
当前任务挂起后,只有其他任务才能唤醒被挂起的任务。任务挂起后,系统会重新进行任务调度,运行下一个优先级最高的就绪任务。 唤醒挂起任务需要调用函数
OSTaskResume(任务优先级)
。
删除一个ucos-II中的任务:OSTaskDelReq(任务优先级);
在uc/os中删除一个任务,一般情况下是自己删除自己。如果任务A直接调用OSTaskDel (INT8U prio) 函数来直接删除任务B,任务B占用的资源会得不到释放,系统到最后会没有内存可用。所以当任务A想要删除任务B的时候,会先调用OSTaskDelReq(INT8U prio)函数来请求任务B删除自己,比如B的优先级是10,即调用
OSTaskDelReaq(10);
任务B会通过
OSTaskDelRea (INT8U prio)
函数来查询是否有其他任务请求删除自己。若有,则释放自己占用的资源、内存,调用
OSTaskDel(OS_PRIO_SELF)
来删除自己。
示例:按下按键key1,led1任务挂起;按下key2,led1任务恢复;按下key3,删除led2任务(任务睡眠,无法恢复);程序在点亮多个二极管程序上继续。
/*-------------------key任务---------------*/
OSTaskCreate(
key_task, //指向任务代码的指针
(void *)0, //任务开始执行时,传递给任务参数的指针
(OS_STK *) & KEY_TASK_STK[KEY_STK_SIZE-1],//分配给任务堆栈的栈顶指针
KEY_TASK_PRIO//分配给任务的优先级
);
void key_task(void *pdata)
{
pdata=pdata;//防止编译器报错
while(1)
{
/*k1按下*/
if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_5)==Bit_RESET)//如果按键K1按下
{
OSTaskSuspend(LED1_TASK_PRIO);//挂起LED1任务,LED1停止闪烁
}
/*k2按下*/
if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_1)==Bit_RESET)//如果按键K2按下
{
OSTaskResume(LED1_TASK_PRIO);//恢复LED1任务,LED1恢复闪烁
}
/*k3按下*/
if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_2)==Bit_RESET)//如果按键K3按下
{
OSTaskDelReq(LED2_TASK_PRIO);//发出删除LED2任务请求,任务睡眠,无法恢复(需要现在LED2任务中加入判断删除)
}
/*k4按下*/
if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_3)==Bit_RESET)//如果按键K4按下
{
/*创建LED2任务*/
OSTaskCreate(
led2_task, //指向任务代码的指针
(void *)0, //任务开始执行时,传递给任务参数的指针
(OS_STK *) & LED2_TASK_STK[LED2_STK_SIZE-1],//分配给任务堆栈的栈顶指针
LED2_TASK_PRIO//分配给任务的优先级
);
}
delay_ms(10);
}
}
实验现象: 按下key1,此时led1挂起(亮的时候挂起就常亮了);按下key2,此时led1恢复任务,正常闪烁;按下key3,删除led2任务,任务led2进入睡眠;按下key4,创建led2任务。
5.信号量
信号量、邮箱、消息队列是被称为时间的中间环节。任务依赖这些中间环节来实现任务之间的通信。
前置的一些概念
事件控制块ECB:使用事件控制块ECB来描述信号量、邮箱、消息队列这些事件。事件控制块包含等待任务表在内的所有有关事件的数据(事件的类型、信号量计数器、消息或消息队列的指针、等待事件的任务组、任务等待列表)。
把一个任务置于等待状态要调用OS_EventTaskWait()
函数。函数原型为:
void OS_EventTaskWait(
OS_EVENT *pevent //事件控制块的指针,就是你创建的事件
)
OS_EventTaskWait
将在任务调用函数OSXXXPend()
请求一个事件时,被OSXXXPend
调用。
如果一个正在等待的任务具备了可以运行的条件,那就要使它进入就绪状态,要调用OS_EventTaskRdy()
函数。该函数作用是把调用 这个函数的任务 在任务等待列表中 的位置 清0(解除等待状态)后,再把任务在任务就绪表中对应的位置1,然后引发一次任务调度。 函数原型如下:
OS_EventTaskRdy(
OS_EVENT *pevent, //事件控制块的指针
void *msg, //未使用
INT8U msk //清除TCB状态标志掩码
);
OS_EventTaskRdy()
函数将在任务调用函数OSXXXPost()
发送一个事件时,被函数OSXXXPost
调用。
一个正在等待事件的任务已经超过了等待的事件,却因没有获取事件等原因而未具备可以运行的条件,却又要使它进入就绪状态,这时要调用OS_EventIO函数。该函数将在任务调用OSXXXPend()
请求一个事件时调用。
信号量
信号量为操作系统用于处理临界区和实现进程间同步提供了一种有效的机制。 操作系统中常用P操作和V操作来表明信号量的行为。
信号量:在多任务系统中,用于控制共享资源的使用权、标志事件的发生、使两个任务的行为同步。 ucos中信号量由两部分组成:信号量的计数值(可以为二进制也可以为其他整数)和等待该信号任务的等待任务表。
ucos通过OSSemPend()
和OSSemPost()
来支持信号量的P、V操作。P操作减少信号量的值,新的信号量的值不大于0会造成操作阻塞;v操作则增加信号量的值。
声明信号量
OS_EVENT * Fun_semp; //声明信号量
创建信号量
函数原型:OS_EVENT * OSSemCreate( INT16U cnt //信号量计数器初值 );
创建信号量:Fun_Semp = OSSemCreate(1);
请求信号量(P操作):
等待信号量,执行等待信号量的时候,也会让出cpu来执行其他程序。
函数原型如下:
void OSSemPend(OS_EVENT * pevent, //信号量的指针
INT16U timeout, //timeout为等待时限
INT8U * err); //错误信息
注意:若timeout为0,则代表任务的等待时间为无限长
如果不希望任务等待则调用以下函数:
INT16U OSSemAccept(OS_EVENT * pevent);
发送信号量(V操作)
INT8U OSSemPost(OS_EVENT * pevent);
删除信号量
如果应用程序不需要某个信号量,那么可调用函数OSSemDel()
来删除该信号量。该函数的原型如下:
OS_EVENT * OSSemDel(
OS_EVENT * pevent, //信号量的指针
INT8U opt, //删除条件选项
INT8U * err //错误信息
);
其中的参数 opt 用来指明信号量的删除条件。
opt = OS_DEL_NO_PEND; //当等待任务表中没有等待任务时才删除
opt = OS_DEL_ALLWAYS; //无论等待任务表中有没有等待任务都删除
信号量查询与设置
信号量信息查询
INT8U OSSemQuery (OS_EVENT *pevent,
OS_SEM_DATA *p_sem_data);
信号量设置
void OSSemSet (OS_EVENT *pevent,
INT16U cnt,
INT8U *perr);
互斥信号量
OSMutexCreate
函数创建互斥信号量;互斥信号量通过OSMutexPend
函数请求资源;通过OSMutexPost
函数发送互斥信号量。
6 邮箱
邮箱:在两个任务间来回传递参数。消息邮箱是在两个需要通信的任务之间通过传递数据缓冲区指针的方法来进行通信的。
- 消息缓冲区:在多任务系统中,需要在任务与任务之间通过传递一个数据(这种数据叫“消息”)的方式来进行通信。存储该数据的缓冲区 称作消息缓冲区。
- 邮箱:用来传递消息缓冲区指针的数据结构 叫邮箱。
- 消息邮箱:在UCOSII中,通过事件控制块的OSEventPrt 来传递消息缓冲区指针,同时使事件控制块的成员OSEventType 为常数 OS_EVENT_TYPE_MBOX,则该事件控制块 就叫消息邮箱。
白话理解
通过邮箱传递一个数据msg来控制延时,任务A使用请求邮箱函数,等待邮箱发来的数据,收到数据后将数据拿出来,执行某些功能。任务也可以通过邮箱发送消息。
创建消息邮箱
消息邮箱指针=OSMboxCreate(消息邮箱数据块指针)
建立并初始化一个消息邮箱,在初始化时,通常将“消息邮箱数据块指针”设置为0,,如:MailBoxes_Event = OSMboxCreate( (void*)0 );
OS_EVENT *OSMboxCreate(void msg); //msg,消息的指针;函数返回值为消息邮箱的指针;
OSMboxAccept(消息邮箱指针)
返回指针不为0,则表示接收到消息邮箱的新数据,同时会清除”消息邮箱的数据块”指针,允许再次发送邮箱新数据。
请求邮箱函数
OSMboxPend(消息邮箱指针,0,&err);
*err=OS_ERR_NONE,且返回指针不为0,则表示接收到消息邮箱的新数据,同时会清除”消息邮箱的数据块”指针,允许再次发送邮箱新数据。
//作用: 查看邮箱指针OSEventPrt是否为NULL,如果不是NULL就把邮箱中的消息指针返回给调用函数的任务,同时用OS_NO_ERR 通过函数
//的参数err通知任务获取消息成功。如果邮箱指针为NULL,则使任务进入等待状态,并引发1次任务调度。
void *OSMboxPend(OS_EVENT *pevent,INT16U timeout,INT8U *err);
//pevent,请求邮箱指针;timeout,等待时限;err,错误信息;
向邮箱发送消息函数
OSMboxPost(消息邮箱指针pevent, 消息邮箱数据块指针);
返回值OS_ERR_NONE,且pevent->OSEventPtr = pmsg才表示消息邮箱发送成功
INT8U OSMboxPost(OS EVENT *pevent,void *msg); // pevent,消息邮箱的指针;msg ,消息指针;
OSMboxPostOpt(消息邮箱指针pevent, 消息邮箱数据块指针, opt)
返回值OS_ERR_NONE,且pevent->OSEventPtr = pmsg才表示消息邮箱发送成功
删除消息邮箱
OSMboxDel()
删除消息邮箱(消息邮箱指针、删除条件、出错代码指针)
OS_EVENT *OSMboxDel(OS_EVENT *pevent,INT8U opt,INT8U *err); //pevent,消息邮箱指针;opt,删除选项;err,错误信息;
查询邮箱状态函数
OSMboxQuery()
查询一个邮箱的当前状态(信号量指针、状态数据结构指针)
INT8U *OSMboxQuery(OS_EVENT *pevent,OS_MBOX_DATA *pdata); //pevent,消息邮箱指针;pdata,存放邮箱信息的结构;
7.消息队列
队列是多个邮箱的数组,可以看做是个指针数组,任务之间可以按照一定顺序以指针定义的变量来传递,即是发送一个个指针给任务,任务获得指针,来处理指向的变量。 这个方式有先进先出,后进后出。
消息队列由事件控制块、队列控制块、消息三部分组成。 信号量、邮箱、队列的最大不同在于它们发送的内容不同:
- 信号量是一个触发信号,也是一个计数器。
- 邮箱是信号量的扩展,相当于一个指钋定义的变量。
- 队列是多个邮箱的数组,可以看做是个指针数组。
邮箱只能一次发送一条信息,而队列就可以一次发送多条信息。
函数API如下:
消息队列创建
创建一个消息队列首先需要定义指针数组,然后将各个消息数据缓冲区的首地址存入这个数组中,然后调用OSQCreate()
来创建消息队列。
OSQCreate()
两个参数,一个被当做消息队列的数组,以及这个数组的大小。返回一个OS_EVENT指针。
消息队列删除
OSQDel()
删除一个消息队列之前应该删除所有需要这个消息队列的任务。
请求消息队列
目的是从消息队列中获取消息。
OSQPend(消息队列指针,等待时限,错误信息)
当timeout等待时限被设置为0的时候表示任务将永远等待。
(?linux中如果是NULL表示永远等待,而0表示不加等待直接返回,类似于轮询的模式,可能是ucosii不允许这个参数设置为null所以设定0表示永远等待?)
当消息队列中有消息可用的时候,获取其位于OSQOut指针处的消息,否则将任务挂起,等待消息到来。
消息队列发送
OSQPost()
与OSQPostFront()
与OSQPostOpt()
都是向消息队列中发送消息。OSQPost
属于FIFO模式,将传入的msg存放在读指针OSQOut
处,实现先进先出;OSQPostFront
的属于LIFO模式,将传入的msg存放在读指针OSQIn处,实现后进先出;OSQPostOpt
属于可选择模式,支持FIFO、LIFO、广播模式(让所有队列中等待的任务都处于就绪状态,否则仅仅使得)。
其他函数略,和mutex信号量的都差不多。
任务和中断服务程序都可以调用Post、PostFront、PostOpt函数,但是只有任务才能调用Del、Pend、Query。
示例:
OS_EVENT *Semaphore; //定义一个信号量
void *Msg_Group[10];//定义一个消息数组(指针数组)
//任务函数中创建消息队列,部分省略
Semaphore = OSQCreate(Msg_Group,10);
//任务发送/请求信号量即可
8.信号量集
实际任务中,任务常常要与多个事件同步,即要根据多个信号量组合作用的结果来决定任务的运行方式。ucos—II为了实现多个信号量组合的功能定义了一种特殊的数据结构–信号量集。信号量集所能管理的信号量都是一些二值信号,所有信号量集实质上是一种可以对多个输入信号进行基本逻辑运算(and/or)的组合逻辑。
信号量集的标志组:ucos-II使用标志组的结构OS_FLAG_GRP来描述信号量集。结构如下:
typedef struct{
INT8U OSFlagType; //识别是否为信号集的标志
voiod *OSFlagWaitList; //指向等待任务链表的指针
OS_FLAGS OSFlagFlahs; //所有信号列表
}OS_FLAG_GRP;
任务可以通过OSFlagCreate()
来创建一个信号量集,通过OSFlagPost()
向信号量集发信号。
定义信号的有效状态及等待任务与信号之间的逻辑关系的常数。这部分内容可以参考信号量与信号量集。
9.内存的动态分配
内存控制块
操作系统以分区为单位来管理动态内存,而任务以内存块为单位来获得和释放动态内存。内存分区及内存块的使用情况则由内存控制块来记录。
内存分区及内存控制块的定义
在内存中定义一个内存分区及其内存块的方法非常简单,只需定义一个二维数组即可。例如:
INT8U IntMemBuf[10][10];//定义一个内存区,包含10个内存块,每个内存块由10个u8数据构成。
当然这里的定义只是在内存上划分出了分区及内存块的区域,还不是一个真正的可以动态分配的内存区,只有当把内存控制块与分区关联起来之后,系统才能对其进行相应的管理和控制,它才能是一个真正的动态内存区。
内存控制块OS_MEM的结构
#if (OS_MEM_EN > 0u) && (OS_MAX_MEM_PART > 0u)
typedef struct os_mem { /* MEMORY CONTROL BLOCK */
void *OSMemAddr; /* 内存分区的指针 */
void *OSMemFreeList; /* 内存控制块链表指针 */
INT32U OSMemBlkSize; /* 内存块的长度 */
INT32U OSMemNBlks; /* 分区内内存块的数目 */
INT32U OSMemNFree; /* 分区内当前可分配的内存块的数目 */
#if OS_MEM_NAME_EN > 0u
INT8U *OSMemName; /* Memory partition name */
#endif
} OS_MEM;
动态内存的管理
划分了欲使用的分区和内存块之后,应用程序可以通过
OSMemCreate();
建立一个内存分区,函数原型如下:
OS_MEM *OSMemCreate (void *addr, //内存分区的起始地址
INT32U nblks, //分区中内存块的数目
INT32U blksize, //每个内存块的字节数
INT8U *perr) //错误信息
应用程序需要一个内存块时,通过OSMemGet();
请求获得内存块,函数原型如下:
void *OSMemGet (OS_MEM *pmem, //内存分区的指针
INT8U *perr) //错误信息
当不再使用一个内存块时,必须释放内存块,可以通过OSMemPut();
函数,原型如下:
INT8U OSMemPut (OS_MEM *pmem, //内存分区的指针
void *pblk) //待释放内存块的指针
查询动态内存分区状态函数OSMemQuery();
#if OS_MEM_QUERY_EN > 0u
INT8U OSMemQuery (OS_MEM *pmem, //待查询的内存控制块的指针
OS_MEM_DATA *p_mem_data); //存放分区状态信息的结构的指针
其中p_mem_data是一个OS_MEM_DATA 类型的结构。定义如下:
typedef struct os_mem_data {
void *OSAddr; /* 内存分区的指针 */
void *OSFreeList; /* 分区内内存块链表的头指针 */
INT32U OSBlkSize; /* 内存块的长度 */
INT32U OSNBlks; /* 分区内内存块的数目 */
INT32U OSNFree; /* 分区内空闲内存块的数目 */
INT32U OSNUsed; /* 已被分配的内存块数目 */
} OS_MEM_DATA;