You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Linux C(树莓派):实现午夜特定时间持续信号触发的问题

解决树莓派C程序中定时器循环触发的问题

嘿,我来帮你搞定这个定时器循环触发的问题!首先得搞清楚为啥第一次触发后定时器就“歇菜”了——默认情况下,不管是setitimer还是POSIX的timer_create创建的定时器,大多是单次触发的,只会执行一次信号回调,之后就不会再触发了。你的思路完全正确:在信号处理函数里重新配置定时器,就能实现循环触发的效果。

下面给你一个适配树莓派Linux环境的完整可运行示例,用POSIX定时器实现(比setitimer更灵活):

步骤1:引入必要头文件

#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

#define TIMER_FREQUENCY 60  // 这里先设60秒测试,你可以改成需要的间隔,比如午夜的秒数差
timer_t timer_id;

步骤2:编写信号处理函数

⚠️ 重点提醒:信号处理函数里只能调用信号安全的函数(比如write、_exit),像printf这类函数不是信号安全的,可能会导致程序崩溃或异常。如果要打日志,用write更稳妥:

void timer_handler(int sig) {
    // 这里写你要执行的任务,比如午夜要做的备份、数据同步等操作
    const char task_msg[] = "定时任务执行中...\n";
    write(STDOUT_FILENO, task_msg, strlen(task_msg));

    // 重新设置定时器,实现循环触发
    struct itimerspec new_timer_cfg;
    new_timer_cfg.it_value.tv_sec = TIMER_FREQUENCY;
    new_timer_cfg.it_value.tv_nsec = 0;
    // 因为我们手动重新设置,这里interval设为0即可
    new_timer_cfg.it_interval.tv_sec = 0;
    new_timer_cfg.it_interval.tv_nsec = 0;

    if (timer_settime(timer_id, 0, &new_timer_cfg, NULL) == -1) {
        const char err_msg[] = "重新设置定时器失败!\n";
        write(STDERR_FILENO, err_msg, strlen(err_msg));
        exit(EXIT_FAILURE);
    }
}

步骤3:初始化并启动定时器

int main() {
    struct sigevent timer_event;
    struct itimerspec timer_cfg;
    struct sigaction signal_action;

    // 配置信号事件:用SIGUSR1信号触发处理函数
    timer_event.sigev_notify = SIGEV_SIGNAL;
    timer_event.sigev_signo = SIGUSR1;
    timer_event.sigev_value.sival_ptr = &timer_id;

    // 创建定时器
    if (timer_create(CLOCK_REALTIME, &timer_event, &timer_id) == -1) {
        perror("创建定时器失败");
        exit(EXIT_FAILURE);
    }

    // 绑定信号与处理函数
    signal_action.sa_handler = timer_handler;
    sigemptyset(&signal_action.sa_mask);
    signal_action.sa_flags = 0;
    if (sigaction(SIGUSR1, &signal_action, NULL) == -1) {
        perror("绑定信号处理函数失败");
        exit(EXIT_FAILURE);
    }

    // 配置第一次触发的时间(如果要午夜触发,这里可以改成当前时间到午夜的秒数差)
    timer_cfg.it_value.tv_sec = TIMER_FREQUENCY;  // 第一次等待TIMER_FREQUENCY秒后触发
    timer_cfg.it_value.tv_nsec = 0;
    timer_cfg.it_interval.tv_sec = 0;
    timer_cfg.it_interval.tv_nsec = 0;

    if (timer_settime(timer_id, 0, &timer_cfg, NULL) == -1) {
        perror("启动定时器失败");
        exit(EXIT_FAILURE);
    }

    // 主进程挂起等待信号,比轮询高效太多
    while(1) {
        pause();
    }

    timer_delete(timer_id);  // 实际这里不会执行,因为上面是死循环
    return 0;
}

额外实用技巧

  • 精准午夜触发:如果要每天午夜执行任务,可以计算当前时间到下一个午夜的秒数,替换第一次触发的tv_sec值:
    time_t now = time(NULL);
    struct tm *current_tm = localtime(&now);
    // 计算到午夜的秒数:一天总秒数 - 当天已过秒数
    long sec_to_midnight = 86400 - (current_tm->tm_hour * 3600 + current_tm->tm_min * 60 + current_tm->tm_sec);
    timer_cfg.it_value.tv_sec = sec_to_midnight;
    
  • 不受系统时间影响的定时器:如果怕系统时间被NTP同步修改影响触发,可以把CLOCK_REALTIME换成CLOCK_MONOTONIC,这个时钟是从系统启动开始计时的,不会受系统时间修改影响。
  • 编译命令:编译时需要链接实时库,命令为:gcc timer_demo.c -o timer_demo -lrt

这样修改后,你的定时器就能每次执行完任务后自动重置,实现循环触发的效果啦!

内容的提问来源于stack exchange,提问作者SMatt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 04:25:31