需求

-daemon-forever

分析

forkvforkcloneclone

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

fork
pid = fork();
//pid > 0 父进程
//pid = 0 子进程
//pid < 0 出错
forkfork
forksyscall.Syscall(syscall.SYS_FORK, 0, 0, 0)
execve
execve(pathname, argv, envp);
//pathname 可执行文件路径
//argv 参数列表
//envp 环境变量列表
execve

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

exec.Command
package main

import (
    "os"
    "os/exec"
    "path/filepath"
    "time"
)

func main() {
    //判 断当其是否是子进程,当父进程return之后,子进程会被 系统1 号进程接管
    if os.Getppid() != 1 {
        // 将命令行参数中执行文件路径转换成可用路径
        filePath, _ := filepath.Abs(os.Args[0])
        cmd := exec.Command(filePath, os.Args[1:]...)
        // 将其他命令传入生成出的进程
        cmd.Stdin = os.Stdin // 给新进程设置文件描述符,可以重定向到文件中
        cmd.Stdout = os.Stdout
        cmd.Stderr = os.Stderr
        cmd.Start() // 开始执行新进程,不等待新进程退出
        os.Exit(0)
        // return
    } else {
        // dosomething
        time.Sleep(time.Second * 10)
    }
}
os.StartProcess
if os.Getppid()!=1{   
	args:=append([]string{filePath},os.Args[1:]...)
	os.StartProcess(filePath,args,&os.ProcAttr{Files:[]*os.File{os.Stdin,os.Stdout,os.Stderr}})
	os.Exit(0)
}
syscall.RawSyscall(syscall.SYS_FORK, 0, 0, 0)
pid, _, sysErr := syscall.RawSyscall(syscall.SYS_FORK, 0, 0, 0)
if sysErr != 0 {
	Utils.LogErr(sysErr)
	os.Exit(0)
}
os.Getppid()!=1
Ubuntu Desktopppid
pid

1. 守护进程

exec.Command
func main(){
	daemon := flag.Bool("daemon", false, "run in daemon")
	if *daemon {//父进程,守护进程
		cmd := exec.Command(os.Args[0])
		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		err := cmd.Start()
		if err != nil {
			fmt.Fprintf(os.Stderr, "[-] Error: %s\n", err)
		}
		os.Exit()
	} else {//子进程,业务进程
		DoSomething()
	}
}
-daemonexec.Command-daemonmain

2. 重启进程

通过上述分析,基本已经实现了守护进程创建,重启进程就依葫芦画瓢了。

forever := flag.Bool("forever", false, "run forever")
if *forever{
	for {
		cmd := exec.Command(args[0])
		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		err := cmd.Start()
		if err != nil {
			fmt.Fprintf(os.Stderr, "[-] Error: %s\n", err)
		}
		cmd.Wait()
	}
}
for{...}cmd.Wait()
-daemon-forever

实现

本次实现主要通过方法1进行进程创建,以main函数作为程序入口点,通过传参数不同,来判断父子进程,这样有2个好处:

  1. 参数不同实现启动不同进程;
  2. 守护进程和重启进程对业务进程透明,不影响业务进程逻辑。

直接上代码

go-daemon.go

package main

import (
	"os"
	"os/exec"
	"fmt"
	"flag"
	"log"
	"time"
)

const (
	DAEMON = "daemon"
	FOREVER = "forever"
)

func DoSomething(){
	fp, _ := os.OpenFile("./dosomething.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
	log.SetOutput(fp)
	for{
		log.Printf("DoSomething running in PID: %d PPID: %d\n", os.Getpid(), os.Getppid())
		time.Sleep(time.Second * 5)
	}
}

func StripSlice(slice []string, element string) []string {
	for i := 0; i < len(slice); {
		if slice[i] == element && i != len(slice)-1 {
			slice = append(slice[:i], slice[i+1:]...)
		} else if slice[i] == element && i == len(slice)-1 {
			slice = slice[:i]
		} else {
			i++
		}
	}
	return slice
}

func SubProcess(args []string) *exec.Cmd {
	cmd := exec.Command(args[0], args[1:]...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err := cmd.Start()
	if err != nil {
		fmt.Fprintf(os.Stderr, "[-] Error: %s\n", err)
	}
	return cmd
}

func main(){
	daemon := flag.Bool(DAEMON, false, "run in daemon")
	forever := flag.Bool(FOREVER, false, "run forever")
	flag.Parse()
	fmt.Printf("[*] PID: %d PPID: %d ARG: %s\n", os.Getpid(), os.Getppid(), os.Args)
	if *daemon {
		SubProcess(StripSlice(os.Args, "-"+DAEMON))
		fmt.Printf("[*] Daemon running in PID: %d PPID: %d\n", os.Getpid(), os.Getppid())
		os.Exit(0)
	} else if *forever {
		for {
			cmd := SubProcess(StripSlice(os.Args, "-"+FOREVER))
			fmt.Printf("[*] Forever running in PID: %d PPID: %d\n", os.Getpid(), os.Getppid())
			cmd.Wait()
		}
		os.Exit(0)
	} else {
		fmt.Printf("[*] Service running in PID: %d PPID: %d\n", os.Getpid(), os.Getppid())
	}
	DoSomething()
}

使用

编译

go build -ldflags "-s -w" go-daemon.go

运行

./go-daemon -daemon -forever

代码及参考