Linux 非规范输入模式在 Linux 和 WSL 中的工作方式不同

Linux non-canonical input mode works different in Linux and WSL

下面的程序演示了这个问题。当您使用 clang main.c -o main、运行 构建它并按几个键时,您会看到程序总是在 Linux 上的“调用轮询 ()...”处停止。现在,如果您在最新的 WSL(Windows Linux 的子系统)上执行相同的操作,您将看到它在“调用 read()...”处停止。换句话说,在 Linux 上是 poll() 阻塞,在 WSL 上是 read().

程序基本上将输入设置为非规范模式,VTIMEVMIN 也设置为 0。如果我正在阅读右下方的两个参考文献

然后我认为在这种情况下 read() 永远不应该阻塞,我也不应该看到程序在打印“调用 read()...”后等待。然而在WSL上就是这样。

现在,如果您在编译时传递 -DNONBLOCK 和 运行,在 Linux 和 WSL 上,程序总是在打印“Calling poll()...”后阻塞,这是预期的行为。

我想知道这是 WSL 上的错误,还是我误读了非规范模式文档。

程序:

// clang main.c -o main

#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>

void main_loop();
bool read_until_empty();

int main()
{
#if defined(NONBLOCK)
    int old_stdin_flags = fcntl(STDIN_FILENO, F_GETFL);
    fcntl(STDIN_FILENO, F_SETFL, old_stdin_flags | O_NONBLOCK);
    printf("Enabled non-blocking mode\n");
#endif

    bool fail = false;

    int tty = open("/dev/tty", O_RDWR);
    if (tty == -1) {
        printf("Unable to open /dev/tty\n");
        fail = true;
        goto cleanup;
    }

    struct termios old_termios;
    if (tcgetattr(STDIN_FILENO, &old_termios) == -1) {
        printf("tcgetattr failed\n");
        fail = true;
        goto cleanup;
    }

    struct termios new_termios = old_termios;
    cfmakeraw(&new_termios);
    // These really need to be after cfmakeraw() !!!!
    new_termios.c_cc[VMIN] = 0;
    new_termios.c_cc[VTIME] = 0;
    if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &new_termios) == -1) {
        printf("tcsetattr failed\n");
        fail = true;
        goto cleanup;
    }

    printf("Type 'q' to quit.\n");
    main_loop();

cleanup:
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_termios);

#if defined(NONBLOCK)
    fcntl(STDIN_FILENO, F_SETFL, old_stdin_flags);
    printf("Restored stdin flags\n");
#endif

    if (fail) {
        return 1;
    }
}

void main_loop()
{
    printf("main_loop\n");
    struct pollfd fds[1] = { { .fd = STDIN_FILENO, .events = POLLIN } };
    
    for (;;)
    {
        printf("Calling poll()...\n");
        int poll_ret = poll(fds, 1, -1);
        if (poll_ret > 0)
        {
            printf("stdin ready for reading\n");
            if (read_until_empty())
            {
                return;
            }
        }
    }
}

bool read_until_empty()
{
    uint8_t buf[10000];

    for (;;)
    {
        printf("Calling read()...\n");
        ssize_t n_read = read(STDIN_FILENO, buf, 10000);

        if (n_read == -1)
        {
            if (errno == EAGAIN || errno == EWOULDBLOCK)
            {
                printf("EAGAIN or EWOULDBLOCK\n");
                return false;
            }
            else
            {
                printf("Error %d: %s\n", errno, strerror(errno));
                return false;
            }
        }
        else if (n_read == 0)
        {
            printf("stdin is empty\n");
            return false;
        }
        else
        {
            printf("Read %zd bytes\n", n_read);
            if (buf[0] == 3 || buf[0] == 113)
            {
                return true;
            }
        }
    }
}

询问后不久,我在 WSL 问题跟踪器中找到了错误报告:https://github.com/microsoft/WSL/issues/3507

所以这似乎是 WSL 中的一个错误。