如何从终端"键击缓冲区"?

时间:2017-05-21 19:40:50

标签: c linux bash terminal

假设ping命令正在运行,并且在ping仍在运行时在终端上输入内容。

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

enter image description here

bash如何获取此信息?我确信它没有从stdin获取,因为当我输入"I typed this while ping was running"时,我没有按 Enter (因此stdin为空)。< / p>

因此,这些数据必须存储在&#34;击键缓冲区&#34;中,并且bash从此缓冲区中读取。

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

3 个答案:

答案 0 :(得分:1)

这是关于readline library的问题(这里是关于它的more approachable page)。

你可以通过Python看到这一点,它是在大多数发行版上使用readline支持编译的:

>>> import time
>>> time.sleep(5)
I am typing this during the sleep>>> I am typing this during the sleep

然而,我也碰巧有一个没有readline支持:

>>> import time
>>> time.sleep(5)
I am typing this during the sleep>>> 

(这也可以通过cat | python -i实现,因为cat不使用readline,而python禁用readline,因为它的输入不是终端。)

我猜测会发生什么:

  1. Readline禁用缓冲(可能类似于this)。这样,它可以在键入时接收所有字符。
  2. Readline禁止将字符回显到终端,并接管控制权。 (Here是一种可能的方式。)
  3. Bash禁用了readline,因此echo可以执行其操作。
  4. Echo忽略了这些字符,因此在输入后它们仍然在stdin
  5. Readline有助于恢复控制,禁用缓冲/回显类型字符(对于已经输入的字符来说已经太迟了),并处理来自stdin的字符。
  6. TL; DR:它们会自动回显一次,然后再由readline库回显。

答案 1 :(得分:0)

这就是我认为发生的事情:

  1. 当bash运行时,它会禁用tty设备行缓冲,它还会禁用tty设备回显(现在bash负责回显它收到的所有内容)。
  2. bash执行ping之前,bash重新启用tty设备线缓冲和tty设备回显。然后bash执行ping
  3. 现在我输入"I typed this while ping was running",而ping正在显示其输出。 tty设备会将此字符串回显给终端窗口,但此字符串现在缓存在tty设备中。
  4. ping终止,bash获得控制权。
  5. bash重新禁用tty设备线路缓冲和tty设备回显。禁用tty设备行缓冲将导致tty设备行缓冲区刷新到bashbash将在终端窗口上显示它。

答案 2 :(得分:0)

没有特殊的&#34;读取终端队列&#34; bash中正在进行的机制。它只是普通的readwrite系统调用。

在Linux上,&#34; tty device&#34;总是被缓冲。如果键入的速度比接收程序读取的速度快,则字符不会掉入位桶;它们被放置在终端设备的输入队列中,可以通过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标志。如果设置了此标志(&#34;规范模式&#34;),则在键入NL字符之前,不会唤醒等待tty上的read系统调用的进程。 (实际上,有四个字符会唤醒进程:NL,EOL,EOL2和EOF。)在规范模式下,内核驱动程序还处理一些行编辑字符,例如ERASE字符。 (所有这些字符都可以通过termios配置,这样当我说&#34; NL字符&#34;时,我的意思是&#34;当前配置为NL的字符。默认情况下,NL字符是& #34;输入&#34;键,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 */
  }
}

在bash让子进程执行之前,它会通过将终端重新置于规范模式并启用echo来重置终端。除非正在执行的命令改变终端模式,否则它会一直保持,直到命令退出,此时bash重新获得控制权,转换规范模式并回显,然后返回readline循环。

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

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