如何正确等待事件/流程完成不是父母?

时间:2016-07-01 20:18:22

标签: linux go freebsd kqueue

我正在使用GO检查一个进程(不是父进程)是否已被终止,基本上类似于pwait中的FreeBSD命令,但写在go中。

目前我正在尝试使用for loop的{​​{1}},但我注意到这种方法的CPU使用率非常高 99%,以下是代码:< / p>

kill -0

了解如何改进或正确实施此方法。

提前致谢。

更新

在建议的循环中添加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) } 有助于减少负载。

从提供的链接,似乎可以附加到现有的pid,我会试一试PtraceAttach但不知道这是否有副作用,任何想法?

根据建议我可以使用kqueue

sleep

工作正常,但想知道如何在Linux上实现/实现相同的功能,因为我认为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") } 上没有可用的任何想法?

1 个答案:

答案 0 :(得分:2)

一种解决方案是使用netlink proc连接器,它是内核用来让用户空间了解不同进程事件的套接字。 official documentation有点缺乏,尽管C中有一些good examples可能更适合阅读。

使用proc连接器的主要注意事项是进程必须以root身份运行。如果要求以非root用户身份运行程序,则应考虑其他选项,例如定期轮询/proc以监视更改。正如其他人所指出的那样,任何使用轮询的方法都会受到竞争条件的影响,如果进程终止并且在民意调查之间以相同的PID启动另一个进程。

无论如何,要在Go中使用proc连接器,我们必须从C进行一些翻译。具体来说,我们需要定义来自cn_proc.hproc_eventexit_proc_event结构,以及来自connector.hcn_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_header.what值为PROC_EVENT_EXIT
  • 匹配我们的PID

如果他们符合这些标准,我们可以将相关的流程信息提取到proc_event_exit结构中,该结构包含流程的PID。

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