因为Golang没有Linux的fork()系统调用, 所以实现守护进程要使用一些小技巧. Golang为*nix(unix/linux/FreeBSD...)系统提供了syscall.ForkExec()调用, 这个调用跟fork()调用不一样, syscall.ForkExec需要提供一个要执行的程序路径. syscall.ForkExec()原型如下:

func ForkExec(argv0 string, argv []string, attr *ProcAttr) (pid int, err error)

第一参数是要执行程序的路径, 第二个参数是执行程序时提供的参数列表, 第三参数提供执行程序时的一些属性.

另外需要注意的是, syscall.ForkExec()调用会创建一个新进程然后再执行第一个参数的程序. 所以调用这个函数之后会有两个进程: 一个是调用syscall.ForkExec()的父进程, 另外一个是被新创建的子进程, 子进程会执行参数中的程序.

有了syscall.ForkExec()调用之后, 我们就可以实现一个守护进程功能了. 一般守护进程的流程如下:

1. fork一个新的子进程.

2. 为子进程创建一个新的回话.

3. 把标准输入输出指向null设备.

4. 退出父进程.

用C语言来实现代码如下:

void daemonize(void){int fd;if(fork()!=0) exit(0);    setsid();/* 创建新回话 */if((fd = open("/dev/null", O_RDWR,0))!=-1){        dup2(fd, STDIN_FILENO);        dup2(fd, STDOUT_FILENO);        dup2(fd, STDERR_FILENO);if(fd > STDERR_FILENO) close(fd);}}

那么用Golang如何实现呢? 因为Golang的syscall.ForkExec()函数需要指定要执行的程序. 所以不能像C语言一样分叉执行代码. 这时我们可以通过一个小技巧来实现父子进程执行不同的代码, 这个技巧就是通过参数来实现.

我们可以在执行子进程程序时传递一个特有的参数来区分当前进程是否子进程, 例如我们可以传递”--daemon”参数. 因为父进程没有接收到”--daemon”参数, 所以被认为是父进程, 而子进程收到”--daemon”参数, 所以知道是子进程. 代码实现如下:

package daemonimport("errors""os""runtime""syscall")const daemonFlagName ="--daemon"func initDaemonRuntime(){// 创建新回话    _, err := syscall.Setsid()if err !=nil{return}// 把标准输入输出指向null    fd, err := os.OpenFile("/dev/null", os.O_RDWR,0)if err !=nil{return}    _ = syscall.Dup2(int(fd.Fd()),int(os.Stdin.Fd()))    _ = syscall.Dup2(int(fd.Fd()),int(os.Stdout.Fd()))    _ = syscall.Dup2(int(fd.Fd()),int(os.Stderr.Fd()))if fd.Fd()> os.Stderr.Fd(){        _ = fd.Close()}}func Daemon()(int, error){if runtime.GOOS =="windows"{return-1, errors.New("unsupported windows operating system")}    isDaemon :=falsefor i :=1; i < len(os.Args); i++{if os.Args[i]== daemonFlagName {            isDaemon =true}}if isDaemon {// daemon process        initDaemonRuntime()return0,nil}    procPath := os.Args[0]// 添加"--daemon"参数    args := make([]string,0, len(os.Args)+1)    args = append(args, os.Args...)    args = append(args, daemonFlagName)    attr :=&syscall.ProcAttr{Env:   os.Environ(),Files:[]uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()},}    pid, err := syscall.ForkExec(procPath, args, attr)if err !=nil{return-1, err}return pid,nil}

在Daemon()函数中, 首先判断是否有”--daemon”参数, 如果有这个参数说明是子进程, 那么就初始化子进程的运行环境, 然后返回. 如果没有”--daemon”参数, 说明是父进程, 那么就调用syscall.ForkExec()函数来执行当前程序. 执行程序之前记得要添加”--daemon”参数给子进程.