2016-07-01 18 views
9

Tôi đang sử dụng GO để kiểm tra xem liệu một quy trình (không phải là cha mẹ) có bị chấm dứt hay không, về cơ bản giống như lệnh pwait trong FreeBSD nhưng được viết bằng văn bản.Làm cách nào để chờ một sự kiện/quá trình hoàn thành không phải là phụ huynh?

Hiện nay tôi đang cố gắng một for loop với một kill -0, nhưng tôi nhận thấy rằng việc sử dụng CPU là rất cao 99% với cách tiếp cận này, đây là mã:

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

Bất kỳ ý tưởng về làm thế nào để cải thiện hoặc thực hiện đúng cách này.

Xin cảm ơn trước.

CẬP NHẬT

Thêm một sleep trong vòng lặp như gợi ý, giúp giảm tải.

Từ các liên kết được cung cấp, dường như có thể đính kèm với pid hiện tại, tôi sẽ thử PtraceAttach nhưng không biết liệu điều này có thể có phản ứng phụ hay không?

Như đề nghị tôi đã có sẵn để sử dụng 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") 
} 

Hoạt động tốt, nhưng tự hỏi làm thế nào để thực hiện/đạt được như nhau trên linux kể từ khi tôi nghĩ kqueue không có sẵn trên nó, bất kỳ ý tưởng?

+3

Một số ý tưởng từ câu hỏi này: [Cách đợi để thoát khỏi các quy trình không phải trẻ em] (http://stackoverflow.com/q/1157700) –

+2

Đặt một giấc ngủ ngắn trong vòng lặp của bạn. Linux không cung cấp cách hiệu quả để thực hiện điều này – JimB

+1

API 'kqueue' có thể sử dụng được từ' go' (trong gói 'syscall') và nếu không cần tính di động, ngoài * BSD và Darwin, thì BSD' pwait 'tiện ích có thể được dịch sang' go'. – kdhp

Trả lời

1

Một giải pháp sẽ là sử dụng trình kết nối proc netlink, là một ổ cắm mà hạt nhân sử dụng để cho phép người dùng biết về các sự kiện quy trình khác nhau. Các official documentation là hơi thiếu, mặc dù có một vài good examples trong C mà có lẽ tốt hơn để đọc.

Thông báo chính khi sử dụng trình kết nối proc là quá trình phải được chạy dưới dạng gốc. Nếu chạy chương trình của bạn với tư cách là người dùng không phải là người chủ, bạn nên cân nhắc các tùy chọn khác, chẳng hạn như bỏ phiếu định kỳ /proc để xem các thay đổi. Bất kỳ phương pháp nào sử dụng bỏ phiếu, như những người khác đã chỉ ra, là dễ bị một điều kiện chủng tộc nếu quá trình chấm dứt và một trong những khác được bắt đầu với cùng một PID giữa các cuộc thăm dò.

Dù sao, sử dụng kết nối proc tại Gò, chúng tôi sẽ phải làm một số dịch từ C. Cụ thể, chúng ta cần phải xác định proc_eventexit_proc_event cấu trúc từ cn_proc.h, và cn_msgcb_id cấu trúc từ 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 
} 

Chúng tôi cũng cần tạo ổ cắm netlink và liên kết cuộc gọi.

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 
} 

Tiếp theo, chúng tôi phải gửi thông báo PROC_CN_MCAST_LISTEN đến hạt nhân để thông báo cho chúng tôi biết chúng tôi muốn nhận sự kiện. Chúng ta có thể nhập trực tiếp từ C, nơi nó được định nghĩa là enum, để lưu một số gõ, và đặt nó vào một hàm vì chúng ta sẽ phải gọi lại với PROC_CN_MCAST_IGNORE khi chúng ta nhận dữ liệu từ nhân.

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

Sau khi chúng tôi cho hạt nhân biết chúng tôi sẵn sàng nhận sự kiện, chúng tôi có thể nhận chúng trên ổ cắm chúng tôi đã tạo. Khi chúng tôi nhận được chúng, chúng tôi cần phải phân tích chúng và kiểm tra dữ liệu có liên quan. Chúng tôi chỉ quan tâm đến thông điệp mà đáp ứng các tiêu chí sau:

  • Hãy đến từ kernel
  • Có một kiểu header của NLMSG_DONE
  • Có một giá trị proc_event_header.what của PROC_EVENT_EXIT
  • Phù hợp PID của chúng tôi

Nếu chúng đáp ứng các tiêu chí này, chúng tôi có thể trích xuất thông tin quy trình có liên quan thành cấu trúc proc_event_exit, chứa PID của quy trình.

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

Ví dụ mã đầy đủ là here.

Các vấn đề liên quan