【问题标题】:Get notified when a process quits进程退出时收到通知
【发布时间】:2014-04-09 10:46:11
【问题描述】:

我开发了一个 OSX 守护程序应用程序,用 Qt (C++) 和 Objective-C 编写。我使用内核扩展监控其他应用程序和进程何时启动,但需要知道它们何时终止。

有没有什么方法可以接收到其他进程终止的通知,而不必不断轮询目标进程的pid或mach任务?

【问题讨论】:

  • 对于应用程序,这可能会有所帮助:How to detach if an application has closed ?我不知道它是否可以从守护进程中使用。
  • 我不知道 OSX,但我相信它是基于 *nix 的,您可能应该寻求使用类似 signal() 的东西来捕获 SIG_CHILD 信号,当子进程死亡时发送。
  • 谢谢你们,我会调查他们并报告。
  • @MartinR,仔细查看 OSX 通知中心,这似乎只适用于 Cocoa / Carbon 应用程序。我需要能够使用 pid 监视所有进程。例如,一个正在执行的终端命令。
  • @Merlin069: "dtrace" 能够获取该信息,尝试(以 root 身份):dtrace -qn 'proc:::exit { printf("%s (%d) exited\n", execname, pid) }'。 “dtrace”是开源的:opensource.apple.com/source/xnu/xnu-1456.1.26/bsd/dev/dtrace ...

标签: c++ objective-c macos qt process


【解决方案1】:

你可以用 kqueue/kevent 做到这一点。我破解了一个控制台应用程序,然后对其进行了一些重构,因此发生的事情有些明显,并添加了一个辅助函数以更轻松地调用它。只是几乎没有经过测试,但希望它能给你一个前进的道路......

哦,是的,请注意,此代码假定主运行循环正在应用程序中运行...并且它将从该运行循环中调用该块...简单到可以将其替换为另一个运行循环...或者,如果您没有使用任何 CF 运行循环,则必须将 kq 文件描述符添加到您正在使用的任何通知机制中。

编辑

修复了重新启用回调的错误,因为文件描述符回调必须在每次触发后重新启用。另外,让 args 采用多个 PID 来演示监控多个 PID。

当然,您可以轻松地使用调用委托方法而不是使用块,但这并不是重点...

啊……修复资源泄漏……我可能不会修复更多……因为这是一个被黑的例子,但是每次我回去阅读它时,我都会发现一些错误……也许我会只是停止阅读它:-)

//  main.c

#include <CoreFoundation/CoreFoundation.h>
#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>

static void
kqueueCallbackOnExit(CFFileDescriptorRef fileDescriptor,
                     CFOptionFlags flags,
                     void *info)
{
  int fd = CFFileDescriptorGetNativeDescriptor(fileDescriptor);
  struct kevent event;
  if (kevent(fd, NULL, 0, &event, 1, NULL) == 1 && event.udata) {
    void (^cb)(pid_t) = event.udata;
    cb((pid_t)event.ident);
    Block_release(cb);
  }
  CFFileDescriptorEnableCallBacks(
      fileDescriptor, kCFFileDescriptorReadCallBack);
}

static int
createOnProcessExitQueue()
{
  int kq = kqueue();
  if (kq < 0) return -1;

  CFFileDescriptorContext context = {
    .version = 0,
    .info = NULL,
    .retain = NULL,
    .release = NULL,
    .copyDescription = NULL
  };
  CFFileDescriptorRef kqFileDescriptor = CFFileDescriptorCreate(
      NULL, kq, true, kqueueCallbackOnExit, &context);
  if (kqFileDescriptor == NULL) {
    close(kq);
    kq = -1;
    return -1;
  }

  CFRunLoopSourceRef runLoopSource = CFFileDescriptorCreateRunLoopSource(
      NULL, kqFileDescriptor, 0);
  CFRunLoopAddSource(CFRunLoopGetMain(),
      runLoopSource, kCFRunLoopDefaultMode);
  CFRelease(runLoopSource);

  CFFileDescriptorEnableCallBacks(
      kqFileDescriptor, kCFFileDescriptorReadCallBack);
  CFRelease(kqFileDescriptor);

  return kq;
}

static int
onProcessExit(pid_t pid, void (^callback)(pid_t pid))
{
  static int kq;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    kq = createOnProcessExitQueue();
  });

  void (^cb)(pid_t) = Block_copy(callback);
  struct kevent event = {
    .ident = pid,
    .filter = EVFILT_PROC,
    .flags = EV_ADD | EV_ONESHOT,
    .fflags = NOTE_EXIT,
    .data = 0,
    .udata = (void*)cb
  };

  if (kevent(kq, &event, 1, NULL, 0, NULL) != 1) {
    Block_release(cb);
    return -1;
  }
  return 0;
}

int main(int argc, const char * argv[])
{
  for (int i = 0; i < argc; ++i) {
    pid_t pid = atoi(argv[i]);
    printf("watching pid: %d\n", pid);
    fflush(stdout);
    onProcessExit(pid, ^(pid_t pid) {
      printf("process %d just died\n", (int)pid);
      fflush(stdout);
    });
  }

  CFRunLoopRun();
  return 0;
}

【讨论】:

  • 谢谢,这个方法很棒,让我找到了一个更简单的方法,使用 GCD。
  • @TheDarkKnight 您介意详细说明 GCD 代表什么吗?谢谢!
【解决方案2】:

感谢@JodyHagins,我对 kqueue 和 kevent 所做的研究使我找到了this blog,它展示了如何使用 GCD 来监视文件和an example by Apple here。以此为模板,我想出了这个:-

struct ProcessInfo
{
    int pid;
    dispatch_source_t source;
};

// function called back on event
void pid_event(struct ProcessInfo* procinfo)
{
    printf("****** Application exited: %d ******\n", procinfo->pid);
    dispatch_source_cancel(procinfo->source);
}

// function called back when the dispatch source is cancelled
void pid_finalize(struct ProcessInfo* procinfo)
{
    dispatch_release(procinfo->source);
    printf(">>>> Finished with %d <<<<\n", procinfo->pid);
    delete procinfo;
}

// Monitor a process by pid, for termination
void DispatchMonitorProcess(int pid, ProcessInfo* procinfo)
{
    procinfo->pid = pid;

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_source_t dsp = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, pid, DISPATCH_PROC_EXIT, queue);

    dispatch_source_set_event_handler_f(dsp, (dispatch_function_t)pid_event);
    dispatch_source_set_cancel_handler_f(dsp,  (dispatch_function_t)pid_finalize);

    procinfo->source = dsp;
    dispatch_set_context(dsp, procinfo);

    dispatch_resume(dsp);
}

// Monitors the termination of a process with the given pid
void MonitorTermination(int pid)
{           
   DispatchMonitorProcess(pid, new ProcessInfo);
}

【讨论】:

  • 嗨,我想知道你是否知道我在哪里可以找到一个在任何给定时间维护所有现有进程的数据库的实现,使用你的侦听器和更新增量的代码,以及另一个列出所有进程的模块启动时的进程。谢谢
【解决方案3】:

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 秒,让它持续多久。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-20
    • 2015-03-21
    • 1970-01-01
    • 1970-01-01
    • 2014-08-17
    • 1970-01-01
    相关资源
    最近更新 更多