最近看了很多关于调度方面的文章,看的云里雾里,现整理如下,还请大神们多多指教。
目前MCU大部分是不上操作系统的,基本都是裸编。也就是说,我们自己要去管理任务的调度,举个大家常用的例子:
main()
{
//初始化芯片
。。。
//初始化变量,或者其他功能
while(1)
{
task1();
task2();
task3();
......
}
}
那么while(1)就是个最简单的调度器,切没有优先级、没有复杂的调度策略,其耗费资源少。其缺点就是,当任务多的时候,
或者某个任务执行时间长的时候,无法保证"需要及时响应的任务",无法满足任务的“实时”要求。不过可以对任务进行改造,什么意思呢?
所有任务都是按着状态机方式编写,把长的、大的任务分解为几个小的片段,每次只执行一个片段,如:
void task(void )
{
switch(state_a)
{
case 1:
......
break;
case 2:
......
break;
case 3:
......
break;
}
}
这应该就是合作式调度器一个核心思想,谁也不独占MCU,运行完一个状态就主动让出MCU。但是这种方式的状态机编程,仍然有问题,
什么问题呢?就是假如我的某个任务很复杂,状态比较多,而且状态间调转条件复杂,怎么办?
有个办法,用子状态机办法,就是我的父状态机里面嵌套子状态机,把复杂的任务分解。不过这么处理仍然有问题,就是当嵌套层数递增,所消耗的
栈也越多,同时也不利于状态复用。要解决这个问题,我感觉用指针法状态机,应该是解决本质问题的方法之一。为了引出什么是指针状态机,首先来
看一个基于时间驱动型合作式调度器的例子:
//内核数据类型
typedef struct_Core_struct{
void (*pTask)(void);//任务指针函数
_CORE_UWORD Delay;//任务执行间隔
_CORE_UWORD Period;//任务循环执行间隔
_CORE_UBYTE RunMe;//任务执行标志
}Core_Task;
/******************************************************************************
* Function: void Core_Clear_Error(void)
*
* PreCondition: None
*
* Input: None
*
* Output: None
*
* Side Effects: None
*
* Overview: 内核任务更新函数
*
* Note: 放到心跳定时器中断中
*****************************************************************************/
void Core_Update(void)
{
_CORE_UBYTE Index;
for(Index=0;Index<CORE_MAX_TASKS;Index++)
{
if(core_task_data[Index].pTask)
{
if(core_task_data[Index].Delay==0)
{
core_task_data[Index].RunMe+=0x01;
if(core_task_data[Index].Period)
{
core_task_data[Index].Delay=core_task_data[Index].Period;
}
}
else
{
core_task_data[Index].Delay-=0x01;
}
}
}
}
/******************************************************************************
* Function: _CORE_BOOL Core_User_DelayTask
*
* PreCondition: None
*
* Input: None
*
* Output: 1:成功;0:失败
*
* Side Effects: None
*
* Overview: 内核调度函数
*
* Note: 必须在main在主循环里调用(所有任务必须都是非阻塞)
*****************************************************************************/
void Core_User_Scheduling(void)
{
_CORE_UBYTE Index=0;
for(Index=0;Index<CORE_MAX_TASKS;Index++)
{
if(core_task_data[Index].RunMe)
{
if(core_task_data[Index].pTask)
{
(core_task_data[Index].pTask());
}
core_task_data[Index].RunMe-=0x01;
if(core_task_data[Index].Period==0)
{
Core_User_DelateTask_Index(Index);
}
}
}
}
这是一个基于时间片调度器关键性代码,Core_Update负责刷新任务队列,Core_User_Scheduling负责
调度。这种调度思想非常适合做周期性任务和一次性任务。假如,有一个通信LED执行灯,当有通讯时候
闪烁,通信结束则灭。那怎么实现这个任务呢?简单通过这个调度器,无法实现。我认为可以增加message
机制,可以解决这个问题。扯远了,通过这个例子,想必大家应该知道什么是基于指针的调度了。有了这个
前提,那么我们把通过改造这个调度算法,同时把任务拆分为一个子状态,同时每个子状态就是一个函数,
就可以实现指针法调度器。
下面是一个例子,例子是从其他网站摘入的代码,如果有异议,我会删除:
//! \brief task control block
typedef struct __task task_t;
//! \brief task prototype
typedef void* task_routine_t(task_t *ptTask);
/*
typedef void* task_routine_t(task_t *ptTask);用的时候task_routine_t *fnRoutine;
typedef void* (*task_routine_t)(task_t *ptTask);用的时候task_routine_t fnRoutine;
*/
//! \name task control block structure
//! @{
struct __task {
task_routine_t *fnRoutine; //!< task routine
bool bLocked;
void *pArg; //!< task argument
};
//! @}
//! \brief declare a task pool
#ifndef TASK_POOL_SIZE
#define TASK_POOL_SIZE 4
#endif
static task_t s_tTaskPool[TASK_POOL_SIZE] = {0};
bool new_task(task_routine_t *fnRoutine, void *pArg)
{
uint8_t n;
bool bResult = false;
if (NULL == fnRoutine) {
return bResult;
}
//! search for free task. As shared resource involved, atom access is required.
SAFE_ATOM_CODE(
for (n = 0; n < UBOUND(s_tTaskPool); n++) {
if (NULL == s_tTaskPool[n].fnRoutine) {
s_tTaskPool[n].fnRoutine = fnRoutine;
s_tTaskPool[n].pArg = pArg;
s_tTaskPool[n].bLocked = false;
bResult = true;
break;
}
}
)
return bResult;
}
bool scheduler(void)
{
static uint8_t s_tTaskCounter = 0;
static uint8_t s_tFreeCounter = 0;
task_t *ptTask;
//! access shared resource, atom access is required
SAFE_ATOM_CODE(
ptTask = &s_tTaskPool[s_tTaskCounter++];
if (s_tTaskCounter >= UBOUND(s_tTaskPool)) {
s_tTaskCounter = 0;
}
if (NULL != ptTask->fnRoutine) {
s_tFreeCounter = 0;
if (!ptTask->bLocked) {
ptTask->bLocked = true;
} else {
ptTask = NULL;
}
} else {
s_tFreeCounter++;
if (UBOUND(s_tTaskPool) <= s_tFreeCounter) {
s_tFreeCounter = UBOUND(s_tTaskPool);
EXIT_SAFE_ATOM_CODE();
return false;
}
ptTask = NULL;
}
)
if (NULL != ptTask) {
ptTask->fnRoutine = (task_routine_t *)ptTask->fnRoutine(ptTask);
SAFE_ATOM_CODE(
ptTask->bLocked = false;
)
}
return true;
}
static void idle_task(void)
{
sleep();
}
int main(void)
{
...
while(1) {
if (!scheduler()) {
//! return false means system is idle, run idle to enter sleep mode.
idle_task();
}
}
return 0;
}
//! the most amazing part is here
/*! \note this is a simple demonstration for the fact that the scheduler can works well in
*! real multi-task system from a simple front/back-end system to multi-task OS environment.
*! Is it useful? *^_^* Try to think the multi-core and multi-thread programmng model.
*/
ISR(TIM0_COMPA_vect)
{
//! 1ms compare match interrupt service routine
//! we can call scheduler both at super loop and ISR simultaneously. this is useful!
scheduler();
}
...
ISR(ADC_vect)
{
...
scheduler();
}
...