在OSX系统上检测进程退出

4
我希望在OSX上进程退出之前能够收到通知,以便在进程死亡之前收集关于该进程的统计信息。(具体示例:我想聚合具有许多快速产生和死亡子进程但使用大量CPU的进程的CPU使用情况。通过像proc_pidinfo这样的采样CPU使用情况时,出生和死亡速率类似于我的采样率的进程无法充分捕获到我的聚合统计数据中。我希望在进程死亡时收到通知,以便我可以总结它们的总用户和系统时间。

到目前为止,看起来最好的方法是使用libdtrace,但我无法弄清楚如何使用dtrace命令设置进程退出探针,更不用说从C程序中设置libdtrace了。如果有关设置此类dtrace探针以及如何使用libdtrace的教程的提示将不胜感激。

编辑:

好的,根据一些评论者的建议,我成功创建了一个使用libdtrace并可靠地触发进程退出的程序。这很棒,但不幸的是,我似乎无法获得正在退出的进程的PID。调用printf()来格式化整数的D程序存在问题。具体来说,如果我运行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); }'。 - Ken Thomases
我认为上述内容仅在进程退出后才会触发。如果您想在进程退出时捕获它,可以使用探针 syscall::*exit*:entry。要在那里停止进程,您必须允许 "破坏性" 操作,并使用探针主体中的 stop() 操作:sudo dtrace -w -n 'syscall::*exit*:entry { stop(); }。然后,您可以随意检查进程。要让进程恢复,您必须使用 pidresume() 操作:sudo dtrace -w -n 'BEGIN { pidresume($pid); } -p <the PID>`。 - Ken Thomases
谢谢大家,这实际上就是我想要的!现在唯一的问题是如何使用libdtrace。谢谢! - staticfloat
2个回答

5

这个主题有一份苹果技术笔记。

TN2050 监测进程生命周期而无需轮询

对于监测任意进程,此指南建议使用 kqueues

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

清单 8 使用 kqueues 监测特定进程

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,非常棒! - staticfloat

0

以下示例以UNIX进程ID作为参数,观察最多20秒,并在该时间内报告进程是否终止。

// 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不足以满足他们的目的。 - Ken Thomases

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接