2016-07-01 17 views
9

Ich verwende GO, um zu überprüfen, ob ein Prozess (nicht übergeordnet) beendet wurde, im Grunde so etwas wie der pwait Befehl in FreeBSD aber geschrieben in gehen.Wie man richtig auf ein Ereignis/einen Prozess wartet, um zu beenden, nicht das Elternteil zu sein?

Derzeit versuche ich ein for loop mit einem kill -0, aber ich merke, dass die CPU-Auslastung ist sehr hoch 99% mit diesem Ansatz, hier ist der Code:

package main 

import (
    "fmt" 
    "os" 
    "strconv" 
    "syscall" 
    "time" 
) 

func main() { 

    if len(os.Args) != 2 { 
     fmt.Printf("usage: %s pid", os.Args[0]) 
     os.Exit(1) 
    } 

    pid, err := strconv.ParseInt(os.Args[1], 10, 64) 
    if err != nil { 
     panic(err) 
    } 

    process, err := os.FindProcess(int(pid)) 

    err = process.Signal(syscall.Signal(0)) 
    for err == nil { 
     err = process.Signal(syscall.Signal(0)) 
     time.Sleep(500 * time.Millisecond) 
    } 
    fmt.Println(err) 
} 

Jede Idee, wie man verbessern oder richtig umsetzen.

Vielen Dank im Voraus.

UPDATE

eine sleep innerhalb der Schleife Hinzufügen wie vorgeschlagen, hilft, die Belastung zu reduzieren.

Von den bereitgestellten Links scheint es möglich, an die vorhandene PID anzuhängen, werde ich versuchen PtraceAttach aber weiß nicht, ob dies Nebenwirkungen haben kann, keine Ahnung?

Wie vorgeschlagen, dass ich zur Verfügung stand verwenden kqueue: fein

package main 

import (
    "fmt" 
    "log" 
    "os" 
    "strconv" 
    "syscall" 
) 

func main() { 
    if len(os.Args) != 2 { 
     fmt.Printf("usage: %s pid", os.Args[0]) 
     os.Exit(1) 
    } 

    pid, err := strconv.ParseInt(os.Args[1], 10, 64) 
    if err != nil { 
     panic(err) 
    } 

    process, _ := os.FindProcess(int(pid)) 

    kq, err := syscall.Kqueue() 
    if err != nil { 
     fmt.Println(err) 
    } 

    ev1 := syscall.Kevent_t{ 
     Ident: uint64(process.Pid), 
     Filter: syscall.EVFILT_PROC, 
     Flags: syscall.EV_ADD, 
     Fflags: syscall.NOTE_EXIT, 
     Data: 0, 
     Udata: nil, 
    } 

    for { 
     events := make([]syscall.Kevent_t, 1) 
     n, err := syscall.Kevent(kq, []syscall.Kevent_t{ev1}, events, nil) 
     if err != nil { 
      log.Println("Error creating kevent") 
     } 
     if n > 0 { 
      break 
     } 
    } 

    fmt.Println("fin") 
} 

funktioniert, aber frage mich, wie das gleiche auf Linux zu implementieren/zu erreichen, da ich kqueue nicht auf sie denken, irgendwelche Ideen?

+3

Einige Ideen aus dieser Frage: [Wie warten auf den Ausgang von Nicht-Kinder-Prozesse] (http://stackoverflow.com/q/1157700) –

+2

Legen Sie einen kurzen Schlaf in Ihre for-Schleife. Linux bietet keinen effizienten Weg, dies zu tun – JimB

+1

Die 'kqueue' API ist von' go' (im 'syscall' Paket) verwendbar, und wenn Portabilität nicht erforderlich ist, über * BSD und Darwin hinaus, dann ist der BSD' pwait 'Utility könnte in' go' übersetzt werden. – kdhp

Antwort

1

Eine Lösung wäre die Verwendung des Netlink Proc-Connectors, bei dem es sich um einen Socket handelt, den der Kernel verwendet, um Userspace über verschiedene Prozessereignisse zu informieren. Die official documentation fehlt etwas, obwohl es ein paar good examples in C gibt, die wahrscheinlich besser zu lesen sind.

Der Hauptgrund für die Verwendung des proc-Konnektors ist, dass der Prozess als root ausgeführt werden muss. Wenn Sie Ihr Programm als Benutzer ohne Rootberechtigung ausführen, sollten Sie andere Optionen in Betracht ziehen, z. B. das periodische Abfragen von /proc auf Änderungen. Jeder Ansatz, der das Abfragen verwendet, wie andere darauf hingewiesen haben, ist anfällig für eine Wettlaufsituation, wenn der Prozess beendet wird und ein anderer zwischen den Abfragen mit der gleichen PID gestartet wird.

Wie dem auch sei, das proc-Anschluss in Go zu verwenden, werden wir einige Insbesondere Übersetzung von C. zu tun haben, müssen wir die proc_event und exit_proc_event structs von cn_proc.h und die cn_msg und cb_id structs von connector.h definieren.

// CbID corresponds to cb_id in connector.h 
type CbID struct { 
    Idx uint32 
    Val uint32 
} 

// CnMsg corresponds to cn_msg in connector.h 
type CnMsg struct { 
    ID CbID 
    Seq uint32 
    Ack uint32 
    Len uint16 
    Flags uint16 
} 

// ProcEventHeader corresponds to proc_event in cn_proc.h 
type ProcEventHeader struct { 
    What uint32 
    CPU uint32 
    Timestamp uint64 
} 

// ExitProcEvent corresponds to exit_proc_event in cn_proc.h 
type ExitProcEvent struct { 
    ProcessPid uint32 
    ProcessTgid uint32 
    ExitCode uint32 
    ExitSignal uint32 
} 

Wir müssen auch einen Netlink-Socket und Call Bind machen.

sock, err := unix.Socket(unix.AF_NETLINK, unix.SOCK_DGRAM, unix.NETLINK_CONNECTOR) 

if err != nil { 
    fmt.Println("socket: %v", err) 
    return 
} 

addr := &unix.SockaddrNetlink{Family: unix.AF_NETLINK, Groups: C.CN_IDX_PROC, Pid: uint32(os.Getpid())} 
err = unix.Bind(sock, addr) 

if err != nil { 
    fmt.Printf("bind: %v\n", err) 
    return 
} 

Als nächstes haben wir die PROC_CN_MCAST_LISTEN Nachricht an den Kernel schicken Sie es wissen zu lassen, wir Ereignisse empfangen möchten. Wir können dies direkt von C importieren, wo es als Aufzählung definiert ist, um ein wenig Tipparbeit zu sparen, und es in eine Funktion einfügen, da wir es mit PROC_CN_MCAST_IGNORE erneut aufrufen müssen, wenn wir keine Daten mehr vom Kernel erhalten.

// #include <linux/cn_proc.h> 
// #include <linux/connector.h> 
import "C" 

func send(sock int, msg uint32) error { 
    destAddr := &unix.SockaddrNetlink{Family: unix.AF_NETLINK, Groups: C.CN_IDX_PROC, Pid: 0} // the kernel 
    cnMsg := CnMsg{} 
    header := unix.NlMsghdr{ 
     Len: unix.NLMSG_HDRLEN + uint32(binary.Size(cnMsg) + binary.Size(msg)), 
     Type: uint16(unix.NLMSG_DONE), 
     Flags: 0, 
     Seq: 1, 
     Pid: uint32(unix.Getpid()), 
    } 
    msg.ID = CbID{Idx: C.CN_IDX_PROC, Val: C.CN_VAL_PROC} 
    msg.Len = uint16(binary.Size(msg)) 
    msg.Ack = 0 
    msg.Seq = 1 
    buf := bytes.NewBuffer(make([]byte, 0, header.Len)) 
    binary.Write(buf, binary.LittleEndian, header) 
    binary.Write(buf, binary.LittleEndian, cnMsg) 
    binary.Write(buf, binary.LittleEndian, msg) 

    return unix.Sendto(sock, buf.Bytes(), 0, destAddr) 
} 

Nachdem wir der Kernel lassen wissen, dass wir bereit sind, Ereignisse zu empfangen, können wir sie auf dem Sockel erhalten wir geschaffen sind. Sobald wir sie erhalten haben, müssen wir sie analysieren und nach relevanten Daten suchen. Wir kümmern sich nur um Nachrichten, die die folgenden Kriterien erfüllen:

  • aus dem Kernel Kommen
  • einen Kopftyp haben von NLMSG_DONE
  • Haben Sie einen proc_event_header.what Wert von PROC_EVENT_EXIT
  • unsere PID passt

Wenn sie diese Kriterien erfüllen, können wir die relevanten Prozessinformationen in eine proc_event_exit Struktur extrahieren, die die PID des Prozesses enthält.

for { 
    p := make([]byte, 1024) 
    nr, from, err := unix.Recvfrom(sock, p, 0) 

    if sockaddrNl, ok := from.(*unix.SockaddrNetlink); !ok || sockaddrNl.Pid != 0 { 
     continue 
    } 

    if err != nil { 
     fmt.Printf("Recvfrom: %v\n", err) 
     continue 
    } 

    if nr < unix.NLMSG_HDRLEN { 
     continue 
    } 

    // the sys/unix package doesn't include the ParseNetlinkMessage function 
    nlmessages, err := syscall.ParseNetlinkMessage(p[:nr]) 

    if err != nil { 
     fmt.Printf("ParseNetlinkMessage: %v\n", err) 
     continue 
    } 

    for _, m := range(nlmessages) { 
     if m.Header.Type == unix.NLMSG_DONE { 
      buf := bytes.NewBuffer(m.Data) 
      msg := &CnMsg{} 
      hdr := &ProcEventHeader{} 
      binary.Read(buf, binary.LittleEndian, msg) 
      binary.Read(buf, binary.LittleEndian, hdr) 

      if hdr.What == C.PROC_EVENT_EXIT { 
       event := &ExitProcEvent{} 
       binary.Read(buf, binary.LittleEndian, event) 
       pid := int(event.ProcessTgid) 
       fmt.Printf("%d just exited.\n", pid) 
      } 
     } 
    } 
} 

Ein vollständiges Codebeispiel ist here.