我目前的控制終端和`/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

相關內容