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
相关产品推荐
相关产品推荐

