4. 实现超时控制避免无限等待

任务说明

在嵌入式与分布式系统中,调用外部设备或服务时若缺乏超时控制,一旦对方无响应,调用方将无限期阻塞,导致任务挂死、资源泄漏甚至系统崩溃。本案例实现一个轻量级超时控制模块,为阻塞式函数调用提供“限时等待”能力:在指定时间内未获得完成信号则立即返回超时错误,由调用方决定重试或降级处理。

超时控制模块实现


/* timeout_ctl.h */
#ifndef TIMEOUT_CTL_H
#define TIMEOUT_CTL_H

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

typedef enum {
    TIMEOUT_OK = 0,        /* 在期限内收到完成信号 */
    TIMEOUT_EXPIRED,       /* 超时未收到完成信号 */
    TIMEOUT_INVALID_PARAM  /* 参数错误 */
} timeout_result_t;

/* 超时控制器句柄 */
typedef struct {
    uint32_t start_tick;   /* 起始节拍 */
    uint32_t timeout_ms;   /* 超时时间(ms) */
    bool     started;      /* 是否已启动 */
} timeout_ctl_t;

void            timeout_init(timeout_ctl_t *ctl, uint32_t timeout_ms);
timeout_result_t timeout_start(timeout_ctl_t *ctl);
timeout_result_t timeout_check(const timeout_ctl_t *ctl);
timeout_result_t timeout_wait_completion(timeout_ctl_t *ctl, volatile bool *done_flag);

#endif /* TIMEOUT_CTL_H */

/* timeout_ctl.c */
#include "timeout_ctl.h"

/* 硬件平台相关:获取当前系统节拍(ms) */
/* 在STM32等平台上可替换为HAL_GetTick(),在Linux可替换为自定义ms时钟 */
extern uint32_t system_get_tick_ms(void);

void timeout_init(timeout_ctl_t *ctl, uint32_t timeout_ms)
{
    if (ctl == NULL) return;
    ctl->start_tick   = 0;
    ctl->timeout_ms   = timeout_ms;
    ctl->started      = false;
}

timeout_result_t timeout_start(timeout_ctl_t *ctl)
{
    if (ctl == NULL || ctl->timeout_ms == 0) {
        return TIMEOUT_INVALID_PARAM;
    }
    ctl->start_tick = system_get_tick_ms();
    ctl->started    = true;
    return TIMEOUT_OK;
}

timeout_result_t timeout_check(const timeout_ctl_t *ctl)
{
    uint32_t elapsed;

    if (ctl == NULL || !ctl->started) {
        return TIMEOUT_INVALID_PARAM;
    }
    elapsed = system_get_tick_ms() - ctl->start_tick;
    if (elapsed >= ctl->timeout_ms) {
        return TIMEOUT_EXPIRED;
    }
    return TIMEOUT_OK;
}

/* 核心函数:阻塞等待done_flag变为true,但最多等待timeout_ms */
timeout_result_t timeout_wait_completion(timeout_ctl_t *ctl, volatile bool *done_flag)
{
    timeout_result_t ret;

    if (ctl == NULL || done_flag == NULL) {
        return TIMEOUT_INVALID_PARAM;
    }

    ret = timeout_start(ctl);
    if (ret != TIMEOUT_OK) {
        return ret;
    }

    while (*done_flag == false) {
        if (timeout_check(ctl) == TIMEOUT_EXPIRED) {
            return TIMEOUT_EXPIRED;  /* 超时,立即返回 */
        }
        /* 轻量延时,避免忙等待占用过高CPU */
        /* 实际项目中可替换为taskYIELD()或thread_yield() */
        for (volatile int i = 0; i < 1000; i++);
    }
    return TIMEOUT_OK;
}

使用示例:模拟外部设备响应


/* main.c 示例 */
#include <stdio.h>
#include <stdbool.h>
#include "timeout_ctl.h"

/* 模拟系统节拍(ms) */
static uint32_t s_tick = 0;
uint32_t system_get_tick_ms(void) { return s_tick; }

/* 模拟外部设备:50ms后置完成标志 */
static volatile bool s_device_done = false;
static void simulate_device_response(void)
{
    /* 模拟设备处理耗时50ms */
    s_tick += 50;
    s_device_done = true;
}

int main(void)
{
    timeout_ctl_t ctl;
    timeout_result_t result;

    /* 场景1:设备在100ms内响应,应返回TIMEOUT_OK */
    s_device_done = false;
    timeout_init(&ctl, 100);
    simulate_device_response();   /* 50ms后完成 */
    result = timeout_wait_completion(&ctl, &s_device_done);
    printf("场景1结果: %s\n", result == TIMEOUT_OK ? "成功" : "失败");

    /* 场景2:设备永不响应,超时30ms,应返回TIMEOUT_EXPIRED */
    s_device_done = false;
    timeout_init(&ctl, 30);
    /* 不调用simulate_device_response,模拟设备挂死 */
    result = timeout_wait_completion(&ctl, &s_device_done);
    printf("场景2结果: %s\n", result == TIMEOUT_EXPIRED ? "超时" : "失败");

    return 0;
}

编译与运行


gcc -Wall -Wextra -o timeout_demo main.c timeout_ctl.c
./timeout_demo

验证方法与判定标准

运行程序后观察输出:

  • 场景1输出“成功”——设备在50ms置位完成标志,未超过100ms时限,函数正常返回TIMEOUT_OK;
  • 场景2输出“超时”——设备未响应,30ms后函数返回TIMEOUT_EXPIRED,调用方不再无限等待。

若两个场景输出与上述一致,则说明超时控制模块工作正常。在真实嵌入式环境中,将system_get_tick_ms()替换为硬件节拍函数(如HAL_GetTick),并将忙等待替换为操作系统延时或看门狗刷新即可直接使用。

超时控制时序图 调用方任务 超时控制器 外部设备 调用wait_completion 发起操作 50ms后完成 done_flag = true 返回 TIMEOUT_OK 场景1:设备在100ms内响应 → 正常返回,无阻塞 调用wait_completion 发起操作(设备挂死) 等待30ms 返回 TIMEOUT_EXPIRED 场景2:设备30ms未响应 → 超时返回,调用方不再无限等待