问题描述
最近我正在研究 dumb-init ,如果我正确认识到它正在尝试:
- 以PID1的身份运行,就像一个简单的初始化系统(收获僵尸进程)
- 信号代理/转发(bash无法做到)
在和此处,他们都提到bash
能够收录僵尸进程,因此我正在尝试验证这一点,但无法使其正常工作.
首先,我编写了一个简单的Go程序,该程序产生了10个僵尸进程:
func main(){信号:= make(chan os.Signal,1)signal.Notify(信号,syscall.SIGINT,syscall.SIGTERM,syscall.SIGKILL)转到func(){对于我:= 0;我<10;我++ {sleepCmd:= exec.Command("sleep","1")_ = sleepCmd.Start()}}()fmt.Println(等待信号")信号:=<-信号fmt.Println()fmt.Printf(收到的%s,退出\ n",sig.String())}
为其构建图像:
FROM golang:1.15-alpine3.12作为生成器WORKDIR/复制 ..运行build -o main main.go来自高山:3.12运行apk --no-cache --update add dumb-init bashWORKDIR/复制--from = builder/main/复制--from = builder/entrypoint.sh/运行chmod + x/entrypoint.shENTRYPOINT ["/main"]
如果我运行 docker run -d< image>
,它会按预期工作,我可以在 ps
中看到10个僵尸进程:
vagrant @ vagrant:/vagrant/dumb-init $ ps aux |grep睡眠根4388 0.0 0.0 0 0 0Z 13:54 0:00 [sleep]< defunct>根4389 0.0 0.0 0 0 0?Z 13:54 0:00 [sleep]< defunct>根4390 0.0 0.0 0 0 0Z 13:54 0:00 [sleep]< defunct>根4391 0.0 0.0 0 0?Z 13:54 0:00 [sleep]< defunct>根4392 0.0 0.0 0 0 0Z 13:54 0:00 [sleep]< defunct>根4393 0.0 0.0 0 0?Z 13:54 0:00 [sleep]< defunct>根4394 0.0 0.0 0 0 0Z 13:54 0:00 [sleep]< defunct>根4395 0.0 0.0 0 0 0Z 13:54 0:00 [sleep]< defunct>根4396 0.0 0.0 0 0?Z 13:54 0:00 [sleep]< defunct>根4397 0.0 0.0 0 0?Z 13:54 0:00 [sleep]< defunct>
第二步是验证 bash
是否确实具有收割过程的能力,因此我将Docker映像ENTRYPOINT更新为entrypoint.sh,该程序将我的程序仅用bash包装:
#!/bin/bash/聪明的
如果我在容器中运行 ps
,则僵尸进程仍然挂在那里:
/#psPID用户时间命令1个根目录0:00 {entrypoint.sh}/bin/bash/entrypoint.sh7根0:00/聪明13根0:00 [睡眠]14根0:00 [睡眠]15根0:00 [睡眠]16根0:00 [睡眠]17根0:00 [睡眠]18 root 0:00 [sleep]19根0:00 [睡眠]20 root 0:00 [sleep]21根0:00 [睡眠]22根0:00 [睡眠]31根0:00/bin/sh39根0:00 ps
尝试了其他几种方法,但仍无法弄清楚如何正确获得僵尸进程.
感谢您的帮助.
我在 c
中编写了一个小型演示,可以帮助证明 bash
已经获得了僵尸进程,并且如果他没有的话会怎么样.
首先解释僵尸进程的定义.僵尸进程是完成工作并生成退出代码的进程.资源由内核保存,等待父级收集退出代码.
要成为僵尸,父母需要忽略孩子的出口(不要发出 wait
并忽略 SIGCHLD
).
收割僵尸
以下 c
代码正在创建两个僵尸进程.一个属于主进程,一个属于第一个孩子.
#include< stdio.h>#include< stdlib.h>#include< signal.h>#include< pthread.h>#include< sys/wait.h>#include< unistd.h>int main(){printf(启动程序!\ n");int pid = fork();如果(pid == 0){pid = fork();//创建一个孩子僵尸如果(pid == 0){printf(子进程的僵尸进程%i \ n",getpid());出口(10);} 别的 {printf(子进程%i正在运行!\ n",getpid());睡眠(10);//等待10秒printf(子进程%i正在退出!\ n",getpid());退出(0);}}否则,如果(pid> 0){pid = fork();如果(pid == 0){printf(来自父进程的僵尸进程%i \ n",getpid());} 别的 {printf(父进程%i ... \ n",getpid());睡眠(5);printf(父进程将因分段失败而崩溃!\ n");int * p = 0;p = 10;}}else perror("fork()");退出(-1);}
我还创建了一个docker容器,用于编译文件和子容器.整个项目位于以下git 存储库
中在运行构建和演示之后,控制台中将显示以下打印输出:
root @ d2d87f4aafbc:/zombie#./zombie&ps -eaf --forest[1] 8正在启动程序!父进程8 ...父进程的僵尸进程11子进程10正在运行!子进程的僵尸进程12UID PID PPID C STIME TTY TIME CMD根1 0 0 10:43积分/0 00:00:00/bin/bash根8 1 0 10:43 pts/0 00:00:00 ./zombie根10 8 0 10:43 pts/0 00:00:00 \ _ ./zombie根12 10 0 10:43 pts/0 00:00:00 |\ _ [zombie]< defunct>根11 8 0 10:43点/0 00:00:00 \ _ [僵尸]< defunct>根9 1 0 10:43 pts/0 00:00:00 ps -eaf --forestroot @ d2d87f4aafbc:/zombie#父进程将因分段失败而崩溃!ps -eaf --forestUID PID PPID C STIME TTY TIME CMD根1 0 0 10:43积分/0 00:00:00/bin/bash根10 1 0 10:43 pts/0 00:00:00 ./zombie根12 10 0 10:43 pts/0 00:00:00 \ _ [zombie]< defunct>根13 1 0 10:43 pts/0 00:00:00 ps -eaf --forest[1] +出口255./zombieroot @ d2d87f4aafbc:/zombie#子进程10正在退出!ps -eaf --forestUID PID PPID C STIME TTY TIME CMD根1 0 0 10:43积分/0 00:00:00/bin/bash根14 1 0 10:43 pts/0 00:00:00 ps -eaf --forest
主进程(PID 8)创建了两个子进程.
- 一个孩子(PID 10),它将创建一个僵尸孩子(PID 12),并且将睡眠10秒钟.
- 将成为僵尸的孩子(PID 11).
创建进程后,父进程将休眠5秒钟并创建分段错误,从而留下僵尸.
当主进程死亡时,PID 11将被 bash
继承并进行清理(收获).PID 10仍在工作(睡眠是一种过程的工作),他被 bash
独处,因为PID 11尚未调用 wait
,所以PID 12仍在僵尸.
5秒钟后,PID 11完成睡眠并退出.Bash收获并继承了PID 12,之后Bash收获了PID 12
离开僵尸
另一个 c
应用程序只是将 bash
作为子进程执行,而将其保留为PID 1,他将忽略僵尸.
#docker run -ti --rm test/zombie/ignoreroot @ b9d49363cb57:/zombie#./zombie&ps -eaf --forest[1] 10正在启动程序!父进程10 ...父进程的僵尸进程13子进程12正在运行!子进程的僵尸进程14UID PID PPID C STIME TTY TIME CMD根1 0 0 11:18积分/0 00:00:00/僵尸/忽略根7 1 0 11:18 pts/0 00:00:00 sh -c/bin/bash根8 7 0 11:18 pts/0 00:00:00 \ _/bin/bash根10 8 0 11:18 pts/0 00:00:00 \ _ ./zombie根12 10 0 11:18 pts/0 00:00:00 |\ _ ./僵尸根14 12 0 11:18 pts/0 00:00:00 ||\ _ [zombie]< defunct>根13 10 0 11:18 pts/0 00:00:00 |\ _ [zombie]< defunct>根11 8 0 11:18 pts/0 00:00:00 \ _ ps -eaf --forestroot @ b9d49363cb57:/zombie#pParent进程将因分段失败而崩溃!ps -eaf --forestUID PID PPID C STIME TTY TIME CMD根1 0 0 11:18积分/0 00:00:00/僵尸/忽略根7 1 0 11:18 pts/0 00:00:00 sh -c/bin/bash根8 7 0 11:18 pts/0 00:00:00 \ _/bin/bash根15 8 0 11:18 pts/0 00:00:00 \ _ ps -eaf --forest根12 1 0 11:18 pts/0 00:00:00 ./zombie根14 12 0 11:18 pts/0 00:00:00 \ _ [zombie]< defunct>根13 1 0 11:18 pts/0 00:00:00 [zombie]< defunct>[1] +出口255./zombieroot @ b9d49363cb57:/zombie#子进程12正在退出!ps -eaf --forestUID PID PPID C STIME TTY TIME CMD根1 0 0 11:18积分/0 00:00:00/僵尸/忽略根7 1 0 11:18 pts/0 00:00:00 sh -c/bin/bash根8 7 0 11:18 pts/0 00:00:00 \ _/bin/bash根16 8 0 11:18 pts/0 00:00:00 \ _ ps -eaf --forest根12 1 0 11:18 pts/0 00:00:00 [zombie]< defunct>根13 1 0 11:18 pts/0 00:00:00 [zombie]< defunct>根14 1 0 11:18 pts/0 00:00:00 [zombie]< defunct>root @ b9d49363cb57:/僵尸#
所以现在,系统中还有3个僵尸挂了.
Recently I'm studying dumb-init and if I realized correctly it's trying to:
- runs as PID1, acting like a simple init system(reaping zombie processes)
- signal proxy/forwarding (which bash doesn't do)
In both here and here they all mentioned that bash
is capable of reaping zombie process so I'm trying to verify this but couldn't make it work.
First of all I wrote a simple Go program which spawn 10 zombie process:
func main() {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL)
go func() {
for i := 0; i < 10; i++ {
sleepCmd := exec.Command("sleep", "1")
_ = sleepCmd.Start()
}
}()
fmt.Println("awaiting signal")
sig := <-sigs
fmt.Println()
fmt.Printf("received %s, exiting\n", sig.String())
}
build a image for it:
FROM golang:1.15-alpine3.12 as builder
WORKDIR /
COPY . .
RUN go build -o main main.go
FROM alpine:3.12
RUN apk --no-cache --update add dumb-init bash
WORKDIR /
COPY --from=builder /main /
COPY --from=builder /entrypoint.sh /
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/main"]
and if I run docker run -d <image>
it works as expected I can see 10 zombies process in ps
:
vagrant@vagrant:/vagrant/dumb-init$ ps aux | grep sleep
root 4388 0.0 0.0 0 0 ? Z 13:54 0:00 [sleep] <defunct>
root 4389 0.0 0.0 0 0 ? Z 13:54 0:00 [sleep] <defunct>
root 4390 0.0 0.0 0 0 ? Z 13:54 0:00 [sleep] <defunct>
root 4391 0.0 0.0 0 0 ? Z 13:54 0:00 [sleep] <defunct>
root 4392 0.0 0.0 0 0 ? Z 13:54 0:00 [sleep] <defunct>
root 4393 0.0 0.0 0 0 ? Z 13:54 0:00 [sleep] <defunct>
root 4394 0.0 0.0 0 0 ? Z 13:54 0:00 [sleep] <defunct>
root 4395 0.0 0.0 0 0 ? Z 13:54 0:00 [sleep] <defunct>
root 4396 0.0 0.0 0 0 ? Z 13:54 0:00 [sleep] <defunct>
root 4397 0.0 0.0 0 0 ? Z 13:54 0:00 [sleep] <defunct>
the 2nd step is to verify bash
is actually capable of reaping process, so I update my docker image ENTRYPOINT to entrypoint.sh, which just wrap my program with bash:
#!/bin/bash
/clever
if I run ps
in the container the zombie processes are still hanging there:
/ # ps
PID USER TIME COMMAND
1 root 0:00 {entrypoint.sh} /bin/bash /entrypoint.sh
7 root 0:00 /clever
13 root 0:00 [sleep]
14 root 0:00 [sleep]
15 root 0:00 [sleep]
16 root 0:00 [sleep]
17 root 0:00 [sleep]
18 root 0:00 [sleep]
19 root 0:00 [sleep]
20 root 0:00 [sleep]
21 root 0:00 [sleep]
22 root 0:00 [sleep]
31 root 0:00 /bin/sh
39 root 0:00 ps
Tried a few other way but still couldn't figure out how to reap the zombie process correctly.
thanks for the help.
I wrote small demo in c
that can help to demonstrate that bash
had reaped the zombie processes and how it would look like if he had not.
First to explain the definition of zombie process. The zombie process is a process who had finished the work and generated an exit code. The resources are kept by the kernel waiting for the parent to collect the exit code.
To have zombie, parent needs to ignore the child's exit (don't issue wait
and ignore SIGCHLD
).
Reaping the zombies
The following c
code is creating two zombie processes. One belonging to the main process, and one that belongs to the first child.
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <pthread.h>
#include <sys/wait.h>
#include <unistd.h>
int main()
{
printf("Starting Program!\n");
int pid = fork();
if (pid == 0)
{
pid = fork(); // Create a child zombie
if (pid == 0) {
printf("Zombie process %i of the child process\n", getpid());
exit(10);
} else {
printf("Child process %i is running!\n", getpid());
sleep(10); // wait 10s
printf("Child process %i is exiting!\n", getpid());
exit(0);
}
}
else if (pid > 0)
{
pid = fork();
if (pid == 0) {
printf("Zombie process %i from the parent process\n", getpid());
} else {
printf("Parent process %i...\n", getpid());
sleep(5);
printf("Parent process will crash with segmentation failt!\n");
int* p = 0;
p = 10;
}
}
else perror("fork()");
exit(-1);
}
I also created a docker container that will compile the file and the child. The whole project is available in following git repository
After running the build, and the demo, the following printout is shown in the console:
root@d2d87f4aafbc:/zombie# ./zombie & ps -eaf --forest
[1] 8
Starting Program!
Parent process 8...
Zombie process 11 from the parent process
Child process 10 is running!
Zombie process 12 of the child process
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 10:43 pts/0 00:00:00 /bin/bash
root 8 1 0 10:43 pts/0 00:00:00 ./zombie
root 10 8 0 10:43 pts/0 00:00:00 \_ ./zombie
root 12 10 0 10:43 pts/0 00:00:00 | \_ [zombie] <defunct>
root 11 8 0 10:43 pts/0 00:00:00 \_ [zombie] <defunct>
root 9 1 0 10:43 pts/0 00:00:00 ps -eaf --forest
root@d2d87f4aafbc:/zombie# Parent process will crash with segmentation failt!
ps -eaf --forest
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 10:43 pts/0 00:00:00 /bin/bash
root 10 1 0 10:43 pts/0 00:00:00 ./zombie
root 12 10 0 10:43 pts/0 00:00:00 \_ [zombie] <defunct>
root 13 1 0 10:43 pts/0 00:00:00 ps -eaf --forest
[1]+ Exit 255 ./zombie
root@d2d87f4aafbc:/zombie# Child process 10 is exiting!
ps -eaf --forest
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 10:43 pts/0 00:00:00 /bin/bash
root 14 1 0 10:43 pts/0 00:00:00 ps -eaf --forest
The main process (PID 8) creates two children.
- A child (PID 10) that will create a zombie child (PID 12) and will sleep for 10 seconds.
- A child that will become zombie (PID 11).
After the creation of the processes, the parent process will sleep for 5s and create segmentation fault, leaving the zombies.
When the main process dies, the PID 11 is inherited by bash
and it's cleaned up (reaped). PID 10 is still working (sleeping is a kind of work for a process) he is left alone by bash
, since PID 11 had not invoked wait
, the PID 12 is still zombie.
After 5 seconds, PID 11 had finished sleeping and exited. Bash had reaped and inherited PID 12 after which bash had reaped PID 12
Leaving zombies
The other c
application is just executing the bash
as a child process, leaving it to be the PID 1, and he will ignore the zombies.
# docker run -ti --rm test /zombie/ignore
root@b9d49363cb57:/zombie# ./zombie & ps -eaf --forest
[1] 10
Starting Program!
Parent process 10...
Zombie process 13 from the parent process
Child process 12 is running!
Zombie process 14 of the child process
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 11:18 pts/0 00:00:00 /zombie/ignore
root 7 1 0 11:18 pts/0 00:00:00 sh -c /bin/bash
root 8 7 0 11:18 pts/0 00:00:00 \_ /bin/bash
root 10 8 0 11:18 pts/0 00:00:00 \_ ./zombie
root 12 10 0 11:18 pts/0 00:00:00 | \_ ./zombie
root 14 12 0 11:18 pts/0 00:00:00 | | \_ [zombie] <defunct>
root 13 10 0 11:18 pts/0 00:00:00 | \_ [zombie] <defunct>
root 11 8 0 11:18 pts/0 00:00:00 \_ ps -eaf --forest
root@b9d49363cb57:/zombie# pParent process will crash with segmentation failt!
ps -eaf --forest
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 11:18 pts/0 00:00:00 /zombie/ignore
root 7 1 0 11:18 pts/0 00:00:00 sh -c /bin/bash
root 8 7 0 11:18 pts/0 00:00:00 \_ /bin/bash
root 15 8 0 11:18 pts/0 00:00:00 \_ ps -eaf --forest
root 12 1 0 11:18 pts/0 00:00:00 ./zombie
root 14 12 0 11:18 pts/0 00:00:00 \_ [zombie] <defunct>
root 13 1 0 11:18 pts/0 00:00:00 [zombie] <defunct>
[1]+ Exit 255 ./zombie
root@b9d49363cb57:/zombie# Child process 12 is exiting!
ps -eaf --forest
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 11:18 pts/0 00:00:00 /zombie/ignore
root 7 1 0 11:18 pts/0 00:00:00 sh -c /bin/bash
root 8 7 0 11:18 pts/0 00:00:00 \_ /bin/bash
root 16 8 0 11:18 pts/0 00:00:00 \_ ps -eaf --forest
root 12 1 0 11:18 pts/0 00:00:00 [zombie] <defunct>
root 13 1 0 11:18 pts/0 00:00:00 [zombie] <defunct>
root 14 1 0 11:18 pts/0 00:00:00 [zombie] <defunct>
root@b9d49363cb57:/zombie#
So now, we have 3 zombies left in the system, hanging.
这篇关于如何使用bash在docker容器中获得僵尸进程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!