You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);
    }
}

编译与容器构建命令

  1. 静态编译程序:
gcc -static test.c -o test
  1. Dockerfile内容:
FROM scratch
COPY test /test
ENTRYPOINT ["/test"]
  1. 构建镜像:
docker build -t myimage .
  1. 运行容器:
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

问题原因

  1. PID 1进程的信号处理特性:程序作为容器内的PID 1进程运行,Linux内核对PID 1进程的信号处理有特殊限制——默认会忽略除SIGKILL、SIGSTOP之外的所有信号,即使通过signal()注册了处理函数,也可能无法正常触发。
  2. signal()函数的局限性:signal()是老旧API,行为在不同系统中存在差异,对于PID 1进程,它无法保证信号处理程序被正确注册。
  3. 变量访问的编译器优化:stop变量未声明为volatile sig_atomic_t,编译器可能将其缓存到寄存器中,导致信号处理函数对stop的修改无法被主循环及时感知。
  4. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 21:19:59