当进程要求输入或输出到屏幕时,tty 会做什么?

What does the tty do when a process asks for input or outputs to the screen?

我阅读了很多关于 tty 的文章。它们都是从 tty 名称的历史原因开始的。请忽略这个,只描述今天存在的 tty 系统。然后他们谈论 tty 是一个文件,在终端中启动的进程的 stdin、stdout 和 sterr 都映射到这个文件。 三个文件如何映射到一个文件?

有人说 tty 允许在输入之前进行行编辑并执行其他行规则。有一个 Linus Akesson 的 blog post which says that each tty has its own stdin and stdout . The blog post,我仍在努力研究它解释说内核中实际上有一个 tty 驱动程序 一个 tty 设备文件。然后是控制终端、会话、终端仿真器、原始模式和熟模式、pty 等等。

为了更好地理解 tty 是什么,有人可以向我解释一下在这种简单情况下会发生什么: 打开一个终端,它 运行 是默认的 shell。来自 shell 的进程是 运行 并且它要求输入。

以及输出部分:当同一个进程输出一些东西时,它是否写入tty设备?但是tty不是已经输出了当前的编辑缓冲行吗?

如果在不回答上述问题的情况下有更好的方法来描述 tty 的功能,那么请回答如果我遗漏了一些关键部分,请按您认为必要的方式填写。

结果真的很长,所以打起精神来...

TTY 设备

您正在将 TTY 视为一个屏幕,例如, 80x24 块。 但 TTY 是一个控制台:它包含一个输入设备(通常连接到键盘)和一个输出设备(通常连接到屏幕)。

TTY 抽象

当 TTY 连接到(物理或模拟)设备时,Unix 进程看不到设备,它们看到的是抽象。此抽象由输入流、输出流和控制接口组成。

控制界面可以转 on/off 一些“花哨”的功能,比如将它接收到的输入不仅发送到使用终端的进程,还发送到它自己的输出流(该功能称为“回显”并且可以使用 stty echo) 进行控制,但是您在终端输出中看到某些内容并不意味着它已连接到任何形式的标准输出。

这个抽象是由 TTY 驱动程序和线路规程在内核中实现的。您可以将行规程视为 Strategy 设计模式。

这个抽象必须对用户空间可用,Unix 驱动程序将任何东西导出到用户空间的方式是创建特殊文件,例如 /dev/tty.

TTY 文件允许您 read() 输入流,write() 到输出流,并通过 ioctl()

转换功能 on/off

TTY 文件

通常,每次启动新终端时,驱动程序都会创建一个新的 TTY 文件。

任何进程都可以打开 tty 文件,无论该文件是进程的 stdin、stdout、stderr、所有这些还是其中的 none。

您可以自己看看:打开终端并输入 tty。假设它打印了 /dev/pts/3.

现在打开另一个终端并运行:

exec 10>/dev/pts/3 # open /dev/pts/3 as file descriptor 10
echo hello >&10 # write "hello" through file descriptor 10

这将导致 echohello 写入文件描述符 10,这是第一个终端。因此,第一个终端将打印 hello.

标准流

Unix 实现了 3 个标准流,stdin、stdout 和 stderr。这些从终端驱动程序或线路规程中没有得到任何特殊处理,并且它们的大部分实现都在 shell.

当您启动终端仿真器时,它会打开一个 tty 文件,比如 /dev/pts/3。然后它创建一个新进程 (fork()),打开 /dev/pts/3 作为文件描述符 0 (stdin)、1 (stdout) 和 2 (stderr),然后执行 shell.

这意味着当 shell 启动时,它有终端文件、流和控制接口。当 shell 写入 stdout 或 stderr 时,两次写入都会转到 TTY 的输出流。

当shell执行另一个进程时,进程继承/dev/pts/3作为它的文件描述符0、1、2,除非shell做重定向,或者执行的程序改变这些文件描述符。

具体答案

现在我们可以回答您的问题了:

What happens when the call scanf is made?

scanf() 调用 read(STDIN),后者调用 TTY 驱动程序的 read().

实现

在 cooked 模式下,这将阻塞直到输入流缓冲了整行。在原始模式下,它将阻塞直到至少读取一个字符。

然后将 TTY 输入缓冲区复制到 scanf 的缓冲区。

How does the terminal know that scanf is called?

没有。如果您在程序正在 运行ning 而不是等待您的输入时在终端中键入内容,它将缓冲在终端的输入流中。

然后,无论何时调用 scanf,它都会读取该缓冲区。如果不调用scanf,则程序结束,控制returns到shell。 shell 读取该缓冲区。你可以通过 运行ning sleep 30 看到它,当它是 运行ning 时,输入另一个命令并按回车键。 shell 将在 sleep 完成后执行它:

bash-4.3$ sleep 30
echo hello
bash-4.3$ echo hello
hello
bash-4.3$ 

The editing buffer in the terminal which we see afterwards(the line where we enter text) - where does it come from? Does this buffer exist in the tty device file and is being outputted like an stdout file is printed?

缓冲区存在于内核中并附加到 TTY 文件。

如果终端的回显功能打开,线路规程不仅会将输入流发送到输入缓冲区,还会发送到输出流。

如果终端处于 cooked(默认)模式,行规程将给予特殊处理字符,如退格键(是的,退格键是一个字符,ASCII 8)。在退格的情况下,它将从输入缓冲区中删除最后一个字符,并向输出流发送一个控制序列以从屏幕中删除最后一个字符。

请注意,缓冲区与您在屏幕上看到的内容是分开管理的。

Which process is controlling this buffer? The tty driver?

缓冲区在内核中,不受进程控制,而是由 TTY 驱动程序控制的行规程控制。

What happens when we press enter? Does the tty driver 'submit' the line to the stdin part of the tty device?

当您按下“enter”时,一个换行符 (\n) 被添加到缓冲区,并且,如果有任何进程正在等待终端输入,输入缓冲区将被复制到进程的缓冲区,并且进程变得畅通并继续 运行.

比较有意思的问题是什么当您按下不是“输入”的东西时会发生。在原始模式下,是否“输入”并不重要,因为 \n 没有得到任何特殊处理。然而,在熟化模式下,输入缓冲区不会被复制,读取过程也不会被通知。

How does the process know that input has been submitted.

进程调用例如scanf() which read(STDIN),这将阻塞进程直到输入可用。当输入可用时,TTY 驱动程序将解除阻塞进程(即唤醒它)。

请注意,这对 TTY 文件或 STDIN 并不特殊,它适用于所有文件,这就是 read() 的工作方式。

还要注意 scanf() 不知道 STDIN 是否是 TTY 文件。

When the same process outputs something, does it write to the tty device?

当你调用 printf() 之类的东西时,它会调用 write(STDOUT),它会调用 TTY 驱动程序的 write() 实现,它会写入 TTY 的输出流。

再次注意,printf() 不知道 STDOUT 是否是 TTY 文件。

But isnt the tty already outputting the current editing buffer line?

在 Unix 中,一个文件(任何文件,不仅仅是 TTY 文件)可以被多个写入器打开和写入,并且不保证它们之间的同步。

正如您在上面的 echo hello >&10 示例中看到的,终端中的进程 运行ning 并不是唯一可以写入 TTY 输出流的东西,但即使是不相关的进程也可以写入 TTY 的输出流。

并且当启用 echo 时,线路规程也可以写入 TTY 的输出流。

所有这些写入都将交错,驱动程序不会尝试同步它们或理解它们。