Goo*_*vie 6 linux daemon process go
问题:
我正在 linux 上用 golang 编写程序,该程序需要执行长时间运行的进程,以便:
我正在以 root 权限运行我的程序。
尝试的解决方案:
func Run(pathToBin string, args []string, uid uint32, stdLogFile *os.File) (int, error) {
cmd := exec.Command(pathToBin, args...)
cmd.SysProcAttr = &syscall.SysProcAttr{
Credential: &syscall.Credential{
Uid: uid,
},
}
cmd.Stdout = stdLogFile
if err := cmd.Start(); err != nil {
return -1, err
}
go func() {
cmd.Wait() //Wait is necessary so cmd doesn't become a zombie
}()
return cmd.Process.Pid, nil
}
Run Code Online (Sandbox Code Playgroud)
这个解决方案似乎满足了我几乎所有的要求,除了当我将 SIGTERM/SIGKILL 发送到我的程序时,底层进程崩溃了。事实上,我希望我的后台进程尽可能独立:它与我的程序有不同的父进程号、组进程号等。我想将它作为守护进程运行。
stackoverflow 上的其他解决方案建议cmd.Process.Release()
用于类似用例,但它似乎不起作用。
在我的情况下不适用的解决方案:
事实上,我可以使用易于import
从 github 等导入的库。
太长了;
\n只需使用https://github.com/hashicorp/go-reap
\n有一句很棒的俄语表达:“不要试图诞生自行车”,意思是不要重新发明轮子并保持简单。我认为这也适用于这里。如果我是你,我会重新考虑使用以下之一:
\n这个问题已经解决了;)
\n您的问题不精确或者您要求的是非标准功能。
\n\n\n事实上,我希望我的后台进程尽可能独立:它与我的程序具有不同的父 pid、组 pid 等。我想将它作为守护进程运行。
\n
这不是进程继承的工作原理。你不能让进程 A 启动进程 B 并以某种方式将 B 的父进程更改为 C。据我所知,这在 Linux 中是不可能的。
\n换句话说,如果进程 A (pid 55) 启动进程 B (100),则 B 的父进程 pid 必须为 55。
\n避免这种情况的唯一方法是让其他东西启动 B 进程,例如 atd、crond 或其他东西 - 这不是您所要求的。
\n如果父进程 55 死亡,那么 PID 1 将成为 100 的父进程,而不是某个任意进程。
\n你的说法“它有不同的父pid”没有意义。
\n\n\n我想将它作为守护进程运行。
\n
那太好了。然而,在 GNU / Linux 系统中,所有守护进程都有一个父 pid,并且这些父进程都有一个父 pid 一直到 pid 1,严格按照父 -> 子规则。
\n\n\n当我向程序发送 SIGTERM/SIGKILL 时,底层进程崩溃。
\n
我无法重现这种行为。请参阅概念验证存储库中的 case8 和 case7 。
\nmake case8\nexport NOSIGN=1; make build case7 \nunset NOSIGN; make build case7\n
Run Code Online (Sandbox Code Playgroud)\n\n$ make case8\n{ sleep 6 && killall -s KILL zignal; } &\n./bin/ctrl-c &\nsleep 2; killall -s TERM ctrl-c\nkill with:\n { pidof ctrl-c; pidof signal ; } | xargs -r -t kill -9 \nmain() 2476074\nbashed 2476083 (2476081)\nbashed 2476084 (2476081)\nbashed 2476085 (2476081)\nzignal 2476088 (2476090)\ngo main() got 23 urgent I/O condition\ngo main() got 23 urgent I/O condition\nzignal 2476098 (2476097)\ngo main() got 23 urgent I/O condition\nzignal 2476108 (2476099)\nmain() wait...\np 2476088\np 2476098\np 2476108\np 2476088\ngo main() got 15 terminated\nsleep 1; killall -s TERM ctrl-c\np 2476098\np 2476108\np 2476088\ngo main() got 15 terminated\nsleep 1; killall -s TERM ctrl-c\np 2476098\np 2476108\np 2476088\nBash c 2476085 EXITs ELAPSED 4\ngo main() got 17 child exited\ngo main() got 23 urgent I/O condition\nmain() children done: 1 %!s(<nil>)\nmain() wait...\ngo main() got 15 terminated\ngo main() got 23 urgent I/O condition\nsleep 1; killall -s KILL ctrl-c\np 2476098\np 2476108\np 2476088\nbalmora: ~/src/my/go/doodles/sub-process [main]\n$ p 2476098\np 2476108\nBash _ 2476083 EXITs ELAPSED 6\nBash q 2476084 EXITs ELAPSED 8\n\n\n
Run Code Online (Sandbox Code Playgroud)\nbash 进程在父进程被杀死后继续运行。
\nkillall -s KILL ctrl-c;\n
Run Code Online (Sandbox Code Playgroud)\n所有 3 个“zignal”子进程都在运行,直到被杀死
\nkillall -s KILL zignal;\n
Run Code Online (Sandbox Code Playgroud)\n在这两种情况下,尽管主进程收到了 TERM、HUP、INT 信号,但子进程仍继续运行。由于方便原因,此行为在 shell 环境中有所不同。请参阅有关信号的相关问题。这个特定的答案说明了 SIGINT 的一个关键区别。请注意,应用程序无法捕获 SIGSTOP 和 SIGKILL。
\n在继续讨论问题的其他部分之前,有必要澄清上述内容。
\n到目前为止,您已经解决了以下问题:
\n下一个取决于孩子们是否“附着”在贝壳上
\n最后一个很难重现,但我在 docker 世界中听说过这个问题,所以这个答案的其余部分集中于解决这个问题。
\n正如您所指出的,Cmd.Wait()
避免产生僵尸是必要的。经过一些实验后,我能够使用故意简单的替换/bin/sh
. 这个用 go 实现的“shell”只会运行一个命令,在获取子项方面不会执行太多其他命令。您可以在 github 上研究代码。
导致僵尸的简单包装
\nmake case8\nexport NOSIGN=1; make build case7 \nunset NOSIGN; make build case7\n
Run Code Online (Sandbox Code Playgroud)\n收割者包装纸
\n\n$ make case8\n{ sleep 6 && killall -s KILL zignal; } &\n./bin/ctrl-c &\nsleep 2; killall -s TERM ctrl-c\nkill with:\n { pidof ctrl-c; pidof signal ; } | xargs -r -t kill -9 \nmain() 2476074\nbashed 2476083 (2476081)\nbashed 2476084 (2476081)\nbashed 2476085 (2476081)\nzignal 2476088 (2476090)\ngo main() got 23 urgent I/O condition\ngo main() got 23 urgent I/O condition\nzignal 2476098 (2476097)\ngo main() got 23 urgent I/O condition\nzignal 2476108 (2476099)\nmain() wait...\np 2476088\np 2476098\np 2476108\np 2476088\ngo main() got 15 terminated\nsleep 1; killall -s TERM ctrl-c\np 2476098\np 2476108\np 2476088\ngo main() got 15 terminated\nsleep 1; killall -s TERM ctrl-c\np 2476098\np 2476108\np 2476088\nBash c 2476085 EXITs ELAPSED 4\ngo main() got 17 child exited\ngo main() got 23 urgent I/O condition\nmain() children done: 1 %!s(<nil>)\nmain() wait...\ngo main() got 15 terminated\ngo main() got 23 urgent I/O condition\nsleep 1; killall -s KILL ctrl-c\np 2476098\np 2476108\np 2476088\nbalmora: ~/src/my/go/doodles/sub-process [main]\n$ p 2476098\np 2476108\nBash _ 2476083 EXITs ELAPSED 6\nBash q 2476084 EXITs ELAPSED 8\n\n\n
Run Code Online (Sandbox Code Playgroud)\n运行其他命令的 init/sh (pid 1) 进程
\nkillall -s KILL ctrl-c;\n
Run Code Online (Sandbox Code Playgroud)\nDockerfile
\n\nFROM scratch\n\n# for sh.go\nENV HANG ""\n\n# for sub-process.go\nENV ABORT ""\nENV CRASH ""\nENV KILL ""\n\n# for ctrl-c.go, signal.go\nENV NOSIGN ""\n\nCOPY bin/sh /bin/sh ## <---- wrapped or simple /bin/sh or "init"\nCOPY bin/sub-process /bin/sub-process\nCOPY bin/zleep /bin/zleep\nCOPY bin/fork-if /bin/fork-if\n\n\nCOPY --from=busybox:latest /bin/find /bin/find\nCOPY --from=busybox:latest /bin/ls /bin/ls\nCOPY --from=busybox:latest /bin/ps /bin/ps\nCOPY --from=busybox:latest /bin/killall /bin/killall\n\n
Run Code Online (Sandbox Code Playgroud)\n剩余的代码/设置可以在这里看到:
\n\n其要点是我们使用“父”二进制文件从 go 启动两个子进程sub-process
。第一个孩子是zleep
,第二个孩子是fork-if
。第二个启动一个“守护进程”,除了一些短暂的线程之外,它还运行一个永远循环。过了一段时间,我们杀死了sub-procss
父母,迫使他们sh
接管了这些孩子的抚养权。
由于 sh 的这个简单实现不知道如何处理被遗弃的孩子,因此孩子们变成了僵尸。\n这是标准行为。为了避免这种情况,init 系统通常负责清理任何此类子进程。
\n查看此存储库并运行案例:
\n$ make prep build\n$ make prep build2\n
Run Code Online (Sandbox Code Playgroud)\n第一个将在 docker 容器中使用简单的 /bin/sh,第二个将使用包装在 reaper 中的相同代码。
\n与僵尸:
\n$ make prep build case5\n(\xe2\x80\xa6)\nmain() Daemon away! 16 (/bin/zleep)\nmain() Daemon away! 22 (/bin/fork-if)\n(\xe2\x80\xa6)\nmain() CRASH imminent\npanic: runtime error: invalid memory address or nil pointer dereference\n[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x49e45c]\ngoroutine 1 [running]:\nmain.main()\n /home/jaroslav/src/my/go/doodles/sub-process/sub-process.go:137 +0xfc\ncmd [/bin/sub-process /log/case5 3 /bin/zleep 111 2 -- /dev/stderr 3 /bin/fork-if --] err: exit status 2\nChild \'1\' done\nthread done\nSTAT COMMAND USER ELAPSED PID PPID\nR sh 0 0:02 1 0\nS zleep 3 0:02 16 1\nZ fork-if 3 0:02 22 1\nR fork-child-A 3 0:02 25 1\nR fork-child-B 3 0:02 26 25\nS fork-child-C 3 0:02 27 26\nS fork-daemon 3 0:02 28 27\nR ps 0 0:01 30 1\nChild \'2\' done\nthread done\ndaemon\n(\xe2\x80\xa6)\nSTAT COMMAND USER ELAPSED PID PPID\nR sh 0 0:04 1 0\nZ zleep 3 0:04 16 1\nZ fork-if 3 0:04 22 1\nZ fork-child-A 3 0:04 25 1\nR fork-child-B 3 0:04 26 1\nS fork-child-C 3 0:04 27 26\nS fork-daemon 3 0:04 28 27\nR ps 0 0:01 33 1\n(\xe2\x80\xa6)\n
Run Code Online (Sandbox Code Playgroud)\n与收割者:
\n$ make -C ~/src/my/go/doodles/sub-process case5\n(\xe2\x80\xa6)\nmain() CRASH imminent\n(\xe2\x80\xa6)\nChild \'1\' done\nthread done\nraeper pid 24\nSTAT COMMAND USER ELAPSED PID PPID\nS sh 0 0:02 1 0\nS zleep 3 0:01 18 1\nR fork-child-A 3 0:01 27 1\nR fork-child-B 3 0:01 28 27\nS fork-child-C 3 0:01 30 28\nS fork-daemon 3 0:01 31 30\nR ps 0 0:01 32 1\nChild \'2\' done\nthread done\nraeper pid 27\ndaemon\nSTAT COMMAND USER ELAPSED PID PPID\nS sh 0 0:03 1 0\nS zleep 3 0:02 18 1\nR fork-child-B 3 0:02 28 1\nS fork-child-C 3 0:02 30 28\nS fork-daemon 3 0:02 31 30\nR ps 0 0:01 33 1\nSTAT COMMAND USER ELAPSED PID PPID\nS sh 0 0:03 1 0\nS zleep 3 0:02 18 1\nR fork-child-B 3 0:02 28 1\nS fork-child-C 3 0:02 30 28\nS fork-daemon 3 0:02 31 30\nR ps 0 0:01 34 1\nraeper pid 18\ndaemon\nSTAT COMMAND USER ELAPSED PID PPID\nS sh 0 0:04 1 0\nR fork-child-B 3 0:03 28 1\nS fork-child-C 3 0:03 30 28\nS fork-daemon 3 0:03 31 30\nR ps 0 0:01 35 1\n(\xe2\x80\xa6)\n
Run Code Online (Sandbox Code Playgroud)\n这是相同输出的图片,阅读起来可能不会那么混乱。
\n僵尸
\n收割者
\n获取代码
\ngit clone https://github.com/tox2ik/go-poc-reaper.git\n
Run Code Online (Sandbox Code Playgroud)\n一个终端:
\nmake tail-cases\n
Run Code Online (Sandbox Code Playgroud)\n另一个航站楼
\nmake prep\nmake build\nor make build2\nmake case0 case1\n...\n
Run Code Online (Sandbox Code Playgroud)\n相关问题:
\n去
\n信号
\n相关讨论:
\n相关项目:
\n相关散文:
\n\n\n僵尸进程是指执行完成但进程表中仍有条目的进程。僵尸进程通常发生在子进程中,因为父进程仍然需要读取其 child\xe2\x80\x99s 退出状态。一旦使用 wait 系统调用完成此操作,僵尸进程就会从进程表中消除。这称为收获僵尸进程。
\n
来自 https://www.tutorialspoint.com/what-is-zombie-process-in-linux
\n