<ul id="qxxfc"><fieldset id="qxxfc"><tr id="qxxfc"></tr></fieldset></ul>




      本文內(nèi)容來自The TTY demystified <http://www.linusakesson.net/programming/tty/>
      ,講述了*NIX系統(tǒng)中TTY的歷史與工作原理,看完后解決了我很多疑惑,于是做此翻譯,與大家分享。







      譯者:李秋豪 <http://www.cnblogs.com/liqiuhao/> 江家偉 <http://jiangjiawei.pw/blog/>

      審校:

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





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


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






      歷史

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


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


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


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




      用例


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



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

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

      會話(Session)管理
      。用戶可能想要同時運(yùn)行多個程序,并且一次只與其中一個交互。如果一個程序進(jìn)入無限循環(huán),用戶可能想要終止或掛起它。在后臺啟動的程序應(yīng)該能夠獨(dú)立運(yùn)行,直到它們嘗試向終端寫入(被掛起)。同樣,用戶的輸入應(yīng)該指向前臺程序。對于這些功能,操作系統(tǒng)是在TTY驅(qū)動程序(
      TTY driver drivers/char/tty_io.c)中實現(xiàn)的。

      在操作系統(tǒng)中,如果已經(jīng)進(jìn)程有執(zhí)行上下文,我們就說它是“活著的”(有一個執(zhí)行上下文),這也意味著它可以獨(dú)立執(zhí)行操作。而TTY驅(qū)動程序不是“活”的;
      在面向?qū)ο蟮男g(shù)語中,TTY驅(qū)動程序是被動對象(passive
      object)。它有一些數(shù)據(jù)字段和一些方法,但讓它做某事的唯一方法是當(dāng)它的某個方法從別的進(jìn)程的上下文或內(nèi)核中斷處理程序中調(diào)用時。行規(guī)范(line
      discipline)同樣是一個被動對象。

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

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



      讓我們繼續(xù)討論典型的桌面系統(tǒng)。下圖是Linux控制臺的工作原理:




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

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



      為了便于將終端仿真移入用戶空間,同時仍保持TTY子系統(tǒng)(會話管理和行規(guī)范)的完整,偽終端被發(fā)明了出來(pseudo terminal 或 pty
      )。你可能已經(jīng)猜到,當(dāng)你開始在偽終端中運(yùn)行偽終端時,事情變得更加復(fù)雜,例如screen(1) 或 ssh(1)。

      現(xiàn)在讓我們退一步看看所有這些東西是如何和進(jìn)程聯(lián)系起來的。




      進(jìn)程

      Linux進(jìn)程可以處于下面狀態(tài)之一:



      標(biāo)志位 說明
      D 不可中斷睡眠(等待某個事件)
      S 可中斷睡眠(等待一些事件或者信號)
      T 停止(收到了工作管理信號或者進(jìn)程正在被調(diào)試器追蹤)
      Z 僵尸進(jìn)程(被它的父進(jìn)程終止但是沒有被回收的進(jìn)程)
      R 運(yùn)行或者可運(yùn)行(在運(yùn)行隊列中)
      通過運(yùn)行 ps l, 你可以看到哪個進(jìn)程正在運(yùn)行,以及哪個進(jìn)程正在睡眠。如果一個進(jìn)程處于睡眠狀態(tài), WCHAN 列("wait channel",
      等待隊列的名字)將會告訴你這個進(jìn)程正在等待哪個內(nèi)核事件。
      $ 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"等待隊列對應(yīng)于系統(tǒng)調(diào)用 wait(2)
      ,因此這個隊列中的進(jìn)程的子進(jìn)程不論什么時候改變了狀態(tài),它們都會被移入運(yùn)行狀態(tài)。有兩種睡眠狀態(tài):可中斷睡眠和不可中斷睡眠??芍袛嗨撸ㄗ畛R姷那闆r)意味著當(dāng)進(jìn)程在等待隊列中時,它實際上也可能由于收到了一個信號而被移入運(yùn)行狀態(tài)。如果你深入到內(nèi)核源碼中,你將會發(fā)現(xiàn)每個處理等待事件的內(nèi)核源碼都會檢查在schedule()調(diào)用返回之后是否有待處理的信號,如果有,就從系統(tǒng)調(diào)用
      wait(2)中返回。

      在上面列出的 ps 結(jié)果中, STAT 列展示了每個進(jìn)程的當(dāng)前狀態(tài)。這一列中可能會顯示一個或多個屬性或標(biāo)記:

      s 這個進(jìn)程是會話領(lǐng)導(dǎo)
      + 這個進(jìn)程是前臺進(jìn)程組的一員
      這些屬性被用于工作管理。




      譯者注:我之前翻譯過兩篇有關(guān)于進(jìn)程標(biāo)志的文章,可參考

      Linux 進(jìn)程狀態(tài)標(biāo)識 Process State Definition
      <http://www.cnblogs.com/liqiuhao/p/7704742.html>

      Linux 可運(yùn)行進(jìn)程 Runnable Process Definition
      <http://www.cnblogs.com/liqiuhao/p/7704567.html>




      工作與會話管理

      當(dāng)你按下 ^Z 掛起程序或者使用 & 在后臺運(yùn)行程序時,工作管理就發(fā)生了。一個工作(job)等同于一個進(jìn)程組。shell內(nèi)置的命令如 jobs, fg 和
      bg 可以用來管理一個會話(session)中的所有工作。每一個會話是由一個會話領(lǐng)導(dǎo)(session
      leader),即shell來管理的,它會利用復(fù)雜的協(xié)議,例如信號和一些系統(tǒng)調(diào)用和內(nèi)核打交道。

      下面的例子解釋了進(jìn)程、工作、會話之間的關(guān)系。

      下面的shell交互...



      ...對應(yīng)這些進(jìn)程...



      ...和這些內(nèi)核數(shù)據(jù)結(jié)構(gòu)

      * TTY 驅(qū)動 (/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
      其中基本的思想是每個管道都是一項工作,因為管道中的每個進(jìn)程都應(yīng)該被同時進(jìn)行操作(停止,恢復(fù),終止)。這也是為什么 kill(2)
      允許你發(fā)送信號到整個進(jìn)程組。默認(rèn)情況下,fork(2) 將新創(chuàng)建的子進(jìn)程放置在與其父進(jìn)程相同的進(jìn)程組中,例如,鍵盤上的 ^C
      會影響父進(jìn)程和子進(jìn)程。但是,作為會話領(lǐng)導(dǎo)責(zé)任的一部分,每次啟動管道時,shell都會創(chuàng)建一個新的進(jìn)程組。


      TTY驅(qū)動程序會記錄前臺進(jìn)程組ID(PID),但這只能以被動方式進(jìn)行。會話領(lǐng)導(dǎo)必須在必要時主動更新此信息。同樣,TTY驅(qū)動程序會記錄連接終端的屬性(例如窗口大?。@些信息必須由終端仿真程序甚至用戶主動更新。

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




      信號控制

      現(xiàn)在讓我們更近距離地看看內(nèi)核中的TTY驅(qū)動、行規(guī)范和UART驅(qū)動是如何和用戶態(tài)進(jìn)程交互的。

      UNIX文件,包括TTY設(shè)備文件,可以被讀和寫,并且由于許多TTY相關(guān)的操作都已經(jīng)被定義,可以使用神奇的 ioctl(2)
      系統(tǒng)調(diào)用(UNIX的“瑞士軍刀”)進(jìn)行進(jìn)一步操作。但是,ioctl請求必須在進(jìn)程內(nèi)被初始化,因此它們不能在內(nèi)核需要和應(yīng)用進(jìn)行異步通信的場景下被使用。

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

      因此信號是一個“粗暴”的機(jī)制,它允許內(nèi)核和進(jìn)程進(jìn)行異步通信。UNIX中的信號定義是不規(guī)整或者不統(tǒng)一的;相反,每個信號都是獨(dú)特的,我們必須單獨(dú)研究它們。

      你可以使用命令 kill -l 來看看你的系統(tǒng)實現(xiàn)了哪些命令。結(jié)果看起來像下面這樣:
      $ 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開始的數(shù)字編號。然而當(dāng)它們被在掩碼中(例如在ps -s的輸出里)被使用時,最低有效位對應(yīng)信號1。

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

      SIGHUP

      * 默認(rèn)操作: 終止
      * 可能的操作: 終止, 忽略, 函數(shù)調(diào)用
      當(dāng)檢測到掛斷(hangup)條件時,UART驅(qū)動會將SIGHUP 發(fā)送到整個會話。通常情況下,這會殺死所有進(jìn)程。某些程序(如 nohup(1) 和
      screen(1))會從其會話(和TTY)中分離,以便其子進(jìn)程不會注意到掛斷。

      SIGINT

      * 默認(rèn)操作: 終止
      * 可能的操作: 終止, 忽略, 函數(shù)調(diào)用
      如果輸入流中出現(xiàn)交互式注意( interactive attention )字符(通常為 ^C,其代碼為ASCII碼3),那么SIGINT
      就會由TTY驅(qū)動發(fā)送到當(dāng)前的前臺工作,除非此配置已被關(guān)閉。任何有權(quán)訪問TTY設(shè)備的人都可以更改交互式注意字符并開關(guān)此配置;
      此外,會話管理器會跟蹤每個工作的TTY配置,并在有工作切換時更新TTY。

      SIGQUIT

      * 默認(rèn)操作: 內(nèi)核轉(zhuǎn)儲(core dump)
      * 可能的操作: 內(nèi)核轉(zhuǎn)儲, 忽略, 函數(shù)調(diào)用
      SIGQUIT 的工作方式和 SIGINT相似, 但是使用的字符是 ^\ 并且默認(rèn)操作不同。

      SIGPIPE

      * 默認(rèn)操作: 終止
      * 可能的操作: 終止, 忽略, 函數(shù)調(diào)用
      內(nèi)核會給每一個試圖往沒有讀取者的管道中寫數(shù)據(jù)的進(jìn)程發(fā)送 SIGPIPE 信號。 這是很有用的,因為沒有這個信號的話,類似 yes | head
      這樣的工作就永遠(yuǎn)不會停止了。

      SIGCHLD

      * 默認(rèn)操作: 忽略
      * 可能的操作: 忽略, 函數(shù)調(diào)用
      當(dāng)進(jìn)程死亡或更改狀態(tài)(停止/繼續(xù))時,內(nèi)核會向其父進(jìn)程發(fā)送一個 SIGCHLD 。 SIGCHLD
      信號攜帶著終止進(jìn)程的附加信息,即進(jìn)程標(biāo)識,用戶標(biāo)識,退出狀態(tài)(或終止信號)以及一些執(zhí)行時間的統(tǒng)計信息。會話領(lǐng)導(dǎo)(shell)使用這個信號追蹤其工作。

      SIGSTOP

      * 默認(rèn)操作: 掛起
      * 可能的操作: 掛起
      該信號將無條件地掛起接收者,即其信號動作不能被重新配置。要注意的是,在工作控制期間,SIGSTOP不會由內(nèi)核發(fā)送。相反,^Z 通常會觸發(fā)一個 SIGTSTP
      ,它可以被應(yīng)用程序攔截。然后應(yīng)用程序可以進(jìn)行例如將光標(biāo)移動到屏幕底部等操作,然后使用SIGSTOP將自己置于睡眠狀態(tài)。

      SIGCONT

      * 默認(rèn)操作: 喚醒
      * 可能的操作: 喚醒, 喚醒 + 函數(shù)調(diào)用
      SIGCONT 將“反掛起”(un-suspend,continue)一個停止的進(jìn)程。當(dāng)用戶調(diào)用fg命令時,它會由shell發(fā)送出去。由于 SIGSTOP
      不能被應(yīng)用程序攔截,因此意料之外的SIGCONT 信號可能表明該進(jìn)程在某段時間之前被掛起,然后被喚醒。

      SIGTSTP

      * 默認(rèn)操作: 掛起
      * 可能的操作: 掛起, 忽略, 函數(shù)調(diào)用
      SIGTSTP 與 SIGINT 和 SIGQUIT 的工作原理相似,但是它使用的是 ^Z 字符,并且默認(rèn)的操作是掛起進(jìn)程。

      SIGTTIN

      * 默認(rèn)操作: 掛起
      * 可能的操作: 掛起, 忽略, 函數(shù)調(diào)用
      如果一個后臺工作中的進(jìn)程嘗試從TTY設(shè)備中進(jìn)行讀取,TTY會向整個工作(組)發(fā)送一個 SIGTTIN信號,這通常會掛起這個工作。

      SIGTTOU

      * 默認(rèn)操作: 掛起
      * 可能的操作: 掛起, 忽略, 函數(shù)調(diào)用
      如果一個后臺工作中的進(jìn)程嘗試向TTY設(shè)備中進(jìn)行寫入,TTY會向整個工作(組)發(fā)送一個 SIGTTIN
      信號,這通常會掛起這個工作。這種行為可以通過配置TTY關(guān)閉。

      SIGWINCH

      * 默認(rèn)操作: 忽略
      * 可能的操作: 忽略, 函數(shù)調(diào)用
      如前所述,TTY設(shè)備會記錄終端的窗口大小,但這些信息需要手動更新。只要發(fā)生這種更新,TTY設(shè)備就會向前臺工作發(fā)送 SIGWINCH
      。行為良好的交互式應(yīng)用程序(例如編輯器)會對此作出反應(yīng),從TTY設(shè)備獲取新的終端窗口大小并重繪GUI。




      譯者注:我之前翻譯過一篇有關(guān)于進(jìn)程和信號的文章,可參考

      Linux 進(jìn)程與信號的概念和操作 <http://www.cnblogs.com/liqiuhao/p/7689667.html>




      一個例子

      假設(shè)你正在編輯(基于終端的)編輯器中的文件。此時光標(biāo)位于屏幕中間的某個位置,編輯器正在執(zhí)行一些任務(wù),例如對大文件執(zhí)行搜索和替換操作?,F(xiàn)在你按 ^Z
      ,由于行規(guī)范已被配置為攔截此字符(^Z 是一個單字節(jié),ASCII碼為26),因此你無需等待編輯器完成其任務(wù)然后從TTY設(shè)備開始讀取。相反,行規(guī)范子系統(tǒng)會立即將
      SIGTSTP 發(fā)送到前臺進(jìn)程組。該進(jìn)程組包含編輯器以及由其創(chuàng)建的任何子進(jìn)程。

      編輯器為 SIGTSTP
      安裝(install)了一個信號處理程序,因此內(nèi)核將程序執(zhí)行流轉(zhuǎn)移到信號處理程序代碼中。通過將相應(yīng)的控制序列寫入TTY設(shè)備,該代碼將光標(biāo)移動到屏幕的最后一行。由于編輯器仍處于前臺,控制序列按要求發(fā)送。隨后編輯器會將
      SIGSTOP 發(fā)送到其自己的進(jìn)程組(正如上節(jié)信號中說的那樣)。

      編輯器現(xiàn)在已經(jīng)停止,SIGCHLD
      信號向會話領(lǐng)導(dǎo)通告這個事件,其中包括該進(jìn)程的ID。當(dāng)前臺工作中的所有進(jìn)程都被掛起時,會話領(lǐng)導(dǎo)從TTY設(shè)備讀取當(dāng)前配置,并將其存儲起來以供以后使用。會話領(lǐng)導(dǎo)繼續(xù)使用
      ioctl 調(diào)用將其自身安裝為TTY的當(dāng)前前臺進(jìn)程組。然后,它會打印類似 "[1]+ Stopped" 的內(nèi)容,以通知用戶工作已暫停。

      此時, ps(1) 會告訴你編輯器進(jìn)程處于停止?fàn)顟B(tài)(“T”)。如果我們試圖使用內(nèi)置shell命令bg或使用 kill(1) 向進(jìn)程發(fā)送 SIGCONT
      來喚醒它,編輯器將開始執(zhí)行其SIGCONT
      信號處理程序。而該處理程序會嘗試通過寫入TTY設(shè)備來重新繪制編輯器的GUI界面。但現(xiàn)在編輯器是一個后臺工作,TTY設(shè)備將不允許它進(jìn)行寫入。所以,TTY會給編輯器發(fā)送
      SIGTTOU 信號,令其再次停止。這個事件將通過使用 SIGCHLD傳遞給會話領(lǐng)導(dǎo)(shell),而shell會再次向終端寫入“[1] + Stopped”。

      但是,當(dāng)我們鍵入fg時,shell首先恢復(fù)先前保存的行規(guī)范配置。它通知TTY驅(qū)動編輯器工作應(yīng)該從現(xiàn)在起作為前臺工作。最后,它向進(jìn)程組發(fā)送一個SIGCONT
      信號。編輯器試圖重繪它的GUI,這次它不會被SIGTTOU 中斷,因為它現(xiàn)在是前臺工作的一部分。

      譯者注:






      流控制與I/O阻塞



      在 xterm中運(yùn)行 yes ,你會看到很多“y”出現(xiàn)在你眼前。自然,yes進(jìn)程能夠很快的產(chǎn)生y,以至于xterm來不及進(jìn)行幀緩沖區(qū)更新,與X服務(wù)器通信(
      譯者注:X Window System <https://en.wikipedia.org/wiki/X_Window_System>
      )以便滾動窗口等操作。那么這些程序是如何進(jìn)行配合的呢?

      答案在于I/O阻塞。偽終端只能在其內(nèi)核緩沖區(qū)內(nèi)保存一定數(shù)量的數(shù)據(jù),當(dāng)該緩沖區(qū)滿并且 yes 嘗試調(diào)用 write(2)時,write(2)將被阻止,并將yes
      進(jìn)程移至可中斷的睡眠狀態(tài),直到xterm能夠讀取緩沖中的字節(jié)。

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

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


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

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

      這里有一個重要的區(qū)別:寫入由于流控制而停止的TTY,或者由于缺少內(nèi)核緩沖區(qū)空間,只會阻塞你的進(jìn)程,而從后臺工作中寫入TTY將導(dǎo)致SIGTTOU
      暫停整個進(jìn)程組。我不知道為什么UNIX的設(shè)計師必須發(fā)明SIGTTOU 和 SIGTTIN
      ,而不是僅僅依靠I/O阻塞,但我最好的猜測是負(fù)責(zé)工作控制的TTY驅(qū)動是為了監(jiān)視和操縱整個工作——而不是其中的單個進(jìn)程。




      配置TTY設(shè)備



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

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

      我不會詳細(xì)討論 termios(3) 接口的細(xì)節(jié),但是如果你正在編寫C程序并希望在 ^C 變成 SIGINT之前攔截 ^C
      ,或者禁用行規(guī)范或字符回顯,或?qū)⒏囊粋€串的口波特率,關(guān)閉流控制等,你就會發(fā)現(xiàn)你需要上述的手冊頁(man page)。

      這里還有一個名為 stty(1)的命令行工具來操作TTY設(shè)備。 它使用的是 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 參數(shù)是讓stty顯示所有的設(shè)置。默認(rèn)情況下,它將查看連接到shell的TTY設(shè)備,但可以通過-F指定其他的設(shè)備。




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

      屬性 設(shè)備 說明
      rows, columns TTY驅(qū)動
      該TTY設(shè)備的終端的大?。ㄒ宰址鳛榛鶞?zhǔn))?;旧希皇莾?nèi)核空間中的一對變量,你可以自由設(shè)置和獲取。設(shè)置它們將導(dǎo)致TTY驅(qū)動程序向前臺工作發(fā)送SIGWINCH
      。
      line 行規(guī)范 該TTY設(shè)備的行規(guī)范. 0 代表 N_TTY. 所有可用的數(shù)值在 /proc/tty/ldiscs中有列出. 使用未列出的數(shù)值等價于使用
      N_TTY, 但是不要依賴于這一點.
      speed UART 波特率。偽終端忽略這個參數(shù)。
      嘗試以下操作:啟動一個 xterm。記下它的TTY設(shè)備( tty命令獲得)及其窗口大?。ㄓ蓅tty -a獲得)。接著在xterm中啟動 vim
      (或其他一些全屏終端應(yīng)用程序)。vim編輯器會向TTY設(shè)備查詢當(dāng)前的終端窗口大小,以此填充整個窗口?,F(xiàn)在,從另一個shell窗口輸入:
      stty -F X rows Y
      其中X是剛才獲得的TTY設(shè)備,Y是終端高度的一半。這將更新內(nèi)核內(nèi)存中的TTY數(shù)據(jù)結(jié)構(gòu),并向編輯器發(fā)送 SIGWINCH ,vim
      將使用可用窗口區(qū)域的上半部分重繪GUI。




      stty -a 輸出的第二行列出了所有特殊的字符,開一個新的 xterm 然后試試這個:
      stty intr o
      現(xiàn)在,"o"而不是 ^C將向前臺工作發(fā)送 SIGINT 。嘗試運(yùn)行一些程序,比如 cat,并看看你能不能用 ^C殺死它。然后,嘗試在其中輸入“hello”。




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




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




      icanon用于將行規(guī)范切換為規(guī)則(基于行)模式。在一個新的 xterm中試試這個,關(guān)閉這個模式:
      stty -icanon; cat
      現(xiàn)在所有的行編輯字符,例如退格或者^U都會停止工作。另外注意到cat 會一次接受一個字符(并連續(xù)輸出),而不是一次接受一行。




      echo 是啟用字符回顯的開關(guān)(默認(rèn)也是開著的)?,F(xiàn)在重新啟動規(guī)則模式(stty icanon)然后試試這個:
      stty -echo; cat

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




      tostop 是控制后臺進(jìn)程是否允許寫入終端的開關(guān),先試試這個:
      stty tostop; (sleep 5; echo hello, world) &
      & 會使得該命令作為后臺工作運(yùn)行。五秒鐘后,該工作將嘗試寫入TTY。 TTY驅(qū)動程序?qū)⑹褂?SIGTTOU
      將其掛起,并且shell可能會立即報告此事件,或者發(fā)出別的提示。現(xiàn)在嘗試下面的代碼:
      stty -tostop; (sleep 5; echo hello, world) &
      五秒鐘之后,后臺工作會在你當(dāng)前的光標(biāo)位置輸出 hello, world 。

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




      結(jié)語

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

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







      譯者注:更多有關(guān)于tty、shell、console的知識,可以參考

      * What is the exact difference between a 'terminal', a 'shell', a 'tty' and a
      'console'?
      <https://unix.stackexchange.com/questions/4126/what-is-the-exact-difference-between-a-terminal-a-shell-a-tty-and-a-con>
      * Terminal emulator <https://en.wikipedia.org/wiki/Terminal_emulator>
      * xterm <https://en.wikipedia.org/wiki/Xterm>
      *
      X Window System <https://en.wikipedia.org/wiki/X_Window_System>

      * TTY(4) <http://man7.org/linux/man-pages/man4/tty.4.html>
      * pty(7) <https://linux.die.net/man/7/pty>
      *
      ptmx(4) <https://linux.die.net/man/4/ptmx>


      友情鏈接
      ioDraw流程圖
      API參考文檔
      OK工具箱
      云服務(wù)器優(yōu)惠
      阿里云優(yōu)惠券
      騰訊云優(yōu)惠券
      京東云優(yōu)惠券
      站點信息
      問題反饋
      郵箱:[email protected]
      QQ群:637538335
      關(guān)注微信

        <ul id="qxxfc"><fieldset id="qxxfc"><tr id="qxxfc"></tr></fieldset></ul>
          国产精品久久久久久久久鸭下载 | 超碰97人人操 | 黑人大战欧美在线看片 | 成人午夜网站 | 亚洲+日产+专区 | 无码做爰XXXⅩ | 日韩AV一区二区在线观看 | 99热日韩 | 肌肌对肌肌30分钟无遮挡 | 青娱乐超碰在线 |