如何理解SA_RESTART”被信号中断的系统调用自动重启“?
"被信号中断的系统调用自动重启"是 Linux 信号处理中的一个重要机制,由 SA_RESTART
标志控制。以下是深入解释:
核心概念
当进程执行阻塞型系统调用(如 read()
, write()
, accept()
, wait()
等)时:
- 如果系统调用执行期间收到信号
- 且该信号设置了信号处理函数(非忽略/默认终止)
- 系统调用会被中断,立即返回用户空间执行信号处理函数
此时系统调用有两种后续处理方式:
- 不自动重启:系统调用返回
-1
,并设置errno = EINTR
(被中断) - 自动重启(设置
SA_RESTART
):内核自动重新执行被中断的系统调用
工作流程对比
实际代码示例
未设置 SA_RESTART 的情况:
#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_RESTARTsigaction(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 的情况:
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()
函数中:if (sig_handler_ignored(...) || (sa->sa_flags & SA_RESTART)) {restart_syscall(); // 重启逻辑 }
-
编程最佳实践:
- 对需要可靠性的服务程序,建议设置 SA_RESTART
- 对需要快速响应信号的程序(如监控),避免使用 SA_RESTART
- 即使使用 SA_RESTART,也应处理关键操作的 EINTR:
do {ret = accept(sockfd, ...); } while (ret == -1 && errno == EINTR); // 安全重启
典型应用场景
-
网络服务器:
sa.sa_flags = SA_RESTART; // 确保accept不会因信号失败 sigaction(SIGCHLD, &sa, NULL); // 处理子进程退出
当子进程退出发送
SIGCHLD
时,accept()
自动重启 -
交互式程序:
sa.sa_flags = 0; // 不设置SA_RESTART sigaction(SIGALRM, &sa, NULL); // 超时信号
当
read()
被SIGALRM
中断时,可执行超时处理逻辑
这种机制平衡了系统调用的可靠性和信号的响应能力,是 Linux 信号处理体系的核心设计之一。