Golang exec 进程并拒绝它
Golang exec process and to disown it
我正在尝试用我的守护进程分叉进程,并试图在我的守护进程崩溃的情况下与它们断绝关系。常规 os/exec
是高级的,因此我选择了 syscall.ForkExec
并生成了以下代码:
package main
import (
"fmt"
"os"
"os/exec"
"syscall"
"time"
)
func main() {
cmd := "myproc"
binary, lookErr := exec.LookPath(cmd)
if lookErr != nil {
panic(lookErr)
}
fmt.Println(binary)
os.Remove("/tmp/stdin")
os.Remove("/tmp/stdout")
os.Remove("/tmp/stderr")
fstdin, err1 := os.Create("/tmp/stdin")
fstdout, err2 := os.Create("/tmp/stdout")
fstderr, err3 := os.Create("/tmp/stderr")
if err1 != nil || err2 != nil || err3 != nil {
fmt.Println(err1, err2, err3)
panic("WOW")
}
argv := []string{"hi"}
procAttr := syscall.ProcAttr{
Dir: "/tmp",
Files: []uintptr{fstdin.Fd(), fstdout.Fd(), fstderr.Fd()},
Env: []string{"VAR1=ABC123"},
Sys: &syscall.SysProcAttr{
Foreground: false,
},
}
pid, err := syscall.ForkExec(binary, argv, &procAttr)
fmt.Println("Spawned proc", pid, err)
time.Sleep(time.Second * 100)
}
我还制作了一个简单的应用程序,它可以休眠并打印 hello world 并将其放到路径中。
#include <stdio.h>
int main(){
while(1){
printf("hello world");
fflush(stdout);
usleep(300000);
}
}
它有效,但是,进程没有像我预期的那样发送到后台,我的 go 进程仍然拥有子进程。 SysProcAttr
在 Linux 中具有以下值:
type SysProcAttr struct {
Chroot string // Chroot.
Credential *Credential // Credential.
Ptrace bool // Enable tracing.
Setsid bool // Create session.
Setpgid bool // Set process group ID to Pgid, or, if Pgid == 0, to new pid.
Setctty bool // Set controlling terminal to fd Ctty (only meaningful if Setsid is set)
Noctty bool // Detach fd 0 from controlling terminal
Ctty int // Controlling TTY fd
Foreground bool // Place child's process group in foreground. (Implies Setpgid. Uses Ctty as fd of controlling TTY)
Pgid int // Child's process group ID if Setpgid.
Pdeathsig Signal // Signal that the process will get when its parent dies (Linux only)
Cloneflags uintptr // Flags for clone calls (Linux only)
UidMappings []SysProcIDMap // User ID mappings for user namespaces.
GidMappings []SysProcIDMap // Group ID mappings for user namespaces.
// GidMappingsEnableSetgroups enabling setgroups syscall.
// If false, then setgroups syscall will be disabled for the child process.
// This parameter is no-op if GidMappings == nil. Otherwise for unprivileged
// users this should be set to false for mappings work.
GidMappingsEnableSetgroups bool
}
我也尝试了以下但它导致了错误:
Sys: &syscall.SysProcAttr{
Setsid: true,
Setctty: true,
Foreground: false,
},
Spawned proc 0 inappropriate ioctl for device
还有以下内容:
Sys: &syscall.SysProcAttr{
Setsid: true,
Setctty: true,
Foreground: false,
Noctty: true,
Setpgid: true,
},
Spawned proc 0 operation not permitted (with root privilleges)
我doing/assuming哪里错了?
注意: 尽管说 os/exec 是高级的,但我也尝试了以下方法,但结果相同。
cs := exec.Command(binary)
cs.SysProcAttr = &syscall.SysProcAttr{
Setctty: true,
}
err := cs.Run()
fmt.Println(err)
Start() method 应该可以满足您的需求。我的示例 Go 程序终止后附加一个简单文本文件的脚本继续到 运行:
package main
import (
"os/exec"
)
func main() {
cmd := exec.Command("./appender.sh")
cmd.Start()
}
使用 Start()
分叉的进程即使在其父进程死亡后仍将继续。
func forker() {
cmd := exec.Command("sleep", "3")
cmd.Start()
time.Sleep(2 * time.Second)
os.Exit(1)
}
此处 sleep
进程将愉快地继续存在 3 秒,即使父进程仅存在 2 秒:
$ forker &; while true; do ps -f; sleep 1; done
UID PID PPID C STIME TTY TIME CMD
501 71423 69892 0 3:01PM ttys003 0:00.07 forker
501 71433 71432 0 3:01PM ttys003 0:00.00 sleep 3
UID PID PPID C STIME TTY TIME CMD
501 71423 69892 0 3:01PM ttys003 0:00.07 forker
501 71433 71432 0 3:01PM ttys003 0:00.00 sleep 3
UID PID PPID C STIME TTY TIME CMD
501 71433 1 0 3:01PM ttys003 0:00.00 sleep 3
注意当父进程 71432
退出时,sleep
进程的父进程 ID (PPID
) 是如何变为 1
的。这意味着 sleep
进程已被孤立。
似乎立即放弃的好策略是生成 os.Argv[0]
进程(自生成),然后生成目标进程(Setsid:true),然后退出第一个生成的进程。这样,gradchild 进程立即获得 ppid 1。可选地,第一个生成的进程可以在退出之前通过 stdout 将 granchild 的 pid 传递给父进程。
守护进程与 go 的 goroutine 调度不能很好地配合,所以细节变得相当粗糙。 godaemon has a small implementation with good discussion and links to more good discussion, and there's also the more popular and more traditionally implemented go-daemon.
您遇到的 ioctl
错误几乎肯定来自使用 Setctty
。
Sys: &syscall.SysProcAttr{
Foreground: false,
Setsid: true,
},
应该得到像 os/exec
的 Start
那样的语义,其中子进程不会立即重新成为父进程,但会安全地被提升 if/when 父进程死亡。
我认为,您看到的 operation not permitted
是关于 /tmp/
中的管道,来自 运行 下的 strace:
openat(AT_FDCWD, "/tmp/stdin", O_RDWR|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 3
epoll_create1(EPOLL_CLOEXEC) = 4
epoll_ctl(4, EPOLL_CTL_ADD, 3, {EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, {u32=471047936, u64=140179613654784}}) = -1 EPERM (Operation not permitted)
epoll_ctl(4, EPOLL_CTL_DEL, 3, 0xc420039c24) = -1 EPERM (Operation not permitted)
openat(AT_FDCWD, "/tmp/stdout", O_RDWR|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 5
epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, {u32=471047936, u64=140179613654784}}) = -1 EPERM (Operation not permitted)
epoll_ctl(4, EPOLL_CTL_DEL, 5, 0xc420039c24) = -1 EPERM (Operation not permitted)
openat(AT_FDCWD, "/tmp/stderr", O_RDWR|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 6
epoll_ctl(4, EPOLL_CTL_ADD, 6, {EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, {u32=471047936, u64=140179613654784}}) = -1 EPERM (Operation not permitted)
epoll_ctl(4, EPOLL_CTL_DEL, 6, 0xc420039c24) = -1 EPERM (Operation not permitted)
我正在尝试用我的守护进程分叉进程,并试图在我的守护进程崩溃的情况下与它们断绝关系。常规 os/exec
是高级的,因此我选择了 syscall.ForkExec
并生成了以下代码:
package main
import (
"fmt"
"os"
"os/exec"
"syscall"
"time"
)
func main() {
cmd := "myproc"
binary, lookErr := exec.LookPath(cmd)
if lookErr != nil {
panic(lookErr)
}
fmt.Println(binary)
os.Remove("/tmp/stdin")
os.Remove("/tmp/stdout")
os.Remove("/tmp/stderr")
fstdin, err1 := os.Create("/tmp/stdin")
fstdout, err2 := os.Create("/tmp/stdout")
fstderr, err3 := os.Create("/tmp/stderr")
if err1 != nil || err2 != nil || err3 != nil {
fmt.Println(err1, err2, err3)
panic("WOW")
}
argv := []string{"hi"}
procAttr := syscall.ProcAttr{
Dir: "/tmp",
Files: []uintptr{fstdin.Fd(), fstdout.Fd(), fstderr.Fd()},
Env: []string{"VAR1=ABC123"},
Sys: &syscall.SysProcAttr{
Foreground: false,
},
}
pid, err := syscall.ForkExec(binary, argv, &procAttr)
fmt.Println("Spawned proc", pid, err)
time.Sleep(time.Second * 100)
}
我还制作了一个简单的应用程序,它可以休眠并打印 hello world 并将其放到路径中。
#include <stdio.h>
int main(){
while(1){
printf("hello world");
fflush(stdout);
usleep(300000);
}
}
它有效,但是,进程没有像我预期的那样发送到后台,我的 go 进程仍然拥有子进程。 SysProcAttr
在 Linux 中具有以下值:
type SysProcAttr struct {
Chroot string // Chroot.
Credential *Credential // Credential.
Ptrace bool // Enable tracing.
Setsid bool // Create session.
Setpgid bool // Set process group ID to Pgid, or, if Pgid == 0, to new pid.
Setctty bool // Set controlling terminal to fd Ctty (only meaningful if Setsid is set)
Noctty bool // Detach fd 0 from controlling terminal
Ctty int // Controlling TTY fd
Foreground bool // Place child's process group in foreground. (Implies Setpgid. Uses Ctty as fd of controlling TTY)
Pgid int // Child's process group ID if Setpgid.
Pdeathsig Signal // Signal that the process will get when its parent dies (Linux only)
Cloneflags uintptr // Flags for clone calls (Linux only)
UidMappings []SysProcIDMap // User ID mappings for user namespaces.
GidMappings []SysProcIDMap // Group ID mappings for user namespaces.
// GidMappingsEnableSetgroups enabling setgroups syscall.
// If false, then setgroups syscall will be disabled for the child process.
// This parameter is no-op if GidMappings == nil. Otherwise for unprivileged
// users this should be set to false for mappings work.
GidMappingsEnableSetgroups bool
}
我也尝试了以下但它导致了错误:
Sys: &syscall.SysProcAttr{
Setsid: true,
Setctty: true,
Foreground: false,
},
Spawned proc 0 inappropriate ioctl for device
还有以下内容:
Sys: &syscall.SysProcAttr{
Setsid: true,
Setctty: true,
Foreground: false,
Noctty: true,
Setpgid: true,
},
Spawned proc 0 operation not permitted (with root privilleges)
我doing/assuming哪里错了? 注意: 尽管说 os/exec 是高级的,但我也尝试了以下方法,但结果相同。
cs := exec.Command(binary)
cs.SysProcAttr = &syscall.SysProcAttr{
Setctty: true,
}
err := cs.Run()
fmt.Println(err)
Start() method 应该可以满足您的需求。我的示例 Go 程序终止后附加一个简单文本文件的脚本继续到 运行:
package main
import (
"os/exec"
)
func main() {
cmd := exec.Command("./appender.sh")
cmd.Start()
}
使用 Start()
分叉的进程即使在其父进程死亡后仍将继续。
func forker() {
cmd := exec.Command("sleep", "3")
cmd.Start()
time.Sleep(2 * time.Second)
os.Exit(1)
}
此处 sleep
进程将愉快地继续存在 3 秒,即使父进程仅存在 2 秒:
$ forker &; while true; do ps -f; sleep 1; done
UID PID PPID C STIME TTY TIME CMD
501 71423 69892 0 3:01PM ttys003 0:00.07 forker
501 71433 71432 0 3:01PM ttys003 0:00.00 sleep 3
UID PID PPID C STIME TTY TIME CMD
501 71423 69892 0 3:01PM ttys003 0:00.07 forker
501 71433 71432 0 3:01PM ttys003 0:00.00 sleep 3
UID PID PPID C STIME TTY TIME CMD
501 71433 1 0 3:01PM ttys003 0:00.00 sleep 3
注意当父进程 71432
退出时,sleep
进程的父进程 ID (PPID
) 是如何变为 1
的。这意味着 sleep
进程已被孤立。
似乎立即放弃的好策略是生成 os.Argv[0]
进程(自生成),然后生成目标进程(Setsid:true),然后退出第一个生成的进程。这样,gradchild 进程立即获得 ppid 1。可选地,第一个生成的进程可以在退出之前通过 stdout 将 granchild 的 pid 传递给父进程。
守护进程与 go 的 goroutine 调度不能很好地配合,所以细节变得相当粗糙。 godaemon has a small implementation with good discussion and links to more good discussion, and there's also the more popular and more traditionally implemented go-daemon.
您遇到的 ioctl
错误几乎肯定来自使用 Setctty
。
Sys: &syscall.SysProcAttr{
Foreground: false,
Setsid: true,
},
应该得到像 os/exec
的 Start
那样的语义,其中子进程不会立即重新成为父进程,但会安全地被提升 if/when 父进程死亡。
我认为,您看到的 operation not permitted
是关于 /tmp/
中的管道,来自 运行 下的 strace:
openat(AT_FDCWD, "/tmp/stdin", O_RDWR|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 3
epoll_create1(EPOLL_CLOEXEC) = 4
epoll_ctl(4, EPOLL_CTL_ADD, 3, {EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, {u32=471047936, u64=140179613654784}}) = -1 EPERM (Operation not permitted)
epoll_ctl(4, EPOLL_CTL_DEL, 3, 0xc420039c24) = -1 EPERM (Operation not permitted)
openat(AT_FDCWD, "/tmp/stdout", O_RDWR|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 5
epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, {u32=471047936, u64=140179613654784}}) = -1 EPERM (Operation not permitted)
epoll_ctl(4, EPOLL_CTL_DEL, 5, 0xc420039c24) = -1 EPERM (Operation not permitted)
openat(AT_FDCWD, "/tmp/stderr", O_RDWR|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 6
epoll_ctl(4, EPOLL_CTL_ADD, 6, {EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, {u32=471047936, u64=140179613654784}}) = -1 EPERM (Operation not permitted)
epoll_ctl(4, EPOLL_CTL_DEL, 6, 0xc420039c24) = -1 EPERM (Operation not permitted)