【问题标题】:Detect process exit on OSX在 OSX 上检测进程退出
【发布时间】:2014-07-11 03:39:27
【问题描述】:

我想在 OSX 上的进程退出之前收到通知,以便我可以在进程终止之前收集有关该进程的统计信息。 (示例细节:我想汇总具有许多子进程的进程的 CPU 使用率,这些子进程会快速生成和死亡,但会使用大量 CPU。当通过 proc_pidinfo 等方式对 CPU 使用率进行采样时,进程的出生和死亡速度相似在我的汇总统计信息中没有充分捕获到我的采样率。我希望在进程死亡时收到通知,以便我可以总结它们的总用户和系统时间。

到目前为止,似乎效果最好的方法是使用libdtrace,但我不知道如何使用dtrace 命令设置进程退出探测,更不用说设置了来自 C 程序的 libdtrace。任何有关设置此类 dtrace 探针的提示以及有关如何使用 libdtrace 的教程将不胜感激。

编辑:

好的,根据一些评论者的建议,我设法创建了一个使用 libdtrace 并可靠地触发进程退出的程序。这很棒,但不幸的是,我似乎无法获得正在退出的进程的 PID。似乎在我的 D 程序中调用 printf() 并尝试格式化整数被严重破坏了。具体来说,如果我运行this program,它应该在进程退出时打印出进程的名称和PID,它会惨遭失败。它打印一些其他整数,并且无论我尝试输出什么,都会打印该整数。如果我将 D 程序从

syscall::*exit*:entry {
    printf(\"%s %d\\n\", execname, curpsinfo->pr_pid);
};

只是

syscall::*exit*:entry {
    printf(\"%d\\n\", 100);
};

它只打印出神秘整数的前三位。请注意,进程名称是正确的,我相信只是整数-> 字符串转换失败。使用上述 D 程序运行主 dtrace 程序可以正常工作,但我想将其集成到我已经编写了很多的 C 程序中,并且管道子命令输出到该程序中并不是我真正想要的方式继续前进。

帮助了解如何让libdtrace 的缓冲输出正常工作,或者将 PID 作为整数而不是字符串获取。

【问题讨论】:

  • 我不知道libdtrace,但是你可以使用下面的dtrace命令来监控进程退出:sudo dtrace -n 'proc:::exit { trace(pid); trace(execname); }'
  • 我相信上述内容只会在进程退出后触发。如果你想在它退出时捕捉它,你可以使用探针syscall::*exit*:entry。要在那里停止进程,您必须允许使用 -w 选项到 dtrace 的“破坏性”操作,并在探针主体中使用 stop() 操作:sudo dtrace -w -n 'syscall::*exit*:entry { stop(); }。然后,您可以在闲暇时检查该过程。要让进程恢复,您必须使用pidresume() 操作:sudo dtrace -w -n 'BEGIN { pidresume($pid); } -p `.
  • 谢谢大家,这实际上正是我想要的!现在唯一的问题是使用 libdtrace。谢谢!

标签: c macos


【解决方案1】:

对此主题有一份 Apple 技术说明。

TN2050 Observing Process Lifetimes Without Polling

对于监控任意进程,本指南建议kqueues

我们可以通过这种方式[unable to detect application running with another user (via switch user)或者这种方式[Programmatically check if a process is running on Mac]获取正在运行的进程的PID。

清单 8 使用 kqueue 监控特定进程

static pid_t gTargetPID = -1;
    // We assume that some other code sets up gTargetPID.

- (IBAction)testNoteExit:(id)sender
{
    FILE *                  f;
    int                     kq;
    struct kevent           changes;
    CFFileDescriptorContext context = { 0, self, NULL, NULL, NULL };
    CFRunLoopSourceRef      rls;

    // Create the kqueue and set it up to watch for SIGCHLD. Use the 
    // new-in-10.5 EV_RECEIPT flag to ensure that we get what we expect.

    kq = kqueue();

    EV_SET(&changes, gTargetPID, EVFILT_PROC, EV_ADD | EV_RECEIPT, NOTE_EXIT, 0, NULL);
    (void) kevent(kq, &changes, 1, &changes, 1, NULL);

    // Wrap the kqueue in a CFFileDescriptor (new in Mac OS X 10.5!). Then 
    // create a run-loop source from the CFFileDescriptor and add that to the 
    // runloop.

    noteExitKQueueRef = CFFileDescriptorCreate(NULL, kq, true, NoteExitKQueueCallback, &context);
    rls = CFFileDescriptorCreateRunLoopSource(NULL, noteExitKQueueRef, 0);
    CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
    CFRelease(rls);

    CFFileDescriptorEnableCallBacks(noteExitKQueueRef, kCFFileDescriptorReadCallBack);

    // Execution continues in NoteExitKQueueCallback, below.
}

static void NoteExitKQueueCallback(
    CFFileDescriptorRef f, 
    CFOptionFlags       callBackTypes, 
    void *              info
)
{
    struct kevent   event;

    (void) kevent( CFFileDescriptorGetNativeDescriptor(f), NULL, 0, &event, 1, NULL);

    NSLog(@"terminated %d", (int) (pid_t) event.ident);

    // You've been notified!
}

【讨论】:

  • 这确实在进程退出后立即通知我,但是当进程退出时,我无法从中获取我需要的信息!谢谢你把我放到 kqueues 上,非常整洁!
【解决方案2】:

The following example takes a UNIX process ID as argument, and watches up to 20 seconds, and reports if the process terminates in that time

// cc test.c -framework CoreFoundation -O
#include <CoreFoundation/CoreFoundation.h>
#include <unistd.h>
#include <sys/event.h>
static void noteProcDeath(CFFileDescriptorRef fdref, CFOptionFlags callBackTypes, void *info) {
    struct kevent kev;
    int fd = CFFileDescriptorGetNativeDescriptor(fdref);
    kevent(fd, NULL, 0, &kev, 1, NULL);
    // take action on death of process here
    printf("process with pid '%u' died\n", (unsigned int)kev.ident);
    CFFileDescriptorInvalidate(fdref);
    CFRelease(fdref); // the CFFileDescriptorRef is no longer of any use in this example
}
// one argument, an integer pid to watch, required
int main(int argc, char *argv[]) {
    if (argc < 2) exit(1);
    int fd = kqueue();
    struct kevent kev;
    EV_SET(&kev, atoi(argv[1]), EVFILT_PROC, EV_ADD|EV_ENABLE, NOTE_EXIT, 0, NULL);
    kevent(fd, &kev, 1, NULL, 0, NULL);
    CFFileDescriptorRef fdref = CFFileDescriptorCreate(kCFAllocatorDefault, fd, true, noteProcDeath, NULL);
    CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack);
    CFRunLoopSourceRef source = CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, fdref, 0);
    CFRunLoopAddSource(CFRunLoopGetMain(), source, kCFRunLoopDefaultMode);
    CFRelease(source);
    // run the run loop for 20 seconds
    CFRunLoopRunInMode(kCFRunLoopDefaultMode, 20.0, false);
    return 0;
}

对于那些几乎不知道 C 的人:
构建:cc test.c -framework CoreFoundation -O
运行:./a.out 57168
57168 是被监控进程的pid。杀死它来测试!

当然,你可以延长 20 秒,让它持续多久。

【讨论】:

  • OP 对 9dan 的回答的评论解释了为什么 kqueue 不足以达到他们的目的。
猜你喜欢
  • 1970-01-01
  • 2011-07-29
  • 2012-11-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多