douqian5920 2016-07-01 20:18
浏览 78

如何正确地等待事件/过程完成而不是父进程?

I am using GO to check if a process (not been parent) has ben terminated, basically something like the pwait command in FreeBSD but written in go.

Currently I am trying a for loop with a kill -0, but I notice that the CPU usage is very high 99% with this approach, here is the 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)
}

Any idea of how to improve or properly implement this.

Thanks in advance.

UPDATE

Adding a sleep within the loop like suggested, helps reducing the load.

From the provided links, seems to be possible to attach to the existing pid, I will give a try PtraceAttach but don't know if this may have side effects, any idea?

As suggested I was available to use 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")
}

Works fine, but wondering how to implement/achieve the same on linux since I think kqueue not available on it, any ideas ?

  • 写回答

1条回答 默认 最新

  • dongsheng8158 2017-02-10 21:15
    关注

    One solution would be to use the netlink proc connector, which is a socket the kernel uses to let userspace know about different process events. The official documentation is somewhat lacking, although there are a couple of good examples in C which are probably better to read.

    The main caveat to using the proc connector is the process must be run as root. If running your program as a non-root user is a requirement, you should consider other options, such as periodically polling /proc to watch for changes. Any approach which uses polling, as others have pointed out, is susceptible to a race condition if the process is terminated and another one is started with the same PID in between polls.

    Anyway, to use the proc connector in Go, we will have to do some translation from C. Specifically, we need to define the proc_event and exit_proc_event structs from cn_proc.h, and the cn_msg and cb_id structs from 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
    }
    

    We also need to make a netlink socket and call 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
    ", err)
        return
    }
    

    Next, we have to send the PROC_CN_MCAST_LISTEN message to the kernel to let it know we want to receive events. We can import this directly from C, where it's defined as an enum, to save some typing, and put it in a function since we will have to call it again with PROC_CN_MCAST_IGNORE when we are done receiving data from the kernel.

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

    After we let the kernel know we're ready to receive events, we can receive them on the socket we're created. Once we receive them, we need to parse them, and check for relevant data. We only care about messages that meet the following criteria:

    • Come from the kernel
    • Have a header type of NLMSG_DONE
    • Have a proc_event_header.what value of PROC_EVENT_EXIT
    • Match our PID

    If they meet these criteria, we can extract the relevant process information into a proc_event_exit struct, which contains the PID of the process.

    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
    ", 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
    ", 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.
    ", pid)
                }
            }
        }
    }
    

    A full code example is here.

    评论

报告相同问题?

悬赏问题

  • ¥100 set_link_state
  • ¥15 虚幻5 UE美术毛发渲染
  • ¥15 CVRP 图论 物流运输优化
  • ¥15 Tableau online 嵌入ppt失败
  • ¥100 支付宝网页转账系统不识别账号
  • ¥15 基于单片机的靶位控制系统
  • ¥15 真我手机蓝牙传输进度消息被关闭了,怎么打开?(关键词-消息通知)
  • ¥15 装 pytorch 的时候出了好多问题,遇到这种情况怎么处理?
  • ¥20 IOS游览器某宝手机网页版自动立即购买JavaScript脚本
  • ¥15 手机接入宽带网线,如何释放宽带全部速度