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()
.
程序基本上将输入设置为非规范模式,VTIME
和 VMIN
也设置为 0。如果我正在阅读右下方的两个参考文献
- https://www.gnu.org/software/libc/manual/html_node/Canonical-or-Not.html
- https://www.gnu.org/software/libc/manual/html_node/Noncanonical-Input.html
然后我认为在这种情况下 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 中的一个错误。
下面的程序演示了这个问题。当您使用 clang main.c -o main
、运行 构建它并按几个键时,您会看到程序总是在 Linux 上的“调用轮询 ()...”处停止。现在,如果您在最新的 WSL(Windows Linux 的子系统)上执行相同的操作,您将看到它在“调用 read()...”处停止。换句话说,在 Linux 上是 poll()
阻塞,在 WSL 上是 read()
.
程序基本上将输入设置为非规范模式,VTIME
和 VMIN
也设置为 0。如果我正在阅读右下方的两个参考文献
- https://www.gnu.org/software/libc/manual/html_node/Canonical-or-Not.html
- https://www.gnu.org/software/libc/manual/html_node/Noncanonical-Input.html
然后我认为在这种情况下 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 中的一个错误。