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
时给你一个想法,在我的情况下,这些简单的标志更改产生了不同。