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),并将忙等待替换为操作系统延时或看门狗刷新即可直接使用。