1. 程式人生 > >完了,這個硬體成精了,它竟然繞過了 CPU...

完了,這個硬體成精了,它竟然繞過了 CPU...

我們之前瞭解過了 Linux 的程序和執行緒、Linux 記憶體管理,那麼下面我們就來認識一下 Linux 中的 I/O 管理。 Linux 系統和其他 UNIX 系統一樣,IO 管理比較直接和簡潔。所有 IO 裝置都被當作`檔案`,通過在系統內部使用相同的 read 和 write 一樣進行讀寫。 ## Linux IO 基本概念 Linux 中也有磁碟、印表機、網路等 I/O 裝置,Linux 把這些裝置當作一種 `特殊檔案` 整合到檔案系統中,一般通常位於 `/dev` 目錄下。可以使用與普通檔案相同的方式來對待這些特殊檔案。 特殊檔案一般分為兩種: 塊特殊檔案是一個能儲存`固定大小塊`資訊的裝置,它支援**以固定大小的塊,扇區或群集讀取和(可選)寫入資料**。每個塊都有自己的`實體地址`。通常塊的大小在 512 - 65536 之間。所有傳輸的資訊都會以`連續`的塊為單位。塊裝置的基本特徵是每個塊都較為對立,能夠獨立的進行讀寫。常見的塊裝置有 **硬碟、藍光光碟、USB 盤**與字元裝置相比,塊裝置通常需要較少的引腳。 ![](https://img2020.cnblogs.com/blog/1515111/202008/1515111-20200816094529189-68826331.png) 塊特殊檔案的缺點基於給定固態儲存器的塊裝置比基於相同型別的儲存器的位元組定址要慢一些,因為必須在塊的開頭開始讀取或寫入。所以,要讀取該塊的任何部分,必須尋找到該塊的開始,讀取整個塊,如果不使用該塊,則將其丟棄。要寫入塊的一部分,必須尋找到塊的開始,將整個塊讀入記憶體,修改資料,再次尋找到塊的開頭處,然後將整個塊寫回裝置。 另一類 I/O 裝置是`字元特殊檔案`。字元裝置以`字元`為單位傳送或接收一個字元流,而不考慮任何塊結構。字元裝置是不可定址的,也沒有任何尋道操作。常見的字元裝置有 **印表機、網路裝置、滑鼠、以及大多數與磁碟不同的裝置**。 ![](https://img2020.cnblogs.com/blog/1515111/202008/1515111-20200816094602402-963219952.png) 每個裝置特殊檔案都會和 `裝置驅動` 相關聯。每個驅動程式都通過一個 `主裝置號` 來標識。如果一個驅動支援多個裝置的話,此時會在主裝置的後面新加一個 `次裝置號` 來標識。主裝置號和次裝置號共同確定了唯一的驅動裝置。 我們知道,在計算機系統中,CPU 並不直接和裝置打交道,它們中間有一個叫作 `裝置控制器(Device Control Unit)`的元件,例如硬碟有磁碟控制器、USB 有 USB 控制器、顯示器有視訊控制器等。這些控制器就像代理商一樣,它們知道如何應對硬碟、滑鼠、鍵盤、顯示器的行為。 絕大多數字符特殊檔案都不能隨機訪問,因為他們需要使用和塊特殊檔案不同的方式來控制。比如,你在鍵盤上輸入了一些字元,但是你發現輸錯了一個,這時有一些人喜歡使用 `backspace` 來刪除,有人喜歡用 `del` 來刪除。為了中斷正在執行的裝置,一些系統使用 `ctrl-u` 來結束,但是現在一般使用 `ctrl-c` 來結束。 ## 網路 I/O 的另外一個概念是`網路`, 也是由 UNIX 引入,網路中一個很關鍵的概念就是 `套接字(socket)`。套接字允許使用者連線到網路,正如郵筒允許使用者連線到郵政系統,套接字的示意圖如下 ![](https://img2020.cnblogs.com/blog/1515111/202008/1515111-20200816094641493-1398611310.png) 套接字的位置如上圖所示,套接字可以動態建立和銷燬。成功建立一個套接字後,系統會返回一個`檔案描述符(file descriptor)`,在後面的建立連結、讀資料、寫資料、解除連線時都需要使用到這個檔案描述符。每個套接字都支援一種特定型別的網路型別,在建立時指定。一般最常用的幾種 * 可靠的面向連線的位元組流 * 可靠的面向連線的資料包 * 不可靠的資料包傳輸 可靠的面向連線的位元組流會使用`管道` 在兩臺機器之間建立連線。能夠保證位元組從一臺機器按照順序到達另一臺機器,系統能夠保證所有位元組都能到達。 除了資料包之間的分界之外,第二種型別和第一種型別是類似的。如果傳送了 3 次寫操作,那麼使用第一種方式的接受者會直接接收到所有位元組;第二種方式的接受者會分 3 次接受所有位元組。除此之外,使用者還可以使用第三種即不可靠的資料包來傳輸,使用這種傳輸方式的優點在於高效能,有的時候它比可靠性更加重要,比如在流媒體中,效能就尤其重要。 以上涉及兩種形式的傳輸協議,即 `TCP` 和 `UDP`,TCP 是 `傳輸控制協議`,它能夠傳輸可靠的位元組流。`UDP` 是 `使用者資料報協議`,它只能夠傳輸不可靠的位元組流。它們都屬於 TCP/IP 協議簇中的協議,下面是網路協議分層 ![](https://img2020.cnblogs.com/blog/1515111/202008/1515111-20200816094648592-452949937.png) 可以看到,TCP 、UDP 都位於網路層上,可見它們都把 IP 協議 即 `網際網路協議` 作為基礎。 一旦套接字在源計算機和目的計算機建立成功,那麼兩個計算機之間就可以建立一個連結。通訊一方在本地套接字上使用 `listen` 系統呼叫,它就會建立一個緩衝區,然後阻塞直到資料到來。另一方使用 `connect` 系統呼叫,如果另一方接受 connect 系統呼叫後,則系統會在兩個套接字之間建立連線。 socket 連線建立成功後就像是一個管道,一個程序可以使用本地套接字的檔案描述符從中讀寫資料,當連線不再需要的時候使用 `close` 系統呼叫來關閉。 ## Linux I/O 系統呼叫 Linux 系統中的每個 I/O 裝置都有一個`特殊檔案(special file)`與之關聯,什麼是特殊檔案呢? > 在作業系統中,特殊檔案是一種在檔案系統中與硬體裝置相關聯的檔案。特殊檔案也被稱為 `裝置檔案(device file)`。特殊檔案的目的是將裝置作為檔案系統中的檔案進行公開。特殊檔案為硬體裝置提供了藉口,用於檔案 I/O 的工具可以進行訪問。因為裝置有兩種型別,同樣特殊檔案也有兩種,即字元特殊檔案和塊特殊檔案 對於大部分 I/O 操作來說,只用合適的檔案就可以完成,並不需要特殊的系統呼叫。然後,有時需要一些裝置專用的處理。在 POSIX 之前,大多數 UNIX 系統會有一個叫做 `ioctl` 的系統呼叫,它用於執行大量的系統呼叫。隨著時間的發展,POSIX 對其進行了整理,把 ioctl 的功能劃分為面向終端裝置的獨立功能呼叫,現在已經變成獨立的系統呼叫了。 下面是幾個管理終端的系統呼叫 ![](https://img2020.cnblogs.com/blog/1515111/202008/1515111-20200816094710736-29221241.png) ## Linux IO 實現 Linux 中的 IO 是通過一系列裝置驅動實現的,每個裝置型別對應一個裝置驅動。裝置驅動為作業系統和硬體分別預留介面,通過裝置驅動來遮蔽作業系統和硬體的差異。 當用戶訪問一個特殊的檔案時,由檔案系統提供此特殊檔案的主裝置號和次裝置號,並判斷它是一個塊特殊檔案還是字元特殊檔案。主裝置號用於標識字元裝置還是塊裝置,次裝置號用於引數傳遞。 每個`驅動程式` 都有兩部分:這兩部分都是屬於 Linux 核心,也都執行在核心態下。上半部分執行在呼叫者上下文並且與 Linux 其他部分互動。下半部分執行在核心上下文並且與裝置進行互動。驅動程式可以呼叫記憶體分配、定時器管理、DMA 控制等核心過程。可被呼叫的核心功能都位於 `驅動程式 - 核心介面` 的文件中。 I/O 實現指的就是對字元裝置和塊裝置的實現 ### 塊裝置實現 系統中處理塊特殊檔案 I/O 部分的目標是為了使傳輸次數儘可能的小。為了實現這個目標,Linux 系統在磁碟驅動程式和檔案系統之間設定了一個 `快取記憶體(cache)` ,如下圖所示 ![](https://img2020.cnblogs.com/blog/1515111/202008/1515111-20200816094658817-808223415.png) 在 Linux 核心 2.2 之前,Linux 系統維護著兩個快取:`頁面快取(page cache)` 和 `緩衝區快取(buffer cache)`,因此,儲存在一個磁碟塊中的檔案可能會在兩個快取中。2.2 版本以後 Linux 核心只有一個統一的快取一個 `通用資料塊層(generic block layer)` 把這些融合在一起,實現了磁碟、資料塊、緩衝區和資料頁之間必要的轉換。那麼什麼是通用資料塊層? >通用資料塊層是一個核心的組成部分,用於處理對系統中所有塊裝置的請求。通用資料塊主要有以下幾個功能 > >將資料緩衝區放在記憶體高位處,當 CPU 訪問資料時,頁面才會對映到核心線性地址中,並且此後取消對映 > >實現 `零拷貝`機制,磁碟資料可以直接放入使用者模式的地址空間,而無需先複製到核心記憶體中 > >管理磁碟卷,會把不同塊裝置上的多個磁碟分割槽視為一個分割槽。 > >利用最新的磁碟控制器的高階功能,例如 DMA 等。 cache 是提升效能的利器,不管以什麼樣的目的需要一個數據塊,都會先從 cache 中查詢,如果找到直接返回,避免一次磁碟訪問,能夠極大的提升系統性能。 如果頁面 cache 中沒有這個塊,作業系統就會把頁面從磁碟中調入記憶體,然後讀入 cache 進行快取。 cache 除了支援讀操作外,也支援寫操作,一個程式要寫回一個塊,首先把它寫到 cache 中,而不是直接寫入到磁碟中,等到磁碟中快取達到一定數量值時再被寫入到 cache 中。 Linux 系統中使用 `IO 排程器` 來保證減少磁頭的反覆移動從而減少損失。I/O 排程器的作用是對塊裝置的讀寫操作進行排序,對讀寫請求進行合併。Linux 有許多排程器的變體,從而滿足不同的工作需要。最基本的 Linux 排程器是基於傳統的 `Linux 電梯排程器(Linux elevator scheduler)`。Linux 電梯排程器的主要工作流程就是按照磁碟扇區的地址排序並存儲在一個`雙向連結串列` 中。新的請求將會以連結串列的形式插入。這種方法可以有效的防止磁頭重複移動。因為電梯排程器會容易產生飢餓現象。因此,Linux 在原基礎上進行了修改,維護了兩個連結串列,在 `最後日期(deadline)` 內維護了排序後的讀寫操作。預設的讀操作耗時 0.5s,預設寫操作耗時 5s。如果在最後期限內等待時間最長的連結串列沒有獲得服務,那麼它將優先獲得服務。 ### 字元裝置實現 和字元裝置的互動是比較簡單的。由於字元裝置會產生並使用字元流、位元組資料,因此對隨機訪問的支援意義不大。一個例外是使用 `行規則(line disciplines)`。一個行規可以和終端裝置相關聯,使用 `tty_struct` 結構來表示,它表示與終端裝置交換資料的直譯器,當然這也屬於核心的一部分。例如:行規可以對行進行編輯,映射回車為換行等一系列其他操作。 > 什麼是行規則? > > 行規是某些類 UNIX 系統中的一層,終端子系統通常由三層組成:上層提供字元裝置介面,下層硬體驅動程式與硬體或偽終端進行互動,中層規則用於實現終端裝置共有的行為。 ![](https://img2020.cnblogs.com/blog/1515111/202008/1515111-20200816094719328-824135113.png) ### 網路裝置實現 網路裝置的互動是不一樣的,雖然 `網路裝置(network devices)` 也會產生字元流,因為它們的`非同步(asynchronous)` 特性是他們不易與其他字元裝置在同一介面下整合。網路裝置驅動程式會產生很多資料包,經由網路協議到達使用者應用程式中。 ## Linux 中的模組 UNIX 裝置驅動程式是被`靜態載入`到核心中的。因此,只要系統啟動後,裝置驅動程式都會被載入到記憶體中。隨著個人電腦 Linux 的出現,這種靜態連結完成後會使用一段時間的模式被打破。相對於小型機上的 I/O 裝置,PC 上可用的 I/O 裝置有了數量級的增長。絕大多數使用者沒有能力去新增一個新的應用程式、更新裝置驅動、重新連線核心,然後進行安裝。 Linux 為了解決這個問題,引入了 `可載入(loadable module)` 機制。可載入是在系統執行時新增到核心中的程式碼塊。 當一個模組被載入到核心時,會發生下面幾件事情:第一,在載入的過程中,模組會被動態的重新部署。第二,系統會檢查程式程式所需的資源是否可用。如果可用,則把這些資源標記為正在使用。第三步,設定所需的中斷向量。第四,更新驅動轉換表使其能夠處理新的主裝置型別。最後再來執行裝置驅動程式。 在完成上述工作後,驅動程式就會安裝完成,其他現代 UNIX 系統也支援可載入機制。 你好,我是 cxuan,我自己手寫了四本 PDF,分別是 Java基礎總結、HTTP 核心總結、計算機基礎知識,作業系統核心總結,我已經整理成為 PDF,可以關注公眾號 Java建設者 回覆 PDF 領取優質資料。 ![](https://img2020.cnblogs.com/blog/1515111/202008/1515111-20200816095103772-441870