tty
在终端中运行命令时,它会返回/dev/pts/10
.
除此之外还有文件/dev/stdout
/dev/stdin
和/dev/stderr
. 与它们交互直接在终端中显示结果。
user@laptop:build$ tty
/dev/pts/10
user@laptop:build$ echo "Test" > /dev/stdout
Test
user@laptop:build$ echo "Test" > /dev/stdin
Test
user@laptop:build$ echo "Test" > /dev/stderr
Test
此外,从 shell 启动的任何 cli 应用程序都将为stdout
/ stderr
/打开文件描述符stdin
。即,如果您运行打印某些内容的脚本,则打印相当于写入stdout
.
到目前为止stdout
//是 shell 使用的唯一接口stderr
。stdin
应用程序也是如此。
某些操作系统组件最终会将写入的数据移动stdout
到终端,否则我将看不到任何打印的内容。
与终端之间的连接何时何地stdout/stdin/stderr
发生,以便与std*
终端上的某些东西进行交互?
我想挑战的粗略假设是:
/dev/stdout
,/dev/stdin
并且/dev/stderr
是由运行的 shell 创建的,没有 shell,它们就不存在。shell 使用代表终端 ( ) 的实际设备文件建立通信通道,并通过、和
/dev/pts/10
公开终端功能。这样,shell 为应用程序提供了一个简单的文件界面,而不是让每个应用程序都担心如何处理设备文件以进行简单的打印。/dev/stdout
/dev/stdin
/dev/stderr
更新
即使/dev/pts/10
是伪终端,我也会更重视在不引入伪终端概念的情况下能够给出答案的答案。我从一个角度来看,它只会分散问题的答案:
与终端之间的连接何时何地
stdout/stdin/stderr
发生,以便与/dev/std*
终端上的某些东西进行交互?
/dev/pts/10
是伪终端设备对的从端。另一端是打开主克隆设备/dev/ptmx
并/dev/pts/10
作为对接收的程序(每次打开/dev/ptmx
,您都会获得不同的从设备)。/dev/ptmx
和之间的连接/dev/pts/10
基本上是一个带有扭曲的双向管道。当您打开终端仿真器应用程序时:
/dev/ptmx
并获取另一端的名称。它配置另一端并打开它,shell 没有设置这三个文件描述符,它从其父进程继承它们。与其子代从 shell 继承文件描述符的方式相同。
备注:在 Linux 系统
/dev/stdin
上,/dev/stdout
和/dev/stderr
是真实文件,通过一系列符号链接指向/proc/<pid>/0
,/proc/<pid>/1
和/proc/<pid>/2
,然后又指向真实的输入/输出设备:在您的情况下/dev/pts/10
.这三个标准流的存在由 C 库保证。
编辑:为了解决您更新的问题,让我们澄清一下答案的一些要点:
/proc/pts/*
内容都由创建并显示的终端读取,读取的所有内容/proc/pts/*
都来自连接到终端的输入设备,/dev/stdout
上往往是一个符号链接到/proc/self/fd/1
,而/dev/stdin
一个符号链接到/proc/self/fd/0
. 虚拟/proc
文件系统会小心地向每个应用程序显示/proc/self
一个指向 的符号链接/proc/<pid>
,其中<pid>
是应用程序进程 ID。/proc/<pid>/fd
指向应用程序打开的或从其父级继承的文件、管道和其他内容。每个应用程序都保证打开三个文件描述符:0
读取输入、1
写入输出、2
写入错误。在您的情况下,它是/dev/pts/10
.如果您不将输出重定向到另一个文件,则 shell 运行的每个命令都会写入终端。此规则的例外情况是,如果您的命令的进程组与终端的前台进程组不同,则写入将失败并被
SIGTTOU
发送到命令。可以使用stty tostop
和控制此行为stty -tostop
:一个 POSIX 兼容的程序可以期望从其父进程继承文件描述符 #0、#1 和 #2(也分别通过编程常量和)
stdin
,处于已打开、准备好使用的状态。stdout
stderr
在最简单的情况下,对于在文本控制台上登录的会话中的命令行程序,在没有应用重定向的情况下,这个继承链一直追溯到
getty
为登录会话初始化 TTY 设备的过程。使用 GUI 登录时,显示管理器进程
gdm/kdm/sddm/lightdm/xdm/<whatever>dm
(在会话中,或者作为桌面环境的一部分,或者使用桌面菜单或图标开始。/dev/null
$HOME/.xsession-errors
例如,对于 SSH 会话,
sshd
用于初始化会话的进程会分配一个伪 TTY 设备对,将stdin/out/err
文件描述符指向其中的一半,然后exec()
编辑用户的 shell。该分支的另一端将保留伪 TTY 设备对的另一半,并将处理网络和伪 TTY 设备之间的传出/传入流量的加密/解密,直到会话结束。当终端模拟器在 GUI 会话中启动时,它的行为与初始化新会话时的进程基本相同
sshd
:它分配一个伪 TTY,fork()
s 本身,一个副本设置会话,包括指向文件描述符 #0, #1 和 #2 到伪 TTY,最后exec()
是用户的 shell,而 fork 的另一端将继续处理实际维护终端窗口的可视化表示的任务。因此,简而言之,(伪?)TTY 设备通过初始化终端会话的东西连接到 stdin/stdout/stderr,并且可能在它和您的应用程序之间的所有进程都简单地将它们传递给一个链继承,对那些文件描述符什么都不做,只是让它们按原样传递给它们的子进程。
当在 shell 命令行中使用重定向操作符时,作为 shell
fork()
自身的临时副本,为实际exec()
执行命令做准备,fork()
临时副本之后将关闭相应的文件描述符,在其位置打开重定向操作符指定的东西,然后exec()
是命令,以便它将继承修改后的 stdin/out/err 文件描述符。在某些 Unix 风格的系统中
/dev/std*
,设备可能由 shell 处理。但是 Linux 让它们更加“真实”。在 Linux 中
/dev/stdin
,/dev/stdout
和/dev/stderr
只是指向/proc
文件系统的普通旧符号链接:这些链接是在系统启动时初始化
udev
基于 RAM 的文件系统时创建的。/dev
它们只是普通的普通符号链接,没有什么神奇之处。But
/proc
是一个完全虚拟的文件系统,它实时反映系统中进程的状态,因此它有几个“神奇”的属性:/proc/self
是一个符号链接,指向查看它的进程/proc/<PID>
的目录:/proc/<PID>/fd
是一个包含符号链接的目录,其名称对应于进程打开的文件描述符<PID>
,并指向与该文件描述符关联的任何内容。因此,当一个进程
/dev/pts/10
尝试访问/dev/stdin
时,符号链接将其指向/proc/self/fd/0
......当/proc/self/fd/0
被访问时,/proc
文件系统驱动程序查看内核的进程表,使用它来查找正在访问它的进程的文件描述符表, 并显示/proc/self/fd/0
为/dev/pts/10
...的符号链接,正是因为该进程当前/dev/pts/10
与其文件描述符 #0 相关联。在 Solaris 11 上,这些
/dev/std*
设备是指向目录的符号链接/dev/fd/
,这同样是“神奇的”:在这里,Solaris
/dev
文件系统驱动程序使用目录中的设备节点来实现魔术,而不是像 Linux 出于历史原因那样/dev/fd
重定向到文件系统。/proc
在 shell 中运行命令
当命令在 shell 中运行时,就会发生这种情况。
fork
:shell 如何在两个进程中运行。std in/out/err
:但如果没有重定向,它什么也不做。新进程从原始 shell 继承了这些,并且 shell 已经具有正确的值。exec
来运行一个新程序:这个新程序将继承 的值std in/out/err
,并替换新的 shell。这个新的 shell 非常短暂(现在在文档中提到,因为它只是一个实现细节)。它与子外壳不同。
新命令打开
/dev/stdin
当新程序打开
/dev/stdin
时,内核中的文件系统代码看到这是一个符号链接,/proc/self/fd/0
然后看到这/dev/self
是一个符号链接,/proc/nnnn
其中 nnnn 是进程的 pid,所以 this 指向/proc/nnnn/fd/0
which 指向文件,例如/dev/pts/10
. 打开/dev/stdin
将创建一个新的文件描述符。不需要正常打开dev/stdin
,因为文件描述符 0 已经指向该文件。(只有当程序不是为了读取标准输入而编写的,而是可以从文件中读取时,您才需要这样做。)(所有这些对于 stdout 和 stderr 也是如此。)
中的文件
/proc
不是真实文件(未存储在任何地方);这些是由文件系统在访问(从不写入磁盘)时动态创建的,该文件系统从内核中的数据结构(而不是磁盘)中查找数据。