在Go中启动一个进程并与之分离。

huangapple go评论88阅读模式
英文:

Start a process in Go and detach from it

问题

我需要在Go中启动一个新的进程,具有以下要求:

  • 即使Go进程终止,启动的进程也应该继续运行
  • 我需要能够设置运行该进程的Unix用户/组
  • 我需要能够设置继承的环境变量
  • 我需要对标准输入/输出/错误进行控制

以下是一个尝试的代码示例:

var attr = os.ProcAttr {
	Dir: "/bin",
	Env: os.Environ(),
	Files: []*os.File{
		os.Stdin,
		os.Stdout,
		os.Stderr,
	},
}
process, err := os.StartProcess("sleep", []string{"1"}, &attr)

这段代码可以正常工作,但与要求相比有以下缺点:

  • 没有设置Unix用户/组的方法
  • 当Go进程(父进程)停止时,启动的进程也会结束

如果只需要在Linux上运行,可能会简化问题。

英文:

I need to start a new process in Go with the following requirements:

  • The starting process should run even after the Go process is terminated
  • I need to be able to set the Unix user/group that's running it
  • I need to be able to set the environment variables inherited
  • I need control over std in/out/err

Here is an attempt:

var attr = os.ProcAttr {
Dir: "/bin",
Env: os.Environ(),
Files: []*os.File{
    os.Stdin,
    "stdout.log",
    "stderr.log",
  },
}
process, err := os.StartProcess("sleep", []string{"1"}, &attr)

This works fine but has the following shortcomings from the requirements:

  • No way to set Unix user/group
  • The started process ends when the Go process (parent) stops

This needs to run on Linux only if that simplifies things.

答案1

得分: 24

你可以使用process.Release来将子进程与父进程分离,并使其在父进程死亡后继续运行。

*os.ProcAttr.Sys.Credentials属性的定义如下:通过使用该属性,你可以设置进程的用户和组ID。

以下是你示例的工作版本(我没有检查进程ID是否实际上是设置的那个):

package main

import "fmt"
import "os"
import "syscall"

const (
	UID  = 501
	GUID = 100
)

func main() {
	// Credential字段用于设置进程的UID、GID和附加的GIDS
	// 你需要以root身份运行程序才能这样做
	var cred = &syscall.Credential{UID, GUID, []uint32{}}
	// Noctty标志用于将进程与父tty分离
	var sysproc = &syscall.SysProcAttr{Credential: cred, Noctty: true}
	var attr = os.ProcAttr{
		Dir:   ".",
		Env:   os.Environ(),
		Files: []*os.File{os.Stdin, nil, nil},
		Sys:   sysproc,
	}
	process, err := os.StartProcess("/bin/sleep", []string{"/bin/sleep", "100"}, &attr)
	if err == nil {
		// 文档中并不清楚,但是Release实际上是将进程分离
		err = process.Release()
		if err != nil {
			fmt.Println(err.Error())
		}
	} else {
		fmt.Println(err.Error())
	}
}

希望对你有帮助!

英文:
  1. You can use process.Release to detach the child process from the parent one and make it survive after parent death
  2. Look at the definition of *os.ProcAttr.Sys.Credentials attribute : it looks like using the attribute you can set process user and group ID.

Here is a working version of your example (I did not check if process ID's where actually the one set )

package main

import "fmt"
import "os"
import "syscall"

const (
	UID = 501
	GUID = 100
	)


func main() {
	// The Credential fields are used to set UID, GID and attitional GIDS of the process
	// You need to run the program as  root to do this
        var cred =  &syscall.Credential{ UID, GUID, []uint32{} }
	// the Noctty flag is used to detach the process from parent tty
	var sysproc = &syscall.SysProcAttr{  Credential:cred, Noctty:true }
	var attr = os.ProcAttr{
		Dir: ".",
		Env: os.Environ(),
		Files: []*os.File{
			os.Stdin,
			nil,
			nil,
		},
            Sys:sysproc,

	}
	process, err := os.StartProcess("/bin/sleep", []string{"/bin/sleep", "100"}, &attr)
	if err == nil {

		// It is not clear from docs, but Realease actually detaches the process
		err = process.Release();
		if err != nil {
			fmt.Println(err.Error())
		}
		
 	} else {
		fmt.Println(err.Error())
	}
}

答案2

得分: 2

我发现似乎可以跨平台地重新运行程序,只需使用一个特殊的标志重新运行程序。在你的主程序中,检查是否存在这个标志。如果在启动时存在,那么你就处于“分叉”状态。如果不存在,重新运行带有该标志的命令。

func rerunDetached() error {
    cwd, err := os.Getwd()
    if err != nil {
        return err
    }
    args := append(os.Args, "--detached")
    cmd := exec.Command(args[0], args[1:]...)
    cmd.Dir = cwd
    err = cmd.Start()
    if err != nil {
        return err
    }
    cmd.Process.Release()
    return nil
}

这将简单地使用相同的参数重新运行你的进程,并在参数中添加 --detached。当你的程序启动时,检查是否存在 --detached 标志,以确定是否需要调用 rerunDetached。这有点像一个适用于不同操作系统的简易版 fork()

英文:

What I have found that seems to work cross-platform is to re-run the program with a special flag. In your main program, check for this flag. If present on startup, you're in the "fork". If not present, re-run the command with the flag.

func rerunDetached() error {
	cwd, err := os.Getwd()
    if err != nil {
	   return err
    }
    args := append(os.Args, "--detached")
    cmd := exec.Command(args[0], args[1:]...)
    cmd.Dir = cwd
    err = cmd.Start()
    if err != nil {
	   return err
    }
    cmd.Process.Release()
    return nil
}

This will simply re-run your process with the exact parameters and append --detached to the arguments. When your program starts, check for the --detached flag to know if you need to call rerunDetached or not. This is sort of like a poor mans fork() which will work across different OS.

huangapple
  • 本文由 发表于 2014年4月12日 22:35:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/23031752.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定