2. 定时器事件驱动周期任务调度器

任务说明

本案例实现一个软件定时器模块,支持单次(one-shot)与周期(periodic)两种模式,并集成到事件循环中统一调度。定时器到期后产生事件,由事件循环分发至对应回调处理。

软件定时器设计

定时器模块维护一个按到期时间排序的链表,每个节点包含到期时刻、周期标志、回调函数及用户参数。事件循环每次迭代时检查链表头部节点是否到期,若到期则触发回调;周期定时器到期后重新计算下次到期时间并保持链表有序,单次定时器则从链表中移除。该设计将定时器检查与事件分发解耦,使定时器事件与其它事件(如IO事件)共用同一调度循环。

软件定时器链表(按到期时间升序) Timer A 到期: 100ms 周期: 否 Timer B 到期: 250ms 周期: 100ms Timer C 到期: 500ms 周期: 否 NULL 事件循环集成 获取当前时间 tick_ms() 检查定时器链表头部 到期则触发回调 处理其它事件 IO / 信号等 定时器到期回调流程 定时器到期 调用回调函数 周期? 重新入链

代码实现


// timer.h
#ifndef TIMER_H
#define TIMER_H

#include <stdint.h>
#include <stdbool.h>

typedef void (*timer_cb_t)(void *arg);

typedef struct timer_node {
    uint32_t expire_ms;      // 到期时间
    uint32_t period_ms;      // 周期值,0表示单次
    timer_cb_t cb;
    void *arg;
    struct timer_node *next;
} timer_node_t;

void timer_init(void);
uint32_t tick_ms(void);
void timer_start(timer_node_t *node, uint32_t delay_ms,
                 uint32_t period_ms, timer_cb_t cb, void *arg);
void timer_stop(timer_node_t *node);
void timer_process(void);

#endif

// timer.c
#include "timer.h"
#include <stdlib.h>

static timer_node_t *head = NULL;
static volatile uint32_t tick_counter = 0;

void timer_init(void) {
    head = NULL;
    tick_counter = 0;
}

uint32_t tick_ms(void) {
    return tick_counter;
}

// 由硬件定时器中断或主循环周期性调用
void timer_tick_update(void) {
    tick_counter++;
}

static void list_insert(timer_node_t *node) {
    timer_node_t **pp = &head;
    while (*pp && (*pp)->expire_ms <= node->expire_ms) {
        pp = &(*pp)->next;
    }
    node->next = *pp;
    *pp = node;
}

void timer_start(timer_node_t *node, uint32_t delay_ms,
                 uint32_t period_ms, timer_cb_t cb, void *arg) {
    node->expire_ms = tick_ms() + delay_ms;
    node->period_ms = period_ms;
    node->cb = cb;
    node->arg = arg;
    node->next = NULL;
    list_insert(node);
}

void timer_stop(timer_node_t *node) {
    timer_node_t **pp = &head;
    while (*pp) {
        if (*pp == node) {
            *pp = node->next;
            node->next = NULL;
            return;
        }
        pp = &(*pp)->next;
    }
}

void timer_process(void) {
    uint32_t now = tick_ms();
    while (head && head->expire_ms <= now) {
        timer_node_t *node = head;
        head = node->next;
        node->next = NULL;

        if (node->period_ms > 0) {
            // 周期定时器:重新计算到期时间并插入链表
            node->expire_ms = now + node->period_ms;
            list_insert(node);
        }
        if (node->cb) {
            node->cb(node->arg);
        }
    }
}

// main.c 示例
#include "timer.h"
#include <stdio.h>

static timer_node_t t1, t2;

void one_shot_cb(void *arg) {
    printf("[%u] 单次定时器触发, arg=%d\n", tick_ms(), *(int*)arg);
}

void periodic_cb(void *arg) {
    printf("[%u] 周期定时器触发\n", tick_ms());
}

int main(void) {
    int val = 42;
    timer_init();

    // 单次定时:100ms后触发一次
    timer_start(&t1, 100, 0, one_shot_cb, &val);
    // 周期定时:每50ms触发一次
    timer_start(&t2, 30, 50, periodic_cb, NULL);

    // 模拟事件循环:每次循环推进1ms
    for (int i = 0; i < 200; i++) {
        timer_tick_update();   // 模拟1ms时基
        timer_process();       // 检查并处理到期定时器
    }
    return 0;
}

编译与运行


gcc -Wall -o timer_demo main.c timer.c
./timer_demo

验证方法

运行程序后观察输出:单次定时器应在tick约100时触发一次,打印arg=42;周期定时器首次在tick约30时触发,之后每50ms触发一次(即tick约80、130、180处)。若输出符合上述时间规律且单次定时器仅触发一次,则判定实现正确。可通过调整delay_ms与period_ms参数验证不同定时组合。