操作系統(tǒng)訪問IO設(shè)備的過程,經(jīng)歷過很多個層次,就像網(wǎng)絡(luò)層協(xié)議一樣,每一層干每一層的事情,每一層又向上層提供接口,操作系統(tǒng)訪問IO設(shè)備的層次結(jié)構(gòu)如下圖所示: IO結(jié)構(gòu)層次 因此,本篇文章分為5個部分,逐步闡述操作系統(tǒng)訪問IO設(shè)備的過程
IO設(shè)備分類:IO設(shè)備大致分為塊設(shè)備,字符設(shè)備,網(wǎng)絡(luò)設(shè)備,定時器等,本篇文章主要關(guān)注的是塊設(shè)備,字符設(shè)備,網(wǎng)絡(luò)設(shè)備,這些設(shè)備是我們程序開發(fā)中經(jīng)常涉及的部分。 塊設(shè)備: 塊設(shè)備將數(shù)據(jù)分隔成多個固定大小的塊進行存儲,塊是數(shù)據(jù)存儲的最小單位,每個塊的大小可以為512字節(jié)-65536字節(jié),每個塊都有唯一的地址,因此塊設(shè)備是可以尋址的,每次讀寫塊設(shè)備的數(shù)據(jù)時,都是以塊為最小單位讀取或者寫入連續(xù)的多個塊。 例如磁盤就是塊設(shè)備,它的塊大小通常是512個字節(jié)等于磁盤的一個扇區(qū)的大小,因此一個扇區(qū)就是一個塊,磁盤的數(shù)據(jù)是由很多個扇區(qū)構(gòu)成。 通常塊設(shè)備可以順序訪問或者隨機訪問,應(yīng)用程序訪問塊設(shè)備時,通常不是直接訪問的,而是通過文件系統(tǒng),文件系統(tǒng)對塊設(shè)備進行統(tǒng)一管理,將塊設(shè)備的各類細節(jié)封裝起來,文件系統(tǒng)管理的是邏輯設(shè)備即文件,文件系統(tǒng)統(tǒng)一提供read,write,seek等接口間接訪問塊設(shè)備,文件系統(tǒng)將文件名和塊設(shè)備的每一個塊之間的映射關(guān)系隱藏起來,因此塊設(shè)備對于應(yīng)用程序來說是透明。 當(dāng)然有些特殊的應(yīng)用程序例如數(shù)據(jù)庫管理系統(tǒng)可以直接訪問塊設(shè)備,對塊設(shè)備的每個塊采用專用的數(shù)據(jù)結(jié)構(gòu)進行管理,以達到最佳的讀寫效率,這些專用的數(shù)據(jù)結(jié)構(gòu)有B+樹,LSVM樹等,這些數(shù)據(jù)結(jié)構(gòu)都有各自的應(yīng)用場景。 字符設(shè)備: 字符設(shè)備是不可以存儲和不可以尋址的,字符設(shè)備每次讀入或者寫入一個字符,常見的字符設(shè)備包括鼠標,鍵盤,打印機等,操作系統(tǒng)通常會提供一些get或者put的方法來讀取或者寫入一個字節(jié),通常會有標準庫對操作系統(tǒng)提供的get或者put方法進行封裝,例如增加了字符緩沖,支持字符流讀寫,支持按行讀,按行寫,編輯緩沖數(shù)據(jù)等。 網(wǎng)絡(luò)設(shè)備: 網(wǎng)絡(luò)設(shè)備通常用于發(fā)送或者接受網(wǎng)絡(luò)數(shù)據(jù),這些網(wǎng)絡(luò)數(shù)據(jù)通常是網(wǎng)絡(luò)包的形式,不同于塊設(shè)備,操作系統(tǒng)會提供專門的網(wǎng)絡(luò)接口進行網(wǎng)絡(luò)數(shù)據(jù)的發(fā)送和接受,網(wǎng)絡(luò)接口通常就是Socket。 Socket實現(xiàn)了傳輸層的協(xié)議,它就像一個電源插座,任何電器都可以插入電源插座,一旦插入電源插座就可以通電,因此應(yīng)用程序也可以用創(chuàng)建一個Socket,Socket分為服務(wù)端Socket和客戶端Socket,服務(wù)端Socket啟動后,會一直監(jiān)聽來自客戶端Socket的來電,一旦接到來電就可以轉(zhuǎn)給一個接線員,這個接線員也是一個Socket,由它來負責(zé)與客戶端Socket的通信。 Socket通常支持BIO,NIO,AIO幾種模式,不同的操作系統(tǒng)支持的模式不同,通常都支持BIO和NIO,根據(jù)不同的應(yīng)用場景可以選擇不同的模式,沒有絕對可言。 定時器: 定時器通常分為硬定時器和軟定時器。 硬定時器: 硬定時器通常是由晶體震蕩器,計數(shù)器和存儲寄存器組成,當(dāng)把一塊石英晶體適當(dāng)?shù)厍懈詈?,并在它上加上一定的電壓后,它就可以非常精確地產(chǎn)生周期性的時鐘信號,這些信號的頻率可以是幾百赫茲,幾千赫茲等,這個頻率值跟所選的晶體類型有關(guān)。 硬定時器的基本原理是這樣的: 每個定時器會在存儲寄存器設(shè)置一個計數(shù)值,每次定時器啟動時,將這個計數(shù)值設(shè)置到計數(shù)器中,每次晶體震蕩后,產(chǎn)生一個時鐘信號,該信號被送入到計數(shù)器,計數(shù)器就減1,直到計數(shù)器變?yōu)?,那就表示定時器觸發(fā)了,它會給CPU發(fā)送一個時鐘中斷,CPU接收到這個中斷后會進入時鐘處理程序。 時鐘處理程序會進行一些防止進程時間片超時,進程CPU使用情況記賬,維護計算機時間,計算機硬件監(jiān)控,進程性能數(shù)據(jù)剖析等工作。 硬件定時器可以分為一次性定時器和永久性定時器,一次性定時器表示定時器觸發(fā)后后,就自動停止了,永久性定時器則每次定時器觸發(fā)后,會將存儲寄存器中的計數(shù)值重新賦值給計數(shù)器,然后重新啟動定時器,一直運行,直到計算機關(guān)閉。 軟定時器: 有些高性能的工作需要高頻率地執(zhí)行,例如一個高性能的網(wǎng)絡(luò),網(wǎng)絡(luò)帶寬為千兆級別以上,為了保證高效率地輸出,每隔12us就要發(fā)送一個數(shù)據(jù)包,如果采用硬定時器,每隔12us產(chǎn)生中斷后,會進行一系列的處理,包括進程上下文,流水線,高速緩沖,TLB,MMU等重新切換等,這一繁雜的切換工作需要的時間可能已經(jīng)接近12us,那么這就意味著從中斷開始到真正開始發(fā)送數(shù)據(jù)包,會經(jīng)過一段時間,這個時間對于一個高性能的網(wǎng)絡(luò)來說是不能容忍的,因此這種場景可以采用軟定時器,軟定時器不需要中斷,每次發(fā)送數(shù)據(jù)包時,會根據(jù)硬定時器+12us設(shè)置一個超時時間,每次發(fā)送完數(shù)據(jù)包切換到用戶態(tài)后,用戶程序檢查這個超時時間是不是已經(jīng)過期了,如果過期了就立即發(fā)送一個數(shù)據(jù)包,對于這類高性能的場景,軟定時器比較合適,當(dāng)然如果沒有要求那么高的性能要求的話,一般的硬定時器中斷足夠應(yīng)付了。 設(shè)備控制器:操作系統(tǒng)是不直接操作IO設(shè)備,常常是通過設(shè)備控制器間接訪問IO設(shè)備,可以說設(shè)備控制器是CPU與IO設(shè)備的橋梁,設(shè)備控制器可以連接單個設(shè)備,也可以同時連接多個設(shè)備,如果連接多個設(shè)備,設(shè)備控制器和設(shè)備之間需要增加一個仲裁設(shè)備,這個仲裁設(shè)備負責(zé)仲裁設(shè)備控制器要和那個IO設(shè)備進行交互。 設(shè)備控制器負責(zé)的工作如下:
設(shè)備控制器提供了4種寄存器即狀態(tài)寄存器,數(shù)據(jù)輸入寄存器,數(shù)據(jù)輸出寄存器,控制寄存器,操作系統(tǒng)和設(shè)備控制器之間的交互就是通過這些設(shè)備寄存器,因此它們的交互方式類似于生產(chǎn)者和消費者的關(guān)系。 數(shù)據(jù)輸出寄存器: 操作系統(tǒng)可以將CPU寄存器的數(shù)據(jù)發(fā)送到數(shù)據(jù)輸出寄存器,設(shè)備控制器得知數(shù)據(jù)輸出寄存器有數(shù)據(jù)后,就將數(shù)據(jù)輸出寄存器的數(shù)據(jù)發(fā)送給IO設(shè)備。 數(shù)據(jù)輸入寄存器: 設(shè)備控制器讀入IO設(shè)備的數(shù)據(jù)后,存儲在數(shù)據(jù)輸入寄存器,然后操作系統(tǒng)讀取數(shù)據(jù)輸入寄存器的數(shù)據(jù)到CPU寄存器中。 狀態(tài)寄存器: 存儲設(shè)備相關(guān)的狀態(tài),例如設(shè)備是否已經(jīng)就緒,數(shù)據(jù)輸入寄存器是不是有數(shù)據(jù)了等,操作系統(tǒng)可以讀取該寄存器的值來查看設(shè)備的各類狀態(tài)。 控制寄存器: 操作系統(tǒng)可以寫入控制寄存器從而告知設(shè)備控制器執(zhí)行什么IO操作。 另外,為了協(xié)調(diào)CPU和IO設(shè)備的讀寫速度差異,設(shè)備控制器通常有數(shù)據(jù)緩沖,用于緩沖從設(shè)備讀入的數(shù)據(jù),或者從操作系統(tǒng)寫入的數(shù)據(jù),例如操作系統(tǒng)將數(shù)據(jù)寫入到數(shù)據(jù)輸出寄存器后,設(shè)備控制器將數(shù)據(jù)輸出寄存器的數(shù)據(jù)直接寫入到數(shù)據(jù)緩沖,而不是直接發(fā)送到IO設(shè)備,這個很好理解,因為設(shè)備控制器寫入數(shù)據(jù)到IO設(shè)備的速度與CPU的發(fā)送速度相比差異太大了。 操作系統(tǒng)怎么讀寫設(shè)備寄存器呢,有兩種方式 1.IO指令 每個設(shè)備寄存器都有一個地址,它與內(nèi)存的地址不同,這個地址稱為IO端口地址,操作系統(tǒng)支持的IO端口地址有65536個即0~64K-1,IO端口地址的范圍叫做IO端口空間,內(nèi)存地址范圍叫做內(nèi)存空間,因此IO端口空間與內(nèi)存空間是獨立的。 假如CPU要讀取一個端口地址對應(yīng)設(shè)備寄存器,過程如下: 1.CPU將IO端口地址放到地址總線上,然后在控制總線上設(shè)置一個READ信號,此時只是CPU只是要表明要讀取某個地址的數(shù)據(jù)了,至于這個地址來自哪個空間,還需要下一步操作。 2.CPU設(shè)備另外一條信號線,設(shè)置信號線要訪問IO端口空間。 3.所有的設(shè)備控制器接收到該信號線后,發(fā)現(xiàn)是訪問IO端口空間,就會檢查地址總線上的地址是不是自己的寄存器的地址,總有一個設(shè)備控制器接受這個地址,不是自己的地址設(shè)備控制器直接忽略這個信號。 下面表格為PC設(shè)備中常見的設(shè)備IO端口地址分布圖
另外,CPU提供了專門的IO指令,通過IO指令發(fā)送內(nèi)容到IO端口地址或者從IO端口地址獲取內(nèi)容來實現(xiàn)設(shè)備寄存器的讀寫, 常見的IO指令有IN和OUT IN AX,DX DX寄存器存儲的是IO端口,該指令表示獲取IO端口指向的設(shè)備寄存器的內(nèi)容到AX寄存器。 OUT DX,AX DX寄存器存儲的IO端口,該指令表示將AX寄存器的內(nèi)容寫入到IO端口指向的設(shè)備寄存器。 2.內(nèi)存映射IO CPU訪問任何數(shù)據(jù)都是通過地址總線,一個32位的地址總線可以尋址的范圍是4個G,從上文知道,如果有IO端口地址空間的話,需要單獨的一個信號線表明地址線訪問的那個空間,假設(shè)該信號線訪問的是內(nèi)存空間。 這個內(nèi)存空間內(nèi)大部分地址分給了主存,還有一小部分地址落在了其它的內(nèi)存或者設(shè)備寄存器,例如圖形控制器的數(shù)據(jù)緩沖,BIOS的ROM等,通常這部分地址在地址總線范圍的低地址處,如下圖 內(nèi)存映射IO 因此通過內(nèi)存映射IO,我們不需要專門的IO指令,直接通過內(nèi)存相關(guān)的指令例如MOV,LOAD等命令就可以讀寫設(shè)備寄存器或者設(shè)備控制器的數(shù)據(jù)緩沖,當(dāng)?shù)刂房偩€上寫入了地址后,主存,設(shè)備控制器,BIOS等都會檢查這個地址是不是屬于自己的范圍,如果是自己的范圍內(nèi)的地址,就會接受這個地址,并且做出響應(yīng)。 操作系統(tǒng)與設(shè)備控制器交互的方式通常有3種: 程序控制IO 程序控制IO也叫輪詢,假設(shè)CPU寫數(shù)據(jù)到IO設(shè)備,通常應(yīng)用程序會在用戶空間分配一個緩沖,用戶緩沖要發(fā)送的數(shù)據(jù),然后進行系統(tǒng)調(diào)用,通過系統(tǒng)調(diào)用,內(nèi)核會將用戶緩沖拷貝到內(nèi)核緩沖,這個內(nèi)存緩沖可以認為是一個發(fā)送數(shù)組,然后對發(fā)送數(shù)組的每個字節(jié)逐個按照以下流程寫入到IO設(shè)備,流程如下圖所示: CPU輪詢方式會一直檢查狀態(tài)寄存器,一直到狀態(tài)寄存器不忙時,才會寫入發(fā)送數(shù)組的當(dāng)前字節(jié)到數(shù)據(jù)輸出寄存器,同時設(shè)置控制寄存器的寫位和就緒位,通知設(shè)備控制器要執(zhí)行一個寫操作而且數(shù)據(jù)已經(jīng)準備好了。 設(shè)備控制器檢查到控制寄存器的就緒位已經(jīng)就緒后,會進一步檢查要執(zhí)行什么操作,發(fā)現(xiàn)寫位為1,就會從數(shù)據(jù)輸出寄存器獲取數(shù)據(jù),寫到IO設(shè)備,完成后,就會清除控制寄存器的就緒位,忙位,故障位,這樣CPU就會繼續(xù)寫入數(shù)據(jù)了。 IO設(shè)備的寫速度與CPU相比差的十萬八千里,因此CPU大部分的時間都在檢查狀態(tài)寄存器的忙位,浪費了大量的CPU時間,這也是輪詢方式最大的缺點。 中斷驅(qū)動IO 假設(shè)CPU寫數(shù)據(jù)到IO設(shè)備,通常應(yīng)用程序會在用戶空間分配一個緩沖,用戶緩沖要發(fā)送的數(shù)據(jù),然后進行系統(tǒng)調(diào)用,通過系統(tǒng)調(diào)用,內(nèi)核會將用戶緩沖拷貝到內(nèi)核緩沖,這個內(nèi)存緩沖可以認為是一個發(fā)送數(shù)組,然后對發(fā)送數(shù)組的每個字節(jié)逐個按照以下流程寫入到IO設(shè)備,假設(shè)發(fā)送數(shù)組剩余要發(fā)送的字節(jié)數(shù)為count,如下圖所示為中斷驅(qū)動IO的流程圖 由上圖所示分為3個子流程 CPU寫入流程: CPU寫入流程與輪詢看起來有點類似,不一樣的地方在于,用戶進程在寫入第一個數(shù)據(jù)到寄存器后,就被阻塞了,CPU此時調(diào)度其它進程運行。 設(shè)備控制器流程: 這個流程與輪詢中的設(shè)備控制器流程比較類似,不同的是,設(shè)備控制器寫數(shù)據(jù)到IO設(shè)備后,會發(fā)送一個設(shè)備中斷信號給CPU,通知CPU可以繼續(xù)發(fā)送數(shù)據(jù)了。 CPU中斷處理程序: 每次CPU執(zhí)行時,會先檢查中斷線上是不是有中斷信號,如果有中斷信號,就會去執(zhí)行中斷處理程序,對于設(shè)備的中斷處理程序,如果沒有發(fā)送的數(shù)據(jù)了,就將用戶進程加入到CPU的就緒隊列中,CPU后續(xù)會調(diào)度用戶進程,阻塞就解除了,調(diào)用就返回了,如果有發(fā)送的數(shù)據(jù),就繼續(xù)發(fā)送到設(shè)備控制器的寄存器中。 中斷處理程序返回后,會被切換到被中斷的進程,繼續(xù)執(zhí)行。 中斷驅(qū)動IO相比輪詢來說,不需要CPU一直等IO設(shè)備寫入完成,而是可以將寫入寄存器后,直接阻塞,調(diào)度其它的進程來使用CPU,當(dāng)IO設(shè)備寫入完成后,發(fā)送中斷信號,通知CPU,CPU執(zhí)行中斷處理程序繼續(xù)寫入下一個字節(jié),最終發(fā)送完成后,解除阻塞,這樣CPU就可以充分利用,減少了CPU的浪費。 DMA 假設(shè)CPU寫數(shù)據(jù)到IO設(shè)備,通常應(yīng)用程序會在用戶空間分配一個緩沖,用戶緩沖要發(fā)送的數(shù)據(jù),然后進行系統(tǒng)調(diào)用,通過系統(tǒng)調(diào)用,內(nèi)核會將用戶緩沖拷貝到內(nèi)核緩沖,這個內(nèi)存緩沖可以認為是一個發(fā)送數(shù)組。 DMA的處理流程如下: DMA DMA的處理流程也可以分為3個子流程 CPU流程 : CPU傳輸發(fā)送數(shù)組的起始地址,發(fā)送數(shù)組的大小給DMA,此時用戶進程A就從CPU運行隊列移除,此時用戶進程A阻塞,CPU調(diào)度其它的進程運行。 DMA流程: DMA根據(jù)發(fā)送數(shù)組的起始地址,檢查尚未發(fā)送的字節(jié)數(shù)count,如果count<0, 那么就是傳輸數(shù)據(jù)給設(shè)備寄存器,然后就等待設(shè)備控制器的應(yīng)答,如果 count=0,那么DMA發(fā)送完成了,它會給CPU發(fā)送一個中斷信號,CPU收到這個中斷信號后,會執(zhí)行DMA的中斷處理程序,將用戶進程A加入到CPU的就緒隊列,后續(xù)CPU會調(diào)度用戶進程A去運行,這個時候用戶進程A調(diào)用返回。 設(shè)備控制器流程: 設(shè)備控制器的流程與輪詢和中斷類似,只是設(shè)備控制器發(fā)送完數(shù)據(jù)后,會發(fā)送一個應(yīng)答給DMA,DMA收到這個應(yīng)答后,會將coun-1,然后調(diào)度發(fā)送下一個字節(jié)。 可以看出DMA不需要設(shè)備控制器發(fā)送中斷給CPU,它成為了CPU的代理,CPU可以專注地做其它的事情,只是DMA傳輸完成后,才會中斷一下CPU。 另外DMA每次控制設(shè)備控制器寫數(shù)據(jù)時,都會先控制住地址總線,這樣就會跟CPU搶時鐘周期,這個也是不可以避免的。 DMA控制器可以連接多個設(shè)備控制器,通過一定的算法,來切換每次控制的設(shè)備控制器,另外復(fù)雜一些的DMA控制器也可以一次控制設(shè)備控制器發(fā)送多個字節(jié),這樣一次性占據(jù)地址總線的時間就比較長,這樣的效率也會更高,只是CPU如果此時需要用地址總線,那就需要等待一段時間了,CPU周期被浪費了。 設(shè)備驅(qū)動程序我們上面闡述操作系統(tǒng)通過控制設(shè)備控制器來間接控制IO設(shè)備,其實就是設(shè)備驅(qū)動程序來控制設(shè)備控制器。 設(shè)備驅(qū)動程序通常位于內(nèi)核,它封裝對設(shè)備控制器操作的所有細節(jié)和差異,并且提供了標準的接口供內(nèi)核的IO子系統(tǒng)調(diào)用,這一點類似于網(wǎng)絡(luò)協(xié)議的分層結(jié)構(gòu),每一層將數(shù)據(jù)封裝好后提供給上一層使用,上一層不用考慮下一層的實現(xiàn)細節(jié)。 可以說設(shè)備驅(qū)動程序?qū)⒂布虸O子系統(tǒng)調(diào)用分離開來,每一類設(shè)備都需要專門的設(shè)備驅(qū)動程序進行控制,一般這些設(shè)備驅(qū)動程序都是由設(shè)備提供商來負責(zé)編寫,通常不同的操作系統(tǒng)設(shè)備驅(qū)動程序都有不同的開發(fā)標準,因此只要設(shè)備提供商按照操作系統(tǒng)要求的標準開發(fā)設(shè)備驅(qū)動程序,增加任何的IO設(shè)備和控制器對于操作系統(tǒng)來說都是透明的,當(dāng)然這對操作系統(tǒng)來說好處多多,對于設(shè)備提供商來說好處不多,他們需要為各種操作系統(tǒng)系統(tǒng)編寫驅(qū)動程序,這樣才能爭取不同操作系統(tǒng)的用戶。 通常設(shè)備驅(qū)動程序不添加任何用戶策略,只負責(zé)與硬件打交道,它通常會將上層IO子系統(tǒng)發(fā)送過來的抽象的讀寫請求,轉(zhuǎn)化為實際的對硬件的IO操作,保證硬件可用并提供硬件原始數(shù)據(jù),原始的數(shù)據(jù)交由IO子系統(tǒng)進行二次加工,IO子系統(tǒng)再根據(jù)根據(jù)各類用戶策略對原始數(shù)據(jù)進行處理。 IO子系統(tǒng)IO子系統(tǒng)位于內(nèi)核,處于設(shè)備驅(qū)動程序的上一層,它是設(shè)備無關(guān)的,主要提供文件系統(tǒng),IO調(diào)度,緩沖,高速緩沖,設(shè)備保護,錯誤處理等。 增加新的設(shè)備驅(qū)動程序?qū)O子系統(tǒng)沒有i影響,因為每個設(shè)備驅(qū)動程序向IO子系統(tǒng)提供標準的訪問接口。 文件系統(tǒng): 文件系統(tǒng)主要負責(zé)對塊設(shè)備進行統(tǒng)一管理,抽象出文件這個邏輯設(shè)備的概念,建立文件與塊設(shè)備的每個塊的映射關(guān)系等,文件系統(tǒng)也負責(zé)根據(jù)文件名定位到設(shè)備驅(qū)動程序和具體的某個設(shè)備,這個以后單獨寫文章闡述。 IO調(diào)度 操作系統(tǒng)為每個設(shè)備維護一個IO請求隊列,當(dāng)進程進行IO請求時,會將這些IO請求加入到這個隊列中,如果按照先進先出的方式順序訪問設(shè)備,從系統(tǒng)總體來講,并不一定是高效的,通常需要合適的IO調(diào)度算法來高效和充分地利用設(shè)備。 另外不同的IO請求有不同的優(yōu)先級,有些IO請求優(yōu)先級天生比較高,它可以獲得優(yōu)先執(zhí)行的權(quán)利,例如一個缺頁IO請求就比其它的普通應(yīng)用程序的請求更加緊急,也會有更高的優(yōu)先級。 緩沖 采用緩沖有以下幾個理由: 一:設(shè)備控制器通常有單獨的數(shù)據(jù)緩沖,應(yīng)用程序也需要有自己的緩沖,應(yīng)用程序?qū)憯?shù)據(jù)時,通常寫入到自己的緩沖,而不是直接寫到設(shè)備控制器的緩沖,這是由于IO設(shè)備處理數(shù)據(jù)較慢,設(shè)備控制器的緩沖,很容易就滿了,因此應(yīng)用程序設(shè)置自己的緩沖,這樣即使設(shè)備控制器緩沖滿了,應(yīng)用程序照樣可以寫數(shù)據(jù),不至于阻塞,另外應(yīng)用程序設(shè)置自己的緩沖,可以靈活增加緩沖的容量,也可以設(shè)置多個緩沖,例如雙緩沖,循環(huán)緩沖等。 二.通常應(yīng)用程序自己的緩沖在用戶空間,操作系統(tǒng)可以將用戶緩沖中的數(shù)據(jù)發(fā)送到設(shè)備控制器上的數(shù)據(jù)緩沖,設(shè)備控制器再發(fā)送數(shù)據(jù)緩沖的數(shù)據(jù)到設(shè)備,這樣有兩個問題,一個是:用戶空間的緩沖通常是在用戶空間的某些頁上,一旦這些頁被換出了,操作系統(tǒng)讀取這些頁的數(shù)據(jù)時發(fā)現(xiàn)頁不在就會進行缺頁處理,這個很影響效率,另外一個是:操作系統(tǒng)發(fā)送數(shù)據(jù)時,用戶空間的緩沖被修改了,此時就造成了數(shù)據(jù)的破壞,因此通常操作系統(tǒng)會在內(nèi)核開辟一塊空間叫做內(nèi)核緩沖,應(yīng)用程序?qū)懭霐?shù)據(jù)時,先將用戶緩沖復(fù)制到內(nèi)核緩沖,內(nèi)核緩沖再寫入到設(shè)備控制器的數(shù)據(jù)緩沖,此后內(nèi)核就可以放心地發(fā)送數(shù)據(jù)了,不用擔(dān)心頁被換出或者數(shù)據(jù)被修改了,這樣的復(fù)制非常影響效率,因此有些操作系統(tǒng)會采用虛擬內(nèi)存映射和寫時復(fù)制的方式將用戶緩沖和內(nèi)核緩沖指向內(nèi)核的一塊共享緩沖,如果用戶修改了共享緩沖,會通過寫時復(fù)制的方式,拷貝修改的部分到用戶空間,不影響原來的共享緩沖。 整體來說緩沖不光是寫出的緩沖,讀入的緩沖也是同樣的道理,這里不再闡述。 高速緩存: 操作系統(tǒng)可以開辟一塊內(nèi)存,用于存儲熱點的設(shè)備數(shù)據(jù),采用一定的緩存失效算法,剔除失效的緩存,這樣可以每次進行IO讀取操作時,則可以直接從高速緩存中獲取,不在進行任何IO操作。 有時候高速緩存和緩沖可以合二為一,例如一個寫緩沖存儲了要寫出到設(shè)備的數(shù)據(jù),此時如果再讀取這些數(shù)據(jù)時,就可以把寫緩沖當(dāng)做高速緩沖,直接從寫緩沖讀取。 設(shè)備保護: IO設(shè)備保護有以下幾種方式: 1.IO特權(quán)指令,IO指令通常都是特權(quán)指令,這些指令只有操作系統(tǒng)才可執(zhí)行,這樣就杜絕了應(yīng)用程序執(zhí)行IO指令,應(yīng)用程序可以通過系統(tǒng)調(diào)用進行IO操作。 2.通過內(nèi)存映射IO方式訪問設(shè)備控制器數(shù)據(jù)緩沖或者設(shè)備寄存器時,地址總線上一部分地址用于映射到設(shè)備控制器數(shù)據(jù)緩沖或者設(shè)備寄存器,這部分地址由操作系統(tǒng)的內(nèi)存保護模塊進行控制,禁止用戶空間訪問這些地址。 3.IO子系統(tǒng)中文件系統(tǒng)中所有的文件都有訪問的權(quán)限,限制文件的訪問就間接地對文件對應(yīng)的IO設(shè)備進行了保護。 錯誤處理: IO設(shè)備產(chǎn)生的錯誤往往比較多而且雜,當(dāng)錯誤發(fā)生時,IO子系統(tǒng)會盡最大努力對這些錯誤進行處理,許多IO錯誤與特定的設(shè)備有關(guān),這類的錯誤只能交由設(shè)備驅(qū)動程序去處理,但IO子系統(tǒng)有個錯誤處理的流程,按照這個流程進行處理是通用的和設(shè)備無關(guān)的,以下為錯誤處理框架中的幾個關(guān)鍵環(huán)節(jié)的處理過程。 一:一些IO類的錯誤是編程導(dǎo)致的,比如應(yīng)用程序?qū)︽I盤,掃描儀,鼠標進行寫入操作,這些設(shè)備是不支持寫入的,還有就是應(yīng)用程序提供了一個無效的緩沖區(qū)或者參數(shù),這些編程導(dǎo)致的錯誤,IO子系統(tǒng)直接返回相應(yīng)的錯誤碼給應(yīng)用程序。 二:另外一種IO類的錯誤就是設(shè)備確實有問題了,例如應(yīng)用程序試圖寫入一個損壞的磁盤塊,這類錯誤交由設(shè)備驅(qū)動程序解決,如果設(shè)備驅(qū)動程序?qū)嵲诓恢涝趺唇鉀Q,則再返回IO子系統(tǒng),由IO子系統(tǒng)返回給應(yīng)用程序。 三:還有一些IO類錯誤可以交由用戶去決定怎么去解決,例如一個讀磁盤發(fā)生錯誤,可以由IO子系統(tǒng)告知用戶,例如彈出一個對話框,讓用戶去選擇重試次數(shù),忽略錯誤,或者干脆停掉進程。 操作系統(tǒng)訪問IO設(shè)備的流程舉例假設(shè)應(yīng)用程序進行一個系統(tǒng)調(diào)用read讀取磁盤數(shù)據(jù),這個read是阻塞的,設(shè)備驅(qū)動程序與設(shè)備控制器交互方式采用DMA,下面來闡述下整個流程如下圖所示: 1.IO子系統(tǒng)根據(jù)文件名可以定位到設(shè)備驅(qū)動程序,設(shè)備寄存器端口,磁盤物理地址,每個操作系統(tǒng)有各自的實現(xiàn)方式,通常這一部分通常由文件系統(tǒng)來實現(xiàn)。 2.IO子系統(tǒng)根據(jù)磁盤物理地址去高速緩沖中查找,如果高速緩沖命中,則將高速緩沖中的數(shù)據(jù)拷貝到用戶空間緩沖,調(diào)用返回。 5.IO子系統(tǒng)將應(yīng)用程序A從CPU的運行隊列移除,加入到設(shè)備的等待隊列,這個時候應(yīng)用程序阻塞了。 6~8.IO子系統(tǒng)根據(jù)設(shè)備寄存器端口和磁盤物理地址封裝IO請求,將IO請求加入到設(shè)備IO請求隊列中,IO子系統(tǒng)根據(jù)IO請求調(diào)度算法,開始調(diào)用IO請求,最終將應(yīng)用進程A的IO請求發(fā)送給設(shè)備驅(qū)動程序。 9~11.設(shè)備驅(qū)動程序在內(nèi)核空間開辟一塊內(nèi)存空間,作為內(nèi)核緩沖,這塊內(nèi)核緩沖用來接收來自磁盤的數(shù)據(jù),然后設(shè)備驅(qū)動程序控制DMA,發(fā)送控制請求給DMA,然后設(shè)備驅(qū)動程序阻塞了,阻塞的方法有很多中,比如wait。 12.DMA控制磁盤控制器,完成讀取數(shù)據(jù)后,發(fā)送中斷信號給CPU。 13.CPU接受到中斷信號后,開始執(zhí)行中斷處理程序,程序會檢查IO請求是否已經(jīng)完成,請求的完成狀態(tài)等,然后封裝IO請求結(jié)果,發(fā)送給IO子系統(tǒng)。 14. IO子系統(tǒng)收到請求結(jié)果后,會喚醒設(shè)備驅(qū)動程序,喚醒的方式比如singal,同時IO子系統(tǒng)會拷貝內(nèi)核緩沖的數(shù)據(jù)到用戶緩沖,同時將返回結(jié)果設(shè)置到應(yīng)用程序A的用戶空間。 15.IO子系統(tǒng)將應(yīng)用程序A從設(shè)備隊列中移除,加入到CPU就緒隊列中。 16.CPU調(diào)度運行應(yīng)用進程A,應(yīng)用進程A將返回結(jié)果返回。 |
|
來自: 好漢勃士 > 《操作系統(tǒng)綜述》