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

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

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

$ tty
/dev/pts/2

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

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

    $ echo hello > /dev/tty
    hello
    
    $ cat < /dev/tty
    world
    world
    ^C
    
  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
    

对于具有不同控制终端的不同会话, /dev/tty保证if是它们的控制终端。它怎么可能是不同的控制终端,而不是符号链接或硬链接?

那么他们有什么联系和区别呢?任何帮助深表感谢!

这篇文章源自之前的一篇文章命令 `tty` 和文件 `/dev/tty` 的输出都指向当前 bash 进程的控制终端吗?

答案1

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

文件/dev/tty是一个字符文件,主设备号为 5,次设备号为 0,通常模式为 0666,所有者为 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(看莫斯维的回答,这也对 ) 的性质给出了更合理的解释/dev/tty

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

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

答案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

相关内容