前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Golang实现守护进程

Golang实现守护进程

作者头像
用户7686797
发布2020-08-25 16:26:17
3.4K0
发布2020-08-25 16:26:17
举报
文章被收录于专栏:Linux内核那些事Linux内核那些事

因为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语言来实现代码如下:

  1. void daemonize(void) {
  2. int fd;
  3. if (fork() != 0) exit(0);
  4. setsid(); /* 创建新回话 */
  5. if ((fd = open("/dev/null", O_RDWR, 0)) != -1) {
  6. dup2(fd, STDIN_FILENO);
  7. dup2(fd, STDOUT_FILENO);
  8. dup2(fd, STDERR_FILENO);
  9. if (fd > STDERR_FILENO) close(fd);
  10. }
  11. }

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

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

  1. package daemon
  2. import (
  3. "errors"
  4. "os"
  5. "runtime"
  6. "syscall"
  7. )
  8. const daemonFlagName = "--daemon"
  9. func initDaemonRuntime() {
  10. // 创建新回话
  11. _, err := syscall.Setsid()
  12. if err != nil {
  13. return
  14. }
  15. // 把标准输入输出指向null
  16. fd, err := os.OpenFile("/dev/null", os.O_RDWR, 0)
  17. if err != nil {
  18. return
  19. }
  20. _ = syscall.Dup2(int(fd.Fd()), int(os.Stdin.Fd()))
  21. _ = syscall.Dup2(int(fd.Fd()), int(os.Stdout.Fd()))
  22. _ = syscall.Dup2(int(fd.Fd()), int(os.Stderr.Fd()))
  23. if fd.Fd() > os.Stderr.Fd() {
  24. _ = fd.Close()
  25. }
  26. }
  27. func Daemon() (int, error) {
  28. if runtime.GOOS == "windows" {
  29. return -1, errors.New("unsupported windows operating system")
  30. }
  31. isDaemon := false
  32. for i := 1; i < len(os.Args); i++ {
  33. if os.Args[i] == daemonFlagName {
  34. isDaemon = true
  35. }
  36. }
  37. if isDaemon { // daemon process
  38. initDaemonRuntime()
  39. return 0, nil
  40. }
  41. procPath := os.Args[0]
  42. // 添加"--daemon"参数
  43. args := make([]string, 0, len(os.Args)+1)
  44. args = append(args, os.Args...)
  45. args = append(args, daemonFlagName)
  46. attr := &syscall.ProcAttr{
  47. Env: os.Environ(),
  48. Files: []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()},
  49. }
  50. pid, err := syscall.ForkExec(procPath, args, attr)
  51. if err != nil {
  52. return -1, err
  53. }
  54. return pid, nil
  55. }

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-01-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Linux内核那些事 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档