garfield

【原创】飞思卡尔MQX操作系统任务调度分析

0
阅读(4151)

MQX BSP 即板级支持包,是指针对某一特定的板子的系统软件包,包括MQX 操作系统内核,MFS 文件系统(如果需要支持文件系统功能的话),RTCS 协议栈(如果需要)、USB 协议栈(如果需要)以及板子上所有硬件的驱动程序。Freescale 公司针对主要的coldfire 处理器,开发了比较完整的驱动程序,基本上包含了所有coldfire 芯片集成的片上模块。因此,在大多数情况下,我们都无需开发驱动程序。
Freescale 提供了多个其官方评估板的BSP。

MQX 提供了以下几种方式的任务调度:
 FIFO(先进先出)
 轮询
 使用任务队列
你可以分别对每个任务和处理器设置成FIFO 或者轮询的调度方式。FIFO 调度或轮询调度的任务任意组合最终构成了应用程序。

1.FIFO 调度
FIFO 是默认的任务调度方式。在此种方式下,下一个运行的任务是具有最高优先级且等待时间最长的那个。当发生以下任意一种情况时,唤醒的任务停止运行:
 (1)由于调用了MQX 阻塞功能函数,唤醒的任务主动放弃处理器。
 (2)产生了一个比已唤醒任务优先级高的中断。
 (3)一个比唤醒任务优先级高的任务准备就绪。
可以用函数_task_set_priority()改变任务优先级。
_task_get_priority():获取任务优先级。
_task_set_priority():设置任务优先级。
函数原型:
source\kernel\ta_prio.c
_mqx_uint _task_get_priority(
_task_id task_id,
_mqx_uint_ptr priority_ptr)
_mqx_uint _task_set_priority(
_task_id task_id,
_mqx_uint new_priority,
_mqx_uint_ptr old_priority_ptr)
参数:task_id[输入]——以下情况之一:
(1)想要设置或者获取信息的那个任务的ID 号;
(2)MQX_NULL_TASK_ID(用正在调用的任务)
(3)priority_ptr[输出]——指向优先级的指针
(4)new_priority[输入]——新优先级
(5)old_priority_ptr[输出]——指向先前优先级的指针
返回值:MQX_OK
错误——MQX_INVALID_PARAMETER:新优先级的数值比最低允许优先级的数值还要大;
MQX_INVALID_TASK_ID:task_id 无效。
说明:MQX 可以提高正在等待信号量或互斥体的任务的优先级。
示例:提高当前任务优先级
_task_get_priority(_task_get_id(), &priority);

if (priority > 0) {
priority--;
if (_task_set_priority(_task_get_id(), priority, &temp) = MQX_OK)
...
}
2.轮询调度
轮询调度与FIFO调度相似,但是它还有额外限制:轮询的任务有最长时间限制(时间片),在此时间内该任务可被唤醒。只有在任务模板中已设定MQX_TIME_SLICE_TASK属性的任务才用轮询调度。任务的时间片是由任务模板中的DEFAULT_TIME_SLICE的值来确定的。但是,如果该值是0,时间片的值就是处理器的默认值。开始时,处理器默认的时间片是定时器所设定的时间间隔的十倍。比方说大部分的BSP定时器时间间隔是5ms,那么默认时间片通常就是50ms。也可以通过调用函数_sched_set_rr_interval()或_sched_set_rr_interal_ticks()来改变默认时间片,其中传递的任务ID参数为MQX_DEFAULT_TASK_ID.
当已经唤醒的轮询任务的时间超过其时间片(过期)时,MQX会保存任务内容。然后MQX就会根据就绪队列来确定接下来唤醒哪个任务,随后将该任务唤醒。MQX将已过期的任务放在就绪队列的末尾,这样就可以开始控制就绪队列中的下一个任务。如果就绪队列中没有其他任务了,那么那个过期任务就继续运行。
用轮询调度的方式,相同优先级的任务运用处理器的时间是公平合理的。
每一个任务都可能是以下某一种逻辑状态:
(1)阻塞——由于任务在等待某个条件,它并没有准备好被唤醒。当条件具备时,任务就变为就绪状态。
(2)就绪——任务已经准备好但还没有被唤醒。这是由于该任务优先级与已唤醒任务的优先级相同或者更低。
(3)唤醒——任务正在运行。
如果一个已唤醒的任务被阻塞了或被其他任务抢先了,MQX就会进行一个派遣操作,也就是根据就绪队列来检查下面将要唤醒哪个任务。MQX将优先级最高的就绪任务唤醒。如果在同一优先级的几个任务都就绪了,在就绪队列最前面的那个任务将被唤醒。这样来看,每个就绪队列都是按照FIFO顺序排列的。
_sched_set_policy():设置调度方式
函数原型:
source\kernel\sc_spol.c
_mqx_unit _sched_set_policy{
_task_id task_id,
_mqx_uint policy}
参数:task_id[输入]——以下情况之一:
(1)将要设置其信息的任务ID号;
(2)MQX_DEFAULT_TASK_ID(为处理器设置调度方式);
(3)MQX_NULL_TASK_ID(为正在调用的任务设置调度方式)
(4)policy[输入]——新的调度方式:MQX_SCHED_FIFO或MQX_SCHED_RR
返回值:先前的调度方式(成功);
MAX_MQX_UINT(失败)
任务错误代码:MQX_SCHED_INVALID_POLICY——不允许的调度方式;

MQX_SCHED_INVALID_ID——在此处理器上无效的task_id。
特征:调用_task_set_error()来设置任务错误代码。
_sched_get_policy:获取任务的调度方式
函数原型:
source\kernel\sc_spol.c
_mqx_unit _sched_get_policy{
_task_id task_id,
_mqx_uint_ptr policy_ptr}
参数:task_id[输入]——以下情况之一:
(1)将要获取其信息的任务ID号;
(2)MQX_DEFAULT_TASK_ID(获取处理器调度的方式);
(3)MQX_NULL_TASK_ID(设置正在调用的任务的调度方式)
(4)policy_ptr[输出]——指向调度方式的指针
返回值:MQX_OK(成功)
MQX_SCHED_INVALID_TASK_ID(失败:在此处理器上不允许的task_id)
示例:设置调度方式为轮询方式并且确认更改
_mqx_uint policy;
...
policy = _sched_set_policy(_task_get_id(), MQX_SCHED_RR);
...
result = _sched_get_policy(_task_get_id(), &policy);
_sched_set_rr_interval():设置以ms为单位的时间片。
_sched_set_rr_interval_ticks():设置以系统节拍为单位的时间片。
函数原型:
source\kernel\sc_srr.c
uint32 _sched_set_rr_interval(
_task_id task_id,
uint_32 ms_interval)
source\kernel\sc_srrt.c
uint_32 _sched_set_rr_interval_ticks(
_task_id task_id,
MQX_TICK_STRUCT_PTR new_rr_interval_ptr,
MQX_TICK_STRUCT_PTR old_rr_interval_ptr)
参数:task_id[输入]:以下几种情况:
(1)将要设置其信息的任务ID号;
(2)MQX_DEFAULT_TASK_ID(为处理器设置时间片);
(3)MQX_NULL_TASK_ID(为正在调用的任务设置时间片)
(4)ms_interal[输入]——新的时间片(以ms为单位)
(5)new_rr_interal_ptr[输入]——指向新时间片的指针(以系统节拍为单位)
(6)old_rr_interal_ptr[输入]——指向原时间片的指针(以系统节拍为单位)
返回值:原来的时间片(成功);
MAX_MQX_UINT(失败)
特征:当失败时,调用函数_task_set_error()设置任务错误代码为

MQX_SCHED_INVAIL_TASK_ID.
示例:将一以唤醒任务的时间片设为50ms
uint_32 result;
...
result = _sched_set_rr_interval(task_get_id(), 50);
_sched_get_rr_interval():获取以ms为单位的时间片。
_sched_get_rr_interval_ticks():获取以系统节拍为单位的时间片。
函数原型:
source\kernel\sc_grr.c
uint_32 _sched_get_rr_interval(
_task_id task_id
uint_32_ptr ms_ptr)
source\kernel\se_grrt.c
_mqx_uint _sched_get_rr_interval_ticks(
_task_id task_id
MQX_TICK_STRUCT_PTR tick_time_ptr)
参数:task_id[输入]——以下几种情况:
(1)将要获取其信息的任务ID号;
(2)MQX_DEFAULT_TASK_ID(获取处理器的时间片);
(3)MQX_NULL_TASK_ID(获取正在调用任务的时间片)
(4)ms_ptr[输出]——指向时间片的指针(以ms为单位)
(5)tick_time_ptr[输出]——指向时间片的指针(以系统节拍为单位)
返回值:MQX_OK(成功)
MAX_MQX_UINT(_sched_get_rr_interval()失败)
任务错误代码(_sched_get_rr_interval_ticks()失败)
任务错误代码:MQX_SCHED_INVALID_PARAMETER_PTR——time_ptr为NULL.
MQX_SCHED_INVALID_TASK_ID——task_id无效
示例:
uint_32 time_slice;
...
result = _sched_get_rr_interval(_task_get_id(), &time_slice);
_sched_yield():将已唤醒任务放在就绪队列的末尾。
函数原型:
source\kernel\sc_yield.c
void _sched_yield(void)
特征:可能会派遣其他任务。
说明:这个函数可以很好的体现出来时间片的作用。如果就绪队列中没有其他任务了,那么那个唤醒任务就继续执行。
示例:
_mqx_uint counter = 0;
...
if (++counter == TIME_SLICE_COUNT) {

counter = 0;
_sched_yield();
}
_task_block():阻塞已唤醒任务。
函数原型:
source\psp\cpu_family\dispatch.assembler
void _task_block(void)
特征:派遣另一个任务。
说明:这个函数可以将已唤醒任务从任务继续队列中移除,并且设置任务描述符中STATE域的BLOCKED位为1。其他任务通过_task_ready()函数将阻塞函数变为就绪状态后,已被阻塞的函数才又开始运行。
_task_ready():将任务放入就绪队列中从而使其准备运行。
函数原型:
source\kernel\ta_rdy.c
void _task_ready(
pointer td_ptr)
参数:td_ptr[输入]——指向将要变为就绪状态的任务描述符的指针。
任务错误代码:MQX_LIVALID_TASK_ID:task_id无效。
MQX_INVALID_TASK_STATE:任务已经存在于就绪队列中。
特征:如果刚刚变为就绪状态的任务的优先级高于正在调用任务的优先级,那么MQX就唤醒新任务。或许会设置任务错误代码。
说明:这个功能函数是唯一可以把被_task_block()设置为阻塞状态的任务再变为就绪状态的方式。
示例:以下两个函数创立了一个快速、协同的任务调度机制,可以代替任务队列。
#include mqx_prv.h
#define WAIT_BLOCKED 0xF1
Restart(_task_id tid) {
TD_STRUCT_PTR td_ptr = _task_get_td(tid);
_int_disable();
if ((td_ptr != NULL) && (td_ptr->STATE == WAIT_BLOCKED)){
_task_ready(td_ptr);
}
_int_enable();
}
Wait() {
TD_STRUCT_PTR td_ptr = _task_get_td(_task_get_id());
_int_disable();
td_ptr->STATE = WAIT_BLOCKED;
_task_block();
_int_enable();
}

3 抢先
已被唤醒的任务可以被其他任务抢先。当一个高优先级的任务就绪,然后它又被唤醒,这样就是抢先了原唤醒任务。原任务还处于就绪状态,但是它已经不是一个唤醒任务了。当中断处理器使一个高优先级的任务就绪,或者是一个唤醒任务使另一个高优先级任务就绪时,也会发生任务抢先。
_task_start_preemption():使能当前任务的抢先机制。
_task_stop_preemption():关闭当前任务的抢先机制。
函数原型:
source\kernel\ta_prem.c
void _task_start_preemption(void)
void _task_stop_preemption(void)
特征:改变任务的抢先能力,仍然可以处理中断
示例程序
#include
#include
#include "main.h"
/* Task IDs */
#define MAIN_TASK 1
#define HELLO_TASK 2
#define BYE_TASK 3
extern void hello_task(uint_32);
extern void Main_task(uint_32);
extern void bye_task(uint_32);
TASK_TEMPLATE_STRUCT MQX_template_list[] =
{
{MAIN_TASK, Main_task, 1500, 8, "main",
MQX_AUTO_START_TASK|MQX_TIME_SLICE_TASK,0,0},
{HELLO_TASK, hello_task, 1500, 8, "hello_task",
MQX_TIME_SLICE_TASK,0,0},
{BYE_TASK, bye_task, 1500, 8, "bye_task",
MQX_TIME_SLICE_TASK,0,0},
{0 }
};
void Main_task(uint_32 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 for(;;)
printf("\n Main \n");
_mqx_exit(0);
}
void hello_task(uint_32 initial_data)
{
_task_id bye_task_id;
bye_task_id=_task_create(0,BYE_TASK,0);
if(bye_task_id==MQX_NULL_TASK_ID)
printf("Could not create bye_task");
for(;;)
printf("\n Hello \n");
}
void bye_task(uint_32 initial_data)
{
for(;;)
printf(“\n Bye \n”);
}
程序分析:本实例的任务模板列表中创建了三个任务,这三个任务都具有MQX_TIME_SLICE_TASK属性,时间片都为默认值,其中Main_task中创建了hello_task,hello_task中创建了bye_task,三个任务具有相同的优先级。运行时,三任务轮询调度,每个任务运行的时间都为默认时间片值。

Baidu
map