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
//stderr
是stdin
shell 唯一可以使用的接口。对于应用程序来说也是如此。
某些操作系统组件最终会将写入的数据移动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*
终端上的某些内容实际进行交互?
答案1
/dev/pts/10
是a的从端伪终端设备对。另一端是打开主克隆设备/dev/ptmx
并/dev/pts/10
成对接收的程序(每次打开/dev/ptmx
,您都会获得不同的从属设备)。/dev/ptmx
和之间的连接/dev/pts/10
基本上是双向管道有一个扭曲。
当您打开终端模拟器应用程序时:
- 它打开
/dev/ptmx
并获取另一端的名称。它配置另一端并将其打开, - 它分叉,
- 新进程打开伪终端设备的另一端并连接其标准输入,标准输出和标准错误对它,
- 新进程执行 shell。
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/*
都来自连接到终端的输入设备, - 在 Linux 上
/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
:
stty tostop
echo "/dev/stdout points to the terminal, but I won't print anything" &
stty -tostop
echo "You can see me" &
答案2
符合 POSIX 标准的程序可以期望从其父进程继承文件描述符 #0、#1 和 #2(也分别称为编程常量stdin
、stdout
和)stderr
已打开、可供使用的状态。
在最简单的情况下,在文本控制台上登录的会话中的命令行程序中,如果没有应用重定向,则此继承链会一直追溯到getty
为登录会话初始化 TTY 设备的进程。
当使用 GUI 登录时,显示管理器进程 ( gdm/kdm/sddm/lightdm/xdm/<whatever>dm
) 通常会将标准输入和输出设置为/dev/null
以及标准错误 设置为$HOME/.xsession-errors
或类似的会话第一个进程,并且这些文件描述符同样由所有启动的 GUI 程序继承在会话中,作为桌面环境的一部分,或者使用桌面菜单或图标启动。
例如,对于 SSH 会话,sshd
分叉以初始化会话的进程将分配一对伪 TTY 设备对,将文件描述符指向stdin/out/err
其中的一半,然后exec()
编辑用户的 shell。该分支的另一端将保留伪 TTY 设备对的另一半,并将处理网络和伪 TTY 设备之间的传出/传入流量的加密/解密,直到会话结束。
当终端仿真器在 GUI 会话中启动时,它的行为与初始化新会话时的过程基本相同sshd
:它分配一个伪 TTY,fork()
s 本身,并且一个副本设置会话,包括指向文件描述符 #0, #1 和 #2 到伪 TTY,最后exec()
是用户的 shell,分叉的另一侧将继续处理实际维护终端窗口的可视化表示的任务。
所以,简而言之,(伪?)TTY 设备通过初始化终端会话的东西连接到 stdin/stdout/stderr,并且该设备和您的应用程序之间可能存在的所有进程只是将它们以链的方式传递下来。继承,由什么都不做到这些文件描述符,只是让它们按原样传递给子进程。
当在 shell 命令行中使用重定向运算符时,由于 shellfork()
会为其自身创建一个临时副本,以准备实际exec()
执行该命令,fork()
临时副本将关闭相应的文件描述符,并在其位置打开重定向运算符指定的内容,然后是exec()
命令,以便它将继承修改后的 stdin/out/err 文件描述符。
在某些 Unix 风格的系统中,/dev/std*
设备可能由 shell 处理。但 Linux 让它们变得更加“真实”。
在 Linux 中,/dev/stdin
、/dev/stdout
和/dev/stderr
只是指向/proc
文件系统的普通旧符号链接:
$ ls -l /dev/std*
lrwxrwxrwx 1 root root 15 Feb 4 08:22 /dev/stderr -> /proc/self/fd/2
lrwxrwxrwx 1 root root 15 Feb 4 08:22 /dev/stdin -> /proc/self/fd/0
lrwxrwxrwx 1 root root 15 Feb 4 08:22 /dev/stdout -> /proc/self/fd/1
这些链接是在系统启动时初始化udev
基于 RAM 的文件系统时创建的。/dev
它们只是普通的符号链接,没有什么神奇的。
但它/proc
是一个完全虚拟的文件系统,可以实时反映系统中进程的状态,因此它具有几个“神奇”的属性:
/proc/self
/proc/<PID>
是指向目录的符号链接查看它的进程的:
$ ls -l /proc/self # the PID of this ls command will be 10839
lrwxrwxrwx 1 root root 0 Feb 4 08:22 /proc/self -> 10839/
$ ls -l /proc/self # the PID of this ls command will be 10843
lrwxrwxrwx 1 root root 0 Feb 4 08:22 /proc/self -> 10843/
/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/
,这同样“神奇”:
$ uname -sr
SunOS 5.11
$ ls -l /dev/std*
lrwxrwxrwx 1 root root 0 Jun 17 2019 /dev/stderr -> ./fd/2
lrwxrwxrwx 1 root root 0 Jun 17 2019 /dev/stdin -> ./fd/0
lrwxrwxrwx 1 root root 0 Jun 17 2019 /dev/stdout -> ./fd/1
在这里,Solaris/dev
文件系统驱动程序使用目录中的设备节点来实现魔法,而不是像 Linux 由于历史原因而/dev/fd
重定向到文件系统。/proc
答案3
在 shell 中运行命令
当命令在 shell 中运行时,就会发生这种情况。
- shell调用
fork
:shell如何在两个进程中运行。 - 新的 shell 设置
std in/out/err
:但如果没有重定向,它什么也不做。新进程从原始 shell 继承了这些值,并且 shell 已经具有正确的值。 - 新的 shell 调用
exec
运行一个新程序:这个新程序将继承 的值std in/out/err
,并替换新的 shell。
这个新的 shell 非常短暂(现在在文档中提到,因为它只是一个实现细节)。它与子外壳不同。
新命令打开/dev/stdin
当新程序打开时/dev/stdin
,内核中的文件系统代码会看到这是一个指向/proc/self/fd/0
它的符号链接,然后会看到这是一个指向其中 nnnn 是进程 pid 的/dev/self
符号链接,因此 this 指向指向该文件的文件,例如。打开将创建一个新的文件描述符。在正常情况下打开不是必要的,因为文件描述符0已经指向该文件。/proc/nnnn
/proc/nnnn/fd/0
/dev/pts/10
/dev/stdin
dev/stdin
(只有当程序不是为了读取 stdin 而编写的,而是可以从文件中读取时,才需要执行此操作。)(所有这对于 stdout 和 stderr 也是如此。)
中的文件/proc
不是真实文件(没有存储在任何地方);这些是在文件系统访问时动态创建的(从不写入磁盘),该文件系统从内核中的数据结构(而不是从磁盘)查找数据。