2016-07-01 12 views
9

Używam GO, aby sprawdzić, czy proces (nie był nadrzędny) ma ben zakończona, w zasadzie coś jak komenda pwait w FreeBSD, ale napisane w programie go.Jak właściwie czekać na zakończenie wydarzenia/procesu, który nie jest rodzicem?

Obecnie staram się for loop z kill -0, ale widzę, że użycie procesora jest bardzo wysoka 99% z tym podejściem, oto kod:

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

Każdy pomysł, jak poprawić lub właściwie to zaimplementuj.

Z góry dziękuję.

UPDATE

Dodawanie sleep wewnątrz pętli jak zasugerował, pomaga w zmniejszeniu obciążenia.

Z podanych linków, wydaje się być możliwe dołączenie do istniejącego pid, spróbuję PtraceAttach, ale nie wiem, czy to może mieć efekty uboczne, jakiś pomysł?

Jak sugeruje byłam dostępna do wykorzystania kqueue:

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") 
} 

działa prawidłowo, ale zastanawiasz się, jak wdrożyć/osiągnąć to samo na linux ponieważ myślę kqueue niedostępne w nim, jakieś pomysły?

+3

Kilka pomysłów na to pytanie: [Jak czekać na wyjście z procesów innych niż dzieci] (http://stackoverflow.com/q/1157700) –

+2

Umieść krótki sen w pętli for. Linux nie zapewnia wydajnego sposobu na wykonanie tego kodu. – JimB

+1

Interfejs API 'kqueue' może być użyty z' go' (w pakiecie 'syscall'), a jeśli przenośność nie jest wymagana, poza * BSD i Darwin, a następnie BSD' pwait 'narzędzie może być przetłumaczone na' go'. – kdhp

Odpowiedz

1

Jednym z rozwiązań byłoby użycie złącza proc sieciowego, które jest gniazdem, którego używa jądro, aby informować użytkownika o różnych zdarzeniach procesowych. The official documentation jest nieco brakuje, chociaż istnieje kilka good examples w C, które są prawdopodobnie lepiej przeczytać.

Głównym zastrzeżeniem używania złącza proc jest proces musi być uruchamiany jako root. Jeśli wymagane jest uruchomienie programu jako użytkownik inny niż root, powinieneś rozważyć inne opcje, takie jak okresowe odpytywanie /proc, aby obejrzeć zmiany. Każde podejście, które wykorzystuje polling, jak zauważyli inni, jest podatne na warunki wyścigu, jeśli proces zostanie przerwany, a inny zostanie uruchomiony z tym samym PID pomiędzy sondami.

W każdym razie, aby użyć złącza proc w Go, będziemy musieli zrobić jakieś tłumaczenie z C. W szczególności, musimy zdefiniować proc_event i exit_proc_event konstrukcjom z cn_proc.h, a cn_msg i cb_id kodowanym od connector.h.

// 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 
} 

Musimy również wykonać gniazdo netlink i powiązać połączenie.

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 
} 

Następnie musimy wysłać wiadomość PROC_CN_MCAST_LISTEN do jądra do niech wiedzą chcemy odbierać zdarzenia. Możemy zaimportować to bezpośrednio z C, gdzie jest to zdefiniowane jako wyliczenie, aby zapisać pewne pisanie i umieścić je w funkcji, ponieważ będziemy musieli zadzwonić ponownie z PROC_CN_MCAST_IGNORE, gdy skończymy otrzymywanie danych z jądra.

// #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) 
} 

Po pozwolimy jądro wiedzieć, że jesteśmy gotowi do odbierania zdarzeń, możemy otrzymać je na gnieździe mamy utworzone. Gdy je otrzymamy, musimy je przeanalizować i sprawdzić odpowiednie dane. Mamy tylko dbają o wiadomościach, które spełniają następujące kryteria:

  • pochodzą z jądra
  • mieć typ nagłówka NLMSG_DONE
  • mieć wartość proc_event_header.what z PROC_EVENT_EXIT
  • dopasować naszą PID

Jeśli spełniają te kryteria, możemy wyodrębnić odpowiednie informacje o procesie do struktury proc_event_exit, która zawiera identyfikator PID procesu.

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

Pełny przykład kodu to here.

Powiązane problemy