简体   繁体   English

在进程退出时收到通知

[英]Get notified when a process quits

I have developed an OSX daemon application, written in Qt (C++) and Objective-C.我开发了一个 OSX 守护程序应用程序,用 Qt (C++) 和 Objective-C 编写。 I monitor when other applications and processes are launched, with the use of a kernel extension, but need to know when they are terminated.我使用内核扩展监控其他应用程序和进程何时启动,但需要知道它们何时终止。

Is there any way to receive a notification of the termination of other processes, without having to constantly poll the target process's pid or mach task?有没有什么办法可以接收到其他进程终止的通知,而不必不断轮询目标进程的pid或mach任务?

You can do that with kqueue/kevent.你可以用 kqueue/kevent 做到这一点。 I hacked up a console application, then refactored it a little so it was somewhat obvious what was going on, and add made a helper function to more easily call it.我编写了一个控制台应用程序,然后对其进行了一些重构,以便清楚地了解发生了什么,并添加了一个辅助函数以更轻松地调用它。 Only barely tested, but hopefully it gives you a way to go forward...只是勉强测试,但希望它给你一个前进的道路......

Oh yeah, note that this code assumes the main run loop is running in the app... and it will call the block from within that runloop... simple enough to replace it with another run loop... or, if you are not using any CF run loop, you will have to add the kq file descriptor to whatever notification mechanism you are using.哦,是的,请注意,此代码假定主运行循环正在应用程序中运行……并且它将从该运行循环中调用该块……很简单,可以将其替换为另一个运行循环……或者,如果您是不使用任何 CF 运行循环,您必须将 kq 文件描述符添加到您使用的任何通知机制。

EDIT编辑

Fixed bug to re-enable the callback since file-descriptor callbacks must be re-enabled after each firing.修复了重新启用回调的错误,因为每次触发后都必须重新启用文件描述符回调。 Also, made args take multiple PIDs to demonstrate monitoring multiple PIDs.此外,make args 采用多个 PID 来演示监控多个 PID。

Of course, you could easily use invoke a delegate method rather than using blocks, but that's not really the point...当然,您可以轻松地使用调用委托方法而不是使用块,但这并不是重点......

Argh.... fix resource leak... I may not fix more... since it's a hacked example, but every time I go back and read it, I find something wrong... maybe I'll just stop reading it :-)啊……修复资源泄漏……我可能不会修复更多……因为它是一个被黑的例子,但每次我回去阅读它时,我都会发现一些错误……也许我会停止阅读它:-)

//  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;
}

Thanks to @JodyHagins, the research I did on kqueue and kevent led me to this blog which shows how GCD can be used to monitor a file and an example by Apple here .感谢 @JodyHagins,我对 kqueue 和 kevent 所做的研究使我找到了这个博客该博客展示了如何使用 GCD 来监视文件和Apple 的示例 With that as a template, I came up with this: -以此为模板,我想出了这个:-

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

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 以下示例将 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;
}

For those who barely know C:对于那些几乎不知道 C 的人:
build: cc test.c -framework CoreFoundation -O构建: cc test.c -framework CoreFoundation -O
run: ./a.out 57168运行: ./a.out 57168
57168 is the pid of the process being monitored. 57168 是被监控进程的pid。 Kill it to test!杀死它进行测试!

Surely you can increase the 20 seconds to make it last as long as you want.当然,您可以增加 20 秒以使其持续多久。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM