一文搞懂Linux操作系統(tǒng)I/ O機(jī)制原理(流程圖詳解)


我們之前的文章提到了操作系統(tǒng)的三個(gè)抽象,它們分別是進(jìn)程、地址空間和文件,除此之外,操作系統(tǒng)還要控制所有的 I/O 設(shè)備。操作系統(tǒng)必須向設(shè)備發(fā)送命令,捕捉中斷并處理錯(cuò)誤。它還應(yīng)該在設(shè)備和操作系統(tǒng)的其余部分之間提供一個(gè)簡(jiǎn)單易用的接口。操作系統(tǒng)如何管理 I/O 是我們接下來(lái)的重點(diǎn)。
不同的人對(duì) I/O 硬件的理解也不同。對(duì)于電子工程師而言,I/O 硬件就是芯片、導(dǎo)線、電源和其他組成硬件的物理設(shè)備。而我們程序員眼中的 I/O 其實(shí)就是硬件提供給軟件的接口,比如硬件接受到的命令、執(zhí)行的操作以及反饋的錯(cuò)誤。我們著重探討的是如何對(duì)硬件進(jìn)行編程,而不是其工作原理。
一,I/O 設(shè)備
什么是 I/O 設(shè)備?I/O 設(shè)備又叫做輸入/輸出設(shè)備,它是人類(lèi)用來(lái)和計(jì)算機(jī)進(jìn)行通信的外部硬件。輸入/輸出設(shè)備能夠向計(jì)算機(jī)發(fā)送數(shù)據(jù)(輸出)并從計(jì)算機(jī)接收數(shù)據(jù)(輸入)。
I/O 設(shè)備(I/O devices)可以分成兩種:塊設(shè)備(block devices) 和 字符設(shè)備(character devices)。
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【891587639】整理了一些個(gè)人覺(jué)得比較好的學(xué)習(xí)書(shū)籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。?!前100名進(jìn)群領(lǐng)取,額外贈(zèng)送一份價(jià)值699的內(nèi)核資料包(含視頻教程、電子書(shū)、實(shí)戰(zhàn)項(xiàng)目及代碼)? ? ?


二,塊設(shè)備
塊設(shè)備是一個(gè)能存儲(chǔ)固定大小塊信息的設(shè)備,它支持以固定大小的塊,扇區(qū)或群集讀取和(可選)寫(xiě)入數(shù)據(jù)。每個(gè)塊都有自己的物理地址。通常塊的大小在 512 - 65536 之間。所有傳輸?shù)男畔⒍紩?huì)以連續(xù)的塊為單位。塊設(shè)備的基本特征是每個(gè)塊都較為對(duì)立,能夠獨(dú)立的進(jìn)行讀寫(xiě)。常見(jiàn)的塊設(shè)備有 硬盤(pán)、藍(lán)光光盤(pán)、USB盤(pán)與字符設(shè)備相比,塊設(shè)備通常需要較少的引腳。

塊設(shè)備的缺點(diǎn)
基于給定固態(tài)存儲(chǔ)器的塊設(shè)備比基于相同類(lèi)型的存儲(chǔ)器的字節(jié)尋址要慢一些,因?yàn)楸仨氃趬K的開(kāi)頭開(kāi)始讀取或?qū)懭?。所以,要讀取該塊的任何部分,必須尋找到該塊的開(kāi)始,讀取整個(gè)塊,如果不使用該塊,則將其丟棄。要寫(xiě)入塊的一部分,必須尋找到塊的開(kāi)始,將整個(gè)塊讀入內(nèi)存,修改數(shù)據(jù),再次尋找到塊的開(kāi)頭處,然后將整個(gè)塊寫(xiě)回設(shè)備。
字符設(shè)備
另一類(lèi) I/O 設(shè)備是字符設(shè)備。字符設(shè)備以字符為單位發(fā)送或接收一個(gè)字符流,而不考慮任何塊結(jié)構(gòu)。字符設(shè)備是不可尋址的,也沒(méi)有任何尋道操作。常見(jiàn)的字符設(shè)備有 打印機(jī)、網(wǎng)絡(luò)設(shè)備、鼠標(biāo)、以及大多數(shù)與磁盤(pán)不同的設(shè)備。

下面顯示了一些常見(jiàn)設(shè)備的數(shù)據(jù)速率:

設(shè)備控制器
首先需要先了解一下設(shè)備控制器的概念:
設(shè)備控制器是處理 CPU 傳入和傳出信號(hào)的系統(tǒng)。設(shè)備通過(guò)插頭和插座連接到計(jì)算機(jī),并且插座連接到設(shè)備控制器。設(shè)備控制器從連接的設(shè)備處接收數(shù)據(jù),并將其存儲(chǔ)在控制器內(nèi)部的一些特殊目的寄存器(special purpose registers) 也就是本地緩沖區(qū)中。
特殊用途寄存器,顧名思義是僅為一項(xiàng)任務(wù)而設(shè)計(jì)的寄存器。例如,cs,ds,gs 和其他段寄存器屬于特殊目的寄存器,因?yàn)樗鼈兊拇嬖谑菫榱吮4娑翁?hào)。 eax,ecx 等是一般用途的寄存器,因?yàn)槟憧梢詿o(wú)限制地使用它們。 例如,你不能移動(dòng) ds,但是可以移動(dòng) eax,ebx。
通用目的寄存器比如有:eax、ecx、edx、ebx、esi、edi、ebp、esp
特殊目的寄存器比如有:cs、ds、ss、es、fs、gs、eip、flag
每個(gè)設(shè)備控制器都會(huì)有一個(gè)應(yīng)用程序與之對(duì)應(yīng),設(shè)備控制器通過(guò)應(yīng)用程序的接口通過(guò)中斷與操作系統(tǒng)進(jìn)行通信。設(shè)備控制器是硬件,而設(shè)備驅(qū)動(dòng)程序是軟件。
I/O 設(shè)備通常由機(jī)械組件(mechanical component)和電子組件(electronic component)構(gòu)成。電子組件被稱(chēng)為 設(shè)備控制器(device controller)或者 適配器(adapter)。在個(gè)人計(jì)算機(jī)上,它通常采用可插入(PCIe)擴(kuò)展插槽的主板上的芯片或印刷電路卡的形式。

機(jī)械設(shè)備就是它自己,它的組成如下:

控制器卡上通常會(huì)有一個(gè)連接器,通向設(shè)備本身的電纜可以插入到這個(gè)連接器中,很多控制器可以操作 2 個(gè)、4 個(gè)設(shè)置 8 個(gè)相同的設(shè)備。
控制器與設(shè)備之間的接口通常是一個(gè)低層次的接口。例如,磁盤(pán)可能被格式化為 2,000,000 個(gè)扇區(qū),每個(gè)磁道 512 字節(jié)。然而,實(shí)際從驅(qū)動(dòng)出來(lái)的卻是一個(gè)串行的比特流,從一個(gè)前導(dǎo)符(preamble)開(kāi)始,然后是一個(gè)扇區(qū)中的 4096 位,最后是一個(gè)校驗(yàn)和 或 ECC(錯(cuò)誤碼,Error-Correcting Code)。前導(dǎo)符是在對(duì)磁盤(pán)進(jìn)行格式化的時(shí)候?qū)懮先サ?,它包括柱面?shù)和扇區(qū)號(hào),扇區(qū)大小以及類(lèi)似的數(shù)據(jù),此外還包含同步信息。
控制器的任務(wù)是把串行的位流轉(zhuǎn)換為字節(jié)塊,并進(jìn)行必要的錯(cuò)誤校正工作。字節(jié)塊通常會(huì)在控制器內(nèi)部的一個(gè)緩沖區(qū)按位進(jìn)行組裝,然后再對(duì)校驗(yàn)和進(jìn)行校驗(yàn)并證明字節(jié)塊沒(méi)有錯(cuò)誤后,再將它復(fù)制到內(nèi)存中。
三,內(nèi)存映射 I/O
每個(gè)控制器都會(huì)有幾個(gè)寄存器用來(lái)和 CPU 進(jìn)行通信。通過(guò)寫(xiě)入這些寄存器,操作系統(tǒng)可以命令設(shè)備發(fā)送數(shù)據(jù),接收數(shù)據(jù)、開(kāi)啟或者關(guān)閉設(shè)備等。通過(guò)從這些寄存器中讀取信息,操作系統(tǒng)能夠知道設(shè)備的狀態(tài),是否準(zhǔn)備接受一個(gè)新命令等。
為了控制寄存器,許多設(shè)備都會(huì)有數(shù)據(jù)緩沖區(qū)(data buffer),來(lái)供系統(tǒng)進(jìn)行讀寫(xiě)。例如,在屏幕上顯示一個(gè)像素的常規(guī)方法是使用一個(gè)視頻 RAM,這一 RAM 基本上只是一個(gè)數(shù)據(jù)緩沖區(qū),用來(lái)供程序和操作系統(tǒng)寫(xiě)入數(shù)據(jù)。 那么問(wèn)題來(lái)了,CPU 如何與設(shè)備寄存器和設(shè)備數(shù)據(jù)緩沖區(qū)進(jìn)行通信呢?存在兩個(gè)可選的方式。第一種方法是,每個(gè)控制寄存器都被分配一個(gè) I/O 端口(I/O port)號(hào),這是一個(gè) 8 位或 16 位的整數(shù)。所有 I/O 端口的集合形成了受保護(hù)的 I/O 端口空間,以便普通用戶程序無(wú)法訪問(wèn)它(只有操作系統(tǒng)可以訪問(wèn))。使用特殊的 I/O 指令像是
CPU 可以讀取控制寄存器 PORT 的內(nèi)容并將結(jié)果放在 CPU 寄存器 REG 中。類(lèi)似的,使用
CPU 可以將 REG 的內(nèi)容寫(xiě)到控制寄存器中。大多數(shù)早期計(jì)算機(jī),包括幾乎所有大型主機(jī),如 IBM 360 及其所有后續(xù)機(jī)型,都是以這種方式工作的。
控制寄存器是一個(gè)處理器寄存器而改變或控制的一般行為 CPU 或其他數(shù)字設(shè)備??刂萍拇嫫鲌?zhí)行的常見(jiàn)任務(wù)包括中斷控制,切換尋址模式,分頁(yè)控制和協(xié)處理器控制。
在這一方案中,內(nèi)存地址空間和 I/O 地址空間是不相同的,如下圖所示:

指令:
和
這一設(shè)計(jì)中完全不同。前者讀取 I/O端口 4 的內(nèi)容并將其放入 R0,而后者讀取存儲(chǔ)器字 4 的內(nèi)容并將其放入 R0。這些示例中的 4 代表不同且不相關(guān)的地址空間。
第二個(gè)方法是 PDP-11 引入的,
什么是 PDP-11?

它將所有控制寄存器映射到內(nèi)存空間中,如下圖所示:

內(nèi)存映射的 I/O是在 CPU 與其連接的外圍設(shè)備之間交換數(shù)據(jù)和指令的一種方式,這種方式是處理器和 IO 設(shè)備共享同一內(nèi)存位置的內(nèi)存,即處理器和 IO 設(shè)備使用內(nèi)存地址進(jìn)行映射。
在大多數(shù)系統(tǒng)中,分配給控制寄存器的地址位于或者靠近地址的頂部附近。
下面是采用的一種混合方式:

這種方式具有與內(nèi)存映射 I/O 的數(shù)據(jù)緩沖區(qū),而控制寄存器則具有單獨(dú)的 I/O 端口。x86 采用這一體系結(jié)構(gòu)。在 IBM PC 兼容機(jī)中,除了 0 到 64K - 1 的 I/O 端口之外,640 K 到 1M - 1 的內(nèi)存地址保留給設(shè)備的數(shù)據(jù)緩沖區(qū)。
這些方案是如何工作的呢?當(dāng) CPU 想要讀入一個(gè)字的時(shí)候,無(wú)論是從內(nèi)存中讀入還是從 I/O 端口讀入,它都要將需要的地址放到總線地址線上,然后在總線的一條控制線上調(diào)用一個(gè) READ 信號(hào)。還有第二條信號(hào)線來(lái)表明需要的是 I/O 空間還是內(nèi)存空間。如果是內(nèi)存空間,內(nèi)存將響應(yīng)請(qǐng)求。如果是 I/O 空間,那么 I/O 設(shè)備將響應(yīng)請(qǐng)求。如果只有內(nèi)存空間,那么每個(gè)內(nèi)存模塊和每個(gè) I/O 設(shè)備都會(huì)將地址線和它所服務(wù)的地址范圍進(jìn)行比較。如果地址落在這一范圍之內(nèi),它就會(huì)響應(yīng)請(qǐng)求。絕對(duì)不會(huì)出現(xiàn)地址既分配給內(nèi)存又分配給 I/O 設(shè)備,所以不會(huì)存在歧義和沖突。
內(nèi)存映射 I/O 的優(yōu)點(diǎn)和缺點(diǎn):
這兩種尋址控制器的方案具有不同的優(yōu)缺點(diǎn)。先來(lái)看一下內(nèi)存映射 I/O 的優(yōu)點(diǎn)。
第一,如果需要特殊的 I/O 指令讀寫(xiě)設(shè)備控制寄存器,那么訪問(wèn)這些寄存器需要使用匯編代碼,因?yàn)樵?C 或 C++ 中不存在執(zhí)行 IN 和 OUT指令的方法。調(diào)用這樣的過(guò)程增加了 I/O 的開(kāi)銷(xiāo)。在內(nèi)存映射中,控制寄存器只是內(nèi)存中的變量,在 C 語(yǔ)言中可以和其他變量一樣進(jìn)行尋址。
第二,對(duì)于內(nèi)存映射 I/O ,不需要特殊的保護(hù)機(jī)制就能夠阻止用戶進(jìn)程執(zhí)行 I/O 操作。操作系統(tǒng)需要保證的是禁止把控制寄存器的地址空間放在用戶的虛擬地址中就可以了。
第三,對(duì)于內(nèi)存映射 I/O,可以引用內(nèi)存的每一條指令也可以引用控制寄存器,便于引用。
在計(jì)算機(jī)設(shè)計(jì)中,幾乎所有的事情都要權(quán)衡。內(nèi)存映射 I/O 也是一樣,它也有自己的缺點(diǎn)。首先,大部分計(jì)算機(jī)現(xiàn)在都會(huì)有一些對(duì)于內(nèi)存字的緩存。緩存一個(gè)設(shè)備控制寄存器的代價(jià)是很大的。為了避免這種內(nèi)存映射 I/O 的情況,硬件必須有選擇性的禁用緩存,例如,在每個(gè)頁(yè)面上禁用緩存,這個(gè)功能為硬件和操作系統(tǒng)增加了額外的復(fù)雜性,因此必須選擇性的進(jìn)行管理。
第二點(diǎn),如果僅僅只有一個(gè)地址空間,那么所有的內(nèi)存模塊(memory modules)和所有的 I/O 設(shè)備都必須檢查所有的內(nèi)存引用來(lái)推斷出誰(shuí)來(lái)進(jìn)行響應(yīng)。
什么是內(nèi)存模塊?在計(jì)算中,存儲(chǔ)器模塊是其上安裝有存儲(chǔ)器集成電路的印刷電路板。

如果計(jì)算機(jī)是一種單總線體系結(jié)構(gòu)的話,如下圖所示:

讓每個(gè)內(nèi)存模塊和 I/O 設(shè)備查看每個(gè)地址是簡(jiǎn)單易行的。
然而,現(xiàn)代個(gè)人計(jì)算機(jī)的趨勢(shì)是專(zhuān)用的高速內(nèi)存總線,如下圖所示:

裝備這一總線是為了優(yōu)化內(nèi)存訪問(wèn)速度,x86 系統(tǒng)還可以有多種總線(內(nèi)存、PCIe、SCSI 和 USB)。如下圖所示:

在內(nèi)存映射機(jī)器上使用單獨(dú)的內(nèi)存總線的麻煩之處在于,I/O 設(shè)備無(wú)法通過(guò)內(nèi)存總線查看內(nèi)存地址,因此它們無(wú)法對(duì)其進(jìn)行響應(yīng)。此外,必須采取特殊的措施使內(nèi)存映射 I/O 工作在具有多總線的系統(tǒng)上。一種可能的方法是首先將全部?jī)?nèi)存引用發(fā)送到內(nèi)存,如果內(nèi)存響應(yīng)失敗,CPU 再?lài)L試其他總線。
第二種設(shè)計(jì)是在內(nèi)存總線上放一個(gè)探查設(shè)備,放過(guò)所有潛在指向所關(guān)注的 I/O 設(shè)備的地址。此處的問(wèn)題是,I/O 設(shè)備可能無(wú)法以內(nèi)存所能達(dá)到的速度處理請(qǐng)求。
第三種可能的設(shè)計(jì)是在內(nèi)存控制器中對(duì)地址進(jìn)行過(guò)濾,這種設(shè)計(jì)與上圖所描述的設(shè)計(jì)相匹配。這種情況下,內(nèi)存控制器芯片中包含在引導(dǎo)時(shí)預(yù)裝載的范圍寄存器。這一設(shè)計(jì)的缺點(diǎn)是需要在引導(dǎo)時(shí)判定哪些內(nèi)存地址而不是真正的內(nèi)存地址。因而,每一設(shè)計(jì)都有支持它和反對(duì)它的論據(jù),所以折中和權(quán)衡是不可避免的。
直接內(nèi)存訪問(wèn)
無(wú)論一個(gè) CPU 是否具有內(nèi)存映射 I/O,它都需要尋址設(shè)備控制器以便與它們交換數(shù)據(jù)。CPU 可以從 I/O 控制器每次請(qǐng)求一個(gè)字節(jié)的數(shù)據(jù),但是這么做會(huì)浪費(fèi) CPU 時(shí)間,所以經(jīng)常會(huì)用到一種稱(chēng)為直接內(nèi)存訪問(wèn)(Direct Memory Access) 的方案。為了簡(jiǎn)化,我們假設(shè) CPU 通過(guò)單一的系統(tǒng)總線訪問(wèn)所有的設(shè)備和內(nèi)存,該總線連接 CPU 、內(nèi)存和 I/O 設(shè)備,如下圖所示

現(xiàn)代操作系統(tǒng)實(shí)際更為復(fù)雜,但是原理是相同的。如果硬件有DMA 控制器,那么操作系統(tǒng)只能使用 DMA。有時(shí)這個(gè)控制器會(huì)集成到磁盤(pán)控制器和其他控制器中,但這種設(shè)計(jì)需要在每個(gè)設(shè)備上都裝有一個(gè)分離的 DMA 控制器。單個(gè)的 DMA 控制器可用于向多個(gè)設(shè)備傳輸,這種傳輸往往同時(shí)進(jìn)行。
不管 DMA 控制器的物理地址在哪,它都能夠獨(dú)立于 CPU 從而訪問(wèn)系統(tǒng)總線,如上圖所示。它包含幾個(gè)可由 CPU 讀寫(xiě)的寄存器,其中包括一個(gè)內(nèi)存地址寄存器,字節(jié)計(jì)數(shù)寄存器和一個(gè)或多個(gè)控制寄存器??刂萍拇嫫髦付ㄒ褂玫?I/O 端口、傳送方向(從 I/O 設(shè)備讀或?qū)懙?I/O 設(shè)備)、傳送單位(每次一個(gè)字節(jié)或者每次一個(gè)字)以及在一次突發(fā)傳送中要傳送的字節(jié)數(shù)。
為了解釋 DMA 的工作原理,我們首先看一下不使用 DMA 該如何進(jìn)行磁盤(pán)讀取。 首先,控制器從磁盤(pán)驅(qū)動(dòng)器串行地、一位一位的讀一個(gè)塊(一個(gè)或多個(gè)扇區(qū)),直到將整塊信息放入控制器的內(nèi)部緩沖區(qū)。
讀取校驗(yàn)和以保證沒(méi)有發(fā)生讀錯(cuò)誤。然后控制器會(huì)產(chǎn)生一個(gè)中斷,當(dāng)操作系統(tǒng)開(kāi)始運(yùn)行時(shí),它會(huì)重復(fù)的從控制器的緩沖區(qū)中一次一個(gè)字節(jié)或者一個(gè)字地讀取該塊的信息,并將其存入內(nèi)存中。
