Mac OS和FreeBSD之间的fifos kqueue处理的区别?

我正在开发一个使用fifos for IPC的应用程序,并使用事件通知API(例如epoll或kqueue)来监视要读取的数据的fifos。

应用程序期望如果fifo的编写器终止,读者将通过事件通知API接收事件,允许读者注意到编写器终止。

我目前正在将此应用程序移植到macos中,并且我遇到了一些kqueue的奇怪行为。 我已经能够创建这种行为的再现者:

#include  #include  #include  #include  #include  #include  #include  #include  #include  #include  static int child() { char child_fifo_path[64]; char parent_fifo_path[64]; printf("Child %d\n", getpid()); sprintf(child_fifo_path, "/tmp/child-%d", getpid()); sprintf(parent_fifo_path, "/tmp/parent-%d", getpid()); mkfifo(child_fifo_path, 0644); mkfifo(parent_fifo_path, 0644); int parent_fd = open(parent_fifo_path, O_RDONLY); if (parent_fd == -1) { perror("open"); return EXIT_FAILURE; } unsigned char parent_val; read(parent_fd, &parent_val, 1); printf("Received %hhx from parent\n", parent_val); int child_fd = open(child_fifo_path, O_WRONLY); if (child_fd == -1) { perror("open"); return EXIT_FAILURE; } write(child_fd, &parent_val, 1); printf("Wrote %hhx to parent\n", parent_val); close(parent_fd); close(child_fd); return EXIT_SUCCESS; } static int parent(pid_t child_pid) { char child_fifo_path[64]; char parent_fifo_path[64]; printf("Parent %d\n", getpid()); sprintf(child_fifo_path, "/tmp/child-%d", child_pid); sprintf(parent_fifo_path, "/tmp/parent-%d", child_pid); int result = -1; while (result == -1) { struct stat buf; result = stat(child_fifo_path, &buf); if (result == -1) { if (errno != ENOENT) { perror("open"); return EXIT_FAILURE; } } } unsigned char val = 20; int parent_fd = open(parent_fifo_path, O_WRONLY); if (parent_fd == -1) { perror("open"); return EXIT_FAILURE; } write(parent_fd, &val, 1); printf("Wrote %hhx to child\n", val); int child_fd = open(child_fifo_path, O_RDONLY); if (child_fd == -1) { perror("open"); close(parent_fd); return EXIT_FAILURE; } int kq = kqueue(); struct kevent event; EV_SET(&event, child_fd, EVFILT_READ, EV_ADD, 0, 0, 0); result = kevent(kq, &event, 1, NULL, 0, NULL); if (result == -1) { perror("kevent"); close(child_fd); close(parent_fd); return EXIT_FAILURE; } int done = 0; while (!done) { memset(&event, 0, sizeof(event)); printf("Waiting for events\n"); result = kevent(kq, NULL, 0, &event, 1, NULL); if (result == -1) { perror("kevent"); close(child_fd); close(parent_fd); return EXIT_FAILURE; } if (event.ident == child_fd) { if (event.flags & EV_EOF) { printf("Child exited\n"); done = 1; }else if ( event.data > 0 ) { unsigned char child_val; result = read(child_fd, &child_val, 1); if (result == -1) { perror("read"); return EXIT_FAILURE; } printf("Received %hhx from child\n", child_val); } } } return EXIT_SUCCESS; } int main(int argc, char *argv[]) { pid_t child_pid = fork(); if (child_pid == -1) { perror("fork"); return EXIT_FAILURE; } if (child_pid) { return parent(child_pid); } else { return child(); } } 

这个重现器会分叉一个子进程,它创建2个fifos: /tmp/parent-$CHILD_PID/tmp/child-$CHILD_PID 。 父等待直到创建/tmp/parent-$CHILD_PID ,然后/tmp/parent-$CHILD_PID写入一个字节。 子/tmp/parent-$CHILD_PID打开/tmp/parent-$CHILD_PID并阻塞读取父/tmp/parent-$CHILD_PID写入的字节。 完成后,孩子将通过/tmp/child-$CHILD_PID将相同的字节写入父/tmp/child-$CHILD_PID 。 父级使用kqueue来观察对/tmp/child-$CHILD_PID

这一系列的事件很好。

当孩子关闭其引用/tmp/child-$CHILD_PID文件时,会出现此问题。 我发现这个事件没有通过kqueue报告给父母。

最有趣的部分:这段代码可以像我在FreeBSD上所期望的那样工作。

版本信息:

Mac OS X: 10.11.6

FreeBSD 10.4-RELEASE-p3

在这种情况下,macos上的kqueue和FreeBSD之间有区别吗? 如果是这样,是否有一些记录这种差异的文件?

这不是您问题的最佳答案,但我希望可以帮助您找到在macOS和FreeBSD之间使用kqueue时可能影响代码行为的其他差异

在我的情况下,我使用kqueue EVFILT_VNODE来检查更改,但基于操作系统,我需要在使用syscall.Open时定义不同的标志openModeDir

对于macOS( openmode_darwin.go ),我使用这个:

 openModeDir = syscall.O_EVTONLY | syscall.O_DIRECTORY openModeFile = syscall.O_EVTONLY 

对于FreeBSD( openmode.go ),我使用:

 openModeDir = syscall.O_NONBLOCK | syscall.O_RDONLY | syscall.O_DIRECTORY openModeFile = syscall.O_NONBLOCK | syscall.O_RDONLY 

从macOS docs open(2)开始 ,这是标志描述:

 O_EVTONLY descriptor requested for event notifications only 

从FreeBSD open(2)开始 ,没有O_EVTONLY

把这一切放在一起就是我怎么称为kqueue:

 ... watchfd, err := syscall.Open(dir, openModeDir, 0700) if err != nil { return err } kq, err := syscall.Kqueue() if err != nil { syscall.Close(watchfd) return err } ev1 := syscall.Kevent_t{ Ident: uint64(watchfd), Filter: syscall.EVFILT_VNODE, Flags: syscall.EV_ADD | syscall.EV_ENABLE | syscall.EV_CLEAR, Fflags: syscall.NOTE_WRITE | syscall.NOTE_ATTRIB, Data: 0, } ... 

我正在使用go ,但如前所述,希望可以在处理Kqueue时给你一个想法,在我的情况下,这些简单的标志更改产生了不同。