Golang exec process и отказаться от него

Я пытаюсь развить процессы с моим демоном и пытаюсь отречься от них в случае сбоя моего демона. Обычный 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 все еще владеет ребенком. The 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 ответов


процесс разветвленный с 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

обратите внимание, как идентификатор родительского процесса (PPID) из


на Start() метод должно дать вам то, что вы ищете. Мой скрипт для добавления простого текстового файла после того, как моя программа Go завершила работу, продолжал работать:

package main

import (
    "os/exec"
)

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

кажется хорошей стратегией для немедленного отказа от нереста os.Argv[0] process (self-spawn), затем нерестовый целевой процесс (с Setsid: true), а затем выход из первого порожденного процесса. Таким образом, процесс gradchild немедленно получает ppid 1. При необходимости первый порожденный процесс может передавать pid granchild через stdout родительскому перед выходом.


демонизация не играет хорошо с планированием goroutine go, поэтому детали становятся довольно грубыми. godaemon имеет небольшую реализацию с хорошей дискуссией и ссылками на более хорошую дискуссию, а также более популярную и более традиционно реализованную go-daemon.

на ioctl ошибки, которые вы получаете почти наверняка приходят от использования Setctty.

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

вам семантика как os/exec ' s Start, где дочерний процесс не сразу перезаправляется, но будет безопасно поднят, если/когда родитель умрет.

The 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)