2016-07-01 11 views
9

私はのpwaitコマンドのようなものですが、プロセス(親ではない)が終了していないかどうかを確認するのに、GOを使用しています。イベント/プロセスが親でなくなるのを正しく待つ方法は?

現在、私はkill -0for loopをしようとしていますが、私は、CPUの使用率がこのアプローチの99%非常に高いことがわかり、ここでのコードは次のとおりです。

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

改善する方法の任意のアイデアこれを適切に実装してください。

ありがとうございます。提案のよう

UPDATE

ループ内sleepの追加負荷を低減することに役立ちます。

提供されているリンクから、既存のPIDにアタッチすることができるようですが、私は試してみますPtraceAttachこれは副作用、任意のアイデアがあるかどうかわからないのですか?

としては、私は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") 
} 

細かい機能しますが、任意のアイデア、私はそれでkqueue利用できないと思いますので、Linux上で同じことを達成/実装する方法を疑問に思いますか?

+3

この質問のいくつかのアイデア:[子供以外のプロセスの終了を待つ方法](http://stackoverflow.com/q/1157700) –

+2

forループに短いスリープを入れてください。 Linuxはこれを行うための効率的な方法を提供していません – JimB

+1

'kqueue' APIは(' 'syscall'パッケージで)' go'から使用できます。移植性が必要ない場合、* BSDとDarwinを超えていれば、BSD 'pwait 'ユーティリティは' go'に変換される可能性があります。 – kdhp

答えて

1

解決策の1つは、netlink procコネクタを使用することです。これは、カーネルが異なるプロセスイベントについてユーザスペースに知らせるために使用するソケットです。 official documentationはいくぶん欠けていますが、Cの中にはおそらく読むほうが良いgood examplesがいくつかあります。

procコネクタを使用する際の主な注意点は、プロセスをrootとして実行する必要があることです。プログラムをroot以外のユーザーとして実行する必要がある場合は、定期的にポーリングして変更を監視するなどの他のオプションも考慮する必要があります。他の人が指摘したように、ポーリングを使用するアプローチは、プロセスが終了し、ポーリングの間に同じPIDで開始される競合状態の影響を受け易い。とにかく

、移動中のprocコネクタを使用するには、我々は具体的にCからいくつかの翻訳を行う必要があります、私たちはconnector.hからcn_proc.hからproc_eventexit_proc_event構造体、およびcn_msgcb_id構造体を定義する必要があります。

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

また、netlinkソケットを作成してbindを呼び出す必要があります。

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 
} 

次に、我々はそれが私たちがイベントを受信したい知っているようにカーネルにPROC_CN_MCAST_LISTENメッセージを送信する必要があります。これを列挙型として定義されているCから直接インポートして、入力を省き、カーネルからデータを受け取ったときにPROC_CN_MCAST_IGNOREで再度呼び出さなければならないので、関数に入力することができます。私たちは、カーネルは、我々はイベントを受信する準備が整いました知らせた後

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

、私たちが作成しているソケットにそれらを受け取ることができます。受信したら、それらを解析し、関連するデータをチェックする必要があります。私たちは、次の基準を満たすメッセージを気:

  • NLMSG_DONE
  • のヘッダタイプを持っているカーネルから来PROC_EVENT_EXIT
  • proc_event_header.what値が私たちのPID
と一致しまし

これらの基準を満たしている場合は、プロセスのPIDを含むproc_event_exit構造体に関連するプロセス情報を抽出できます。

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

完全なコード例はhereです。

関連する問題