C语言中递归调用alarm()实现子进程定时打印异常问题
问题分析与解决
我尝试创建多个子进程,让它们每隔几秒触发SIGALRM信号并向标准输出打印消息。不想用无限while循环,于是尝试在SIGALRM信号处理函数里递归调用alarm()和pause(),但无法正常输出预期消息;改用无限while循环包裹pause()时功能正常。
相关代码
child.c 核心代码
void handle_sigalarm(int sig){ now = 0; time(&now); printf("[CHILD] | [TIME = %ld] | [PID = %d] | [GATE = %d]\n", (long int)(now - start), getpid(), GATE); alarm(2); pause(); } int main(int argc, char **argv){ signal_handling(); // 处理所有信号的函数,实现见下方 printf("[CHILD] | [TIME = %d] | [PID = %d] | [GATE = %d]\n", 0, getpid(), GATE); time(&start); alarm(2); pause(); // while(1){ // pause(); // } return 0; }
信号处理函数实现
void signal_handling(){ struct sigaction sa1; struct sigaction sa2; struct sigaction sa3; struct sigaction sal; sa1.sa_handler = handle_sigterm; sa2.sa_handler = handle_sigusr1; sa3.sa_handler = handle_sigusr2; sigemptyset(&sal.sa_mask); sal.sa_handler = handle_sigalarm; sal.sa_flags = SA_RESTART; sigaction(SIGTERM, &sa1, NULL); sigaction(SIGUSR1, &sa2, NULL); sigaction(SIGUSR2, &sa3, NULL); sigaction(SIGALRM, &sal, NULL); }
问题原因
问题出在信号处理函数里的pause()调用:
- 当信号处理函数执行时,当前触发的信号(这里是SIGALRM)会被自动加入进程的信号掩码,也就是说在
handle_sigalarm执行期间,新的SIGALRM信号会被挂起,无法触发新的信号处理。 - 你在
handle_sigalarm里调用alarm(2)设置了下一个定时器,紧接着调用pause()等待信号,但此时SIGALRM被屏蔽,定时器触发的SIGALRM会被挂起,而pause()会一直等待其他未被屏蔽的信号,导致进程卡在信号处理函数里,无法继续处理后续的SIGALRM,自然不会再打印消息。 - 另外,递归调用
pause()还会不断占用栈空间,存在栈溢出的风险。
解决方法
不要在信号处理函数里调用pause(),只需要在处理函数中设置下一个定时器,让main函数保持等待状态即可:
修改handle_sigalarm函数:
void handle_sigalarm(int sig){ now = 0; time(&now); printf("[CHILD] | [TIME = %ld] | [PID = %d] | [GATE = %d]\n", (long int)(now - start), getpid(), GATE); alarm(2); // 只设置下一个定时器,不需要调用pause() }
然后在main函数里用无限循环等待信号(这是最直观的实现方式):
int main(int argc, char **argv){ signal_handling(); printf("[CHILD] | [TIME = %d] | [PID = %d] | [GATE = %d]\n", 0, getpid(), GATE); time(&start); alarm(2); while(1){ pause(); } return 0; }
如果确实不想用while循环,可以用sigsuspend配合空掩码实现永久等待,逻辑和while循环一致:
int main(int argc, char **argv){ signal_handling(); printf("[CHILD] | [TIME = %d] | [PID = %d] | [GATE = %d]\n", 0, getpid(), GATE); time(&start); alarm(2); sigset_t mask; sigemptyset(&mask); while(1){ sigsuspend(&mask); // 等待任何未被屏蔽的信号 } return 0; }
这样修改后,每次SIGALRM触发时,信号处理函数打印消息并设置下一个定时器,然后返回,main函数的pause()/sigsuspend()会继续等待下一个信号,就能实现每隔2秒打印一次消息的需求。
内容的提问来源于stack exchange,提问作者Peter Augerinos
相关产品推荐
相关产品推荐

