我正在使用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")
}
上没有可用的任何想法?
答案 0 :(得分:2)
一种解决方案是使用netlink proc连接器,它是内核用来让用户空间了解不同进程事件的套接字。 official documentation有点缺乏,尽管C中有一些good examples可能更适合阅读。
使用proc连接器的主要注意事项是进程必须以root身份运行。如果要求以非root用户身份运行程序,则应考虑其他选项,例如定期轮询/proc
以监视更改。正如其他人所指出的那样,任何使用轮询的方法都会受到竞争条件的影响,如果进程终止并且在民意调查之间以相同的PID启动另一个进程。
无论如何,要在Go中使用proc连接器,我们必须从C进行一些翻译。具体来说,我们需要定义来自cn_proc.h的proc_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
}
我们还需要创建一个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
如果他们符合这些标准,我们可以将相关的流程信息提取到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。