咱們以前的文章提到了操做系統的三個抽象,它們分別是進程、地址空間和文件,除此以外,操做系統還要控制全部的 I/O 設備。操做系統必須向設備發送命令,捕捉中斷並處理錯誤。它還應該在設備和操做系統的其他部分之間提供一個簡單易用的接口。操做系統如何管理 I/O 是咱們接下來的重點。程序員
不一樣的人對 I/O 硬件的理解也不一樣。對於電子工程師而言,I/O 硬件就是芯片、導線、電源和其餘組成硬件的物理設備。而咱們程序員眼中的 I/O 其實就是硬件提供給軟件的接口,好比硬件接受到的命令、執行的操做以及反饋的錯誤。咱們着重探討的是如何對硬件進行編程,而不是其工做原理。算法
I/O 設備
什麼是 I/O 設備?I/O 設備又叫作輸入/輸出設備,它是人類用來和計算機進行通訊的外部硬件。輸入/輸出設備可以向計算機發送數據(輸出)並從計算機接收數據(輸入)。編程
I/O 設備(I/O devices)能夠分紅兩種:塊設備(block devices) 和 字符設備(character devices)。緩存
塊設備
塊設備是一個能存儲固定大小塊信息的設備,它支持以固定大小的塊,扇區或羣集讀取和(可選)寫入數據。每一個塊都有本身的物理地址。一般塊的大小在 512 - 65536 之間。全部傳輸的信息都會以連續的塊爲單位。塊設備的基本特徵是每一個塊都較爲對立,可以獨立的進行讀寫。常見的塊設備有 硬盤、藍光光盤、USB 盤網絡
與字符設備相比,塊設備一般須要較少的引腳。
electron
塊設備的缺點ide
基於給定固態存儲器的塊設備比基於相同類型的存儲器的字節尋址要慢一些,由於必須在塊的開頭開始讀取或寫入。因此,要讀取該塊的任何部分,必須尋找到該塊的開始,讀取整個塊,若是不使用該塊,則將其丟棄。要寫入塊的一部分,必須尋找到塊的開始,將整個塊讀入內存,修改數據,再次尋找到塊的開頭處,而後將整個塊寫回設備。優化
字符設備
另外一類 I/O 設備是字符設備。字符設備以字符爲單位發送或接收一個字符流,而不考慮任何塊結構。字符設備是不可尋址的,也沒有任何尋道操做。常見的字符設備有 打印機、網絡設備、鼠標、以及大多數與磁盤不一樣的設備。操作系統
下面顯示了一些常見設備的數據速率。設計
設備控制器
首先須要先了解一下設備控制器的概念。
設備控制器是處理 CPU 傳入和傳出信號的系統。設備經過插頭和插座鏈接到計算機,而且插座鏈接到設備控制器。設備控制器從鏈接的設備處接收數據,並將其存儲在控制器內部的一些特殊目的寄存器(special purpose registers) 也就是本地緩衝區中。
特殊用途寄存器,顧名思義是僅爲一項任務而設計的寄存器。例如,cs,ds,gs 和其餘段寄存器屬於特殊目的寄存器,由於它們的存在是爲了保存段號。eax,ecx 等是通常用途的寄存器,由於你能夠無限制地使用它們。例如,你不能移動 ds,可是能夠移動 eax,ebx。通用目的寄存器好比有:eax、ecx、edx、ebx、esi、edi、ebp、esp特殊目的寄存器好比有:cs、ds、ss、es、fs、gs、eip、flag」
每一個設備控制器都會有一個應用程序與之對應,設備控制器經過應用程序的接口經過中斷與操做系統進行通訊。設備控制器是硬件,而設備驅動程序是軟件。
I/O 設備一般由機械組件(mechanical component)和電子組件(electronic component)構成。電子組件被稱爲 設備控制器(device controller)或者 適配器(adapter)。在我的計算機上,它一般採用可插入(PCIe)擴展插槽的主板上的芯片或印刷電路卡的形式。
機械設備就是它本身,它的組成以下
控制器卡上一般會有一個鏈接器,通向設備自己的電纜能夠插入到這個鏈接器中,不少控制器能夠操做 2 個、4 個設置 8 個相同的設備。
控制器與設備之間的接口一般是一個低層次的接口。例如,磁盤可能被格式化爲 2,000,000 個扇區,每一個扇區 512 字節。然而,實際從驅動出來的倒是一個串行的比特流,從一個前導符(preamble)開始,而後是一個扇區中的 4096 位,最後是一個校驗和 或 ECC(錯誤碼,Error-Correcting Code)。前導符是在對磁盤進行格式化的時候寫上去的,它包括柱面數和扇區號,扇區大小以及相似的數據,此外還包含同步信息。
控制器的任務是把串行的位流轉換爲字節塊,並進行必要的錯誤校訂工做。字節塊一般會在控制器內部的一個緩衝區按位進行組裝,而後再對校驗和進行校驗並證實字節塊沒有錯誤後,再將它複製到內存中。
內存映射 I/O
每一個控制器都會有幾個寄存器用來和 CPU 進行通訊。經過寫入這些寄存器,操做系統能夠命令設備發送數據,接收數據、開啓或者關閉設備等。經過從這些寄存器中讀取信息,操做系統可以知道設備的狀態,是否準備接受一個新命令等。
爲了控制寄存器,許多設備都會有數據緩衝區(data buffer),來供系統進行讀寫。例如,在屏幕上顯示一個像素的常規方法是使用一個視頻 RAM,這一 RAM 基本上只是一個數據緩衝區,用來供程序和操做系統寫入數據。
那麼問題來了,CPU 如何與設備寄存器和設備數據緩衝區進行通訊呢?存在兩個可選的方式。第一種方法是,每一個控制寄存器都被分配一個 I/O 端口(I/O port)號,這是一個 8 位或 16 位的整數。全部 I/O 端口的集合造成了受保護的 I/O 端口空間,以便普通用戶程序沒法訪問它(只有操做系統能夠訪問)。使用特殊的 I/O 指令像是
IN REG,PORT
CPU 能夠讀取控制寄存器 PORT 的內容並將結果放在 CPU 寄存器 REG 中。相似的,使用
OUT PORT,REG
CPU 能夠將 REG 的內容寫到控制寄存器中。大多數早期計算機,包括幾乎全部大型主機,如 IBM 360 及其全部後續機型,都是以這種方式工做的。
控制寄存器是一個處理器寄存器而改變或控制的通常行爲 CPU 或其餘數字設備。控制寄存器執行的常見任務包括中斷控制,切換尋址模式,分頁控制和協處理器控制。」
在這一方案中,內存地址空間和 I/O 地址空間是不相同的,以下圖所示
指令
IN R0,4
和
MOV R0,4
這一設計中徹底不一樣。前者讀取 I/O端口 4 的內容並將其放入 R0,然後者讀取存儲器字 4 的內容並將其放入 R0。這些示例中的 4 表明不一樣且不相關的地址空間。
第二個方法是 PDP-11 引入的,
什麼是 PDP-11?
」
它將全部控制寄存器映射到內存空間中,以下圖所示
內存映射的 I/O是在 CPU 與其鏈接的外圍設備之間交換數據和指令的一種方式,這種方式是處理器和 IO 設備共享同一內存位置的內存,即處理器和 IO 設備使用內存地址進行映射。
在大多數系統中,分配給控制寄存器的地址位於或者靠近地址的頂部附近。
下面是採用的一種混合方式
這種方式具備與內存映射 I/O 的數據緩衝區,而控制寄存器則具備單獨的 I/O 端口。x86 採用這一體系結構。在 IBM PC 兼容機中,除了 0 到 64K - 1 的 I/O 端口以外,640 K 到 1M - 1 的內存地址保留給設備的數據緩衝區。
這些方案是如何工做的呢?當 CPU 想要讀入一個字的時候,不管是從內存中讀入仍是從 I/O 端口讀入,它都要將須要的地址放到總線地址線上,而後在總線的一條控制線上調用一個 READ 信號。還有第二條信號線來代表須要的是 I/O 空間仍是內存空間。若是是內存空間,內存將響應請求。若是是 I/O 空間,那麼 I/O 設備將響應請求。若是隻有內存空間,那麼每一個內存模塊和每一個 I/O 設備都會將地址線和它所服務的地址範圍進行比較。若是地址落在這一範圍以內,它就會響應請求。絕對不會出現地址既分配給內存又分配給 I/O 設備,因此不會存在歧義和衝突。
內存映射 I/O 的優勢和缺點
這兩種尋址控制器的方案具備不一樣的優缺點。先來看一下內存映射 I/O 的優勢。
第二點,若是僅僅只有一個地址空間,那麼全部的內存模塊(memory modules)和全部的 I/O 設備都必須檢查全部的內存引用來推斷出誰來進行響應。
什麼是內存模塊?在計算中,存儲器模塊是其上安裝有存儲器集成電路的印刷電路板。」
若是計算機是一種單總線體系結構的話,以下圖所示
讓每一個內存模塊和 I/O 設備查看每一個地址是簡單易行的。
然而,現代我的計算機的趨勢是專用的高速內存總線,以下圖所示
裝備這一總線是爲了優化內存訪問速度,x86 系統還能夠有多種總線(內存、PCIe、SCSI 和 USB)。以下圖所示
在內存映射機器上使用單獨的內存總線的麻煩之處在於,I/O 設備沒法經過內存總線查看內存地址,所以它們沒法對其進行響應。此外,必須採起特殊的措施使內存映射 I/O 工做在具備多總線的系統上。一種可能的方法是首先將所有內存引用發送到內存,若是內存響應失敗,CPU 再嘗試其餘總線。
第二種設計是在內存總線上放一個探查設備,放過全部潛在指向所關注的 I/O 設備的地址。此處的問題是,I/O 設備可能沒法之內存所能達到的速度處理請求。
第三種可能的設計是在內存控制器中對地址進行過濾,這種設計與上圖所描述的設計相匹配。這種狀況下,內存控制器芯片中包含在引導時預裝載的範圍寄存器。這一設計的缺點是須要在引導時斷定哪些內存地址而不是真正的內存地址。於是,每一設計都有支持它和反對它的論據,因此折中和權衡是不可避免的。
直接內存訪問
不管一個 CPU 是否具備內存映射 I/O,它都須要尋址設備控制器以便與它們交換數據。CPU 能夠從 I/O 控制器每次請求一個字節的數據,可是這麼作會浪費 CPU 時間,因此常常會用到一種稱爲直接內存訪問(Direct Memory Access) 的方案。爲了簡化,咱們假設 CPU 經過單一的系統總線訪問全部的設備和內存,該總線鏈接 CPU 、內存和 I/O 設備,以下圖所示
現代操做系統實際更爲複雜,可是原理是相同的。若是硬件有DMA 控制器,那麼操做系統只能使用 DMA。有時這個控制器會集成到磁盤控制器和其餘控制器中,但這種設計須要在每一個設備上都裝有一個分離的 DMA 控制器。單個的 DMA 控制器可用於向多個設備傳輸,這種傳輸每每同時進行。
無論 DMA 控制器的物理地址在哪,它都可以獨立於 CPU 從而訪問系統總線,如上圖所示。它包含幾個可由 CPU 讀寫的寄存器,其中包括一個內存地址寄存器,字節計數寄存器和一個或多個控制寄存器。控制寄存器指定要使用的 I/O 端口、傳送方向(從 I/O 設備讀或寫到 I/O 設備)、傳送單位(每次一個字節或者每次一個字)以及在一次突發傳送中要傳送的字節數。
爲了解釋 DMA 的工做原理,咱們首先看一下不使用 DMA 該如何進行磁盤讀取。
DMA 控制器經過在總線上發出一個讀請求到磁盤控制器而發起 DMA 傳送,這是第二步。這個讀請求就像其餘讀請求同樣,磁盤控制器並不知道或者並不關心它是來自 CPU 仍是來自 DMA 控制器。一般狀況下,要寫的內存地址在總線的地址線上,因此當磁盤控制器去匹配下一個字時,它知道將該字寫到什麼地方。寫到內存就是另一個總線循環了,這是第三步。當寫操做完成時,磁盤控制器在總線上發出一個應答信號到 DMA 控制器,這是第四步。
而後,DMA 控制器會增長內存地址並減小字節數量。若是字節數量仍然大於 0 ,就會循環步驟 2 - 步驟 4 ,直到字節計數變爲 0 。此時,DMA 控制器會打斷 CPU 並告訴它傳輸已經完成了。操做系統開始運行時,它不會把磁盤塊拷貝到內存中,由於它已經在內存中了。
不一樣 DMA 控制器的複雜程度差異很大。最簡單的 DMA 控制器每次處理一次傳輸,就像上面描述的那樣。更爲複雜的狀況是一次同時處理不少次傳輸,這樣的控制器內部具備多組寄存器,每一個通道一組寄存器。在傳輸每個字以後,DMA 控制器就決定下一次要爲哪一個設備提供服務。DMA 控制器可能被設置爲使用 輪詢算法,或者它也有可能具備一個優先級規劃設計,以便讓某些設備受到比其餘設備更多的照顧。假如存在一個明確的方法分辨應答信號,那麼在同一時間就能夠掛起對不一樣設備控制器的多個請求。
許多總線可以以兩種模式操做:每次一字模式和塊模式。一些 DMA 控制器也可以使用這兩種方式進行操做。在前一個模式中,DMA 控制器請求傳送一個字並獲得這個字。若是 CPU 想要使用總線,它必須進行等待。設備可能會偷偷進入而且從 CPU 偷走一個總線週期,從而輕微的延遲 CPU。這種機制稱爲 週期竊取(cycle stealing)。
在塊模式中,DMA 控制器告訴設備獲取總線,而後進行一系列的傳輸操做,而後釋放總線。這一操做的形式稱爲 突發模式(burst mode)。這種模式要比周期竊取更有效由於獲取總線佔用了時間,而且一次總線得到的代價是能夠同時傳輸多個字。缺點是若是此時進行的是長時間的突發傳送,有可能將 CPU 和其餘設備阻塞很長的時間。
在咱們討論的這種模型中,有時被稱爲 飛越模式(fly-by mode),DMA 控制器會告訴設備控制器把數據直接傳遞到內存。一些 DMA 控制器使用的另外一種模式是讓設備控制器將字發送給 DMA 控制器,而後 DMA 控制器發出第二條總線請求,將字寫到任何能夠寫入的地方。採用這種方案,每一個傳輸的字都須要一個額外的總線週期,可是更加靈活,由於它還能夠執行設備到設備的複製,甚至是內存到內存的複製(經過事先對內存進行讀取,而後對內存進行寫入)。
大部分的 DMA 控制器使用物理地址進行傳輸。使用物理地址須要操做系統將目標內存緩衝區的虛擬地址轉換爲物理地址,並將該物理地址寫入 DMA 控制器的地址寄存器中。另外一種方案是一些 DMA 控制器將虛擬地址寫入 DMA 控制器中。而後,DMA 控制器必須使用 MMU 才能完成虛擬到物理的轉換。僅當 MMU 是內存的一部分而不是 CPU 的一部分時,才能夠將虛擬地址放在總線上。
重溫中斷
在一臺我的計算機體系結構中,中斷結構會以下所示
當一個 I/O 設備完成它的工做後,它就會產生一箇中斷(默認操做系統已經開啓中斷),它經過在總線上聲明已分配的信號來實現此目的。主板上的中斷控制器芯片會檢測到這個信號,而後執行中斷操做。
若是在中斷前沒有其餘中斷操做阻塞的話,中斷控制器將馬上對中斷進行處理,若是在中斷前還有其餘中斷操做正在執行,或者有其餘設備發出級別更高的中斷信號的話,那麼這個設備將暫時不會處理。在這種狀況下,該設備會繼續在總線上置起中斷信號,直到獲得 CPU 服務。
爲了處理中斷,中斷控制器在地址線上放置一個數字,指定要關注的設備是哪一個,並聲明一個信號以中斷 CPU。中斷信號致使 CPU 中止當前正在作的工做而且開始作其餘事情。地址線上會有一個指向中斷向量表 的索引,用來獲取下一個程序計數器。這個新獲取的程序計數器也就表示着程序將要開始,它會指向程序的開始處。通常狀況下,陷阱和中斷從這一點上看使用相同的機制,而且經常共享相同的中斷向量。中斷向量的位置能夠硬連線到機器中,也能夠位於內存中的任何位置,由 CPU 寄存器指向其起點。
中斷服務程序開始運行後,中斷服務程序經過將某個值寫入中斷控制器的 I/O 端口來確認中斷。告訴它中斷控制器能夠自由地發出另外一箇中斷。經過讓 CPU 延遲響應來達到多箇中斷同時到達 CPU 涉及到競爭的狀況發生。一些老的計算機沒有集中的中斷控制器,一般每一個設備請求本身的中斷。
硬件一般在服務程序開始前保存當前信息。對於不一樣的 CPU 來講,哪些信息須要保存以及保存在哪裏差異很大。無論其餘的信息是否保存,程序計數器必需要被保存,這對全部的 CPU 來講都是相同的,以此來恢復中斷的進程。全部可見寄存器和大量內部寄存器也應該被保存。
上面說到硬件應該保存當前信息,那麼保存在哪裏是個問題,一種選擇是將其放入到內部寄存器中,在須要時操做系統能夠讀出這些內部寄存器。這種方法會形成的問題是:一段時間內設備沒法響應,直到全部的內部寄存器中存儲的信息被讀出後,才能恢復運行,以避免第二個內部寄存器重寫內部寄存器的狀態。
第二種方式是在堆棧中保存信息,這也是大部分 CPU 所使用的方式。可是,這種方法也存在問題,由於使用的堆棧不肯定,若是使用的是當前堆棧,則它極可能是用戶進程的堆棧。堆棧指針甚至不合法,這樣當硬件試圖在它所指的地址處寫入時,將會致使致命錯誤。若是使用的是內核堆棧,堆棧指針是合法的而且指向一個固定的頁面,這樣的機會可能會更大。然而,切換到內核態須要切換 MMU 上下文,而且可能使高速緩存或者 TLB 失效。靜態或動態從新裝載這些東西將增長中斷處理的時間,浪費 CPU 時間。
精確中斷和不精確中斷
另外一個問題是:現代 CPU 大量的採用流水線而且有時還採用超標量(內部並行)。在一些老的系統中,每條指令執行完畢後,微程序或硬件將檢查是否存在未完成的中斷。若是存在,那麼程序計數器和 PSW 將被壓入堆棧中開始中斷序列。在中斷程序運行以後,舊的 PSW 和程序計數器將從堆棧中彈出恢復先前的進程。
下面是一個流水線模型
在流水線滿的時候出現一箇中斷會發生什麼狀況?許多指令正處於不一樣的執行階段,中斷出現時,程序計數器的值可能沒法正確地反應已經執行過的指令和還沒有執行的指令的邊界。事實上,許多指令可能部分執行,不一樣的指令完成的程度或多或少。在這種狀況下,程序計數器更有可能反應的是將要被取出並壓入流水線的下一條指令的地址,而不是剛剛被執行單元處理過的指令的地址。
在超標量的設計中,可能更加糟糕
每一個指令均可以分解成爲微操做,微操做有可能亂序執行,這取決於內部資源(如功能單元和寄存器)的可用性。當中斷髮生時,某些好久之前啓動的指令可能還沒開始執行,而最近執行的指令可能將要立刻完成。在中斷信號出現時,可能存在許多指令處於不一樣的完成狀態,它們與程序計數器之間沒有什麼關係。
使機器處於良好狀態的中斷稱爲精確中斷(precise interrupt)。這樣的中斷具備四個屬性: