【Linux驅動-快速回顧】一次性快速回顧TTY體系知識點(新手友好)

我將遵循一條嚴格的“問題驅動”和“演進”的邏輯線索來構建整個TTY知識體系。每引入一個新概念,都是為了解決前一個階段出現的問題。這樣,你不僅能知道“是什么”,更能深刻理解“為什么是這樣設計的”。


第〇階段:最原始的需求

  • 需求:一個程序(比如bash shell)需要從一個外部設備(比如一個物理鍵盤和顯示器終端)讀取用戶的鍵盤輸入,并向該設備輸出字符。
  • 最直接的想法:讓程序直接訪問硬件I/O端口或內存地址來操作這個設備(例如,一個串口UART芯片)。
  • 立刻出現的問題
    1. 不具備可移植性:程序代碼寫死了針對特定硬件的操作。如果換一種串口芯片,程序就要重寫。
    2. 缺乏并發管理:如果兩個程序想同時訪問同一個串口,硬件訪問會產生沖突和混亂。
    3. 應用層負擔過重:程序需要自己處理非常底層的細節,比如字節流中的退格、Ctrl+C等特殊字符,這部分邏輯在每個需要交互的程序中都會重復。

為了解決這些問題,Linux內核引入了第一個抽象層。


第一階段:引入驅動程序,封裝硬件差異

  • 解決問題:硬件訪問的不可移植性和并發管理問題(上述問題1和2)。

  • 解決方案TTY驅動 (TTY Driver)

  • 邏輯推演

    1. 內核提供一個中間層,這個中間層專門負責和硬件打交道。這個中間層就是“驅動程序”。
    2. 驅動程序將復雜的硬件操作(讀寫寄存器、處理中斷)封裝起來。
    3. 為了讓所有應用程序都能用同一種方式訪問它,內核將這個驅動程序具象化為一個標準的文件。在Linux中,就是字符設備文件(例如 /dev/ttyS0)。
    4. 現在,應用程序不再直接訪問硬件,而是通過標準的open(), read(), write()系統調用來操作這個設備文件。內核的虛擬文件系統(VFS)會將這些操作最終路由到對應的TTY驅動程序。
    5. 驅動程序內部實現了open, close, write等操作函數(在struct tty_operations中定義),當VFS收到請求時,就調用這些函數。例如,write()操作會調用驅動的write()函數,該函數再將數據寫入硬件。
    6. 當硬件接收到數據時(比如用戶敲擊鍵盤),會產生一個硬件中斷。驅動的中斷服務程序(ISR)被執行,它從硬件讀取數據。
  • 此時的狀態

    • 優點:應用程序與硬件解耦,實現了可移植性。內核通過文件系統解決了并發訪問問題。
    • 遺留的致命問題:應用程序從/dev/ttyS0 read()到的是最原始的字節流。如果用戶輸入hello然后按了退格鍵,程序會讀到'h', 'e', 'l', 'l', 'o', 0x08。程序必須自己解釋0x08是退格符,并處理自己的緩沖區。如果用戶按了Ctrl+C,程序會讀到0x03這個字節,它必須自己判斷這是個中斷信號,然后終止自己。這仍然是巨大的負擔(上述問題3)。

第二階段:引入行規程,處理終端語義

  • 解決問題:應用程序需要自行處理復雜的終端編輯和控制信號的負擔。

  • 解決方案行規程 (Line Discipline, ldisc)

  • 邏輯推演

    1. 我們發現,對退格、Ctrl+C等字符的處理邏輯,對于所有交互式程序(bash, python解釋器等)來說都是共通的。這段通用邏輯不應該放在應用程序里,也不應該放在驅動里(因為驅動應該只關心硬件數據收發),而應該放在一個獨立的、可重用的“處理層”。
    2. 這個處理層被設計出來,并被插入到“用戶-文件系統接口”和“TTY驅動”之間。這就是行規程
    3. 現在,數據流變成了:
      • 寫操作 (應用 -> 硬件):應用write() -> VFS -> 行規程 -> TTY驅動 -> 硬件。
      • 讀操作 (硬件 -> 應用):硬件 -> TTY驅動中斷 -> 行規程 -> VFS -> 應用read()
    4. 行規程的核心功能
      • 上行數據處理 (硬件->應用):當驅動把從硬件收到的原始字節流(如'h', 'e', 'l', 'l', 'o', 0x08)交給行規程時,行規程內部維護一個行緩沖區。它看到0x08(退格),就會從自己的緩沖區里刪除最后一個字符’o’。只有當它看到行結束符(回車\r或換行\n)時,才會把緩沖區里最終正確的內容(“hell”)打包好,通知VFS數據已就緒,讓等待read()的應用程序返回。這個過程稱為規范模式 (Canonical Mode)
      • 信號生成:當行規程收到特定的控制字符,如0x03 (Ctrl+C),它不會將這個字符傳遞給應用程序。相反,它會向與這個終端關聯的前臺進程組發送一個SIGINT信號。
      • 下行數據處理 (應用->硬件):應用程序write()一個換行符\n,行規程可以根據配置,自動將其轉換為回車+換行 (\r\n),以兼容某些老式終端設備。
    5. 引入模式切換:我們意識到,并非所有程序都需要這種行編輯。比如vim編輯器需要立即知道用戶按了j鍵來下移光標,不能等用戶按回車。文件傳輸程序更是需要原始的二進制數據流。因此,行規程必須支持不同的工作模式。
      • 規范模式 (Canonical Mode):默認模式,提供行編輯、行緩沖。為交互式Shell設計。
      • 非規范/原始模式 (Non-canonical/Raw Mode):不進行任何處理,收到任何字符都立即將其傳遞給應用程序。為編輯器、數據傳輸等程序設計。
  • 此時的狀態

    • 優點:我們有了一個非常強大的分層模型。驅動負責硬件,行規程負責終端語義,應用負責自身邏輯。各司其職。
    • 遺留的問題:誰來管理這一切?當open("/dev/ttyS0")時,誰來創建和關聯一個TTY驅動實例和一個行規程實例?當數據在它們之間流動時,誰來調用正確的函數進行傳遞?這些膠水代碼和管理邏輯放在哪里?

我們成功地將系統拆分成了兩個功能明確的組件:
1. TTY驅動:一個純粹的硬件適配器。
2. 行規程:一個純粹的終端語義處理器。

這種分離非常優雅,但也立即產生了一系列新的、尖銳的工程問題。
這些組件雖然各自強大,但它們是相互隔離的,無法自行協同工作。
  • 新出現的核心矛盾
    1. 狀態管理問題:假設用戶A打開了 /dev/ttyS0,希望以9600波特率、規范模式工作。同時,用戶B打開了 /dev/ttyS1,希望以115200波特率、原始模式工作。這兩個會話的狀態(波特率、模式、行編輯緩沖區)是完全獨立的。這個**“會話狀態”**應該存儲在哪里?

      • 不能存在TTY驅動里:驅動代碼是為一類設備(如所有8250串口)服務的,是無狀態的、可共享的。
      • 不能存在行規程模塊里:行規程代碼(如N_TTY)也是通用的,它本身不知道自己正在為哪個具體的會話服務。
      • 因此,必須有一個專門的數據結構,用于表示和存儲每一個被打開的、活動的TTY會話的獨特上下文
    2. 生命周期管理問題:誰來負責在用戶open()設備時,創建上述的“會話狀態”數據結構?又由誰在用戶close()設備時,銷毀它以釋放資源?

    3. “接線員”問題 (Orchestration):現在我們有了驅動和行規程,但它們之間如何通信?

      • 當驅動的中斷程序收到一個字節,它如何知道應該把它交給哪一個行規程實例去處理?(ttyS0ttyS1的行規程實例是不同的)。
      • 當應用程序調用write()時,數據流應該是“應用 -> 行規程 -> 驅動”。這個調用鏈是如何建立的?驅動本身不應該知道行規程的存在,否則就破壞了我們辛苦建立的解耦。必須有一個“中間人”來引導數據正確流轉。

這些問題指向同一個答案:我們需要一個更高層次的框架層,來管理這些組件的生命周期、維護它們的會話狀態,并充當它們之間的“總調度臺”。這個框架層,就是 TTY核心 (TTY Core)


第三階段:引入TTY核心,作為會話管理者和系統框架

  • 解決問題:解決第二階段分離組件后產生的狀態管理、生命周期管理和協同工作的問題。

  • 解決方案:引入 TTY核心 (TTY Core),它不是一個具體的“功能”模塊,而是整個子系統的骨架和大腦

  • 邏輯推演

    1. 解決狀態管理:struct tty_struct的誕生
      為了解決每個TTY會話需要獨立狀態的問題,TTY核心定義了整個體系中最重要的一個數據結構:struct tty_struct

      • 它不是代碼,而是一個數據容器,是一個活動TTY連接的化身
      • 每當一個TTY設備被open(),TTY核心就會為其分配一個tty_struct實例。
      • 這個結構體內部包含了指向所有相關組件的指針,例如 *driver(指向為它服務的TTY驅動)、*ldisc(指向為它服務的行規程實例),以及最重要的termios結構體(保存著該會話的所有配置)。
      • tty_struct完美地解決了狀態存儲問題,它就是那個“會話狀態”的載體。
    2. 解決生命周期和“接線員”問題:TTY核心的職責
      有了tty_struct這個藍圖,TTY核心的職責就變得清晰了:它就是操作這個結構體、并基于它進行調度的總控程序。

      • 統一的入口:用戶的open(), read(), write()等系統調用,不再直接路由到驅動,而是全部先進入TTY核心提供的標準函數,如tty_open(), tty_write()
      • tty_open():TTY核心分配tty_struct,然后根據打開的設備號,找到之前已經向核心“注冊”過的對應TTY驅動,并將驅動信息填入tty_struct。同時,它會掛接一個默認的行規程(通常是N_TTY)到這個tty_struct上。至此,一個完整的、可工作的會話實例被動態組裝完畢。
      • 在數據流中充當調度者
        • 寫操作 (應用 -> 硬件):應用程序的write()調用進入TTY核心的tty_write()。TTY核心查看傳入的tty_struct,找到其關聯的行規程,調用行規程的write函數進行數據處理。然后,它再從tty_struct中找到關聯的TTY驅動,調用驅動的write函數,將處理后的數據交給驅動去發送。整個數據流被完美地串聯起來。
        • 讀操作 (硬件 -> 應用):為了使驅動中斷處理盡可能快和安全,TTY核心提供了一個緩沖機制tty_flip_buffer。驅動的中斷程序只需把從硬件讀到的原始數據塞進這個緩沖區,然后調用tty_flip_buffer_push()通知TTY核心即可。TTY核心會在稍后安全的時間點(軟中斷上下文),從緩沖區取出數據,查看是哪個tty_struct的數據,然后調用其行規程的receive_buf函數進行處理。

第四階段:配置與擴展

我們已經有了一個完整的體系,現在需要讓它變得可配置和適應更現代的場景。

  1. 如何配置這個體系? -> termios 結構

    • 問題:用戶程序如何切換規范/非規范模式?如何設置串口的波特率、數據位、停止位?
    • 解決方案:TTY核心在tty_struct中維護一個名為termios的配置結構體。這個結構體包含了所有可配置的參數(輸入標志c_iflag、輸出標志c_oflag、控制標志c_cflag、本地標志c_lflag等)。
    • 機制:用戶空間程序通過tcgetattr()tcsetattr()這兩個系統調用來讀取和修改內核中的termios結構。當tcsetattr()被調用時,TTY核心會通知行規程和TTY驅動:“配置變了,請更新狀態”。驅動就會根據新的termios值去設置硬件寄存器(例如,修改波特率)。
  2. 如何應用于非物理串口? -> 偽終端 (PTY)

    • 問題:我們在圖形界面下的終端窗口(如gnome-terminal)或者通過ssh遠程登錄,背后并沒有一個物理的/dev/ttyS0設備。但我們使用的bash仍然能正常工作,Ctrl+C也有效。這是如何實現的?
    • 解決方案偽終端 (Pseudo-Terminal, PTY)
    • 機制:PTY是純軟件模擬的TTY設備。它總是成對出現:
      • 主設備端 (Master, PTM):例如 /dev/ptmx。它由“宿主”程序持有,比如gnome-terminalsshd服務。
      • 從設備端 (Slave, PTS):例如 /dev/pts/0。它被分配給子進程,比如bash
    • 邏輯閉環
      1. gnome-terminal啟動,打開PTM。
      2. 它創建子進程來運行bash,并將PTS (/dev/pts/0)作為bash的標準輸入、輸出、錯誤。
      3. bash的角度看,它操作的/dev/pts/0和一個真實的TTY設備毫無區別。因此,整個TTY核心、行規程(N_TTY)都會被掛接上來,Ctrl+C、行編輯等功能完全復用。
      4. 數據流:
        • 用戶在gnome-terminal窗口敲鍵盤 -> gnome-terminal程序把按鍵信息write()到PTM -> 內核將這些數據轉發給配對的PTS -> bash從它的標準輸入(即PTS)read()到數據。
        • bash執行ls命令,輸出結果 -> bash把結果write()到它的標準輸出(即PTS)-> 內核將數據轉發給配對的PTM -> gnome-terminal程序從PTM read()到數據,然后將其繪制到窗口上。
    • 結論:PTY巧妙地將非硬件的I/O源(圖形窗口、網絡套接字)接入了強大的TTY體系,實現了最大程度的代碼和邏輯復用。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/913424.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/913424.shtml
英文地址,請注明出處:http://en.pswp.cn/news/913424.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

深入淺出:讓機器聽懂世界的耳朵——梅爾頻率倒譜系數(MFCCs)

深入淺出:讓機器聽懂世界的耳朵——梅爾頻率倒譜系數(MFCCs) 在人工智能的浪潮中,語音識別、聲紋支付、音樂推薦等技術早已融入我們的日常生活。你是否曾好奇,計算機是如何理解并區分各種復雜的聲音信號的?…

Ubuntu22.04安裝/使用Gazebo時踩的一些坑

首先,本人原本打算安裝gazebo11的,因為官方好像不支持ubuntu22.04,所以要通過PPA和ROS2 humble來安裝,安裝過程跟著教程來的,也就是下面這篇 ubuntu22.04安裝gazebo11(ROS2 Humble)-CSDN博客 …

CPT203-Software Engineering: Introduction 介紹

目錄 1.專業名詞定義 1.1計算機軟件的定義 1.2軟件系統的定義 1.3軟件工程的定義 2.軟件的失敗與成功 2.1 失敗 2.2 成功 3.軟件開發 Professional software development 3.1 分類 3.2 專業軟件開發 professional software development 3.3專業軟件開發產品特性 3.4…

診斷工程師進階篇 --- 車載診斷怎么與時俱進?

我是穿拖鞋的漢子,魔都中堅持長期主義的汽車電子工程師。 老規矩,分享一段喜歡的文字,避免自己成為高知識低文化的工程師: 做到欲望極簡,了解自己的真實欲望,不受外在潮流的影響,不盲從,不跟風。把自己的精力全部用在自己。一是去掉多余,凡事找規律,基礎是誠信;二是…

奧特曼論人工智能、OpenAI與創業

來自Y Combinator的YouTube視頻,展示了OpenAI首席執行官薩姆奧特曼分享的深刻見解。他討論了OpenAI從一個看似瘋狂的通用人工智能(AGI)夢想,如何發展成為一個全球性的現象。奧特曼強調了早期決策的關鍵性、吸引頂尖人才的策略&…

React Ref使用

受控與非受控組件 Ref 1.獲取原生dom 類組件中&#xff1a;在componentDidMount方法內使用document.getElementById的方法獲取到dom元素 1 目標dom增加ref屬性 設置為字符串 <h2 reftitleref></h2>function changeRef(){this.refs.titleref.innerHtml }2 函數組件…

地下管線安全的智能監測先鋒:智能標志樁圖像監測裝置解析?

?在城市與鄉村的地下&#xff0c;縱橫交錯的管線是能源與信息傳輸的關鍵通道。但深埋地下的電纜、燃氣管道等設施&#xff0c;因難以直觀監測&#xff0c;面臨施工誤挖、自然災害等風險。傳統防護手段力不從心&#xff0c;TLKS-PAZ01 智能標志樁圖像監測裝置的誕生&#xff0c…

Camera相機人臉識別系列專題分析之十六:人臉特征檢測FFD算法之libcvface_api.so數據結構詳細注釋解析

【關注我&#xff0c;后續持續新增專題博文&#xff0c;謝謝&#xff01;&#xff01;&#xff01;】 上一篇我們講了&#xff1a; 這一篇我們開始講&#xff1a; Camera相機人臉識別系列專題分析之十六&#xff1a;人臉特征檢測FFD算法之libcvface_api.so數據結構詳細注釋解析…

【字節跳動】數據挖掘面試題0012:數據分析、數據挖掘、數據建模的區別

文章大綱 數據分析、數據挖掘、數據建模的區別一、核心定義與目標二、技術方法差異三、應用場景對比四、三者的關聯與遞進關系五、面試應答策略 數據分析、數據挖掘、數據建模的區別 一、核心定義與目標 數據分析&#xff1a; 是對已有的數據進行收集、清洗、整理&#xff0c;并…

預警:病毒 “黑吃黑”,GitHub 開源遠控項目暗藏后門

在開源生態蓬勃發展的當下&#xff0c;黑客們也將黑手伸向了代碼共享平臺。當黑產開發者以為在共享 “行業秘笈” 時&#xff0c;殊不知已經掉入了黑客布置的陷阱 —— 看似方便的后門遠程控制源碼和游戲作弊外掛源碼等 “圈內資源”&#xff0c;實則是植入了惡意代碼的投毒誘餌…

Qt中的QProcess類

Qt中的QProcess類 QProcess 是 Qt 框架中用于啟動和控制外部進程的類&#xff0c;它屬于 QtCore 模塊。這個類提供了執行外部程序并與它們交互的功能。 一、主要功能 啟動外部程序&#xff1a;可以啟動系統上的其他可執行程序進程通信&#xff1a;通過標準輸入、輸出和錯誤流…

周任務自動化升級:N8N與多維表格無縫聯動全解析

.自動化之言&#xff1a; 在上一篇文章中&#xff0c;我們介紹了如何利用多維表格&#xff08;如飛書多維表格或Notion&#xff09;搭建一個靈活的任務管理系統。現在我們將進一步擴展這個系統&#xff0c;借助 N8N 實現周報的自動匯總與郵件發送&#xff0c;真正實現任務管理…

Go語言的web框架--gin

本章內容&#xff0c;會介紹一下gin的運用&#xff0c;以及gin框架底層的內容&#xff0c;話不多說&#xff0c;開始進入今天的主題吧&#xff01; 一.基本使用 gin框架支持前后端不分離的形式&#xff0c;也就是直接使用模板的形式。 模板是什么&#xff1f; 這里可能有同…

企業為什么需要雙因素認證?

從進入互聯網時代開始&#xff0c;密碼是我們個人日常的重要保護。但是單獨的密碼保護可能已經不再適應當前的數字化時代。密碼已經不再足夠安全最近發生的各種安全漏洞讓我重新審視網絡安全。幾行代碼可能就導致了全球數以百萬的登錄憑證被泄露。今天&#xff0c;僅僅周期性地…

Spring Boot + 本地部署大模型實現:優化與性能提升!

在Spring Boot中集成本地部署的大模型&#xff08;如LLaMA、ChatGLM等&#xff09;并進行優化&#xff0c;需要從模型選擇、推理加速、資源管理和架構設計等多方面入手。以下是完整的優化方案及實現步驟&#xff1a; 一、核心優化策略 1. 模型量化 目標&#xff1a;減少顯存占…

仿mudou庫one thread oneloop式并發服務器

前言 我們所要實現的是一個高并發服務器的組件&#xff0c;使服務器的性能更加高效&#xff0c;是一個高并發服務器的組件&#xff0c;并不包含實際的業務。 首先需要先明確我們所要實現的目標是什么 第一點&#xff0c;實現一個高并發的服務器第二點&#xff0c;在服務器的基礎…

超詳細的私有化安裝部署Dify服務以及安裝過程中問題處理

一、什么是Dify Dify 是一款開源的大語言模型(LLM) 應用開發平臺。它融合了后端即服務&#xff08;Backend as Service&#xff09;和 LLMOps 的理念&#xff0c;使開發者可以快速搭建生產級的生成式 AI 應用。即使你是非技術人員&#xff0c;也能參與到 AI 應用的定義和數據…

國產DSP,QXS320F280049,QXS320F28377D,QXS320F2800137,QXS320F28034

自定義指令集&#xff0c;自研內核架構&#xff0c;基于eclipse自研IDE&#xff0c;工具鏈&#xff0c;算法庫。 根據自研QXS320F280049&#xff0c;做了600W和2KW數字電源方案&#xff0c;1.5KW電機方案&#xff0c;目前已在市場大量投產。 QXS320F290049應用于數字電源&#…

dotnet publish 發布后的項目,例如asp.net core mvc項目如何在ubuntu中運行,并可外部訪問

復制到 Ubuntu 上的是使用 Visual Studio 或 dotnet publish 命令生成的 發布后的輸出文件&#xff08;publish output&#xff09;&#xff0c;而不是原始項目源代碼。在這種情況下&#xff0c;確實沒有 .csproj 文件&#xff0c;所以不能直接用 dotnet run 啟動。但你可以通過…

Linux多線程(十二)之【生產者消費者模型】

文章目錄生產者消費者模型為何要使用生產者消費者模型生產者消費者模型優點基于BlockingQueue的生產者消費者模型BlockingQueueC queue模擬阻塞隊列的生產消費模型單線程生產消費模型多線程生產消費模型生產者消費者模型 consumer/productor 321原則(便于記憶) 為何要使用生產…