Docker容器无法停止问题:静态编译C程序异常排查
容器无法停止的问题分析与解决方案
操作步骤与环境
C程序代码
#include <stdio.h> #include <unistd.h> #include <signal.h> int stop = 0; void handle_signal(int sig) { stop = 1; } void main() { signal(SIGINT, handle_signal); signal(SIGTERM, handle_signal); while (stop==0) { printf("Hello\n"); sleep(1); } }
编译与容器构建命令
- 静态编译程序:
gcc -static test.c -o test
- Dockerfile内容:
FROM scratch COPY test /test ENTRYPOINT ["/test"]
- 构建镜像:
docker build -t myimage .
- 运行容器:
docker run --name mycontainer myimage
问题现象
容器无法通过以下方式停止:
- 终端按
CTRL+C - 执行
docker stop mycontainer - 执行
docker kill mycontainer - 尝试发送信号终止程序
执行docker kill --signal=SIGINT mycontainer时出现错误:
Cannot kill container xxxx: Unknown error after kill: runc did not terminate successfully: exit status 1: unable to signal init: permission denied.
运行环境:Kali Linux,Docker 20.10.25,gcc 13.3.0
问题原因
- PID 1进程的信号处理特性:程序作为容器内的PID 1进程运行,Linux内核对PID 1进程的信号处理有特殊限制——默认会忽略除
SIGKILL、SIGSTOP之外的所有信号,即使通过signal()注册了处理函数,也可能无法正常触发。 signal()函数的局限性:signal()是老旧API,行为在不同系统中存在差异,对于PID 1进程,它无法保证信号处理程序被正确注册。- 变量访问的编译器优化:
stop变量未声明为volatile sig_atomic_t,编译器可能将其缓存到寄存器中,导致信号处理函数对stop的修改无法被主循环及时感知。 - scratch镜像的环境缺失:scratch镜像没有系统init进程,无法处理信号转发逻辑,外部发送的信号无法被程序正确接收。
解决方案
方案一:修改C程序,使用可靠的信号处理机制
修改后的代码如下:
#include <stdio.h> #include <unistd.h> #include <signal.h> // 确保变量修改能被主循环及时感知,且在信号处理中安全访问 volatile sig_atomic_t stop = 0; void handle_signal(int sig) { stop = 1; } int main() { struct sigaction sa; sa.sa_handler = handle_signal; sigemptyset(&sa.sa_mask); // 信号处理期间不阻塞其他信号 sa.sa_flags = 0; // 使用POSIX标准的sigaction注册信号处理函数,行为更可靠 sigaction(SIGINT, &sa, NULL); sigaction(SIGTERM, &sa, NULL); while (!stop) { printf("Hello\n"); sleep(1); } return 0; }
修改说明:
- 用
sigaction()替代signal(),确保信号处理函数在PID 1进程上能正确注册。 - 将
stop声明为volatile sig_atomic_t,避免编译器优化导致的变量不可见问题,同时保证信号处理中的访问安全。
方案二:使用Docker的--init参数
无需修改程序,运行容器时添加--init参数,让Docker启动tini作为PID 1进程,它会负责将外部信号转发给你的程序:
docker run --init --name mycontainer myimage
tini是轻量级init进程,能正确处理信号转发,解决PID 1进程的信号忽略问题。
后续操作
无论采用哪种方案,都需要重新构建镜像(方案一需先重新编译程序):
# 方案一需要执行编译 gcc -static test.c -o test # 重新构建镜像 docker build -t myimage .
内容的提问来源于stack exchange,提问作者Bob5421
相关产品推荐
相关产品推荐

