Golang exec进程并取消它

时间:2016-05-06 22:36:19

标签: linux go fork exec

我正在尝试使用我的守护进程分叉进程,并在我的守护程序崩溃的情况下尝试拒绝它们。常规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)

我在做什么/假设错了? 注意:尽管os / exec是高级别的,我也尝试了以下内容,但它产生了相同的结果。

cs := exec.Command(binary)
cs.SysProcAttr = &syscall.SysProcAttr{
    Setctty: true,
}
err := cs.Run()
fmt.Println(err)

4 个答案:

答案 0 :(得分:8)

分叉为Start()的进程即使在其父级死亡后也会继续。

func forker() {
    cmd := exec.Command("sleep", "3")
    cmd.Start()
    time.Sleep(2 * time.Second)
    os.Exit(1)
}

即使父进程仅存在2秒,sleep进程也会幸运地持续3秒:

  $ 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

注意父进程PPID退出时,sleep进程的父进程ID(1)如何变为71432。这意味着sleep进程已成为孤儿。

答案 1 :(得分:2)

Start() method应该可以为您提供所需内容。我的脚本在我的示例Go程序终止后附加一个简单的文本文件继续运行:

package main

import (
    "os/exec"
)

func main() {
    cmd := exec.Command("./appender.sh")
    cmd.Start()
}

答案 2 :(得分:2)

立即消失似乎很好的策略是生成os.Argv[0]进程(自生成),然后生成目标进程(使用Setsid:true),然后退出第一个生成的进程。这样,gradchild进程立即获得ppid 1.可选的第一个衍生进程可以在退出之前通过stdout将granchild的pid传递给父进程。

答案 3 :(得分:1)

守护进程并不适用于go的goroutine调度,所以细节变得非常粗糙。 godaemon有一个很小的实现,有很好的讨论和更好的讨论的链接,而且还有更受欢迎和更传统的实施go-daemon

ioctl的错误几乎肯定来自Setctty的使用。

Sys: &syscall.SysProcAttr{ Foreground: false, Setsid: true, },

应该为您提供os/exec&#39; s Start这样的语义,其中子进程不会立即重新生成,但如果/当父进程死亡时,将会安全地提升。

我认为,您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)