解密TTY

本文內容來自The TTY demystified ,講述了*NIX系統中TTY的歷史與工做原理,看完後解決了我不少疑惑,因而作此翻譯,與你們分享。php




譯者:李秋豪 江家偉html

審校:linux

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


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

注意:你將閱讀到的東西並非那麼「優雅」。事實上,儘管在用戶角度看很是實用,TTY子系統是由不少繁雜的東西和特殊狀況組成的。爲了理解它們的由來,咱們必須回到過去:vim


歷史

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

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

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

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


用例

以下圖所示,用戶在終端(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的知識,能夠參考

相關文章
相關標籤/搜索