"被信号中断的系统调用自动重启"是 Linux 信号处理中的一个重要机制,由 SA_RESTART 标志控制。以下是深入解释:
核心概念
当进程执行阻塞型系统调用 (如 read(), write(), accept(), wait() 等)时:
- 如果系统调用执行期间收到信号
- 且该信号设置了信号处理函数(非忽略/默认终止)
- 系统调用会被中断,立即返回用户空间执行信号处理函数
此时系统调用有两种后续处理方式:
- 不自动重启 :系统调用返回
-1,并设置errno = EINTR(被中断) - 自动重启 (设置
SA_RESTART):内核自动重新执行被中断的系统调用
工作流程对比
有信号 已设置 未设置 阻塞系统调用开始 信号到达 中断系统调用 执行信号处理函数 SA_RESTART 标志 内核自动重启系统调用 返回用户空间 errno=EINTR 系统调用完成 应用程序处理 EINTR
实际代码示例
未设置 SA_RESTART 的情况:
c
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void handler(int sig) {
printf("Received signal %d\n", sig);
}
int main() {
struct sigaction sa;
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0; // 不设置 SA_RESTART
sigaction(SIGINT, &sa, NULL);
char buf[256];
ssize_t n = read(STDIN_FILENO, buf, sizeof(buf)); // 阻塞读
if (n == -1 && errno == EINTR) {
printf("Read interrupted!\n");
}
return 0;
}
当按下 Ctrl+C(发送 SIGINT)时:
read()被中断- 执行信号处理函数
read()返回-1,errno = EINTR
设置 SA_RESTART 的情况:
c
sa.sa_flags = SA_RESTART; // 关键设置
同样按下 Ctrl+C 后:
read()被中断- 执行信号处理函数
- 内核自动重启
read()调用 - 程序继续阻塞等待输入
技术细节
-
哪些系统调用支持重启:
- 文件I/O:
read,write,open等 - 进程控制:
wait,waitpid - 网络操作:
accept,connect,recv等 - 不支持的:
poll,select,epoll_wait(需手动处理 EINTR)
- 文件I/O:
-
重启的实现位置 :
在 Linux 内核源码
kernel/signal.c的get_signal()函数中:cif (sig_handler_ignored(...) || (sa->sa_flags & SA_RESTART)) { restart_syscall(); // 重启逻辑 } -
编程最佳实践:
-
对需要可靠性的服务程序,建议设置 SA_RESTART
-
对需要快速响应信号的程序(如监控),避免使用 SA_RESTART
-
即使使用 SA_RESTART,也应处理关键操作的 EINTR:
cdo { ret = accept(sockfd, ...); } while (ret == -1 && errno == EINTR); // 安全重启
-
典型应用场景
-
网络服务器:
csa.sa_flags = SA_RESTART; // 确保accept不会因信号失败 sigaction(SIGCHLD, &sa, NULL); // 处理子进程退出当子进程退出发送
SIGCHLD时,accept()自动重启 -
交互式程序:
csa.sa_flags = 0; // 不设置SA_RESTART sigaction(SIGALRM, &sa, NULL); // 超时信号当
read()被SIGALRM中断时,可执行超时处理逻辑
这种机制平衡了系统调用的可靠性和信号的响应能力,是 Linux 信号处理体系的核心设计之一。