前言

golang实现守护进程,包含功能:

1. 守护进程只创建一次

2. 平滑创建业务进程

3. 业务进程挂起,守护进程能监听,并重启新启业务进程

4. 守护进程退出,也能保证业务进程退出

5. 业务进程≈子进程

6. 不影响业务进程逻辑

7. 以Linux平台为主,其他平台暂时没有实施条件

分析

上一篇博文讨论过如何以脚本的形式创建守护进程,这篇讨论如何以纯golang脚本实现守护进程的功能

  • 在 Unix 中,创建一个进程,通过系统调用 fork 实现(及其一些变种,如 vfork、clone)。
  • 在 Go 语言中,Linux 下创建进程使用的系统调用是 clone 。

在 C 语言中,通常会用到 2 种创建进程方法:

fork

程序会从 fork 处一分为二,父进程返回值大于0,并继续运行;子进程获得父进程的栈、数据段、堆和执行文本段的拷贝,返回值等于0,并向下继续运行。通过 fork 返回值可轻松判断当前处于父进程还是子进程。

execve

execve 为加载一个新程序到当前进程的内存,这将丢弃现存的程序文本段,并为新程序重新创建栈、数据段以及堆。通常将这一动作称为执行一个新程序。

在 Go 语言中,创建进程方法主要有 3 种:

exec.Command

os.StartProcess

syscall.RawSyscall(syscall.SYS_FORK, 0, 0, 0)

方法1和方法2通过 os.Getppid()!=1进行判断是否子进程,默认父进程退出之后,子进程会被1号进程接管。

但据Ubuntu Desktop 本地测试,接管孤儿进程的并不是1号进程,因此考虑到程序稳定性和兼容性,不能够以 ppid 作为判断父子进程的依据。

方法3直接进行了系统调用,虽然可以通过 pid 进行判断父子进程,但该方法过于底层。

综上,以exec.Command方式,通过控制参数实现守护进程

实现

相关工具方法:

说明

1、启动go_start二进制文件,方式:./go_start -c param1 -d param2 -e param3,这里第一次进入main方法

2、main方法中,os.Args = [./go_start -c param1 -d param2 -c param3],此时不包含"-daemon"参数,进入step2,走创建守护进程代码分支,执行创建守护进程,exec.Command(./go_start -c param1 -d param2 -e param3 -daemon),第二次进入main方法

3、main方法中,os.Args = [./go_start -c param1 -d param2 -c param3 -daemon],此时包含"-daemon",进入step3,走创建业务进程分支,执行创建业务进程,exec.Command(./go_start -c param1 -d param2 -e param3);此时守护进程存在,每隔5秒监听一次业务进程是否存在,如果存在则不操作;不存在则重新执行创建业务进程exec.Command(./go_start -c param1 -d param2 -e param3);

4、执行具体的业务进程逻辑

验证

ps -ef | grep go_start

]$ 110  1   ./go_start -c param1 -d param2 -c param3             -- ①
]$ 111  1   ./go_start -c param1 -d param2 -c param3 -daemon     -- ②
]$ 112  111 ./go_start -business -c param1 -d param2 -c param3   -- ③

刚开始会出现三个进程,假设进程id如上,一会之后①会消失,这是正常的,因为刚开始的启动就是①,然后只剩下进程②和③

]$ 111  1   ./go_start -c param1 -d param2 -c param3 -daemon     -- ②
]$ 112  111 ./go_start -business -c param1 -d param2 -c param3   -- ③

验证kill业务进程:会启动新的业务进程,守护进程不变;所以执行:kill 112

]$ 111  1   ./go_start -c param1 -d param2 -c param3 -daemon     -- ②
]$ 112  111 [go_start] <defunct>                                 -- ③'
]$ 113  111 ./go_start -business -c param1 -d param2 -c param3   -- ③

这里kill 112后,会出现一个僵尸进程,不影响实际业务进程的创建和运行,不需要理会;假设新创建的业务进程pid为113

验证kill守护进程:整个程序退出,也就是执行ps -ef | grep go_start后,没有对应的守护进程和业务进程,同时僵尸进程也会消失;得益于以下代码,进程组

command.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

综上

纯golang语言形式实现了守护进程,针对启动业务进程,优化点:可以使用go func(){}()协程方式启动更优雅,这里先不实施,待后续有空改进;

缺点:依然要通过参数控制守护进程和业务进程,-daemon -business,期望统一起来,不用参数控制

放在(3)实现

附录

以下是关于信号量的一个记录,当作参考文档