先简单介绍一下MQX:消息队列执行(Message Queue eXecutive,MQX)是Precise Software Technologies公司1989年开发的一款嵌入式实时操作系统。2009年飞思卡尔收购后,开放源代码。MQX由核心组件(必选)和可选性组件构成。如下图所示:
下面直接上手开始使用MQX,这样学习起来更快。准备工作如下:
硬件:Freescale FRDM-K64F开发板一块,MicroUSB线一根
软件:IAR或者Keil,FSLMQXOS_4_1_0_FRDMK64F_GA.exe,驱动,软件安装很简单,就不再介绍了。
在MQX安装目录中,进入<install_dir>\build\frdmk64f\iar,打开build_libs.eww,然后点击Project-Batch build,
编译完之后,然后选择Project-Add Existing Project,找到<install_dir>\mqx\examples\hello\build\iar\hello_frdmk64f\hello_frdmk64f.ewp ,然后编译,之后将生成的bin文件下载到开发板中,使用串口调试助手,可以观察到如下结果:
下面分析下这个例子。
从main函数开始着手:
int main
(
void
)
{ /* Body */
extern const MQX_INITIALIZATION_STRUCT MQX_init_struct;
/* Start MQX */
_mqx( (MQX_INITIALIZATION_STRUCT_PTR) &MQX_init_struct );
return 0;
} /* Endbody */
main函数调用了_mqx() 函数,该函数以MQX初始化结构为参数。基于这一结构中的参数,MQX
完成如下任务:
--------载入并初始化MQX经常使用的数据,包括默认内存池,就绪队列,中断堆栈和任务栈;
--------初始化硬件(如芯片选择);
--------开启定时器;
--------设置默认的时间片值;
--------生成空闲任务,当没有其它任务就绪时将被激活;
--------生成由任务模板列表定义为自启动的任务;
--------开始调度多任务。
MQX初始化结构中定义应用程序和目标硬件的参数如下:
typedef struct mqx_initialization_struct
{
/*!
* \brief Application-unique processor number of the processor.
*
* Minimum is 1, maximum is 255. (Processor number 0 is reserved and is used
* by tasks to indicate their local processor.)
*/
_mqx_uint PROCESSOR_NUMBER;
/*! \brief Lowest address from which MQX allocates dynamic memory and task stacks. */
void *START_OF_KERNEL_MEMORY;
/*!
* \brief Highest address from which MQX allocates dynamic memory and task stacks.
*
* It is the application's responsibility to allocate enough memory for all
* tasks.
*/
void *END_OF_KERNEL_MEMORY;
/*!
* \brief The size of the interrupt stack.
*
* This is the maximum amount of stack space used by all interrupt handlers.
*
*/
_mqx_uint INTERRUPT_STACK_SIZE;
/*! \brief Pointer to the task template list for the processor.
*
* The default name for the list is MQX_template_list[].
*/
TASK_TEMPLATE_STRUCT_PTR TASK_TEMPLATE_LIST;
/*!
* \brief The maximum hardware interrupt priority level of MQX.
*
* All tasks and interrupts run at lower priority (Applicable to CPUs with
* multiple interrupt levels only).
*/
_mqx_uint MQX_HARDWARE_INTERRUPT_LEVEL_MAX;
/*! \brief Maximum number of message pools. */
_mqx_uint MAX_MSGPOOLS;
/*!
* \brief Maximum number of message queues. Minimum is MSGQ_FIRST_USER_QUEUE,
* maximum is 255.
*/
_mqx_uint MAX_MSGQS;
/*!
* \brief A string indicating which device is to be used as
* the default (console) I/O for the processor.
*/
char *IO_CHANNEL;
/*! \brief The open flags to pass to the default I/O device */
char *IO_OPEN_MODE;
/* reserved fields for later use */
/* _mqx_uint RESERVED[2];*/
/*! \brief The location of the interrupt stack (if not NULL). */
void *INTERRUPT_STACK_LOCATION;
/*! \brief Reserved field.
*
* Reserved for future enhancements to MQX; each element of the array must be
* initialized to 0.
*/
_mqx_uint RESERVED[1];
#if MQX_ENABLE_USER_MODE
/*! \brief Lowest adress of kernel area. */
void *START_OF_KERNEL_AREA;
/*! \brief Highest adress of kernel area. */
void *END_OF_KERNEL_AREA;
/*! \brief Lowest adress of default data section (.data .bss). */
void *START_OF_USER_DEFAULT_MEMORY;
/*! \brief Highest adress of default data section (.data .bss). */
void *END_OF_USER_DEFAULT_MEMORY;
/*! \brief Lowest adress of user heap - mem alloc. */
void *START_OF_USER_HEAP;
/*! \brief Highest adress of user heap - mem alloc. */
void *END_OF_USER_HEAP;
/*! \brief Lowest adress of user read/write memory. */
void *START_OF_USER_RW_MEMORY;
/*! \brief Highest adress of user read/write memory. */
void *END_OF_USER_RW_MEMORY;
/*! \brief Lowest adress of user read-only memory. */
void *START_OF_USER_RO_MEMORY;
/*! \brief Highest adress of user read-only memory. */
void *END_OF_USER_RO_MEMORY;
/*! \brief Lowest adress of user no access memory. */
void *START_OF_USER_NO_MEMORY;
/*! \brief Highest adress of user no access memory. */
void *END_OF_USER_NO_MEMORY;
/*! \brief Maximal priority of user's tasks. */
_mqx_uint MAX_USER_TASK_PRIORITY;
/*! \brief Maximal number of user's tasks. */
_mqx_uint MAX_USER_TASK_COUNT;
#endif
} MQX_INITIALIZATION_STRUCT, * MQX_INITIALIZATION_STRUCT_PTR;
main()函数的实参MQX_init_struct的定义如下:
const MQX_INITIALIZATION_STRUCT MQX_init_struct =
{
/* PROCESSOR_NUMBER */ BSP_DEFAULT_PROCESSOR_NUMBER,
/* START_OF_KERNEL_MEMORY */ BSP_DEFAULT_START_OF_KERNEL_MEMORY,
/* END_OF_KERNEL_MEMORY */ BSP_DEFAULT_END_OF_KERNEL_MEMORY,
/* INTERRUPT_STACK_SIZE */ BSP_DEFAULT_INTERRUPT_STACK_SIZE,
/* TASK_TEMPLATE_LIST */ MQX_template_list,
/* MQX_HARDWARE_INTERRUPT_LEVEL_MAX*/ BSP_DEFAULT_MQX_HARDWARE_INTERRUPT_LEVEL_MAX,
/* MAX_MSGPOOLS */ BSP_DEFAULT_MAX_MSGPOOLS,
/* MAX_MSGQS */ BSP_DEFAULT_MAX_MSGQS,
/* IO_CHANNEL */ BSP_DEFAULT_IO_CHANNEL,
/* IO_OPEN_MODE */ BSP_DEFAULT_IO_OPEN_MODE,
0,
0,
#if MQX_ENABLE_USER_MODE
BSP_DEFAULT_START_OF_KERNEL_AREA,
BSP_DEFAULT_END_OF_KERNEL_AREA,
BSP_DEFAULT_START_OF_USER_DEFAULT_MEMORY,
BSP_DEFAULT_END_OF_USER_DEFAULT_MEMORY,
BSP_DEFAULT_START_OF_USER_HEAP,
BSP_DEFAULT_END_OF_USER_HEAP,
BSP_DEFAULT_START_OF_USER_RW_MEMORY,
BSP_DEFAULT_END_OF_USER_RW_MEMORY,
BSP_DEFAULT_START_OF_USER_RO_MEMORY,
BSP_DEFAULT_END_OF_USER_RO_MEMORY,
BSP_DEFAULT_START_OF_USER_NO_MEMORY,
BSP_DEFAULT_END_OF_USER_NO_MEMORY,
BSP_DEFAULT_MAX_USER_TASK_PRIORITY,
BSP_DEFAULT_MAX_USER_TASK_COUNT,
#endif
};
重点看一下上述结构的第五个参数任务模板列表,
typedef struct task_template_struct
{
_mqx_uint TASK_TEMPLATE_INDEX;
void _CODE_PTR_ TASK_ADDRESS)(uint_32);
_mem_size TASK_STACKSIZE;
_mqx_uint TASK_PRIORITY;
char _PTR_ TASK_NAME;
_mqx_uint TASK_ATTRIBUTES;
uint_32 CREATION_PARAMETER;
_mqx_uint DEFAULT_TIME_SLICE;
} TASK_TEMPLATE_STRUCT, _PTR_ TASK_TEMPLATE_STRUCT_PTR;
对于本例子,任务模板列表如下:
const TASK_TEMPLATE_STRUCT MQX_template_list[] =
{
/* Task Index, Function, Stack, Priority, Name, Attributes, Param, Time Slice */
{ HELLO_TASK, hello_task, 1500, 8, "hello", MQX_AUTO_START_TASK, 0, 0 },
{ 0 }
};
对于本例子,打印Hello World是一个自启动任务,该任务优先级为8,hell_task()函数是任务的入口,栈的大小为1500.
上面的例子是在MQX初始化时自启动任务,MQX同样可以在应用程序运行时生成、管理或终止任务。
找到<install_dir>\mqx\examples\hello2\build\iar\hello2_frdmk64f\hello_frdmk64f.ewp ,这个例子的相关代码如下:
const TASK_TEMPLATE_STRUCT MQX_template_list[] =
{
/* Task Index, Function, Stack, Priority, Name, Attributes, Param, Time Slice */
{ WORLD_TASK, world_task, 1000, 9, "world", MQX_AUTO_START_TASK, 0, 0 },
{ HELLO_TASK, hello_task, 1000, 8, "hello", 0, 0, 0 },
{ 0 }
};
/*TASK*-----------------------------------------------------
*
* Task Name : world_task
* Comments :
* This task creates hello_task and then prints " World ".
*
*END*-----------------------------------------------------*/
void world_task
(
uint32_t initial_data
)
{
_task_id hello_task_id;
hello_task_id = _task_create(0, HELLO_TASK, 0);
if (hello_task_id == MQX_NULL_TASK_ID) {
printf ("\n Could not create hello_task\n");
} else {
printf(" World \n");
}
_task_block();
}
/*TASK*-----------------------------------------------------
*
* Task Name : hello_task
* Comments :
* This task prints " Hello".
*
*END*-----------------------------------------------------*/
void hello_task
(
uint32_t initial_data
)
{
printf("\n Hello\n");
_task_block();
}
/* EOF */
编译之后的运行结构如下:
Hello
World
如果修改hello 任务的优先级为9或者更大,那么运行结果是:
World
Hello
当优先级不同时,打印的先后顺序不同,原因是什么呢?
对于第一种情况,即Hello的优先级高于World时,当调用_task_create()时,Hello任务变成Ready状态,由于它的优先级高于World,所以它变为Active(这时Hello任务变为Ready状态),这样它就会打印输出Hello,之后它变为Blocked状态,然后World_task任务将由Ready变为Active状态并检查hello_task任务是否成功创建。如果是,world_task将输出World;否则,world_task将输出一个错
误信息。最后world_task 也变为阻塞状态。
对于第二种情况,即Hello的优先级等于或低于World时,当调用_task_create()时,Hello任务变成Ready状态,由于它的优先级等于或低于World,所以它这时无法变为Active。World_task任务这时依然是Active状态,它检查hello_task任务是否成功创建。如果是,world_task将输出World;否则,world_task将输出一个错
误信息,world_task 最后变为阻塞状态。这时处于Ready状态的Hello task变为Active状态,打印出Hello。
MQX中task的四种状态如下:
做完这个实验之后,我突然想试一下,在第一种情况下输出Hello World之后,让Hello 任务再执行一次,于是我考虑使用_task_ready()函数,本意是让处于blocked状态的Hello task 重新变为Ready 状态并Active。我做的改进是:
/*TASK*-----------------------------------------------------
*
* Task Name : world_task
* Comments :
* This task creates hello_task and then prints " World ".
*
*END*-----------------------------------------------------*/
void world_task
(
uint32_t initial_data
)
{
_task_id hello_task_id;
hello_task_id = _task_create(0, HELLO_TASK, 0);
if (hello_task_id == MQX_NULL_TASK_ID) {
printf ("\n Could not create hello_task\n");
} else {
printf(" World \n");
}
// added by wenxue ,want to use _task_ready()
_task_ready(_task_get_td(hello_task_id)); // 添加了这么一条语句</span>
_task_block();
}
/*TASK*-----------------------------------------------------
*
* Task Name : hello_task
* Comments :
* This task prints " Hello".
*
*END*-----------------------------------------------------*/
void hello_task
(
uint32_t initial_data
)
{
printf("\n Hello\n");
_task_block();
}
/* EOF */
但是结果还是只输出Hello World。而不是我原本期望的Hello World Hello. 我反复的尝试与思考,花了几个小时的时间,最后终于找出原因了。
原因是Hello 这个任务只有两条语句,即先打印,然后就让任务block。所以就算让它处于Ready状态,它也什么事情都不做了。
将hello 任务的代码改为:
void hello_task
(
uint32_t initial_data
)
{
// modified by wenxue,add while(1)
while(1)
{
printf("\n Hello\n");
_task_block();
}
}
就可以打印出Hello World Hello 了。
对于第二种情况,代码改动如下:
const TASK_TEMPLATE_STRUCT MQX_template_list[] =
{
/* Task Index, Function, Stack, Priority, Name, Attributes, Param, Time Slice */
{ WORLD_TASK, world_task, 1000, 9, "world", MQX_AUTO_START_TASK, 0, 0 },
{ HELLO_TASK, hello_task, 1000, 10, "hello", 0, 0, 0 },
{ 0 }
};
/*TASK*-----------------------------------------------------
*
* Task Name : world_task
* Comments :
* This task creates hello_task and then prints " World ".
*
*END*-----------------------------------------------------*/
void world_task
(
uint32_t initial_data
)
{
_task_id hello_task_id;
hello_task_id = _task_create(0, HELLO_TASK, 0);
while(1)
{
if (hello_task_id == MQX_NULL_TASK_ID) {
printf ("\n Could not create hello_task\n");
} else {
printf(" World \n");
}
_task_block();
}
}
/*TASK*-----------------------------------------------------
*
* Task Name : hello_task
* Comments :
* This task prints " Hello".
*
*END*-----------------------------------------------------*/
void hello_task
(
uint32_t initial_data
)
{
// modified by wenxue,add while(1)
printf("\n Hello\n");
// added by wenxue ,want to use _task_ready()
_task_ready(_task_get_td(_task_get_id_from_name("world")));
_task_block();
}
/* EOF */
这样打印的结果是World Hello World
上面的例子其实使用的是MQX默认的调度策略:FIFO,还有另外两种调度策略,分别为轮询和任务队列。这两种策略还需要通过例子学习一下。