nba*_*ari 9 linux freebsd go kqueue
我正在使用GO检查一个进程(不是父进程)是否已经终止,基本上类似于FreeBSD中的pwait命令但是用go编写.
目前,我尝试了for loop
一个kill -0
,但我注意到,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)
}
Run Code Online (Sandbox Code Playgroud)
知道如何改进或正确实现这一点.
提前致谢.
UPDATE
sleep
像建议的那样在循环内添加,有助于减少负载.
从提供的链接,似乎可以附加到现有的pid,我会尝试PtraceAttach但不知道这可能有副作用,任何想法?
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")
}
Run Code Online (Sandbox Code Playgroud)
工作正常,但想知道如何在Linux上实现/实现相同,因为我认为kqueue
没有它,任何想法?
一种解决方案是使用 netlink proc 连接器,它是内核用来让用户空间了解不同进程事件的套接字。官方文档有些缺乏,尽管有一些C 语言的好例子可能更适合阅读。
使用 proc 连接器的主要注意事项是该进程必须以 root 身份运行。如果需要以非 root 用户身份运行程序,则应考虑其他选项,例如定期轮询/proc
以观察更改。正如其他人所指出的,任何使用轮询的方法都容易受到竞争条件的影响,如果进程终止,并且在轮询之间使用相同的 PID 启动另一个进程。
无论如何,要在 Go 中使用 proc 连接器,我们必须对 C 进行一些转换。具体来说,我们需要定义cn_proc.hproc_event
中的和exit_proc_event
结构,以及connector.h中的和结构。cn_msg
cb_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
}
Run Code Online (Sandbox Code Playgroud)
我们还需要创建一个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
}
Run Code Online (Sandbox Code Playgroud)
接下来,我们必须PROC_CN_MCAST_LISTEN
向内核发送消息,让它知道我们想要接收事件。我们可以直接从 C 中导入它,在 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)
}
Run Code Online (Sandbox Code Playgroud)
在我们让内核知道我们准备好接收事件后,我们可以在我们创建的套接字上接收它们。一旦我们收到它们,我们需要解析它们,并检查相关数据。我们只关心符合以下条件的消息:
NLMSG_DONE
proc_event_header.what
值PROC_EVENT_EXIT
如果它们满足这些条件,我们就可以将相关的进程信息提取到一个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)
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
完整的代码示例在这里。