一篇帶你了解Linux的輸入輸出
總線
Intel采用雙獨(dú)立總線(英語:Dual Independent Bus,DIB),使用外部的前端總線到主系統(tǒng)存儲器,和內(nèi)部的后端總線于一個(gè)或多個(gè)中央處理器、CPU緩存間。CPU 里面的內(nèi)存接口,直接和系統(tǒng)總線通信,然后系統(tǒng)總線再接入一個(gè) I/O 橋接器(I/O Bridge)。這個(gè) I/O 橋接器,一邊接入了我們的內(nèi)存總線,使得我們的 CPU 和內(nèi)存通信;另一邊呢,又接入了一個(gè) I/O 總線,用來連接 I/O 設(shè)備。

在物理層面,其實(shí)我們完全可以把總線看作一組“電線”。一般有五類線路。
- 數(shù)據(jù)總線(Data Bus):在CPU與RAM之間來回傳送需要處理或是需要儲存的數(shù)據(jù)。
- 地址總線(Address Bus):用來指定在RAM(Random Access Memory)之中儲存的數(shù)據(jù)的地址。
- 控制總線(Control Bus):將微處理器控制單元(Control Unit)的信號,傳送到周邊設(shè)備,一般常見的為USB Bus和1394 Bus。
- 擴(kuò)展總線(Expansion Bus):可連接擴(kuò)展槽和電腦。
- 局部總線(Local Bus):取代更高速數(shù)據(jù)傳輸?shù)臄U(kuò)展
I/O設(shè)備
輸入輸出設(shè)備,并不只是一個(gè)設(shè)備。大部分的輸入輸出設(shè)備,都有兩個(gè)組成部分。第一個(gè)是它的接口(Interface),第二個(gè)才是實(shí)際的 I/O 設(shè)備(Actual I/O Device)。我們的硬件設(shè)備并不是直接接入到總線上和 CPU 通信的,而是通過接口,用接口連接到總線上,再通過總線和 CPU 通信。比如可以把硬盤分為 IDE(Integrated Drive Electronics)、SCSI(Small Computer System Interface) 、SAS(Serial Attached SCSI) 、SATA(Serial ATA) 、FC(Fibre Channel)都是計(jì)算機(jī)主板上內(nèi)置的各個(gè)接口。硬件只需要提供對應(yīng)接口的設(shè)備驅(qū)動程序。
設(shè)備控制器
接口本身就是設(shè)備控制器。CPU 其實(shí)不是和實(shí)際的硬件設(shè)備打交道,而是和設(shè)備控制器打交道。設(shè)備控制器除了要將設(shè)備與計(jì)算機(jī)連接外,還有很多重要任務(wù)。
- 隨時(shí)監(jiān)控設(shè)備所處的狀態(tài),實(shí)現(xiàn)對設(shè)備的控制與操作。
- 設(shè)備控制器中都有三類寄存器:分別是狀態(tài)寄存器(Status Register)、 命令寄存器(Command Register)以及數(shù)據(jù)寄存器(Data Register)。每個(gè)控制寄存器被分配一個(gè) I/O 端口,我們可以通過特殊的匯編指令(例如 in/out 類似的指令)操作這些寄存器。狀態(tài)寄存器,可以通過檢測狀態(tài)標(biāo)志位,來確定輸入或者輸出操作是否完成。
- 有些設(shè)備還有數(shù)據(jù)緩沖區(qū)。如打印機(jī)等??蓛?nèi)存映射 I/O,可以分配一段內(nèi)存空間給它,就像讀寫內(nèi)存一樣讀寫數(shù)據(jù)緩沖區(qū)。
- 設(shè)備控制器還監(jiān)管對由I/O設(shè)備傳送來的數(shù)據(jù)進(jìn)行差錯(cuò)檢測。若發(fā)現(xiàn)傳送中出現(xiàn)了錯(cuò)誤,通常是將差錯(cuò)檢測碼置位,并向CPU報(bào)告,于是CPU將本次傳送來的數(shù)據(jù)作廢,并重新進(jìn)行一次傳送。這樣便可保證數(shù)據(jù)輸入的正確性。
設(shè)備驅(qū)動程序
用于實(shí)現(xiàn)設(shè)備對具體設(shè)備的管理與操作。要讓設(shè)備工作,必選訪問設(shè)備控制器中的各種寄存器,這部分通過編寫特定的程序代碼來實(shí)現(xiàn)程序,就是“設(shè)備驅(qū)動程序”。主要有以下功能:
- 對設(shè)備進(jìn)行初始化
- 使設(shè)備投入運(yùn)行和退出服務(wù)
- 從設(shè)備接收數(shù)據(jù)并將它們送回內(nèi)核
- 將數(shù)據(jù)從內(nèi)核送到設(shè)備
- 檢測和處理設(shè)備出現(xiàn)的錯(cuò)誤
DMA控制器
有的設(shè)備需要讀取或者寫入大量數(shù)據(jù)。如果所有過程都讓 CPU 協(xié)調(diào)的話,就需要占用 CPU 大量的時(shí)間,比方說,磁盤就是這樣的。這種類型的設(shè)備需要支持 DMA 功能,也就是說,允許設(shè)備在 CPU 不參與的情況下,能夠自行完成對內(nèi)存的讀寫。實(shí)現(xiàn) DMA 機(jī)制需要有個(gè) DMA 控制器幫你的 CPU 來做協(xié)調(diào),就像下面這個(gè)圖中顯示的一樣。
- CPU 只需要對 DMA 控制器下指令,說它想讀取多少數(shù)據(jù),放在內(nèi)存的某個(gè)地方就可以了。
- 接下來 DMA 控制器會發(fā)指令給磁盤控制器,請求數(shù)據(jù)傳送到內(nèi)存。磁盤驅(qū)動器讀取磁盤上的數(shù)據(jù)到磁盤控制器的內(nèi)核緩沖區(qū),磁盤控制器進(jìn)行差錯(cuò)校驗(yàn),保證沒有發(fā)生讀錯(cuò)誤發(fā)生。磁盤控制器的寄存器,CPU與DMA都可以修改。
- 磁盤控制器從其內(nèi)部緩沖區(qū)中讀取數(shù)據(jù)的時(shí)候知道這個(gè)數(shù)據(jù)該寫到什么地方。然后通過內(nèi)存總線將數(shù)據(jù)寫到內(nèi)存。
- 當(dāng)寫操作完成時(shí),磁盤控制器在總線上發(fā)出一個(gè)確認(rèn)成功的信號到DMA控制器。
- DMA 控制器發(fā)中斷通知 CPU 指令完成,CPU 就可以直接用內(nèi)存里面現(xiàn)成的數(shù)據(jù)了。
中斷控制器
硬件的中斷控制器,當(dāng)設(shè)備完成任務(wù)后觸發(fā)中斷到中斷控制器,中斷控制器就通知 CPU,一個(gè)中斷產(chǎn)生了,CPU 需要停下當(dāng)前手里的事情來處理中斷。一般的流程是,一個(gè)設(shè)備驅(qū)動程序初始化的時(shí)候,要先注冊一個(gè)該設(shè)備的中斷處理函數(shù)。中斷的時(shí)候,觸發(fā)的函數(shù)是 do_IRQ。這個(gè)函數(shù)是中斷處理的統(tǒng)一入口。在這個(gè)函數(shù)里面,我們可以找到設(shè)備驅(qū)動程序注冊的中斷處理函數(shù) Handler,然后執(zhí)行它進(jìn)行中斷處理。
磁盤驅(qū)動的實(shí)現(xiàn)
在Linux中,設(shè)備驅(qū)動程序是一組相關(guān)函數(shù)的集合。它包含設(shè)備服務(wù)子程序和中斷處理程序。設(shè)備服務(wù)子程序包含了所有與設(shè)備相關(guān)的代碼,每個(gè)設(shè)備服務(wù)子程序只處理一種設(shè)備或者緊密相關(guān)的設(shè)備。其功能就是從與設(shè)備無關(guān)的軟件中接受抽象的命令并執(zhí)行之。當(dāng)執(zhí)行一條請求時(shí),具體操作是根據(jù)設(shè)備控制器對驅(qū)動程序提供的接口(指的是控制器中的各種寄存器),并利用中斷機(jī)制去調(diào)用中斷服務(wù)子程序配合設(shè)備來完成這個(gè)請求。設(shè)備驅(qū)動程序利用結(jié)構(gòu) file_operations 與文件系統(tǒng)聯(lián)系起來,即設(shè)備的各種操作的入口函數(shù)存在file_operation中。對于特定的設(shè)備來說有一些操作是不必要的,其入口置為NULL。
Linux 內(nèi)核中雖存在許多不同的設(shè)備驅(qū)動程序但它們具有一些共同的特性:
- 驅(qū)動程序?qū)儆趦?nèi)核代碼,設(shè)備驅(qū)動程序是內(nèi)核的一部分,它象內(nèi)核中其它代碼一樣運(yùn)行在內(nèi)核模式,驅(qū)動程序如果出錯(cuò)將會使操作系統(tǒng)受到嚴(yán)重破壞,甚至能使系統(tǒng)崩潰并導(dǎo)致文件系統(tǒng)的破壞和數(shù)據(jù)丟失。
- 為內(nèi)核提供統(tǒng)一的接口,設(shè)備驅(qū)動程序必須為 Linux 內(nèi)核或其它子系統(tǒng)提供一個(gè)標(biāo)準(zhǔn)的接口。例如終端驅(qū)動程序?yàn)長inux 內(nèi)核提供了一個(gè)文件 I/O 接口。
- 驅(qū)動程序的執(zhí)行是屬于內(nèi)核機(jī)制并且使用內(nèi)核服務(wù) 。設(shè)備驅(qū)動可以使用標(biāo)準(zhǔn)的內(nèi)核服務(wù)如內(nèi)存分配、中斷發(fā)送和等待隊(duì)列等等。
- 動態(tài)可加載,多數(shù) Linux 設(shè)備驅(qū)動程序可以在內(nèi)核模塊發(fā)出加載請求時(shí)加載,而不再使用時(shí)將其卸載。這樣內(nèi)核能有效地利用系統(tǒng)資源。
- 可配置,Linux 設(shè)備驅(qū)動程序可以連接到內(nèi)核中。當(dāng)內(nèi)核被編譯時(shí),被連入內(nèi)核的設(shè)備驅(qū)動程序是可配置的。
這樣Linux的輸入輸出就很明朗了
輸入輸出設(shè)備的設(shè)備廠商很多。因?yàn)樵O(shè)備廠商復(fù)雜多變,設(shè)備廠商也同樣復(fù)雜多變,需要層層屏蔽差異化的部分,給上層提供標(biāo)準(zhǔn)化的部分,最終到用戶態(tài),給用戶提供了基于文件系統(tǒng)的統(tǒng)一的接口。