时间片轮询法

时间:2021-11-02 18:14:05

时间片轮询法

时间片轮询法是一种比较简单易用的系统架构之一,它对于系统中的任务调度算法是分时处理。核心思路是把 CPU 的时间分时给各个任务使用。我们常用的定时方法是定时器,把调度器放在定时中,可以简单的实现时间片轮询法。

需要注意的是,这种方法的前提是执行的 每个任务都是短小精悍的,要不然一个任务执行的时间过长,大于其它任务设置的时间片值,那其它任务就无法保证按它预设的时间片来执行。

尤其需要注意任务中延时的使用,可能会产生不可预料的结果。如果任务内部需要延时的时候,或者说单个任务过长,需要保存任务执行到一半的状态,建议使用状态机切割长任务。

时间片轮询法架构

一个时间片轮询应用程序的架构是非常简单的,包括一个任务结构体,一个中断处理函数,一个轮询执行任务函数。

设计一个结构体:

// 任务结构
typedef struct {
    uint8_t task_id;                  // 任务 ID
    uint16_t task_interval;             // 任务运行间隔时间
    void (*task_entry)(void);           // 要运行的任务
    volatile uint16_t task_tick_ms;     // 计时器
}task_info_t;

定时器复用和中断处理

定时器可以是任意的定时器,这里采用系统滴答定时器 (systick) 来定时。systick 的配置就不细讲,假设定时器的定时中断为 10ms(可以自行设定,中断过于频繁效率就低,中断太长,实时性差)。

timing_task_tick 函数就相当于中断服务函数,需要在定时器的中断服务函数中调用此函数。

// 为每个任务计时,每次中断加 10ms
void timing_task_tick(void)
{
    uint8_t task_index = 0;

    while (task_index < ARRAY_SIZE(timing_task_array))
    {
        timing_task_array[task_index].task_tick_ms += CONFIG_SYSTEM_TICK_PERIOD_MS;     // 每次加一个 systick 周期,即 10ms
        task_index++;
    }
}

时间片轮询实例

下面我就就说说怎样应用吧,假设我们有三个任务:时钟显示,按键扫描,和工作状态显示。

定义一个上面定义的那种结构体数组

// 计算任务个数
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))

// 定义了 3 个任务
static task_info_t timing_task_array[] =
{
    {0, 100, task_disp_clock_running, 0},    // 显示时钟
    {1, 20, task_scan_key_running, 0},      // 按键扫描
    {2, 30, task_disp_ws_running, 0},       // 工作状态显示
};

在定义变量时,我们已经初始化了值,这些值的初始化,非常重要,跟具体的执行时间优先级等都有关系,这个需要自己掌握。

  1. 大概意思是,我们有三个任务,每 1s 执行一下时钟显示,因为我们的时钟最小单位是 1s,所以在秒变化后才显示一次就够了。

  2. 由于按键在按下时会参数抖动,而我们知道一般按键的抖动大概是 20ms,那么我们在顺序执行的函数中一般是延伸 20ms,而这里我们每 20ms 扫描一次,是非常不错的出来,即达到了消抖的目的,也不会漏掉按键输入。

  3. 为了能够显示按键后的其他提示和工作界面,我们这里设计每 30ms 显示一次,如果你觉得反应慢了,你可以让这些值小一点。后面的名称是对应的函数名,你必须在应用程序中编写这函数名称和这三个一样的任务。

编写任务函数

//Description : 显示任务
void task_disp_clock_running(void)
{

}

//Description : 扫描任务
void task_scan_key_running(void)
{

}

//Description : 工作状态显示
void task_disp_ws_running(void)
{

}

任务处理

// 任务计划表,轮询执行任务
void timing_task_scheduler(void)
{
    uint8_t task_index = 0;

    while (1)
    {
        for (task_index = 0 ; task_index < ARRAY_SIZE(timing_task_array); task_index++)
        {
            if (timing_task_array[task_index].task_tick_ms >= timing_task_array[task_index].task_interval)
            {
                timing_task_array[task_index].task_tick_ms = 0;
                timing_task_array[task_index].task_entry();
            }
        }
    }
}

程序陷入死循环,依次判断每个任务是否符合执行要求。如果是,则执行相应的任务函数;否则等待计时。