如何从终端读取“击键缓冲区”?

pau*_*aul 3 c linux bash terminal

假设该命令正在运行,我在运行ping时在终端上输入一些内容。ping

现在,当ping终止并bash重新获得控制权时,bash将在终端上打印我在ping运行时键入的内容。这是一个屏幕截图,显示了我的意思:

在此输入图像描述

如何bash获得这些信息?我确信它没有从 中获取它stdin,因为当我输入 时"I typed this while ping was running",我没有按Enter(所以stdin是空的)。

因此,该数据必须存储在“击键缓冲区”中,并bash从该缓冲区中读取。

我的问题是,如何bash从该缓冲区读取(它调用什么函数......)?

ric*_*ici 5

bash 中没有特殊的“读取终端队列”机制。这只是普通的read系统write调用。

在 Linux 上,“tty 设备”始终被缓冲。如果您输入的速度快于接收程序读取输入的速度,则字符不会放入位桶中;它们被放置在终端设备的输入队列中,可以通过系统调用从中检索它们read

read调用已有原型ssize_t read(int fd, void *buf, size_t count);。当终端驱动程序决定调用read应该返回时,它会count从终端的输入队列中删除字节并将它们放入buf. 如果队列不包含count字节,则读取队列中的所有字节;如果它包含多个count字节,则剩余字节保留在队列中等待下一个read

count对终端本身的运行没有影响;当有一个字节可用时,read(0, buf, 1)不会导致返回。read它仅限制放入的字节数buf

有。但是,许多控制设置会影响read呼叫的处理方式。在这种情况下,最重要的是旗帜ICANON。如果设置了此标志(“规范模式”),则在 tty 上等待系统调用的进程read将不会被唤醒,直到键入 NL 字符。(实际上,有四个字符可以唤醒进程:NL、EOL、EOL2 和 EOF。)在规范模式下,内核驱动程序还处理一些行编辑字符,例如 ERASE 字符。(所有这些字符都可以通过配置termios,因此当我说“NL字符”时,我的意思是“当前配置为NL的字符。默认情况下,NL字符是“Enter”键,EOF是control-D。)

如果未设置 ICANON,则终端处于非规范模式,并且应用 VMIN 和 VTIME 设置。VMIN 是进程被唤醒之前必须存在的最小字符数;VTIME 是终端驱动程序在放弃并唤醒进程之前等待输入的最短时间。如果同时设置了 VMIN 和 VTIME,则内核驱动程序将(无限期地)等待第一个字符,然后为每个连续字符等待 VTIME,直到读取 VMIN 字符。如果 VMIN 和 VTIME 均未设置,则read调用将始终立即返回。

与规范模式设置无关,您还可以配置终端的回显行为。在最简单的配置中,您可以设置或不设置 ECHO。如果设置了 ECHO,则在键入可打印字符时,终端驱动程序将回显可打印字符。如果未设置 ECHO,则终端驱动程序不会回显字符。默认情况下,设置 ECHO。

Bash 通常使用 readline 库来处理终端输入。您可以告诉 bash 不要使用 readline,在这种情况下,行为会有所不同;我只会用 readline 来描述正常情况。

当 bash 处于活动状态并控制终端时,readline 会将终端置于非规范模式并关闭回显。它将 VMIN 设置为 1,因此read一旦键入单个字符,调用就会返回,然后进入类似以下循环:

while (1) {
  ssize_t nread = read(0, buf, 1);
  if (nread && isprint(buf[0]))
    write(1, buf, 1);  /* Echo the character */
  else {
    /* Lots of complicated logic to handle other characters */
  }
}
Run Code Online (Sandbox Code Playgroud)

就在 bash 让子进程执行之前,它会通过将终端放回规范模式并打开回显来重置终端。除非正在执行的命令更改终端模式,否则它会保持这种状态,直到命令退出,此时 bash 重新获得控制权,打开规范模式并关闭回显,然后返回到 readline 循环。

因此,假设正在执行的命令不会更改终端设置,并且会执行一段时间(如ping您问题中的示例所示)。执行时ping,终端处于规范模式,回显打开,并且没有人从终端设备读取数据。因此,您输入的任何内容都将被放入终端队列并回显。“任何东西”包括通常会终止 的东西read,例如 Enter 键。那是因为没有read终止的余地。

ping最终完成时,bash 将终端更改回非规范模式并关闭回显,以便终端驱动程序不会回显您现在输入的字符。然后它调用 readline 来启动上面的循环。然而,此时终端队列中有一堆内容,远远多于 VMIN 设置所需的一个字符。因此,该read调用立即返回一个字符,然后write该循环中的调用会回显刚刚读取的字符。当然,该字符已经被终端驱动程序回显,但这一事实并没有神奇地记录在该字符的二进制编码中;这只是一个普通的角色,所以它得到了第二次回应。这种情况一直持续到终端队列被清空或者您之前输入的某些字符需要 readline 的注意。如果终端队列只包含一些普通的可打印字符,它们都会得到回显,并且下一个read调用将被阻止,直到您键入某些内容。