1. 程式人生 > >10.2 輸入輸出接口的編址方式

10.2 輸入輸出接口的編址方式

次方 然而 也有 並不是 單元 平時 重新 3.3 快遞員

計算機組成

10 輸入輸出設備

10.2 輸入輸出接口的編址方式

技術分享圖片

CPU的運算能力很強,但它與外界溝通交流的手段卻非常地單一。它總是希望有這樣“我給你一個地址,你就給我一個數據”非常直白的溝通方法。所以,它平時也只能和存儲器這樣胸懷寬廣,但是同樣頭腦簡單的家夥在一起玩了。然而現在它需要面對外界那麽多的朋友,各個都非常復雜,有時還經常搗些亂,那它恐怕就應付不了了。所以,它就找了I/O接口這樣的幫手幫它打理外部的世界,而它呢還是希望能夠用一個簡單的方式和I/O接口進行交互和溝通。所以,我們就來看一看CPU是怎麽和I/O進行溝通的。

技術分享圖片

這是I/O接口在計算機系統當中的位置。和訪問存儲器中的單元一樣,我們想要CPU訪問I/O接口當中的這些寄存器,也是需要通過編寫指令來實現。那問題就是,I/O接口裏面的這些寄存器的地址究竟是什麽?

技術分享圖片

我們先來看幾個基本概念,在系統當中通常會有多個I/O接口,每個I/O接口內部都有若幹個寄存器。這些寄存器一般被稱為I/O端口,我們不要被這個詞的字面意義所迷惑,這個端口指的並不是我們計算機上的USB接口、網線接口這樣實實在在的接口,而是一個抽象的概念,它實際上指的就是這些在I/O接口芯片內部的寄存器,它們就像在存儲器當中的一個個存儲單元一樣,CPU要訪問它們,就得有特定的地址。因此,每個寄存器,也就是每個I/O端口都需要有自己的地址,這稱為端口地址,也叫做端口號。

那在計算機系統中,如何去設定這些端口號,就稱為I/O端口的編址方式。

技術分享圖片

常見的I/O端口編址方式有兩種:第一種是I/O端口和存儲器分開編址,又被稱為I/O映像的方式。x86體系結構就采用了這種方式。另一種常見的方式是I/O端口和存儲器統一編址,又稱為存儲器映像的I/O方式。ARM、MIPS、PowerPC等體系結構都采用了這樣的方式。

技術分享圖片

那我們先來看分開編址的方式。我們假設這個體系結構地址的寬度為3,它一共可以訪問的地址單元就是2的三次方,總共8個,如果每個單元是一個字節,那它的存儲器最大就是8個字節。然後我們需要在這個計算機系統當中增加一些I/O端口,I/O端口的地址是重新編排的,和存儲器地址無關。一般情況下,我們需要的I/O端口的數量都比存儲器單元要少得多,比如在這個示例的系統當中,我們需要四個I/O端口,那我們就給它分配四個端口號,0、1、2、3。這樣的編址方式就稱為分開編址。

在這種編址方式下,要訪問I/O端口需要用特殊的指令。

技術分享圖片

x86提供了IN和OUT這兩條指令。IN指令用於把I/O端口的內容輸入到CPU當中的寄存器,而OUT指令則是把CPU寄存器當中的內容輸出到I/O端口中。

技術分享圖片

那我們來看一看IN和OUT指令應該如何書寫。

如果要訪問的端口地址在0到255之間,那可以采用兩種方式。一是叫直接尋址,也就是寫一個立即數指定端口地址。例如 in al,80H 這條指令,這個80H就是一個端口號;那 out 80H,al 這條指令說的是從80H這個端口讀出一個字節的內容,並存放到AL寄存器當中去;而 in ax,80H 這條指令則是說,將AX當中的兩個字節的內容,傳送到80H所指定的I/O端口中,當然這個I/O端口對應的應該是一個兩字節的寄存器。

而如果端口地址大於255,則需要將這個地址先保存到DX寄存器當中,然後再執行IN和OUT的操作。我們也來看一個例子,假如我們要訪問第288號端口,這時候就需要先把這個端口號存到DX寄存器當中(mov dx,288),然後在IN和OUT指令當中,使用DX寄存器來指定端口號。這樣就是對288號端口地址進行操作。當然,對於端口地址在0到255之間的,也可以使用間接尋址的方式,那麽x86為什麽要設定這兩種方式呢?主要還是為了指令的長度。我們看到在直接尋址的情況下,我們需要有一個字節的操作碼,還需要有一個字節保存這個端口號,那麽一個字節所能表達的範圍就是0到255,如果端口地址大於255,那原本就需要再增加一個字節來記錄端口號,但這樣指令就太長了。為了縮短指令長度,寧可多增加一個指令,這也是CISC的特點。

所以,對於間接尋址,有另外一個操作碼,這條指令只有一個字節,既沒有立即數,也沒有寄存器的編號。所以,它是默認地采用DX寄存器來保存端口地址。這樣在訪問更大的端口地址時,指令的長度反而可以更短一些。這裏就有一個問題,既然用這個方式能訪問的地址範圍更大,指令長度還更短,那我們為什麽不只用這一種方式呢?還需要去用直接尋址這樣的方式?這個問題就留給你自己思考。

技術分享圖片

那我們再通過一個例子來看一看這樣的指令的操作過程。out 21H,al 這條指令是把AL寄存器當中的一個字節,傳送到21H這個端口號。當CPU從存儲器當中取回了這條指令,通過譯碼發現是一條OUT指令,它就會將AL寄存器當中的內容取出來,放到數據總線上,並將21H放到地址總線上。這時候系統總線應該怎麽辦呢?我們不妨把系統總線看成城市中的一條街道,而把系統總線所連接的存儲器I/O接口看成街道兩旁的一些建築,每個建築裏面還有很多個單位,每個單位都有一個門牌號。現在就好像有一個快遞員接收了一個任務,要把一個包裹送到21H這個地址。於是他就在這個街道上走,查看著每個大樓的門牌號,然後他發現,這兩個存儲器地址範圍,一個是從0到7FFF; 一個是從80000到5個F。那麽在存儲器1當中,實際上是包含了21H這個地址的,但他接著再看,這個I/O接口1當中的地址是00到1F;而I/O接口2當中的地址是20到3F;I/O接口3的地址是40到5F。所以,在I/O接口2當中,也包含了21H這個地址。這個包裹應該送到哪兒呢?所以單憑這個地址,系統總線是無法判定要訪問哪個設備的。因此,CPU發出的信號中,除了地址,還應該有一個別的信號,這個信號指明了當天要訪問的是存儲器還是I/O接口。

在x86的CPU當中,這個信號叫做M/IO。當這個信號為0的時候,表明當前在訪問I/O接口;而這個信號為1的時候,表明在訪問存儲器。這樣系統總線就知道該怎麽辦了,它會在所有的I/O接口當中,尋找這個地址(21H)所對應的端口,這就發現是在I/O接口2中。所以,系統總線會把這個傳輸傳到I/O接口2。I/O接口2可能是一個獨立的芯片,當它在系統總線上采樣到這個地址和一個數據之後,就會在內部找到對應的端口號。我們要註意的一點是,這些I/O接口內部一般只有少數幾個端口。所以,它只會采樣地址的低幾位,然後用這個低位在內部進行索引。在這個例子當中,21H的這個2 是系統總線用來找到這個I/O接口2的,而這個I/O接口2只用接收到地址21H的低位這個1,然後在內部找到對應的端口就可以了。最後這個I/O接口將從數據總線上采樣到數據,也就是AL寄存器當中的內容,保存到它內部的數據輸出寄存器中,這就完成了這條OUT指令所需的操作。至於這個數據輸出寄存器它外面是連接到了幾個小燈泡,還是一個數碼管,那就是這個I/O接口和外設的連接情況了。

技術分享圖片

然後我們再來看一看I/O端口和存儲器統一編址的情況。

還是假設地址寬度為3,那我們在這個統一編址的體系結構當中,總共就只有8個單元。然後根據需要,其中有一部分用來作為I/O端口的地址,其他部分用來作為存儲單元的地址。

技術分享圖片

那我們之前介紹的模型機就采用了統一編址的方式,它的地址總線寬度為4位,這樣一共就有16個單元。而存儲器當中用了的地址是0到7(0000~0111),一共8個單元。而輸入、輸出設備則用了15和16(1110和1111)這兩個地址。另外還有一些地址在這個系統當中沒有使用,那在之後擴展中,可以增加一些存儲器或者增加一些輸入、輸出端口。但是總共只有這16個,是不可以重復的。

當然,因為它是統一編址的。所以,給出任意一個地址,只有唯一的一個單元與之對應。所以,也就不需要剛才x86當中使用的M/IO這樣的信號來指定當前的地址到底是I/O地址還是存儲器地址。

技術分享圖片

那我們來看一看統一編址方式的優缺點。

首先來看優點。因為在統一編址的情況下,是不區分存儲器地址和I/O端口地址的。所以,我們就可以直接用訪問存儲器的指令來訪問I/O端口。而訪問存儲器的指令功能通常比較豐富,比如數據可以有各種的寬度,地址也有多種的產生方式,可以是立即數,可以是寄存器,也可以是寄存器加立即數,甚至還可以放在存儲器當中。所以,這樣就比較方便對I/O端口進行處理。另外,如果要設計單獨的I/O操作的指令,無論它做得怎麽簡單,也是需要額外的一套硬件邏輯,而采用統一編址的方式,CPU中只需要有一套對外部總線的控制邏輯就可以了,內部結構簡單,對外的引腳數目也會少一些。

但是統一編址也有它的缺點,由於I/O端口占用了一部分地址空間,從而使用於存儲器的地址空間變小了。這個問題對於早期的處理器影響還是非常大的,我們想一想,x86的早期只有16位寬的地址,按說只能訪問64KB的存儲器,它為了有更多的存儲器空間,還設計了一個非常復雜的段加偏移的方式才能訪問一兆的地址空間。如果在這個時候還要為了I/O端口占用了一部分地址空間,那就很難接受了,這也是x86選用了分開編址方式的一個重要原因。而當CPU的字長到了32位之後,在很長一段時間,地址空間都不是一個問題。所以,那個時候直接從32位起步的MIPS就采用了統一編址的方式。當然現在到了64位之後,物理的存儲器遠遠用不了這麽大的地址空間。所以,地址空間被擠占這個因素現在已經不成問題了。另外,如果要用訪問存儲器的指令來進行I/O操作,那這些指令往往比單獨設計的I/O指令要長,而且因為這些指令比較復雜,執行的時間可能也會長一些。這也是RISC為什麽普遍采用了統一編址方式的原因。因為RISC的指令都是固定長度的,所以即使設計單獨的I/O指令,也不會比普通的訪存指令更短一些,而x86這樣的CISC采用了變長的指令。所以,就可以設計出更短的專門用於I/O的指令,從而提高指令的密度。

技術分享圖片

那麽了解了統一編址的特點,分開編址的特點,我們也就清楚了,他們的優缺點剛好是相對的。在分開編址的情況下,I/O端口不會擠占存儲器的地址空間;而且因為設計了單獨的I/O指令,它的指令編碼可以做得很短,執行速度也比較快;而I/O地址空間一般是遠遠小於存儲器地址空間的,所以獨立的I/O指令可以使用較短的地址編碼,從而讓地址譯碼變得更為方便。另外,從軟件編程的角度來看,有了單獨的IN和OUT指令,可以很清晰地看出哪些是I/O操作,哪些是存儲器操作,讓程序的結構變得清晰易懂。而分開編址的缺點,我們剛才也都已經說完了,就不再重復了。

技術分享圖片

現在CPU可以用它習慣的方式和I/O接口進行交互,這確實讓事情變得簡單了很多,但是這並不意味著CPU就可以做甩手掌櫃了。其實I/O接口只能幫CPU解決一些溝通交互上的瑣碎細節,真正溝通的核心內容還得CPU自己來做。

10.2 輸入輸出接口的編址方式