完了,這個硬體成精了,它竟然繞過了 CPU…
我們之前了解過了 Linux 的進程和執行緒、Linux 記憶體管理,那麼下面我們就來認識一下 Linux 中的 I/O 管理。
Linux 系統和其他 UNIX 系統一樣,IO 管理比較直接和簡潔。所有 IO 設備都被當作文件
,通過在系統內部使用相同的 read 和 write 一樣進行讀寫。
Linux IO 基本概念
Linux 中也有磁碟、印表機、網路等 I/O 設備,Linux 把這些設備當作一種 特殊文件
整合到文件系統中,一般通常位於 /dev
目錄下。可以使用與普通文件相同的方式來對待這些特殊文件。
特殊文件一般分為兩種:
塊特殊文件是一個能存儲固定大小塊
資訊的設備,它支援以固定大小的塊,扇區或群集讀取和(可選)寫入數據。每個塊都有自己的物理地址
。通常塊的大小在 512 – 65536 之間。所有傳輸的資訊都會以連續
的塊為單位。塊設備的基本特徵是每個塊都較為對立,能夠獨立的進行讀寫。常見的塊設備有 硬碟、藍光光碟、USB 盤與字元設備相比,塊設備通常需要較少的引腳。
塊特殊文件的缺點基於給定固態存儲器的塊設備比基於相同類型的存儲器的位元組定址要慢一些,因為必須在塊的開頭開始讀取或寫入。所以,要讀取該塊的任何部分,必須尋找到該塊的開始,讀取整個塊,如果不使用該塊,則將其丟棄。要寫入塊的一部分,必須尋找到塊的開始,將整個塊讀入記憶體,修改數據,再次尋找到塊的開頭處,然後將整個塊寫回設備。
另一類 I/O 設備是字元特殊文件
。字元設備以字元
為單位發送或接收一個字元流,而不考慮任何塊結構。字元設備是不可定址的,也沒有任何尋道操作。常見的字元設備有 印表機、網路設備、滑鼠、以及大多數與磁碟不同的設備。
每個設備特殊文件都會和 設備驅動
相關聯。每個驅動程式都通過一個 主設備號
來標識。如果一個驅動支援多個設備的話,此時會在主設備的後面新加一個 次設備號
來標識。主設備號和次設備號共同確定了唯一的驅動設備。
我們知道,在電腦系統中,CPU 並不直接和設備打交道,它們中間有一個叫作 設備控制器(Device Control Unit)
的組件,例如硬碟有磁碟控制器、USB 有 USB 控制器、顯示器有影片控制器等。這些控制器就像代理商一樣,它們知道如何應對硬碟、滑鼠、鍵盤、顯示器的行為。
絕大多數字元特殊文件都不能隨機訪問,因為他們需要使用和塊特殊文件不同的方式來控制。比如,你在鍵盤上輸入了一些字元,但是你發現輸錯了一個,這時有一些人喜歡使用 backspace
來刪除,有人喜歡用 del
來刪除。為了中斷正在運行的設備,一些系統使用 ctrl-u
來結束,但是現在一般使用 ctrl-c
來結束。
網路
I/O 的另外一個概念是網路
, 也是由 UNIX 引入,網路中一個很關鍵的概念就是 套接字(socket)
。套接字允許用戶連接到網路,正如郵筒允許用戶連接到郵政系統,套接字的示意圖如下
套接字的位置如上圖所示,套接字可以動態創建和銷毀。成功創建一個套接字後,系統會返回一個文件描述符(file descriptor)
,在後面的創建鏈接、讀數據、寫數據、解除連接時都需要使用到這個文件描述符。每個套接字都支援一種特定類型的網路類型,在創建時指定。一般最常用的幾種
- 可靠的面向連接的位元組流
- 可靠的面向連接的數據包
- 不可靠的數據包傳輸
可靠的面向連接的位元組流會使用管道
在兩台機器之間建立連接。能夠保證位元組從一台機器按照順序到達另一台機器,系統能夠保證所有位元組都能到達。
除了數據包之間的分界之外,第二種類型和第一種類型是類似的。如果發送了 3 次寫操作,那麼使用第一種方式的接受者會直接接收到所有位元組;第二種方式的接受者會分 3 次接受所有位元組。除此之外,用戶還可以使用第三種即不可靠的數據包來傳輸,使用這種傳輸方式的優點在於高性能,有的時候它比可靠性更加重要,比如在流媒體中,性能就尤其重要。
以上涉及兩種形式的傳輸協議,即 TCP
和 UDP
,TCP 是 傳輸控制協議
,它能夠傳輸可靠的位元組流。UDP
是 用戶數據報協議
,它只能夠傳輸不可靠的位元組流。它們都屬於 TCP/IP 協議簇中的協議,下面是網路協議分層
可以看到,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 的功能劃分為面向終端設備的獨立功能調用,現在已經變成獨立的系統調用了。
下面是幾個管理終端的系統調用
Linux IO 實現
Linux 中的 IO 是通過一系列設備驅動實現的,每個設備類型對應一個設備驅動。設備驅動為作業系統和硬體分別預留介面,通過設備驅動來屏蔽作業系統和硬體的差異。
當用戶訪問一個特殊的文件時,由文件系統提供此特殊文件的主設備號和次設備號,並判斷它是一個塊特殊文件還是字元特殊文件。主設備號用於標識字元設備還是塊設備,次設備號用於參數傳遞。
每個驅動程式
都有兩部分:這兩部分都是屬於 Linux 內核,也都運行在內核態下。上半部分運行在調用者上下文並且與 Linux 其他部分交互。下半部分運行在內核上下文並且與設備進行交互。驅動程式可以調用記憶體分配、定時器管理、DMA 控制等內核過程。可被調用的內核功能都位於 驅動程式 - 內核介面
的文檔中。
I/O 實現指的就是對字元設備和塊設備的實現
塊設備實現
系統中處理塊特殊文件 I/O 部分的目標是為了使傳輸次數儘可能的小。為了實現這個目標,Linux 系統在磁碟驅動程式和文件系統之間設置了一個 高速快取(cache)
,如下圖所示
在 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 系統中的一層,終端子系統通常由三層組成:上層提供字元設備介面,下層硬體驅動程式與硬體或偽終端進行交互,中層規則用於實現終端設備共有的行為。
網路設備實現
網路設備的交互是不一樣的,雖然 網路設備(network devices)
也會產生字元流,因為它們的非同步(asynchronous)
特性是他們不易與其他字元設備在同一介面下集成。網路設備驅動程式會產生很多數據包,經由網路協議到達用戶應用程式中。
Linux 中的模組
UNIX 設備驅動程式是被靜態載入
到內核中的。因此,只要系統啟動後,設備驅動程式都會被載入到記憶體中。隨著個人電腦 Linux 的出現,這種靜態鏈接完成後會使用一段時間的模式被打破。相對於小型機上的 I/O 設備,PC 上可用的 I/O 設備有了數量級的增長。絕大多數用戶沒有能力去添加一個新的應用程式、更新設備驅動、重新連接內核,然後進行安裝。
Linux 為了解決這個問題,引入了 可載入(loadable module)
機制。可載入是在系統運行時添加到內核中的程式碼塊。
當一個模組被載入到內核時,會發生下面幾件事情:第一,在載入的過程中,模組會被動態的重新部署。第二,系統會檢查程式程式所需的資源是否可用。如果可用,則把這些資源標記為正在使用。第三步,設置所需的中斷向量。第四,更新驅動轉換表使其能夠處理新的主設備類型。最後再來運行設備驅動程式。
在完成上述工作後,驅動程式就會安裝完成,其他現代 UNIX 系統也支援可載入機制。