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

Docker环境下带CGO的Go进程如何获取信号发送者的PID与PPID

如何在Docker环境中获取信号发送者的真实PID与PPID?

环境结构

  • 包含C信号处理函数的main.go
  • 用go build编译后在Docker中运行,docker-compose.yml配置了-v /:/rootfs:ro(挂载主机根文件系统到容器内)

实验步骤

  • 在Docker外部执行kill命令向容器进程发送信号
  • 执行docker kill命令终止容器进程
  • 进入Docker容器内部执行kill命令向目标进程发送信号

实验结果

  1. Docker外部kill触发:
Received signal: 15
Sent by PID: 0
Signal code: 0
User ID: 0
  1. docker kill触发:
Received signal: 15
Sent by PID: 0
Signal code: 0
User ID: 0
  1. Docker内部kill触发:
Received signal: 15
Sent by PID: 15
Signal code: 0
User ID: 230030
Name:    kworker/1:0H
State:   S (sleeping)
Tgid:    15
Ngid:    0
Pid: 15
PPid:    2
TracerPid:   0
Uid: 0   0   0   0
Gid: 0   0   0   0
FDSize:  64
Groups:
NStgid:  15
NSpid:   15
NSpgid:  0
NSsid:   0
Threads: 1
SigQ:    1/7980
SigPnd:  0000000000000000
ShdPnd:  0000000000000000
SigBlk:  0000000000000000
SigIgn:  ffffffffffffffff
SigCgt:  0000000000000000
CapInh:  0000000000000000
CapPrm:  0000003fffffffff
CapEff:  0000003fffffffff
CapBnd:  0000003fffffffff
CapAmb:  0000000000000000
Speculation_Store_Bypass:    vulnerable
Cpus_allowed:    2
Cpus_allowed_list:   1
Mems_allowed:    1
Mems_allowed_list:   0
voluntary_ctxt_switches: 5
nonvoluntary_ctxt_switches:  0

解决方案

要获取信号发送者的真实PID与PPID,需要结合Linux信号机制和Docker命名空间特性,具体步骤如下:

1. 升级信号处理函数,获取命名空间内的发送者PID

通过sigaction注册带有SA_SIGINFO标志的信号处理函数,这样就能拿到siginfo_t结构体,其中si_pid字段是发送者在当前容器PID命名空间中的ID。

示例C部分代码:

#include <signal.h>

void handle_signal(int sig, siginfo_t *info, void *context);

void setup_signal_handler() {
    struct sigaction sa;
    sa.sa_sigaction = handle_signal;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_SIGINFO;
    // 以SIGTERM为例,可根据需要添加其他信号
    sigaction(SIGTERM, &sa, NULL);
}

2. 通过挂载的主机根文件系统查询真实PID

由于容器挂载了主机/到/rootfs,可以读取主机的proc文件系统来转换命名空间PID到真实PID:

  • 对于容器内获取到的命名空间PID,读取/rootfs/proc/<ns_pid>/status文件中的NSpid字段,第二个数值就是主机上的真实PID
  • 再通过真实PID读取/rootfs/proc/<real_pid>/status中的PPid字段,得到父进程ID

示例Go结合cgo的完整代码:

package main

/*
#include <signal.h>
#include <stdio.h>

extern void handle_signal(int sig, siginfo_t *info, void *context);

void setup_handler() {
    struct sigaction sa;
    sa.sa_sigaction = handle_signal;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_SIGINFO;
    sigaction(SIGTERM, &sa, NULL);
}
*/
import "C"
import (
    "bufio"
    "fmt"
    "os"
    "strings"
    "unsafe"
)

//export handle_signal
func handle_signal(sig C.int, info *C.struct_siginfo_t, context unsafe.Pointer) {
    nsPid := int(info.si_pid)
    fmt.Printf("收到信号: %d\n", sig)
    fmt.Printf("发送者命名空间PID: %d\n", nsPid)

    if nsPid == 0 {
        fmt.Println("注意:发送者PID为0(通常由内核或Docker守护进程触发)")
        return
    }

    // 读取主机proc文件获取真实PID
    statusPath := fmt.Sprintf("/rootfs/proc/%d/status", nsPid)
    file, err := os.Open(statusPath)
    if err != nil {
        fmt.Printf("打开status文件失败: %v\n", err)
        return
    }
    defer file.Close()

    scanner := bufio.NewScanner(file)
    var realPid int
    for scanner.Scan() {
        line := scanner.Text()
        if strings.HasPrefix(line, "NSpid:") {
            parts := strings.Fields(line)
            if len(parts) >= 2 {
                fmt.Sscanf(parts[1], "%d", &realPid)
                break
            }
        }
    }

    if realPid == 0 {
        fmt.Println("获取真实PID失败")
        return
    }
    fmt.Printf("发送者真实PID: %d\n", realPid)

    // 获取真实PPID
    realStatusPath := fmt.Sprintf("/rootfs/proc/%d/status", realPid)
    file, err = os.Open(realStatusPath)
    if err != nil {
        fmt.Printf("打开真实PID的status文件失败: %v\n", err)
        return
    }
    defer file.Close()

    scanner = bufio.NewScanner(file)
    var ppid int
    for scanner.Scan() {
        line := scanner.Text()
        if strings.HasPrefix(line, "PPid:") {
            parts := strings.Fields(line)
            if len(parts) >= 2 {
                fmt.Sscanf(parts[1], "%d", &ppid)
                break
            }
        }
    }
    fmt.Printf("发送者真实PPID: %d\n", ppid)
}

func main() {
    C.setup_handler()
    // 阻塞进程等待信号
    select {}
}

3. 处理PID为0的特殊场景

当si_pid为0时,说明信号由内核直接发送(比如docker kill是Docker守护进程请求内核发送信号),此时可以通过以下方式追踪:

  • 在主机上启用auditd服务,配置监控信号发送事件,通过审计日志找到触发信号的进程
  • 直接查看主机上Docker守护进程的PID(通常是dockerd或containerd),这类信号的最终发起方就是Docker相关进程

内容的提问来源于stack exchange,提问作者WeiAnHsieh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 00:07:08