【问题标题】:Difference in kqueue handling of fifos between Mac OS and FreeBSD?Mac OS 和 FreeBSD 之间对 fifos 的 kqueue 处理有何不同?
【发布时间】:2018-09-04 00:33:15
【问题描述】:

我正在开发一个将 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 将相同的字节写入父进程。 parent 使用 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 有区别吗?如果是这样,是否有一些文档记录了这种差异?

【问题讨论】:

  • 你为什么用静态的?
  • @EdHeal:如果您指的是函数上的static 限定符,可能是因为在此文件之外无法访问这些函数。我一直这样做——我使用的编译选项意味着函数在定义之前必须在范围内有一个原型,除非它们是静态的(GCC 和-Wstrict-prototypes)。如果一个函数将在定义它的源文件之外使用,它将在头文件中有一个原型,因此省略static 不会有问题。如果没有头文件来声明函数,则应标记为static

标签: c macos freebsd kqueue


【解决方案1】:

这不是您问题的最佳答案,但我希望可以帮助您找到在 macOSFreeBSD 之间使用 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 时给你一个想法,在我的例子中,这个简单的标志改变产生了影响。

【讨论】:

    猜你喜欢
    • 2012-11-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-19
    • 1970-01-01
    • 1970-01-01
    • 2016-06-30
    • 1970-01-01
    相关资源
    最近更新 更多