uni*_*eid 5 linux shell terminal kernel tty
我读过很多关于 tty 的帖子。都是从tty这个名字的历史原因说起。请忽略这一点,只描述目前存在的 tty 系统。然后他们讨论 tty 是一个文件,并且在终端中启动的进程的 stdin、stdout 和 sterr 都映射到该文件。三个文件如何映射到一个文件?
有人说 tty 允许在按下 Enter 键之前进行行编辑,并执行其他行规则操作。有一篇博客文章说每个 tty 都有自己的 stdin 和 stdout 。Linus Akesson 的博客文章(我仍在努力解决)解释说,内核中实际上有一个 tty 驱动程序和一个 tty 设备文件。然后是控制终端、会话、终端仿真器、原始模式和熟模式、pty 等等。
为了更好地理解 tty 是什么,有人可以向我解释一下在这个简单的情况下会发生什么:打开一个终端并运行默认的 shell。从 shell 运行一个进程并要求输入。
输出部分:当同一个进程输出某些内容时,它是否写入tty设备?但是tty不是已经输出当前编辑缓冲区行了吗?
如果有更好的方法来描述 tty 的作用而不回答上述问题,那么请这样做。如果我错过了一些关键部分,请填写您认为必要的部分。
roo*_*oot 11
结果真的很长,所以做好准备......
您将 TTY 视为划分为 80x24 块的屏幕。但 TTY 是一个控制台:它包含一个输入设备(通常连接到键盘)和一个输出设备(通常连接到屏幕)。
当 TTY 连接到(物理或模拟)设备时,Unix 进程看不到设备,它们看到的是抽象。该抽象由输入流、输出流和控制接口组成。
控制接口可以打开/关闭一些“奇特”功能,例如将其接收到的输入不仅发送到使用终端的进程,还发送到其自己的输出流(该功能称为“echo”,可以使用 进行控制)stty echo
,但您在终端输出上看到某些内容并不意味着它已连接到任何形式的标准输出。
这种抽象是由 TTY 驱动程序和线路规则在内核中实现的。您可以将线路规则视为策略设计模式。
这种抽象必须可供用户空间使用,并且 Unix 驱动程序将任何内容导出到用户空间的方式是通过创建特殊文件,例如/dev/tty
.
TTY 文件允许您read()
输入流、write()
输出流,并通过以下方式打开/关闭功能ioctl()
通常,每次启动新终端时,驱动程序都会创建一个新的 TTY 文件。
任何进程都可以打开 tty 文件,无论该文件是否是进程的 stdin、stdout、stderr、全部或都不是。
您可以亲自查看:打开终端并输入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
Run Code Online (Sandbox Code Playgroud)
这将导致echo
写入hello
文件描述符 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 进行重定向,或者执行的程序更改这些文件描述符。
现在我们准备回答您的问题:
调用 scanf 时会发生什么?
scanf()
调用read(STDIN)
,它调用 TTY 驱动程序的实现read()
。
在烘焙模式下,这将阻塞,直到输入流缓冲完整行。在原始模式下,它将阻塞,直到至少读取一个字符。
然后 TTY 输入缓冲区被复制到 scanf 的缓冲区。
终端如何知道调用了 scanf ?
事实并非如此。如果您在程序运行时在终端中输入某些内容并且不等待您的输入,则该内容将被缓冲在终端的输入流中。
然后,每当调用 scanf 时(如果有的话),它都会读取该缓冲区。如果不调用 scanf,则程序结束,控制权返回到 shell。然后 shell 读取该缓冲区。您可以通过运行来查看它sleep 30
,在它运行时,键入另一个命令并按 Enter 键。sleep
完成后 shell 会执行它:
bash-4.3$ sleep 30
echo hello
bash-4.3$ echo hello
hello
bash-4.3$
Run Code Online (Sandbox Code Playgroud)
我们随后看到的终端中的编辑缓冲区(我们输入文本的行) - 它来自哪里?该缓冲区是否存在于 tty 设备文件中并且像打印 stdout 文件一样输出?
该缓冲区存在于内核中并附加到 TTY 文件。
如果终端的回显功能打开,线路规则不仅会将输入流发送到输入缓冲区,还会发送到输出流。
如果终端处于“cooked”(默认)模式,行规则将给予特殊处理字符,如退格键(是的,退格键是一个字符,ASCII 8)。在退格的情况下,它将从输入缓冲区中删除最后一个字符,并向输出流发送一个控制序列以从屏幕上删除最后一个字符。
请注意,缓冲区的管理与您在屏幕上看到的内容是分开的。
哪个进程正在控制该缓冲区?tty 驱动程序?
缓冲区位于内核中,不受进程控制,而是由行规则控制,而行规则由 TTY 驱动程序控制。
当我们按下回车键时会发生什么?tty 驱动程序是否将线路“提交”到 tty 设备的 stdin 部分?
当您按“输入”时,换行符 ( \n
) 将添加到缓冲区中,并且如果有任何进程正在等待终端输入,则输入缓冲区将复制到进程的缓冲区,并且进程将解除阻塞并继续运行。
更有趣的问题是,当您按下非“输入”键时会发生什么。在原始模式下,是否“输入”并不重要,因为\n
没有得到任何特殊处理。然而,在cooked模式下,不会复制输入缓冲区,并且不会通知读取过程。
流程如何知道输入已提交。
进程调用例如scanf()
which read(STDIN)
,这将阻塞进程直到输入可用。当输入可用时,TTY 驱动程序将解除阻塞进程的阻塞(即唤醒它)。
请注意,这对于 TTY 文件或 STDIN 来说并不特殊,它适用于所有文件,这就是read()
工作原理。
另请注意,scanf()
不知道 STDIN 是否是 TTY 文件。
当同一进程输出某些内容时,它会写入 tty 设备吗?
当您调用类似 的内容时printf()
,它会调用write(STDOUT)
,后者调用 TTY 驱动程序的 实现write()
,该实现写入 TTY 的输出流。
再次注意,printf()
不知道 STDOUT 是否是 TTY 文件。
但是tty不是已经输出当前编辑缓冲区行了吗?
在 Unix 中,一个文件(任何文件,不仅仅是 TTY 文件)可以由多个写入器打开和写入,并且不保证它们之间的同步。
正如您在上面的示例中所看到的echo hello >&10
,在终端中运行的进程并不是唯一可以写入 TTY 输出流的进程,但即使是不相关的进程也可以写入 TTY 输出流。
当启用 echo 时,线路规则还可以写入 TTY 的输出流。
所有这些写入都将交错进行,驱动程序不会尝试同步它们或理解它们。