我当前的控制终端和`/dev/tty` 之间有什么关系?

Tim*_*Tim 8 tty controlling-terminal pseudoterminal

在 Lubuntu 18.04 上,我在 lxterminal 中运行了一个 shell。它的控制终端是当前的伪终端从机:

$ tty
/dev/pts/2
Run Code Online (Sandbox Code Playgroud)

我想知道我当前的控制终端/dev/pts/2/dev/tty.

  1. /dev/tty就像我当前的控制终端/dev/pts/2

    $ echo hello > /dev/tty
    hello
    
    $ cat < /dev/tty
    world
    world
    ^C
    
    Run Code Online (Sandbox Code Playgroud)
  2. 但它们似乎是不相关的文件,而不是一个符号链接或硬链接到另一个:

    $ 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
    
    Run Code Online (Sandbox Code Playgroud)

对于不同控制终端的不同会话,if /dev/tty保证是它们的控制终端。它如何成为不同的控制终端,而不是符号链接或硬链接?

那么它们之间的关系和区别是什么?任何帮助深表感谢!

这篇文章来自于较早的一个命令`tty`和文件`/dev/tty`的输出都是指当前bash进程的控制终端吗?

Ste*_*itt 13

tty第 4 节中的联机帮助页声明如下:

文件/dev/tty是一个字符文件,主编号为5,次编号为0,通常模式为0666 和owner.group root.tty。它是进程控制终端的同义词,如果有的话。

除了ioctl(2)tty所指的设备支持的ioctl(2)请求外,TIOCNOTTY还支持请求。

TIOCNOTTY

将调用进程与其控制终端分离。

如果该进程是会话领导者,则将SIGHUPSIGCONT信号发送到前台进程组,当前会话中的所有进程都将失去其控制 tty。

ioctl(2)调用仅适用于连接到/dev/tty 的文件描述符 。当守护进程在终端被用户调用时,它会被守护进程使用。该进程尝试打开/dev/tty。如果打开成功,则使用 将自身与终端分离 TIOCNOTTY,而如果打开失败,则显然没有附加到终端,不需要分离自身。

这将部分解释为什么/dev/tty不是控制终端的符号链接:它将支持额外的ioctl,并且可能没有控制终端(但进程总是可以尝试访问/dev/tty)。然而,文档是不正确的:额外ioctl的不仅可以通过 /dev/tty(参见mosvy 的回答,这也对 的性质给出了更合理的解释/dev/tty)。

/dev/tty 可以代表不同的控制终端,而不是链接,因为实现它的驱动程序决定了调用进程的控制终端是什么(如果有的话)。

您可以将其视为/dev/tty控制终端,从而提供仅对控制终端有意义的功能,而/dev/pts/2等是普通终端,其中一个可能恰好是给定进程的控制终端。


小智 6

/dev/tty是一个“神奇”的字符设备,打开时会返回当前终端的句柄。

假设控制终端是/dev/pts/1,则通过via打开的文件描述符/dev/pts/1和通过via打开的文件描述符/dev/tty将引用同一个设备;任何写入、读取或其他文件操作在它们中的任何一个上都会以相同的方式进行。

特别是,它们将接受相同的 ioctl 集,并且TIOCNOTTY不是只能通过/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
Run Code Online (Sandbox Code Playgroud)

而且,它不会真正将当前进程与 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
Run Code Online (Sandbox Code Playgroud)

[第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
Run Code Online (Sandbox Code Playgroud)

/dev/tty不是像/dev/stdin=> /dev/fd/0=> /proc/self/fd/0=>这样的符号链接/dev/pts/0,因为它的发明早于像 procfs 这样的虚拟动态文件系统(并且通常早于符号链接)。许多程序已经开始依赖于其特定的语义(例如,当控制终端不可访问时/dev/tty失败)。ENODEV