![我当前的控制终端和`/dev/tty`之间有什么关系?](https://linux22.com/image/134408/%E6%88%91%E5%BD%93%E5%89%8D%E7%9A%84%E6%8E%A7%E5%88%B6%E7%BB%88%E7%AB%AF%E5%92%8C%60%2Fdev%2Ftty%60%E4%B9%8B%E9%97%B4%E6%9C%89%E4%BB%80%E4%B9%88%E5%85%B3%E7%B3%BB%EF%BC%9F.png)
在 Lubuntu 18.04 上,我在 lxterminal 中运行 shell。它的控制终端是当前的伪终端从机:
$ tty
/dev/pts/2
我想知道我当前的控制终端/dev/pts/2
和/dev/tty
.
/dev/tty
就像我当前的控制终端一样/dev/pts/2
:$ echo hello > /dev/tty hello $ cat < /dev/tty world world ^C
但它们似乎是不相关的文件,而不是一个到另一个的符号链接或硬链接:
$ ls -lai /dev/tty /dev/pts/2 5 crw--w---- 1 t tty 136, 2 May 31 16:38 /dev/pts/2 13 crw-rw-rw- 1 root tty 5, 0 May 31 16:36 /dev/tty
对于具有不同控制终端的不同会话,
/dev/tty
保证if是它们的控制终端。它怎么可能是不同的控制终端,而不是符号链接或硬链接?
那么他们有什么联系和区别呢?任何帮助深表感谢!
这篇文章源自之前的一篇文章命令 `tty` 和文件 `/dev/tty` 的输出都指向当前 bash 进程的控制终端吗?
答案1
tty
第 4 节中的联机帮助页声称如下:
文件/dev/tty是一个字符文件,主设备号为 5,次设备号为 0,通常模式为 0666,所有者为 root.tty。它是进程的控制终端(如果有)的同义词。
除了
ioctl(2)
tty 所指的设备支持的请求ioctl(2)
TIOCNOTTY
支持请求。
TIOCNOTTY
将调用进程与其控制终端分离。
如果该进程是会话领导者,
SIGHUP
则SIGCONT
信号将发送到前台进程组,并且当前会话中的所有进程都会失去其控制 tty。这
ioctl(2)
调用仅适用于连接到的文件描述符 /dev/tty。当用户在终端调用守护进程时,它由守护进程使用。该进程尝试打开/dev/tty。如果打开成功,则使用 来将自身与终端分离TIOCNOTTY
,而如果打开失败,则显然它没有附加到终端,不需要自行分离。
这可以部分解释为什么/dev/tty
不是控制终端的符号链接:它将支持附加的ioctl
,并且可能没有控制终端(但进程总是可以尝试访问/dev/tty
)。但是文档不正确:附加内容ioctl
不仅可以访问通过 /dev/tty
(看莫斯维的回答,这也对 ) 的性质给出了更合理的解释/dev/tty
。
/dev/tty
可以代表不同的控制终端,而不是链接,因为实现它的驱动程序确定调用进程的控制终端是什么(如果有)。
您可以将其视为/dev/tty
控制终端,从而提供仅对控制终端有意义的功能,而/dev/pts/2
etc. 是普通终端,其中之一可能恰好是给定进程的控制终端。
答案2
/dev/tty
是一个“神奇”的字符设备,打开时会返回当前终端的句柄。
假设控制终端是/dev/pts/1
,则通过via打开的文件描述符/dev/pts/1
和通过via打开的文件描述符/dev/tty
将引用同一个设备;任何写入、读取或其他文件操作在它们中的任何一个上都会以相同的方式进行。
特别是,它们将接受相同的 ioctl 集,并且TIOCNOTTY
不是只能通过以下方式获得的额外 ioctl/dev/tty
。
ioctl(fd, TIOCNOTTY)
在引用终端的任何文件描述符上的工作方式都是相同的,前提是它是调用它的进程的控制终端。
/dev/tty
描述符是否是通过打开, /dev/pts/1
,获得的并不重要/dev/ptmx
(在这种情况下,ioctl 将作用于其相应的奴隶),或者最近,通过调用ioctl(master, TIOCGPTPEER, flags)
.
例子:
$ cat <<'EOT' >tiocnotty.c
#include <sys/ioctl.h>
#include <unistd.h>
#include <err.h>
int main(int ac, char **av){
if(ioctl(0, TIOCNOTTY)) err(1, "io(TIOCNOTTY)");
if(ac < 2) return 0;
execvp(av[1], av + 1);
err(1, "execvp %s", av[1]);
}
EOT
$ cc -W -Wall tiocnotty.c -o tiocnotty
$ ./tiocnotty
$ ./tiocnotty </dev/tty
$ tty
/dev/pts/0
$ ./tiocnotty </dev/pts/0
而且,它不会真正将当前进程与 tty“分离”;该进程仍然能够从中读取数据,^C
终端上的 a 将杀死它,等等。它对不是会话领导者的进程的唯一影响是 tty 将不再可通过 访问/dev/tty
,并且将不再是列为控制 tty /proc/PID/stat
:
$ ./tiocnotty cat
^C
$ ./tiocnotty cat
^Z
[2]+ Stopped ./tiocnotty cat
$ ./tiocnotty cat
foo
foo
^D
$ ./tiocnotty cat /dev/tty
cat: /dev/tty: No such device or address
$ ./tiocnotty awk '{print$7}' /proc/self/stat
0
[第7个字段/proc/<pid>/stat
是控制tty的设备id,参见proc(5)
]
如果调用它的进程是会话领导者,它将真正将会话与 tty 分离,并从会话中向前台进程组发送SIGHUP
/对。SIGCONT
但即便如此,终端也会不是被关闭,并且该进程仍然能够从中读取,如果它在以下情况下幸存下来SIGHUP
:
$ script /dev/null -c 'trap "" HUP; exec ./tiocnotty cat'
Script started, file is /dev/null
lol
lol
^C^C^C^C^C # no controlling tty anymore
wtf
wtf
^D # but still reading fine from it
Script done, file is /dev/null
/dev/tty
不是像/dev/stdin
=> /dev/fd/0
=> /proc/self/fd/0
=>这样的符号链接/dev/pts/0
,因为它的发明早于像 procfs 这样的虚拟动态文件系统(并且通常早于符号链接)。许多程序已经开始依赖于其特定的语义(例如,当控制终端不可访问时/dev/tty
失败)。ENODEV