"被信号中断的系统调用自动重启"是 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 信号处理体系的核心设计之一。