为什么即使在非规范模式下我仍然需要按“Enter”才能读取和输出输入?
Why I still need to press 'Enter" in order to let input be read and output even in non-canonical mode?
我正在测试 this GNU libc 手册中的代码:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
/* Use this variable to remember original terminal attributes. */
struct termios saved_attributes;
void
reset_input_mode (void)
{
tcsetattr (STDIN_FILENO, TCSANOW, &saved_attributes);
}
void
set_input_mode (void)
{
struct termios tattr;
char *name;
/* Make sure stdin is a terminal. */
if (!isatty (STDIN_FILENO))
{
fprintf (stderr, "Not a terminal.\n");
exit (EXIT_FAILURE);
}
/* Save the terminal attributes so we can restore them later. */
tcgetattr (STDIN_FILENO, &saved_attributes);
atexit (reset_input_mode);
/* Set the funny terminal modes. */
tcgetattr (STDIN_FILENO, &tattr);
tattr.c_lflag &= ~(ICANON|ECHO); /* Clear ICANON and ECHO. */
tattr.c_cc[VMIN] = 1;
tattr.c_cc[VTIME] = 0;
tcsetattr (STDIN_FILENO, TCSAFLUSH, &tattr);
}
int
main (void)
{
char c;
set_input_mode ();
while (1)
{
read (STDIN_FILENO, &c, 1);
if (c == '[=10=]4') /* C-d */
break;
else
putchar (c);
}
return EXIT_SUCCESS;
}
即使终端设置为非规范模式,我仍然需要按回车键才能接收输入。
但是,如果我将:putchar(c)
更改为 write(STDOUT_FILENO, &c, sizeof(char))
,它会像我想的那样正常工作。
你被用户缓冲打败了! putchar(3)
是 libc I/O API 的一部分,而 write(2)
是系统调用——嗯,不完全是 系统调用,但为了简单起见,让我们暂时考虑一下。
libc 中有三种缓冲类型:无缓冲、块缓冲和行缓冲。如果流是无缓冲的,则数据一写入就进入底层文件(或终端);如果它是块缓冲的,数据会保存在内存块中,直到它被填满,然后一次写入;但是,如果它是行缓冲的,则当找到换行符时,数据将传输到文件(或终端)。
如果流连接到终端,通常是标准输出的情况,它是行缓冲的。所以,这是你的情况:当你按下回车键时,换行符 \n
导致(行)缓冲区被写入标准输出。但是,当您调用 write(2)
时,libc 用户缓冲 被绕过,数据被写入相应的 文件描述符 (STDOUT_FILENO).
所以,正如我之前所说,write(2)
是一个系统调用;但实际上,当您调用 write
时,您是在调用系统调用的库包装器,它处理系统调用遵循的严格协议(例如,它期望参数的位置等)。
顺便说一下,我在这里所说的一切都可以在 putchar(3)
、write(2)
、setbuf(3)
的手册页中找到。括号中的数字参考手册中的部分:2
用于系统调用,3
用于库函数(man man
应该给你部分列表及其主题)。
希望对您有所帮助。
我正在测试 this GNU libc 手册中的代码:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
/* Use this variable to remember original terminal attributes. */
struct termios saved_attributes;
void
reset_input_mode (void)
{
tcsetattr (STDIN_FILENO, TCSANOW, &saved_attributes);
}
void
set_input_mode (void)
{
struct termios tattr;
char *name;
/* Make sure stdin is a terminal. */
if (!isatty (STDIN_FILENO))
{
fprintf (stderr, "Not a terminal.\n");
exit (EXIT_FAILURE);
}
/* Save the terminal attributes so we can restore them later. */
tcgetattr (STDIN_FILENO, &saved_attributes);
atexit (reset_input_mode);
/* Set the funny terminal modes. */
tcgetattr (STDIN_FILENO, &tattr);
tattr.c_lflag &= ~(ICANON|ECHO); /* Clear ICANON and ECHO. */
tattr.c_cc[VMIN] = 1;
tattr.c_cc[VTIME] = 0;
tcsetattr (STDIN_FILENO, TCSAFLUSH, &tattr);
}
int
main (void)
{
char c;
set_input_mode ();
while (1)
{
read (STDIN_FILENO, &c, 1);
if (c == '[=10=]4') /* C-d */
break;
else
putchar (c);
}
return EXIT_SUCCESS;
}
即使终端设置为非规范模式,我仍然需要按回车键才能接收输入。
但是,如果我将:putchar(c)
更改为 write(STDOUT_FILENO, &c, sizeof(char))
,它会像我想的那样正常工作。
你被用户缓冲打败了! putchar(3)
是 libc I/O API 的一部分,而 write(2)
是系统调用——嗯,不完全是 系统调用,但为了简单起见,让我们暂时考虑一下。
libc 中有三种缓冲类型:无缓冲、块缓冲和行缓冲。如果流是无缓冲的,则数据一写入就进入底层文件(或终端);如果它是块缓冲的,数据会保存在内存块中,直到它被填满,然后一次写入;但是,如果它是行缓冲的,则当找到换行符时,数据将传输到文件(或终端)。
如果流连接到终端,通常是标准输出的情况,它是行缓冲的。所以,这是你的情况:当你按下回车键时,换行符 \n
导致(行)缓冲区被写入标准输出。但是,当您调用 write(2)
时,libc 用户缓冲 被绕过,数据被写入相应的 文件描述符 (STDOUT_FILENO).
所以,正如我之前所说,write(2)
是一个系统调用;但实际上,当您调用 write
时,您是在调用系统调用的库包装器,它处理系统调用遵循的严格协议(例如,它期望参数的位置等)。
顺便说一下,我在这里所说的一切都可以在 putchar(3)
、write(2)
、setbuf(3)
的手册页中找到。括号中的数字参考手册中的部分:2
用于系统调用,3
用于库函数(man man
应该给你部分列表及其主题)。
希望对您有所帮助。