我正在开发一个将 fifos 用于 IPC 并使用事件通知 API(例如 epoll 或 kqueue)来监视 fifos 以获取要读取的数据的应用程序。
应用程序期望,如果 fifo 的写入器终止,读取器将通过事件通知 API 接收事件,从而允许读取器注意到写入器终止。
我目前正在将此应用程序移植到 macos,并且我在使用 kqueue 时遇到了一些奇怪的行为。我已经能够创建这种行为的复制器:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>
#include <sys/errno.h>
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 个 fifo:/tmp/parent-$CHILD_PID
和/tmp/child-$CHILD_PID
. 父级等待直到/tmp/parent-$CHILD_PID
被创建,然后向其写入一个字节。子进程打开/tmp/parent-$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 和 FreeBSD 上的 kqueue 有区别吗?如果是这样,是否有一些文档记录了这种差异?