解密TTY


本文內容來自The TTY demystified ,講述了*NIX系統中TTY的歷史與工作原理,看完后解決了我很多疑惑,於是做此翻譯,與大家分享。




譯者:李秋豪 江家偉

審校:

V1.0 Sun May 13 12:42:01 CST 2018


一直以來,TTY子系統都是Linux/Unix設計中的一個關鍵點。不幸的是,這種重要性通常都被忽略了,並且也很難找到相關的介紹性文章。我認為,對Linux中TTYs的基礎知識理解應是每一個開發人員和高級使用者所必備的。

注意:你將閱讀到的東西並不是那么“優雅”。事實上,盡管在用戶角度看非常實用,TTY子系統是由很多繁雜的東西和特殊情況組成的。為了理解它們的由來,我們必須回到過去:


歷史

在1869年,證券報價機(stock ticker)被發明了。這是一台由打字機,一對長電纜和一個自動收錄機打印機組成的電動機械機器,其目的是長距離實時傳播股票的價格。這個概念逐漸演變成更快的基於ASCII的電傳機(teletype)。Teletypes曾經在世界各地的大型網絡中連接,並被稱為Telex,其主要用於傳輸商業電報,但此時尚未連接到任何計算機。

與此同時,計算機(雖然還是又笨重又昂貴)也開始支持多任務處理了,即能夠實時和多個用戶進行交互。當命令行最終取代了古老的批處理模型后,teletypes被用作輸入和輸出設備,因為它們在市場上很容易買到。

但是在市場上有許多種電傳機,它們的模型都略有不同,因此需要計算機在軟件層形成兼容。在UNIX世界中,使用的方法是讓操作系統內核處理所有底層細節,例如字長,波特率,流量控制,奇偶校驗,用於基本行編輯(rudimentary line)的控制代碼等等。而視頻終端(例如20世紀70年代后期出現的VT-100等)的光標移動,彩色輸出和其他高級功能則留給了應用層。

現在,物理電傳機和視頻終端實際上已經滅絕了。除非你在訪問博物館或者你是一個硬件愛好者,否則你看到的所有TTY都是模擬視頻終端,即軟件仿真出來的終端。但我們即將看到,這些遠古的知識依然潛藏在現代TTY設計之中。


用例

如下圖所示,用戶在終端(terminal)打字(物理電傳機),該終端通過一對電纜連接到計算機上的UART(通用異步接收器和發送器)。操作系統中有一個UART驅動程序,用於管理字節的物理傳輸,包括奇偶校驗和流量控制。在一個原始的系統中,UART驅動程序會將傳入的字節直接傳送給某個應用程序進程,但是這種方法將缺乏以下基本特征:

行編輯。大多數用戶都會在輸入時犯錯,所以退格鍵會很有用。這當然可以由應用程序本身來實現,但是根據UNIX設計“哲學”,應用程序應盡可能保持簡單。為了方便起見,操作系統提供了一個編輯緩沖區和一些基本的編輯命令(退格,清除單個單詞,清除行,重新打印),這些命令在行規范(line discipline)內默認啟用。高級應用程序可以通過將行規范設置為原始模式(raw mode)而不是默認的成熟或准則模式(cooked and canonical)來禁用這些功能。大多數交互程序(編輯器,郵件客戶端,shell,及所有依賴cursesreadline的程序)均以原始模式運行,並自行處理所有的行編輯命令。行規范還包含字符回顯和回車換行(譯者注:\r\n\n)間自動轉換的選項。如果你喜歡,可以把它看作是一個原始的內核級sed(1)

另外,內核提供了幾種不同的行規范。一次只能將其中一個連接到給定的串行設備。行規范的默認規則稱為N_TTY(drivers/char/n_tty.c,如果你想繼續探索的話)。其他的規則被用於其他目的,例如管理數據包交換(ppp,IrDA,串行鼠標),但這不在本文的討論范圍之內。

會話(Session)管理。用戶可能想要同時運行多個程序,並且一次只與其中一個交互。如果一個程序進入無限循環,用戶可能想要終止或掛起它。在后台啟動的程序應該能夠獨立運行,直到它們嘗試向終端寫入(被掛起)。同樣,用戶的輸入應該指向前台程序。對於這些功能,操作系統是在TTY驅動程序( TTY driver drivers/char/tty_io.c)中實現的。

在操作系統中,如果已經進程有執行上下文,我們就說它是“活着的”(有一個執行上下文),這也意味着它可以獨立執行操作。而TTY驅動程序不是“活”的; 在面向對象的術語中,TTY驅動程序是被動對象(passive object)。它有一些數據字段和一些方法,但讓它做某事的唯一方法是當它的某個方法從別的進程的上下文或內核中斷處理程序中調用時。行規范(line discipline)同樣是一個被動對象。

現在把它們放在一起看,UART驅動,行規范和TTY驅動這個三元組就可以被稱為TTY設備,即我們常說的TTY。用戶進程可以通過在/dev下操作相應的設備文件來影響任何TTY設備的行為。由於對設備文件寫入權限是必需的,因此當用戶登錄特定的TTY時,該用戶必須成為設備文件的所有者——這通常由login(1)程序完成,該程序以root權限運行。

上圖中的物理電線也可以是長途電話線路(Modem),除了系統必須處理調制解調器掛斷的情況,這並沒有帶來其他的改變:

讓我們繼續討論典型的桌面系統。下圖是Linux控制台的工作原理:

在上圖中,TTY驅動和行規范的行為與前面的示例類似,但不再有UART或物理終端。相反,軟件仿真出視頻終端(字符和圖形字符屬性幀緩沖器的復雜狀態機),並最終被渲染到VGA顯示器。

如果我們在用戶空間也進行終端仿真,情況會變得更加靈活(和抽象)。下圖是xterm(1)及其克隆的工作方式:

為了便於將終端仿真移入用戶空間,同時仍保持TTY子系統(會話管理和行規范)的完整,偽終端被發明了出來(pseudo terminalpty )。你可能已經猜到,當你開始在偽終端中運行偽終端時,事情變得更加復雜,例如 screen(1)ssh(1)

現在讓我們退一步看看所有這些東西是如何和進程聯系起來的。


進程

Linux進程可以處於下面狀態之一:

標志位 說明
D 不可中斷睡眠(等待某個事件)
S 可中斷睡眠(等待一些事件或者信號)
T 停止(收到了工作管理信號或者進程正在被調試器追蹤)
Z 僵屍進程(被它的父進程終止但是沒有被回收的進程)
R 運行或者可運行(在運行隊列中)

通過運行 ps l, 你可以看到哪個進程正在運行,以及哪個進程正在睡眠。如果一個進程處於睡眠狀態, WCHAN 列("wait channel", 等待隊列的名字)將會告訴你這個進程正在等待哪個內核事件。

$ ps l
F   UID   PID  PPID PRI  NI    VSZ   RSS WCHAN  STAT TTY        TIME COMMAND
0   500  5942  5928  15   0  12916  1460 wait   Ss   pts/14     0:00 -/bin/bash
0   500 12235  5942  15   0  21004  3572 wait   S+   pts/14     0:01 vim index.php
0   500 12580 12235  15   0   8080  1440 wait   S+   pts/14     0:00 /bin/bash -c (ps l) >/tmp/v727757/1 2>&1
0   500 12581 12580  15   0   4412   824 -      R+   pts/14     0:00 ps l

"wait"等待隊列對應於系統調用 wait(2) ,因此這個隊列中的進程的子進程不論什么時候改變了狀態,它們都會被移入運行狀態。有兩種睡眠狀態:可中斷睡眠和不可中斷睡眠。可中斷睡眠(最常見的情況)意味着當進程在等待隊列中時,它實際上也可能由於收到了一個信號而被移入運行狀態。如果你深入到內核源碼中,你將會發現每個處理等待事件的內核源碼都會檢查在schedule()調用返回之后是否有待處理的信號,如果有,就從系統調用wait(2)中返回。

在上面列出的 ps 結果中, STAT 列展示了每個進程的當前狀態。這一列中可能會顯示一個或多個屬性或標記:

s 這個進程是會話領導
+ 這個進程是前台進程組的一員

這些屬性被用於工作管理。


譯者注:我之前翻譯過兩篇有關於進程標志的文章,可參考

Linux 進程狀態標識 Process State Definition

Linux 可運行進程 Runnable Process Definition


工作與會話管理

當你按下 ^Z 掛起程序或者使用 & 在后台運行程序時,工作管理就發生了。一個工作(job)等同於一個進程組。shell內置的命令如 jobs, fgbg 可以用來管理一個會話(session)中的所有工作。每一個會話是由一個會話領導(session leader),即shell來管理的,它會利用復雜的協議,例如信號和一些系統調用和內核打交道。

下面的例子解釋了進程、工作、會話之間的關系。

下面的shell交互...

...對應這些進程...

...和這些內核數據結構

  • TTY 驅動 (/dev/pts/0).
  Size: 45x13
  Controlling process group: (101)
  Foreground process group: (103)
  UART configuration (忽略d, since this is an xterm):
    Baud rate, parity, word length and much more.
  Line discipline configuration:
    cooked/raw mode, linefeed correction,
    meaning of interrupt characters etc.
  Line discipline state:
    edit buffer (currently empty),
    cursor position within buffer etc.

  • pipe0
  Readable end (connected to PID 104 as file descriptor 0)
  Writable end (connected to PID 103 as file descriptor 1)
  Buffer

其中基本的思想是每個管道都是一項工作,因為管道中的每個進程都應該被同時進行操作(停止,恢復,終止)。這也是為什么 kill(2) 允許你發送信號到整個進程組。默認情況下, fork(2) 將新創建的子進程放置在與其父進程相同的進程組中,例如,鍵盤上的 ^C 會影響父進程和子進程。但是,作為會話領導責任的一部分,每次啟動管道時,shell都會創建一個新的進程組。

TTY驅動程序會記錄前台進程組ID(PID),但這只能以被動方式進行。會話領導必須在必要時主動更新此信息。同樣,TTY驅動程序會記錄連接終端的屬性(例如窗口大小),但這些信息必須由終端仿真程序甚至用戶主動更新。

正如在上圖中所看到的,幾個進程將 /dev/pts/0 作為它們的標准輸入。但只有前台工作 ls | sort 才會接收來自TTY的輸入。同樣,只有前台工作才被允許寫入TTY設備(默認配置下)。如果cat進程試圖寫入TTY,內核將使用信號將它掛起。


信號控制

現在讓我們更近距離地看看內核中的TTY驅動、行規范和UART驅動是如何和用戶態進程交互的。

UNIX文件,包括TTY設備文件,可以被讀和寫,並且由於許多TTY相關的操作都已經被定義,可以使用神奇的 ioctl(2)系統調用(UNIX的“瑞士軍刀”)進行進一步操作。但是,ioctl請求必須在進程內被初始化,因此它們不能在內核需要和應用進行異步通信的場景下被使用。

The Hitchhiker's Guide to the Galaxy(銀河系漫游指南)中,Douglas Adams提到了一個“死星”,上面居住這一群消沉的人類和某種長着尖牙的動物。這些動物通過狠狠地咬人類的大腿來和人類交流(譯者:喵喵喵?)。這和UNIX驚人地相似:在UNIX中,內核通過發送“癱瘓或者致命”的信號給用戶進程來和進程通信。一些進程可能能夠攔截一些信號,並且嘗試調整適應當前的情況,但是大多數進程不會這么做。

因此信號是一個“粗暴”的機制,它允許內核和進程進行異步通信。UNIX中的信號定義是不規整或者不統一的;相反,每個信號都是獨特的,我們必須單獨研究它們。

你可以使用命令 kill -l 來看看你的系統實現了哪些命令。結果看起來像下面這樣:

$ kill -l
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL
 5) SIGTRAP	 6) SIGABRT	 7) SIGBUS	 8) SIGFPE
 9) SIGKILL	10) SIGUSR1	11) SIGSEGV	12) SIGUSR2
13) SIGPIPE	14) SIGALRM	15) SIGTERM	16) SIGSTKFLT
17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU
25) SIGXFSZ	26) SIGVTALRM	27) SIGPROF	28) SIGWINCH
29) SIGIO	30) SIGPWR	31) SIGSYS	34) SIGRTMIN
35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3	38) SIGRTMIN+4
39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12
47) SIGRTMIN+13	48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14
51) SIGRTMAX-13	52) SIGRTMAX-12	53) SIGRTMAX-11	54) SIGRTMAX-10
55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7	58) SIGRTMAX-6
59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX	

正如你看到的,信號被從1開始的數字編號。然而當它們被在掩碼中(例如在ps -s的輸出里)被使用時,最低有效位對應信號1。

這篇文章將會關注以下信號: SIGHUP, SIGINT, SIGQUIT, SIGPIPE, SIGCHLD,SIGSTOP, SIGCONT, SIGTSTP, SIGTTIN, SIGTTOU 以及SIGWINCH.

SIGHUP

  • 默認操作: 終止
  • 可能的操作: 終止, 忽略, 函數調用

當檢測到掛斷(hangup)條件時,UART驅動會將SIGHUP 發送到整個會話。通常情況下,這會殺死所有進程。某些程序(如 nohup(1)screen(1))會從其會話(和TTY)中分離,以便其子進程不會注意到掛斷。

SIGINT

  • 默認操作: 終止
  • 可能的操作: 終止, 忽略, 函數調用

如果輸入流中出現交互式注意( interactive attention )字符(通常為 ^C,其代碼為ASCII碼3),那么SIGINT 就會由TTY驅動發送到當前的前台工作,除非此配置已被關閉。任何有權訪問TTY設備的人都可以更改交互式注意字符並開關此配置; 此外,會話管理器會跟蹤每個工作的TTY配置,並在有工作切換時更新TTY。

SIGQUIT

  • 默認操作: 內核轉儲(core dump)
  • 可能的操作: 內核轉儲, 忽略, 函數調用

SIGQUIT 的工作方式和 SIGINT相似, 但是使用的字符是 ^\ 並且默認操作不同。

SIGPIPE

  • 默認操作: 終止
  • 可能的操作: 終止, 忽略, 函數調用

內核會給每一個試圖往沒有讀取者的管道中寫數據的進程發送 SIGPIPE 信號。 這是很有用的,因為沒有這個信號的話,類似 yes | head這樣的工作就永遠不會停止了。

SIGCHLD

  • 默認操作: 忽略
  • 可能的操作: 忽略, 函數調用

當進程死亡或更改狀態(停止/繼續)時,內核會向其父進程發送一個 SIGCHLDSIGCHLD 信號攜帶着終止進程的附加信息,即進程標識,用戶標識,退出狀態(或終止信號)以及一些執行時間的統計信息。會話領導(shell)使用這個信號追蹤其工作。

SIGSTOP

  • 默認操作: 掛起
  • 可能的操作: 掛起

該信號將無條件地掛起接收者,即其信號動作不能被重新配置。要注意的是,在工作控制期間,SIGSTOP不會由內核發送。相反,^Z 通常會觸發一個 SIGTSTP,它可以被應用程序攔截。然后應用程序可以進行例如將光標移動到屏幕底部等操作,然后使用SIGSTOP將自己置於睡眠狀態。

SIGCONT

  • 默認操作: 喚醒
  • 可能的操作: 喚醒, 喚醒 + 函數調用

SIGCONT 將“反掛起”(un-suspend,continue)一個停止的進程。當用戶調用fg命令時,它會由shell發送出去。由於 SIGSTOP 不能被應用程序攔截,因此意料之外的SIGCONT 信號可能表明該進程在某段時間之前被掛起,然后被喚醒。

SIGTSTP

  • 默認操作: 掛起
  • 可能的操作: 掛起, 忽略, 函數調用

SIGTSTPSIGINTSIGQUIT 的工作原理相似,但是它使用的是 ^Z 字符,並且默認的操作是掛起進程。

SIGTTIN

  • 默認操作: 掛起
  • 可能的操作: 掛起, 忽略, 函數調用

如果一個后台工作中的進程嘗試從TTY設備中進行讀取,TTY會向整個工作(組)發送一個 SIGTTIN信號,這通常會掛起這個工作。

SIGTTOU

  • 默認操作: 掛起
  • 可能的操作: 掛起, 忽略, 函數調用

如果一個后台工作中的進程嘗試向TTY設備中進行寫入,TTY會向整個工作(組)發送一個 SIGTTIN信號,這通常會掛起這個工作。這種行為可以通過配置TTY關閉。

SIGWINCH

  • 默認操作: 忽略
  • 可能的操作: 忽略, 函數調用

如前所述,TTY設備會記錄終端的窗口大小,但這些信息需要手動更新。只要發生這種更新,TTY設備就會向前台工作發送 SIGWINCH 。行為良好的交互式應用程序(例如編輯器)會對此作出反應,從TTY設備獲取新的終端窗口大小並重繪GUI。


譯者注:我之前翻譯過一篇有關於進程和信號的文章,可參考

Linux 進程與信號的概念和操作


一個例子

假設你正在編輯(基於終端的)編輯器中的文件。此時光標位於屏幕中間的某個位置,編輯器正在執行一些任務,例如對大文件執行搜索和替換操作。現在你按 ^Z,由於行規范已被配置為攔截此字符(^Z 是一個單字節,ASCII碼為26),因此你無需等待編輯器完成其任務然后從TTY設備開始讀取。相反,行規范子系統會立即將 SIGTSTP 發送到前台進程組。該進程組包含編輯器以及由其創建的任何子進程。

編輯器為 SIGTSTP 安裝(install)了一個信號處理程序,因此內核將程序執行流轉移到信號處理程序代碼中。通過將相應的控制序列寫入TTY設備,該代碼將光標移動到屏幕的最后一行。由於編輯器仍處於前台,控制序列按要求發送。隨后編輯器會將 SIGSTOP 發送到其自己的進程組(正如上節信號中說的那樣)。

編輯器現在已經停止,SIGCHLD 信號向會話領導通告這個事件,其中包括該進程的ID。當前台工作中的所有進程都被掛起時,會話領導從TTY設備讀取當前配置,並將其存儲起來以供以后使用。會話領導繼續使用 ioctl 調用將其自身安裝為TTY的當前前台進程組。然后,它會打印類似 "[1]+ Stopped" 的內容,以通知用戶工作已暫停。

此時, ps(1) 會告訴你編輯器進程處於停止狀態(“T”)。如果我們試圖使用內置shell命令bg或使用 kill(1) 向進程發送 SIGCONT來喚醒它,編輯器將開始執行其 SIGCONT信號處理程序。而該處理程序會嘗試通過寫入TTY設備來重新繪制編輯器的GUI界面。但現在編輯器是一個后台工作,TTY設備將不允許它進行寫入。所以,TTY會給編輯器發送 SIGTTOU 信號,令其再次停止。這個事件將通過使用 SIGCHLD傳遞給會話領導(shell),而shell會再次向終端寫入“[1] + Stopped”。

但是,當我們鍵入fg時,shell首先恢復先前保存的行規范配置。它通知TTY驅動編輯器工作應該從現在起作為前台工作。最后,它向進程組發送一個SIGCONT 信號。編輯器試圖重繪它的GUI,這次它不會被SIGTTOU 中斷,因為它現在是前台工作的一部分。

譯者注:


流控制與I/O阻塞

xterm中運行 yes ,你會看到很多“y”出現在你眼前。自然,yes進程能夠很快的產生y,以至於xterm來不及進行幀緩沖區更新,與X服務器通信(譯者注:X Window System)以便滾動窗口等操作。那么這些程序是如何進行配合的呢?

答案在於I/O阻塞。偽終端只能在其內核緩沖區內保存一定數量的數據,當該緩沖區滿並且 yes 嘗試調用 write(2)時,write(2)將被阻止,並將yes 進程移至可中斷的睡眠狀態,直到xterm能夠讀取緩沖中的字節。

如果TTY連接到串行端口,也會發生同樣的情況。假設 yes 能夠以比9600波特的速率傳輸數據,但是如果串行端口被限制在低的多速度上,內核緩沖區很快就會被填滿,並且任何后續的 write(2) 調用都會導致進程睡眠(或收到返回的錯誤號 EAGAIN ,如果進程要求非阻塞I/O的話)。

如果我告訴過你,即使內核緩沖區中還有剩余空間,也可以主動地將TTY置於阻塞狀態,更進一步的說,每個試圖 write(2) 到TTY的進程都會自動阻塞。那么這種功能的用途是什么?

假設我們正在以9600波特率的速度與一些舊的VT-100通信。我們剛剛發送了一個復雜的控制序列,要求終端滾動顯示。此時,終端會因執行滾動操作無法以9600波特的全速率接收新數據。實際上,UART仍然以9600波特運行,但終端中沒有足夠的緩沖空間來保持接收字符。現在就是將TTY置於阻塞狀態的好時機。但是,我們該如何從終端做到這一點?

我們已經看到,TTY設備可以被配置為給某些數據字節特殊的處理。例如,在默認配置中,收到的 ^C 字節不會通過read(2)傳遞給應用程序,而是會將 SIGINT 信號傳遞到前台工作。類似地,可以將TTY配置為對停止流和開始流做出反應,通常分別是 ^S (ASCII碼19)和 ^Q (ASCII碼17)。舊的硬件終端會自動傳輸這些字節,並期望操作系統相應地調節其數據流。這被稱為流控制,這就是為什么當你偶然按下 ^S 時,你的xterm 會“鎖定”。

這里有一個重要的區別:寫入由於流控制而停止的TTY,或者由於缺少內核緩沖區空間,只會阻塞你的進程,而從后台工作中寫入TTY將導致SIGTTOU 暫停整個進程組。我不知道為什么UNIX的設計師必須發明 SIGTTOUSIGTTIN ,而不是僅僅依靠I/O阻塞,但我最好的猜測是負責工作控制的TTY驅動是為了監視和操縱整個工作——而不是其中的單個進程。


配置TTY設備

為了找出你的shell調用的控制TTY,你可以使用前面說過的ps l,或者運行tty(1)命令。

進程可以使用 ioctl(2)讀取或修改打開的TTY設備的配置。 該API在 tty_ioctl(4)中有描述。 由於它是Linux應用程序和內核之間的二進制接口的一部分,它將在Linux版本迭代中得到保持。 但是,該接口是不可移植的,應用程序應該使用 termios(3) 手冊頁中描述的POSIX包裝器。

我不會詳細討論 termios(3) 接口的細節,但是如果你正在編寫C程序並希望在 ^C 變成 SIGINT之前攔截 ^C ,或者禁用行規范或字符回顯,或將更改一個串的口波特率,關閉流控制等,你就會發現你需要上述的手冊頁(man page)。

這里還有一個名為 stty(1)的命令行工具來操作TTY設備。 它使用的是 termios(3) API。

讓我們試試吧!

$ stty -a

speed 38400 baud; rows 73; columns 238; line = 0;

intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;

-parenb -parodd cs8 -hupcl -cstopb cread -clocal -crtscts

-ignbrk brkint ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc -ixany imaxbel -iutf8

opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0

isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke

-a 參數是讓stty顯示所有的設置。默認情況下,它將查看連接到shell的TTY設備,但可以通過-F指定其他的設備。


在上面顯示出的設置中,一些會改變UART參數,一些會影響行規范,一些則用於工作控制。我們先來看看第一行:

屬性 設備 說明
rows, columns TTY驅動 該TTY設備的終端的大小(以字符作為基准)。基本上,它只是內核空間中的一對變量,你可以自由設置和獲取。設置它們將導致TTY驅動程序向前台工作發送SIGWINCH
line 行規范 該TTY設備的行規范. 0 代表 N_TTY. 所有可用的數值在 /proc/tty/ldiscs中有列出. 使用未列出的數值等價於使用 N_TTY, 但是不要依賴於這一點.
speed UART 波特率。偽終端忽略這個參數。

嘗試以下操作:啟動一個 xterm。記下它的TTY設備( tty命令獲得)及其窗口大小(由stty -a獲得)。接着在xterm中啟動 vim (或其他一些全屏終端應用程序)。vim編輯器會向TTY設備查詢當前的終端窗口大小,以此填充整個窗口。現在,從另一個shell窗口輸入:

stty -F X rows Y

其中X是剛才獲得的TTY設備,Y是終端高度的一半。這將更新內核內存中的TTY數據結構,並向編輯器發送 SIGWINCHvim將使用可用窗口區域的上半部分重繪GUI。


stty -a 輸出的第二行列出了所有特殊的字符,開一個新的 xterm 然后試試這個:

stty intr o

現在,"o"而不是 ^C將向前台工作發送 SIGINT 。嘗試運行一些程序,比如 cat,並看看你能不能用 ^C殺死它。然后,嘗試在其中輸入“hello”。


有時候,你可能會遇到退格鍵不起作用的Unix系統——當終端仿真器發送與TTY設備中的擦除設置不匹配的退格碼(ASCII 8或ASCII 127)時,就會發生這種情況。為了解決這個問題,請設置 stty erase ^H (ASCII 8)或 stty erase ^? (ASCII 127)。要注意的是,許多終端應用程序使用readline,這使得行規范處於原始模式,即這些應用程序不受到影響。


最后,stty -a列出了一系列開關(沒有特定順序列出)。其中一些與UART相關,一些影響線路規范行為,一些用於流量控制,一些用於工作控制。短划線( - )表示開關關閉;否則它是開着的。所有的開關都在stty(1)手冊頁中進行了解釋,所以我將簡單地提一下:


icanon用於將行規范切換為規則(基於行)模式。在一個新的 xterm中試試這個,關閉這個模式:

stty -icanon; cat

現在所有的行編輯字符,例如退格或者^U都會停止工作。另外注意到cat 會一次接受一個字符(並連續輸出),而不是一次接受一行。


echo 是啟用字符回顯的開關(默認也是開着的)。現在重新啟動規則模式(stty icanon)然后試試這個:

stty -echo; cat

當你輸入時,你的終端仿真器將信息傳送給內核,而內核通常會將相同的信息回顯給終端仿真器,以便讓你看到之前鍵入的內容。現在沒有了字符回顯,你就不能看到你輸入的內容。不過我們處於熟化(cooked)模式,所以行編輯工具仍在工作。一旦你按下回車鍵,行規范就會把編輯緩沖區的數據傳送給cat,顯示出你剛剛鍵入的內容。


tostop 是控制后台進程是否允許寫入終端的開關,先試試這個:

stty tostop; (sleep 5; echo hello, world) &

& 會使得該命令作為后台工作運行。五秒鍾后,該工作將嘗試寫入TTY。 TTY驅動程序將使用 SIGTTOU將其掛起,並且shell可能會立即報告此事件,或者發出別的提示。現在嘗試下面的代碼:

stty -tostop; (sleep 5; echo hello, world) &

五秒鍾之后,后台工作會在你當前的光標位置輸出 hello, world

最后, stty sane 會將你的TTY設置成一個相對合理的配置。


結語

我希望這篇文章為你提供了足夠的信息去了解TTY驅動和行規范,以及它們與終端,行編輯和工作控制之間的關系。 更多細節可以在我提到的各種手冊頁以及glibc手冊(info libc,"Job Control")中找到。

最后,盡管我沒有足夠的時間來回答所有問題,但我歡迎任何對本網站上的其他網頁提出的反饋意見。 謝謝閱讀!




譯者注:更多有關於tty、shell、console的知識,可以參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM