西电计科院微机原理与系统设计课程笔记(车向泉版)
微機原理與系統設計
以下內容是西安電子科技大學計算機科學與技術學院2021年大三上學期車向泉老師的微機原理與系統設計課程的隨課筆記。筆記的PDF版本和課程的學在西電上課錄像下載鏈接如下(請勿將錄像上傳到B站等網站!!):
鏈接:https://pan.baidu.com/s/12b7Ssu6uygtFn-gfBYoy6g
提取碼:2v5w
?
閱讀時請注意:
1.筆記中的刪除線標注了上課時老師做了講解但筆記沒有詳細記述的內容,有需要可以自己觀看錄播的相應內容學習
2.9月30日的錄播缺失,該節課的內容對應第三章最后的幾小節,期末考試不要求
3.第8章只講了前一部分,該章內容就是第6章、第7章知識的綜合運用,所以沒有記述。第8章和復習課內容對應錄播資源的最后四節,可以自行觀看學習
4.雖然筆記的內容比較多,但實際上32位匯編、C語言與匯編語言的混合編程、總線的大部分內容等等都不是期末考試要求的內容。具體哪些內容不要求在每學期末車老師的復習課里都會說
其他各科筆記匯總
開課介紹
數字電路與邏輯設計:用電路來實現邏輯關系,了解常用的一些模塊和芯片
?
計算機組織與體系結構:這個課以前是計算機組成原理和計算機系統結構兩門課。上學期上課時講過三個概念:計算機系統結構,計算機組成,計算機實現
在設計計算機時,首先要做一個總體的架構,包括指令集,流水線結構等等,這個主體設計就是計算機系統結構要研究的內容
之后要細化為邏輯電路,這就是計算機組成要研究的內容
然后要選擇合適的芯片,芯片之間連接構成電路,最后調試。實實在在地做出來,這是計算機實現要解決的問題
?
單片機電路設計與開發/微機原理與系統設計/嵌入式系統這三門課很像,都對應計算機實現。要構成一個計算機系統,首先要選擇一個CPU,然后增加一些外圍的東西,如存儲器,各種接口芯片。在此基礎上還要設計一些軟件讓它工作,這個整體思路都是一樣的。區別在于:
1.研究的CPU不一樣
單片機的CPU相對來講性能比較弱,像8051是8位CPU,純粹單任務,不支持虛擬存儲管理,多任務,也沒有流水線和高速緩存
嵌入式系統主要研究ARM的Cortex-A系列,有MMU存儲管理單元,支持虛擬存儲管理,多任務,也有流水線和高速緩存,可以移植一些操作系統,如Linux
微機原理則研究Intel和AMD的X86處理器
2.編程用語言不一樣
3.課程教授方式不同
4.涉及領域不一樣
單片機一般用在物聯網,如一些家用電器里面,成本低且可靠性高
ARM的Cortex-A系列一般用在智能手機,平板電腦等手持移動設備上,操作系統以Android為主
x86一般用在筆記本電腦,臺式機,超級計算機等上,操作系統以Linux和Windows為主
?
講了下x86重要性,然后推薦了幾本教材和參考資料
緒論
基本概念
?
要構成一個CPU,得有控制器,運算器和寄存器,然后通過總線連接起來。在早期這些都是分立元件,隨著芯片的集成度提高后,可以把這些構成CPU的東西放在一個芯片里面,這個芯片就叫微處理器。隨著集成度越來越高,芯片里還可以集成一定容量的高速緩存,協處理器,甚至很多個CPU的內核
微處理器還要從內存里取指令然后執行指令。人和計算機交互要通過外設,外設要通過接口才能夠連接到計算機里面。微處理器,內存還有各種各樣的接口放在一起通過系統總線連到一塊就叫微型計算機
想要計算機工作還需要相應的軟件,硬件和軟件加在一塊叫微型計算機系統
?
構成微型計算機的這些東西做到一個芯片里面,好像一個芯片就是一個計算機,這就叫單片機。加上相應的軟件就構成單片機系統
?
從廣義上講,單片機系統就屬于嵌入系統。 狹義上講,嵌入系統指的是性能比較強的專用計算機
SOC (System on Chips) 把一個計算機系統都集成在芯片里面。Intel最新的x86處理器,如i3,i5,i7i3,i5,i7i3,i5,i7就屬于SOC,里面不僅集成了多個CPU內核,還集成了圖形處理單元,一些高速的總線接口,內存控制器等等,把構成計算機的大多數電路都集成在一個芯片里面
?
微處理器概述
介紹了Intel的x86處理器的歷史,中間比較了幾個處理器的參數
8088微處理器是8086微處理器的簡化版,它的內核仍然是16位的CPU,但是外部系統總線數據線只有8根線。訪問內存和接口時一次只能傳輸8位數據
?
?
微型計算機概述
假設有兩臺CPU是x86指令集兼容,但是如主板,接口連接的外設等底層硬件不同的計算機,為什么可以實現安裝相同的操作系統和軟件?
?
操作系統跟硬件之間隔了一層。 計算機剛一加電,啟動之后最先執行的代碼是固化在計算機主板的ROM里面(現在用的是flash存儲器),那段代碼叫BIOS。這個程序要做的是:
1.硬件的自檢
如檢查一下內存有多大,如何控制內存,有什么外設接口等
2.各種外設接口初始化
接口和外設內也會有一些寄存器,里面的內容決定其工作狀態。 加電之后的內容是隨機的,所以要BIOS程序寫入內容初始化使其正常工作
3.給操作系統提供功能調用
BIOS程序執行完后,它的代碼還會駐留在內存里面,然后再從磁盤的引導扇區裝代碼,引導代碼裝操作系統。操作系統在裝載的過程中是通過BIOS程序提供的編程接口,用調用函數或者軟中斷的形式來調用BIOS程序的代碼來訪問硬件的
BIOS程序給操作系統提供的編程接口是標準化的,這樣在不同的硬件上就可以裝相同的操作系統
?
x86處理器剛加電啟動時處于實模式,實模式相當于是一臺快速的8086,只能夠運行16位的程序。所以BIOS程序全部或者說最開始執行的那段代碼肯定要設計成16位程序,通??赡苓€要用匯編語言來設計。 但是隨著操作系統裝載完成,CPU最終要從實模式切換到32位或64位CPU的保護模式,這時操作系統訪問硬件就要調用設備驅動程序提供的函數,而設備驅動程序給某一操作系統提供的編程接口也是標準的
不同的硬件設備驅動程序不一樣,但它給操作系統提供的編程接口調用方式都是一樣的,這樣在不同的硬件上就可以裝相同的操作系統
?
中間介紹了微型計算機的歷史
Intel 單核/多核處理器
單核處理器8086
8086具有以下特點:
1.最大內存尋址空間可以達到1M(=220)1M(=2^{20})1M(=220)字節
2.內部的通用寄存器都是16位的,因為它是16位的CPU
?
16位的寄存器怎么表示20位的內存地址?
對內存采用分段管理方式。段寄存器保存段的起始地址的高16位,低4位默認是0;指針寄存器內存16位的段內偏移
3.x86處理器采用獨立編址。內存與接口和外設里的存儲器各有獨立的地址空間,各有不同的指令和讀寫信號
8086的接口地址空間只有64K字節,不需要分段
?
?
?
8086的內部框圖:
8086內部有14個程序員可見的16位通用寄存器
8088的指令隊列是4個字節,8086是6個字節
分為BIU和EU。由于有指令隊列,二者可并行工作
總線接口單元BIU (Bus Interface Unit) :負責與存儲器,I/O接口出傳遞數據
執行單元EU (Execute Unit) :負責指令的執行
?
8086的AX~\sim~???DX4個寄存器可以拆成2個8位的來用,如低8位叫AL,高8位叫AH
匯編語言中 [SI] 表示寄存器間接尋址
?
?
?
8086的寄存器結構:
SP,BP,SI,DI,IP是指針寄存器,用來存段內偏移。CS,DS,SS,ES是段寄存器,用來存段起始地址的高16位
例題:
這段程序訪問的主存地址是:
MOV DX,0A000H MOV DS,DX MOV SI,9000H MOV AL,[SI]?
答案:A9000H
解析:
1.數據傳送指令不支持立即數到段寄存器之間的直接傳送,所以要初始化段寄存器必須要先把立即數傳送到某一個通用寄存器
2.16進制的立即數形式如果最高位是英文字母,在它的左側必須要加一個0,編譯程序才會認為這是一個立即數
3.最終DS內容為0A000H,SI內容為9000H。0A0000H + 9000H = 0A9000H
?
代碼段寄存器 (Code Segment):程序執行時內存里會存機器指令,這些機器指令所在的段叫代碼段。代碼段段的起始地址的高16位就存在CS
數據段寄存器 (Data Segment):指令執行的過程中肯定需要一些數據要從內存中取,或者最終的結果要存到內存中去。存儲數據的段叫數據段
堆棧段寄存器 (Stack Segment):執行的指令中會有PUSH壓棧和POP出棧指令,主程序調用時要保護當前的斷點,斷點要保護在堆棧里面,免不了要涉及一些和堆棧有關的操作。堆棧的這些數據所在的段叫堆棧段
附加段寄存器 (Extra Segment):也是用來訪問數據的。默認用DS,也可以指定用ES來訪問數據。一般用于跨段訪問數據
MOV AX,[BX] ;默認段寄存器是DS MOV AX,ES:[BX] ;加段超越前綴,此時段寄存器是ES?
?
AX~DXAX\sim DXAX~DX??????大多數情況下都可以隨便用,但也有特殊用途:
數據寄存器AX (Accumulator):一些特殊的指令,比如說乘法指令,被乘數是隱含尋址,默認在AX。最終結果的一部分也會放在AX,除法指令也有類似的規定。包括后面用來訪問接口的IN指令和OUT指令,寄存器必須得用AX
運算結果的低16位默認存在AX,高16位默認存在DX
數據寄存器BX (Base):也可以當指針寄存器來用,用來間接尋址
AX,CX,DX不可以
數據寄存器CX (Count):在一些特殊的指令中用于計數,如匯編語言中用LOOP指令實現循環時CX會記錄剩余循環次數
數據寄存器DX (Data):純粹用來存數據
?
?
指令指針寄存器 (Instruction Point):為了取得下一條指令的內存地址,需要用到CS和IP,分別存代碼段的起始地址的高16位(低4位默認為0)和16位的段內偏移
堆棧指針寄存器 (Stack Point):SS和SP配合共同確定當前堆棧棧頂的位置
注意x86中棧頂是小地址,棧底是大地址
16位的CPU,堆棧里每一個數據必須是16位的。所以沒有 PUSH AL 和 POP AL,只有 PUSH AX 和 POP AX。32位的x86同理
基址指針寄存器 (Base Point):指針寄存器如果用BX,那默認的段寄存器是DS。如果用BP,那默認的段寄存器是SS。故BP一般是用來間接尋址訪問堆棧當中的任意一個元素的
源變址寄存器 (Source Point) 和目的變址寄存器 (Destination Point):訪問數據,默認的段寄存器是DS,指針寄存器可以用BX,SI,DI,只有這3個寄存器可以用來間接尋址訪問數據段
?
標志寄存器/程序狀態字 (PSW/FLAGS) :
輔助進位標志位 (Auxiliary Carry - BCD):加法運算如果運算結果的第3位向第4位有進位,AF置1。BCD數運算有關的指令里可能會用到
第幾位從低位,從0開始編號
進位/借位標志位 (Carry Flag):加法運算運算結果的最高位向更高位有進位或減法運算最高位向更高位有借位,CF置1
這里的加減法理解為無符號數相加減
奇偶標志位 (Parity Flag):運算結果中1的結果如果為偶數個,PF置1??捎糜诩悠媾夹r炍?/p>
符號標志位 (Sign Flag):反映運算結果的最高位,最高位是什么SF就是什么
零標志位 (Zero Flag):運算結果為0,ZF置1
溢出標志位 (Overflow Flag):運算結果溢出時,OF置1
以上標志位都是運算器的硬件根據運算結果自動管理的
?
中斷允許標志位 (Interrupt Enable Flag):關中斷指令CLI可將IF置0,之后CPU不會響應任何來自外設或者接口的中斷請求。開中斷指令STL可將IF置1
方向標志位 (Direction Flag(Strings) ):CLD可將DF清0,串操作指令在重復執行的時候就會從小地址向大地址的方向訪問數組。STD可將DF置1,串操作指令訪問數組時會從大地址往小地址的方向訪問
陷阱標志位 (Trap-Single Step Flag):跟調試有關,單步跟蹤的時候可能用到。TF置1后CPU每執行完一條指令后就會在內部產生一個單步中斷,最終會跳轉到單步中斷所對應的中斷服務程序,即調試工具代碼的一部分
以上標志位有專門的指令進行管理,讓CPU工作在不同的狀態
?
?
?
8086的主存結構:
先看雙體結構:
8088的內存相對簡單,不分存儲體,只有一個8位的存儲體就夠了,接口也一樣,設計較簡單
?
8086把內存分為兩個存儲體,偶地址存儲體和奇地址存儲體,有各自的存儲體選擇信號以實現8位和16位傳送讀寫。偶地址存儲體的選擇信號如果是低電平有效的話,地址線最低位A0 ̄\overline{A_0}A0???????可當作偶地址存儲體的選擇信號。奇地址存儲體的選擇信號則有專門的引腳信號BHE ̄\overline{BHE}BHE?
給這兩個存儲體的都是字的地址,從A1 ̄\overline{A_1}A1??開始,A0 ̄\overline{A_0}A0?????理解成偶地址存儲體的選擇信號
?
要讀16位數據,會存在一次和分兩次的情況:
一次,
?
必須分兩次,
體現了數據對齊的重要性。16位數據在內存中占2個字節,我們就希望在內存里從偶地址開始存放,這樣一個總線周期就能完成讀寫
?
分段結構方式上面已經多次提到。注意各個分段之間可以重疊
6832H:1280H中6832H是段寄存器的內容,1280H是段內偏移
?
下面再看一些特殊的內存區域:
中斷向量區:00000H~003FFH (1KB),每個中斷向量占4個字節
顯示緩沖區:B0000H~B0F9FH (4000B),B8000H~BFFFFH (32KB)
啟動區:FFFF0H~FFFFFH (16B),內為無條件轉移指令,使啟動時跳轉到BIOS程序的起始地址
操作系統裝載在常規內存中
顯示緩沖區實例不要求
?
?
?
8086芯片引腳:
1.電源引腳
2.地址、數據線
考慮到不管是訪問內存還是訪問接口,都是先送地址然后再傳數據,Intel 把地址信號和數據信號復用以節省引腳,在設計電路時外面會增加一些輔助芯片來把復用的信號分離出來。即16位的數據和低16位的地址是復用的,高4位地址線和狀態信號復用
S6:廢用狀態,輸出一直是低電平
S5:表示當前IF的情況,看該引腳在輸出狀態時是輸出高電平還是低電平可知現在是開中斷還是關中斷
S3,S4:一共有4種組合,表示現在CPU正在使用哪一個段寄存器
3.時鐘、復位
CPU剛一加電之后,里面的內容是隨機的,當電源電壓穩定之后,在RESET引腳加大于4個時鐘周期寬度的正脈沖,就可以把CPU內部的CS置為全1,其他寄存器清0
8086的時鐘信號不是方波,高電平持續時間是低電平持續時間的12\frac1221???。外部的時鐘發生器芯片對一個方波進行三分頻
4.中斷
NMI:不可屏蔽中斷請求輸入引腳
INTR:可屏蔽中斷請求輸入引腳
INTA ̄\overline{INTA}INTA:中斷應答引腳
8086/8088 的計算機系統實現中斷時需要在外面增加一個可編程中斷控制器8259,該芯片可以管理8個中斷源,不夠用還可以多片級聯
x86處理器在管理中斷時,每一個中斷源都有1個8位二進制編碼的編號(中斷向量),所以x86處理器最多可以管理256個中斷源
有一些中斷源的編號是固定的,特別是來自CPU內部的中斷源,比如
0號中斷對應除法錯誤(除數為0,結果溢出)
1號中斷叫單步中斷。調試工具如果把TF置1,那么CPU每執行完一條機器指令之后都會在內部產生一個1號中斷,最終進入到單步中斷的中斷服務程序。那個程序就是調試軟件代碼的一部分
3號中斷叫斷點中斷,也是調試用的
4號中斷叫溢出中斷,一些補碼加法結果溢出
?
這些中斷究竟是怎么運作的?
比如CPU內部現在產生0號中斷,一旦中斷產生,CPU馬上獲取對應中斷向量,再根據中斷向量查找主存中的中斷向量表,讀出對應行的前2個字節賦值給IP,后面2個字節賦給CS,這2個寄存器合起來就相當于上學期講的程序計數器PC,被重新賦值后當然就會跳轉到0號中斷源對應的中斷服務程序執行
注意在賦值前必須要把IP和CS這兩個寄存器原來的內容保護在堆棧里,這叫斷點保護。響應中斷時,需要壓入IP,CS,PSW的內容,然后硬件自動把IF置0,再去查中斷向量表
中斷向量表有256行,行號從0~\sim~??????255,每一行對應某一號中斷,占4個字節(前2個字節對應段內偏移,后2個字節是段寄存器的內容),存對應的中斷服務程序。表總共1KB,從內存地址0開始存放,在計算機啟動的過程中由BIOS程序負責初始化
CPU內部的中斷源還有一個,x86處理器的指令集里專門有一條軟中斷指令INT 21H ,21H是立即數,表示某中斷向量。編寫16位匯編語言程序時,經常需要調用DOS操作系統的一些功能,DOS操作系統就是利用軟中斷來給應用程序提供功能調用。上面這條指令在CPU內部一執行就會產生33(=21H)號中斷,對應的中斷服務程序是DOS操作系統代碼的一部分,其目的就是給用戶程序提供各種功能調用
?
以上是CPU內部的中斷源,然后再看來自外部的中斷請求,涉及到INTR和NMI
為什么這兩個一個叫可屏蔽一個叫不可屏蔽?
PSW中有IF,如果通過CLI把IF清0,它實際上就屏蔽來自INTR引腳的中斷請求,對其他的沒有任何影響,這個引腳的中斷請求其實就是來自接口或外設。而NMI不能通過IF屏蔽,通常是非常緊急的中斷請求,比如掉電
INTR和NMI還有一個區別,INTR輸入高電平有效,NMI輸入上升沿有效
?
INTR通過中斷控制器管理來自接口或者外設的好多個中斷源,中斷控制器通過中斷判優決定向CPU發送哪個中斷源的中斷請求。CPU當前指令執行完,并且處在開中斷狀態才會去響應中斷,通過INTA ̄\overline{INTA}INTA??????????發送第一個負脈沖告知中斷控制器其正在響應,同時要準備好中斷向量。接著發送第二個負脈沖,中斷控制器就把中斷向量送到低8位的數據總線上傳給CPU
5.最小模式/最大模式
24~\sim~?31號引腳最小模式下是括號里的功能,最大模式下是括號外的功能
工作在最小模式下,所有的控制信號由8086直接產生,不需要再增加其他的外部芯片,電路規模通常較小,并且計算機系統里通常只有8086一個處理器
工作在最大模式下,那些控制信號就不是由這些引腳直接產生的了,需要外面增加一個別的芯片,比如總線控制器8288芯片來產生內存讀寫,接口讀寫有關的控制信號。由省出來的一部分引腳可以多一些狀態信號和DMA相關的信號,這樣整個計算機系統里還可以接更多的主控設備,包括其他的協處理器之類
BHE ̄\overline{BHE}BHE? :上面提過
S7:廢用狀態
TEST ̄\overline{TEST}TEST??????:輸入引腳,8086如果正在執行WAIT指令,該指令會判斷該引腳的狀態,如果是高電平這條指令就一直等待,如果是低電平,這個指令就執行完了。該指令主要用來和數學協處理器8087同步
READY:輸入引腳,講時序時再說
?
?
?
8086在最小模式下的引腳:
INTA ̄\overline{INTA}INTA?:上面提過
RD ̄,WR ̄\overline{RD},\overline{WR}RD,WR:讀內存讀接口都可以用到,通過M/IO ̄M/\overline{IO}M/IO 來區分是訪問內存還是接口
ALE:地址鎖存。地址信號要么和數據線,要么和狀態復用,那怎么知道什么時候輸出的是地址呢?
一旦8086正在輸出地址,并且地址是穩定的,8086會通過該引腳送來正脈沖。它輸出為高電平時,復用的信號正在輸出地址
DEN ̄\overline{DEN}DEN???:輸出允許。低電平表示正在傳遞數據
DT/R ̄\overline{R}R?:方向控制。高電平表示數據傳輸的方向是發送(從CPU內到CPU外),低電平表示數據傳輸的方向是接收
HOLD,HLDA:和DMA有關。DMA控制器如果想直接控制總線實現內存和外設之間的數據傳送,就要求8086釋放總線的控制權。DMA控制器會首先通過HOLD引腳給8086發高電平,若滿足一定條件,8086就通過HLDA告知DMA自己已釋放總線控制權
?
8088和8086的外部引腳的區別在:
1.8088只有低8位地址和數據復用
2.只有一個存儲體,不需要BHE ̄\overline{BHE}BHE,故34號引腳就是一個基本不用的狀態信號
3.28號引腳是IO/M ̄IO/\overline{M}IO/M?,剛好和8086相反
?
最后結合時序來講一下READY引腳,以8088的一個總線周期(總線周期由時鐘周期組成)為例:
?
上圖一個總線周期傳了一個數據,花了4個時鐘周期的時間。該時序有一個前提條件:內存的速度足夠快,讓CPU等一個時鐘周期(T3周期),內存就能把數據準備好。如果內存比較慢,CPU等一個時鐘周期內存還來不及把選中單元的數據取出來放在數據總線上,希望CPU多等幾個時鐘周期,就要用到READY信號
?
?
T3周期時會判斷READY信號的狀態,如果為高電平,則T3周期結束之后會直接進入T4周期。如果內存比較慢,則外面要有一個專門的電路,當CPU訪問內存時該電路必須要在T3周期之前把READY信號置0來通知CPU多等幾個周期,CPU在T3周期開始會采樣READY信號,發現是低電平則T3周期結束之后不會進入到T4周期而是插入等待周期TWT_WTW??
例題:
8086微處理器主頻為5MHZ,在不插入等待周期的情況下,從主存地址80006H讀一個8位數據所需要的時間為:
?
答案:800ns
?
然后講了下總線驅動的原理和重要性
?
?
8086在最小模式下的系統總線形成:
?
8086的引腳數有限,地址信號和其他信號是復用的,外面必須要增加一些電路以保存地址信號,在整個內存或接口讀寫的過程中地址由外加的電路來提供,這樣才能保證在一個總線周期的時間段內地址總是有效的
現在33號引腳接高電平,8086工作在最小模式下
要實現地址和其他信號的分離需要用到鎖存器,鎖存器有存儲功能,可以把在一個總線周期的第一個時鐘周期輸出的地址保存下來,然后在整個總線周期的時間段內由鎖存器來提供地址信號
地址信號一共21根線(20根地址線+BHE ̄\overline{BHE}BHE?????),如果用8位鎖存器74LS373,就要3片。鎖存器里存的內容希望能一直直接輸出,所以輸出允許OE ̄\overline{OE}OE????接地永遠有效。鎖存器的鎖存信號LE由ALE直接提供
在一個總線周期的第一個時鐘周期,當輸出地址并且地址穩定之后,ALE會輸出一個正脈沖
數據線AD0~AD15AD_{0}\sim AD_{15}AD0?~AD15?不存地址的時候就當數據線用,直接引出來就是數據線。如果負載很多,數據線想加驅動器,用74LS245雙向驅動器。16位的數據,8位的74LS245要用2片,它有兩個控制信號,方向選擇DIR接DT/R ̄DT/\overline{R}DT/R,輸出允許OE ̄\overline{OE}OE接DEN ̄\overline{DEN}DEN
然后剩下單向的控制信號線,如果想加驅動器用單向的74LS244,輸出允許OE1 ̄,OE2 ̄\overline{OE_1},\overline{OE_2}OE1??,OE2?????接地令其永遠有效。這4個信號經過它驅動之后后面可以帶很多的負載
CPU訪問內存和接口的時候最好能有各自獨立的讀寫信號,后面還可以加一些電路,借助數電的知識,這個附加電路的功能不難理解
時鐘發生器8284用來產生時鐘信號
上圖其實不是很嚴謹。READY信號應該由慢速的內存或者接口電路來產生,復位信號也不是由時鐘發生器而是由復位電路來產生,該低電平有效復位信號在8284里反相之后再做為8086的復位信號輸入
現在我們就有了地址,數據和控制信號,系統總線就形成了。各自存儲器芯片和接口芯片就可以直接連接到這些系統總線上
?
對8284工作原理的具體功能講解書上沒有
以8088為例,講了下更具體實際的電路下工作在最小模式下系統總線的形成,不太要求,了解即可
?
?
?
8086在最大模式下的引腳:
33號引腳接地,8086工作在最大模式下
此時所需要的控制信號,比如內存和接口讀寫,中斷應答信號都沒有,所以外面要多加一個總線控制器8288來產生這些信號
S2 ̄,S1 ̄,S0 ̄\overline{S_2},\overline{S_1},\overline{S_0}S2??,S1??,S0??:
8288想從8086知道該產生什么控制信號需要三個狀態信號S0,S1,S2,這三根線8種組合分別代表CPU的8種不同狀態
Passive(被動):三根狀態線輸出111,表示CPU已經釋放總線了,它在總線上現在是一個從設備,處于完全被動的狀態?,F在總線的使用權可能是DMA控制器或者是其他的協處理器
教材的表格里翻譯成”無效“
?
QS0 ̄,QS1 ̄\overline{QS0},\overline{QS1}QS0?,QS1?:四種組合,表示當前指令隊列的情況。一般情況下用不到
LOCK ̄\overline{LOCK}LOCK??????:有些時候希望某條指令在執行時不要釋放總線,可以在這條指令前加lock前綴。這條指令在執行時該引腳輸出低電平,表示現在處于總線封鎖狀態
RQ ̄/GT0 ̄,RQ ̄/GT1 ̄\overline{RQ}/\overline{GT0},\overline{RQ}/\overline{GT1}RQ?/GT0,RQ?/GT1?????????????:跟最小模式下的HOLD,HLDA功能類似?。一個引腳是雙向的,既可以當HOLD也可以當HLDA來用。如果DMA控制器想要申請總線的控制權,通過該引腳發一個負脈沖,CPU如果釋放總線會通過相同的引腳再往外送一個負脈沖告知DMA控制器
?
總結一下,最大模式下:
1.可以接更多可以成為總線主控設備的東西,包括DMA控制器,協處理器之類
2.結合8288芯片可以產生更多的控制信號,可以實現更大規模的計算機系統
?
?
?
8086在最大模式下的系統總線形成:
?
最大模式下CPU不直接提供ALE,DT/R ̄,DEN ̄DT/\overline{R},\overline{DEN}DT/R,DEN????,這些信號由總線控制器8288產生
注意8288產生的是DEN, 要加反相器
8288芯片產生的控制信號有嚴格的時序關系,也應有一個時間上的基準,它的時鐘信號和CPU用的是同一個。8288本身驅動能力很強,故這些輸出的控制信號后面不需要再加驅動,可直接引出作為系統總線的控制信號
以8088為例,講了下更具體實際的電路下工作在最大模式下系統總線的形成,不太要求,了解即可
?
Intel 處理器體系結構的發展
基本沒講,就舉了個書上沒有的宏融合技術的例子
?
Intel 處理器指令系統及匯編語言
匯編語言基礎(一)
為什么要學習匯編語言:
嵌入式系統:在特定硬件下,對程序的大小和運行速度需要高速優化的情況
BIOS程序的設計者,操作系統內核、設備驅動程序的設計者,編譯程序、調試工具的設計者、硬件檢測/測試/診斷程序的設計者、虛擬機/模擬器的設計者
逆向工程
有助于對計算機硬件、操作系統、應用程序之間交互的整體理解
突破高級語言的局限:高級語言中嵌入匯編
?
?
匯編語言格式主要有Intel和AT&T,上課用的Intel格式,編譯程序用微軟的宏匯編 (MASM)
先以模板程序Hello world為例,大致看下基于8086的16位匯編的整體結構和具體編寫方法:
?
?
?
?
段名:標識段的名字,唯一的或已存在的,可以隨便起
對齊方式:可以是BYTE、WORD、DWORD、PARA(16B對齊,不寫默認)、PAGE(256B對齊)
組合方式:可以是PRIVATE(不寫默認)、PUBIC、MEMORY,STACK、COMMON、AT地址。定義堆棧段時通常寫STACK,告訴編譯程序這個段當堆棧來處理
?
?
堆棧段定義了一個100B的空間。db偽指令表示現在要申請以字節為單位的數據塊,字節數為100。后面跟dup關鍵字和一個括號,如果這100B都想初始化為0,括號里就填0,括號里填 ? 編譯之后這100B仍然被初始化為0
偽指令編譯之后不會產生機器指令,它就是給編譯程序一些信息
?
數據段定義了一個字符串,message就是一個用符號表示的內存地址,本質上就是段內偏移,指向所定義的數據的起始位置,這樣在程序中引用字符串就不用直接引用邏輯地址。字符串以字節為單位,還是用db定義。0dh和0ah分別是回車符和換行符,‘$’ 是字符串的結束標識
?
assume偽指令并不能實現對CS,DS,SS三個段寄存器的賦值。有些代碼需要算地址,比如 mov dx,offset message,offset操作符取message地址的16位段內偏移,而編譯程序需要知道默認的段寄存器DS對應的是你自己定義的哪個段才能算段內偏移。assume偽指令告訴編譯程序編譯后面代碼時要認為此時CS,DS,SS已分別指向了你自己定義的代碼段,數據段,堆棧段
第一條指令必須要給它一個標號,整個程序總的結尾END偽指令跟這個標號,以指明主程序的入口點
接下來初始化數據段寄存器,引用段名把data段起始地址的高16位傳送到通用寄存器AX,再傳送到DS。這樣DS就真正指向了你自己定義的數據段
代碼段寄存器和堆棧段寄存器不需要程序初始化,由操作系統負責初始化。操作系統把數據段指向了別的位置,這個寄存器必須要由程序來初始化
data,offset message經過編譯之后實際上是立即數7和0,以后再解釋
接下來3條指令調用DOS操作系統的功能,負責把定義的字符串顯示到屏幕上
DOS操作系統通過 int 21h 軟中斷指令來給應用程序提供功能調用,執行這條指令之前必須要把功能號放在AH。9號功能就是往屏幕上顯示一條字符串,要求段寄存器DS和存段內偏移的DX共同指向要顯示的字符串的起始位置。故offset操作符取message地址的16位段內偏移傳送到DX寄存器,再執行軟中斷指令顯示字符串
不加offset操作符傳送的是內容而不是地址
最后調用DOS操作系統 int 21h 的4C號功能結束當前進程,回收其占用的內存空間
?
?
以后編寫匯編語言程序時就可以參考這個模板來寫
;1) 完整段定義的程序結構STACK SEGMENT PARA STACK 'STACK'DB 500 DUP(0) STACK ENDS DATA SEGMENT....... DATA ENDS CODE SEGMENTASSUME CS:CODE,DS:DATA,ES:DATA,SS:STACK START:MOV AX,DATAMOV DS,AX.......MOV AH,4CHINT 21H CODE ENDSEND STARTMASM的高版本還支持簡化段定義的寫法
;2) 簡化段定義的程序結構.MODEL SMALL ;存儲模型:小型,16位匯編 .STACK 100H ;定義堆棧段及其大小 .DATA ;定義數據段 ....... ;數據聲明 .CODE ;定義代碼段START: ;起始執行地址標號MOV AX,@DATA ;數據段地址MOV DS,AX ;存入數據段寄存器....... ;具體程序代碼MOV AH,4CH INT 21HEND START ;程序結束然后現場演示運行了下Hello world程序
?
?
編譯、鏈接和運行程序
用UltraEdit軟件打開Hellp.exe,對其結構進行了簡單的分析
注意x86處理器數據采用小端存儲
用二進制查看軟件打開可執行文件Hello.exe。后半部分和源程序內容有對應關系,前半部分是DOS操作系統下可執行文件的文件頭,有一些固定的格式,如微軟的操作系統下可執行文件的一個標志就是前兩個字節固定為"4D 5A"
文件頭是編譯程序生成的,會記錄DOS操作系統在執行該可執行文件之前這些寄存器的初值怎么設置。操作系統從磁盤把這個可執行文件往內存裝時,會分析利用文件頭的一些信息,分析完之后可能就會丟棄文件頭。編譯程序用的地址是邏輯地址,它總認為地址是從0開始的,所以他會認為你定義的堆棧段是從地址0開始的,相對應的代碼段就從0080開始,段的起始地址高16位是0008,故CS的初值應為0008。程序的入口點就是代碼段的第一條指令,段內偏移是0,故IP的初值是0000。堆棧段從邏輯地址0開始,故SS的初值應為0。SP的初值為0064,跟x86處理器對堆棧的管理方式有關系
x86處理器棧頂是小地址,棧底是大地址。16位模式下堆棧里的每個數據必須都是16位,占2個字節2個地址。該程序定義堆棧時申請了100B的堆??臻g,邏輯地址從0開始。x86處理器SP永遠指向棧頂上的有用元素,程序剛開始運行堆棧是空的,SP指向邏輯地址100 (64h)
文件頭記錄了堆棧段和代碼段寄存器的初值,但沒有記錄數據段寄存器的初值。DOS操作系統裝載可執行文件之后,DS另作他用,指向用于記錄命令行參數的數據結構程序段前綴PSP,故數據段寄存器得由代碼段里的指令初始化,令其指向自己所定義的數據段
?
將代碼段編譯之后產生的這些機器碼用反匯編工具轉化成匯編語言的形式。代碼段的前3個字節對應的是MOV AX,7,把立即數7傳送到AX。目標寄存器AX16位,故立即數7也用2字節16位來存儲,編譯的機器碼3個字節后2個字節存儲立即數7,第1個字節存的是該指令的操作碼還有AX寄存器的3位編號。后面的也類似
把反匯編的結果跟源代碼比較,可以發現mov ax,data中定義的數據段名稱編譯后變成立即數7,對應數據段起始地址的高16位。這條語句經過編譯程序編譯后產生的機器指令是把立即數7傳送到AX。類似的還有mov dx,offset message
?
操作系統不可能把這個程序從內存地址0開始裝,操作系統從磁盤把該可執行文件讀出來之后會檢查足夠大的內存空閑區域裝入,裝完后這些段寄存器應該怎么賦初值呢?
比如說堆棧段寄存器,它首先查文件頭,文件頭里規定的初值為0,在0的基礎上再加上起始的地址的段寄存器的內容,初始化代碼段寄存器也類似。數據段寄存器的初值文件頭里并沒有規定,操作系統在裝載這個程序到內存,起始地址確定之后需要修改代碼段。文件頭中有重定位項,該程序只有1個重定位項,這2個字節相當于指針,指向文件頭的另外一個位置,從該位置開始的2個16位字分別代表段內偏移和段起始地址的高16位,它們共同指向代碼段中需要重定位的地方
?
8086的內存管理引入分段機制,第一個目的是為了擴大可尋址的范圍,第二個目的是能夠實現程序在內存的任意位置浮動,一個程序在內存里任何一個地址開始裝,只需要重新初始化段寄存器的內容
?
然后驗證了一下可執行文件的具體格式
?
接下來再看32位的Hello world匯編程序,沒有用到通用寄存器,只是2個函數調用
32位應該是不要求的,但是后面講課時涉及的比較多,所以這里簡單記一下
.386 ;告訴編譯程序要產生32位的機器指令 .model flat,stdcall ;定義內存模型,如果這段程序要運行在Windows操作系統下,必須用flat平坦型。平坦的內存模型理解成只分頁不分段 ;函數調用參數傳遞的順序置為stdcall,通過堆棧向函數傳遞參數;利用Windows給應用程序提供的2個函數(子程序),這2個函數在代碼里并沒有,用之前需要聲明原型,比如函數的名稱,函數的參數和參數類型 ;DOS操作系統通過軟中斷給應用程序提供功能調用,Windows通過函數API的形式給應用程序提供功能調用MessageBoxA PROTO, ; 匯編里用PROTO偽指令聲明函數原型 hWnd:DWORD, ; 句柄,指向該窗口所屬的父窗口,沒有父窗口該參數給0lpText:PTR BYTE, ; 指針,指向一個要在窗口內部顯示的字符串lpCaption:PTR BYTE ; 指針,指向一個要顯示在窗口標題欄上的字符串style:DWORD ; 調用時給0,顯示一個最簡單的窗口,只有確定按鈕ExitProcess PROTO,exitCode:DWORD ; 如果程序正常結束,給參數0.data szCaption db 'A MessageBox !',0 szText db 'Hello, World !',0 .code ;4個壓棧指令傳參分別對應style,lpCaption,lpText,hwnd start: push 0 ; MB_OKpush offset szCaption push offset szTextpush 0 ; NULLcall MessageBoxA ; 顯示一個窗口push 0call ExitProcess ; 結束當前進程 end start然后看了一下該程序編譯、鏈接、運行的過程
?
Intel微處理器的組成結構
部分32位x86處理器的內部寄存器
增加的段寄存器FS和GS也是用來訪問數據的,訪問數據時默認的段寄存器仍是DS。32位匯編里普通應用程序沒有權限修改段寄存器的內容,所以也不用太關心
?
通用寄存器主要用于算術運算和數據傳送
每個寄存器可作32位或16位使用。一些16位的寄存器也可以作為兩個單獨的8位使用,其余通用寄存器的低16位有獨立的名字,但不能進一步細分
?
EAX, EBX, ECX, EDX : 高16位不能單獨使用,低16位可以單獨使用,目的是為了和8086兼容
ESI, EDI, EBP, ESP : 下面列出的16位寄存器通常只在編寫運行于實地址模式下的程序時才使用,即編寫32位匯編,必須得用32位。編寫16位匯編,必須得用16位
?
?
匯編語言基礎(二)
匯編語言的基本元素
1.算術運算符號
匯編語言里面的表達式最終得到的就是一個立即數。如mov AL 15/5*2,這條語句編譯之后并不會產生除法指令和乘法指令,編譯程序會計算表達式,產生機器指令把立即數6傳送到AL寄存器
?
2.標識符
程序員選擇的名字,用來識別變量、常量、過程或代碼標號
這個標識符本質上就是一個用符號表示的內存地址
- 可包含1~247個字符
- 大小寫不敏感。(匯編器加“-cp”參數則大小寫敏感)
- 第一個字符必須是字母(A~Z和a~z)、下劃線(_)、@、? 或 $,后續字符可以是數字
- 不能與保留字相同
- 盡量避免用 “@” 和 “_” 作為第一個字符,因為它們即用于匯編器,也用于高級語言編譯器
?
3.指令
程序被加載至內存開始運行后,由處理器執行的語句
?
4.定義數據
?
?
.data value1 BYTE 10h value2 BYTE ? list1 BYTE 10,20,30,40BYTE 50,60,70,80 list2 BYTE 32,41h,00100010b,'a' greeting BYTE "Good afternoon",0dh,0ah,0 array WORD 5 DUP(?) ;五個未初始化的值 value3 DWORD 12345678h?
PTR操作符
Intel處理器采用小端存儲方式
.data myDouble DWORD 12345678h .code mov ax,myDouble ;錯誤 mov ax,WORD PTR myDouble ;ax = 5678h mov ax,WORD PTR [myDouble+2] ;ax = 1234h mov bl,BYTE PTR myDouble ;bl = 78h?
5.符號常量
不是變量,不占用任何實際的存儲空間。常量的定義語句編譯之后不會產生任何機器指令,也不會產生任何數據
-
等號偽指令
COUNT = 500 ;給編譯程序傳達一個信息,告訴它后面不想直接寫500,用COUNT來代替 .data ...... array WORD COUNT DUP(0) ...... .code......mov cx,COUNT ;產生的機器指令把500立即數傳送到cx寄存器 L1: ............loop L1...... -
EQU偽指令
-
TEXTEQU偽指令
?
;計算數組和字符串的大小 list1 BYTE 10,20,30,40 List1Size = ($-list1) ;數組元素所占的字節數。微軟的宏匯編語法中,'$'代表當前語句的地址myString BYTE "This is a long string,"BYTE " Containing any number"BYTE " of characters",0dh,0ah MyString_len = ($ - myString) ;字符串占的字節數,也就是字符串里字符的個數list2 WORD 1000h,2000h,3000h,4000h ;注意數組元素的類型是WORD,16位占2個字節2個地址 List2Size = ($ - list2)/2?
數據傳送、尋址和算術運算
數據傳送指令
1.MOV指令
MOV指令需要遵循的規則:
-
兩個操作數的尺寸必須一致
.DATA VAR1 WORD 30 VAR2 WORD 50 .CODE......MOV AL,VAR1 ;×MOV DX,AL ;× -
兩個操作數不能同時為內存操作數,要通過一個通用寄存器作為中介
MOV VAR2 VAR1 ;× -
目的操作數不能是CS,EIP和IP,想改這幾個寄存器的內容另有專門的指令
-
立即數不能直接送至段寄存器
MOV DS,7 ;× MOV DS,@DATA ;×,編譯之后產生的是一個立即數
?
MOV指令格式:
mov reg,reg mov mem,reg mov reg,mem mov mem,imm mov reg,immreg --> 寄存器,mem --> 內存變量,imm --> 立即數
x86處理器其他的兩個操作數指令,其實就是支持類似這幾種格式
?
內存之間的移動通過寄存器暫存
.data var1 WORD ? var2 WORD ? .code mov ax,var1 mov var2,ax?
2.XCHG指令
含義:exchange data,交換兩個操作數的內容
格式:
xchg reg,reg xchg reg,mem xchg mem,reg?
交換兩個內存操作數:利用寄存器,MOV與XCHG結合使用
mov ax,val1 xchg ax,val2 mov val1 ax?
RISC結構的處理器沒有這樣的指令,要實現xchg ax,bx這樣兩個寄存器之間的交換,可以有好幾種方法:
;方法一:用另外一個寄存器作為中介 mov cx,ax mov ax,bx mov bx,cx;方法二:通過堆棧 push ax mov ax,bx pop bx;方法三:用異或指令 xor ax,bx xor bx,ax xor ax,bx?
?
直接偏移操作數(直接尋址)
MOV AL 7 ;把立即數7傳送到AL寄存器這條指令源操作數是立即數,立即尋址。目的操作數寫的是寄存器的助記符,經過編譯之后產生的機器指令里面存的是這個寄存器的編號,寄存器尋址
要訪問的數據在內存里面,指令中直接給出內存地址,這樣叫直接尋址。我們在寫匯編程序時想直接尋址,后面直接給內存地址太不直觀,還得數究竟它放的邏輯地址是什么,這本來是可以交給編譯程序來做的。所以在定義數據段里那些變量的時候要給變量起名字,用這個名字來表示內存地址,這本質上也屬于直接尋址
變量、數組、字符串、子程序的名字在本質上來講都是一個用符號來表示的內存地址
.data arrayB BYTE 10h,20h,30h,40h,50h ;arrayB這個符號本質上來講就是數組的首地址(邏輯地址),或者說數組第0個元素的地址 .code mov al,arrayB ; AL=10h ;在代碼段里直接引用這個符號地址,它指向數組的第0個元素,把第0個元素傳送到AL寄存器,屬于直接尋址mov al,[arrayB+1] ; AL=20h ;第一個元素。編譯程序完成首地址+偏移量的加法,結果就是一個邏輯地址,仍屬于直接尋址 ;MASM并不要求一定要使用方括號 mov al,[arrayB+2] ; AL=30hmov al,[arrayB+20] ; AL=?? ;匯編語言不管是編譯程序還是執行時,DOS操作系統都不會做數組的越界檢查 ;字和雙字數組 .data arrayW WORD 100h,200h,300h .code mov ax,arrayW ; AX=100h mov ax,[arrayW+2] ; AX=200h 字占2個字節2個地址,數組首地址基礎上+2指向數組第一個元素.data arrayD DWORD 10000h,20000h .code mov eax,arrayD ; EAX=10000h mov eax,[arrayD+4] ; EAX=20000h 雙字32位占4個字節4個地址?
常用的算術運算類指令
1.ADD指令
將同尺寸的源操作數和目的操作數相加,結果在目的操作數中(不改變源操作數)
格式:同MOV指令,add 目的操作數 源操作數
.data var1 DWORD 10000h var2 DWORD 20000h .code mov eax,var1 add eax,var2 ;30000h?
2.SUB指令
將源操作數從目的操作數中減掉,結果在目的操作數中(不改變源操作數)
格式:與MOV、ADD指令相同,sub 目的操作數 源操作數
.data var1 DWORD 30000h var2 DWORD 10000h .code mov eax,var1 sub eax,var2 ;20000h影響的標志位:CF、ZF、SF、OF、AF、PF
?
3.NEG指令
含義:negate, 求負
該指令認為操作數為一個有符號數,是補碼。將操作數按位取反、末位加1
格式:
neg reg meg mem影響的標志位:CF、ZF、SF、OF、AF、PF
?
;例子:實現算術表達式 Rval = -Xval + (Yval-Zval).data Rval SDWORD ? ;微軟宏匯編高版本新引入的變量類型SDWORD,有符號的32位整數,理解成32位補碼 Xval SDWORD 26 Yval SDWORD 30 Zval SDWORD 40 .code mov eax,Xval neg eax ; EAX = -26 mov ebx,Yval sub ebx,Zval ; EBX = -10 add eax,ebx mov Rval,eax ; -36?
算術運算影響的標志
1.零標志位
mov cx,1 sub cx,1 ; ZF = 1 mov ax,0FFFFh ; 全1理解成補碼,真值就是-1 inc ax ; ZF = 1 inc ax ; ZF = 0?
2.符號標志位
mov cx,0 sub cx,1 ; SF = 1 add cx,2 ; SF = 0?
3.進位標志位
INC和DEC指令不影響進位標志
mov al,0FFh add al,1 ; CF = 1mov ax,00FFh add ax,1 ; CF = 0mov ax,0FFFFh add ax,1 ; CF = 1mov al,1 sub al,2 ; CF = 1。最高位向更高位有進位或借位時置1?
4.溢出標志位
OF=Cn⊕Cn?1OF=C_n\oplus C_{n-1}OF=Cn?⊕Cn?1?。其中CnC_nCn?為符號位產生的進位,即標志位CFCFCF;Cn?1C_{n-1}Cn?1?為最高有效位向符號位產生的進位
mov al,+127 add al,1 ; OF = 1mov al,-128 sub al,1 ; OF = 1mov al,-128 ; AL = 10000000b neg al ; AL = 10000000b, OF = 1mov al,+127 ; AL = 01111111b neg al ; AL = 10000001b, OF = 1。?
CPU如何知道一個數字是有符號的還是無符號的?
CPU并不知道——只有程序員才知道。CPU在執行指令之后機械地設置各種狀態標志,它并不知道那些對程序員是重要的,程序員自己來選擇哪些標志和忽略哪些標志
?
;例子程序(AddSub3).386 .MODEL flat, stdcall .STACK 4096 ExitProcess PROTO,dwExitCode:DWORD ;PROTO偽指令左側跟函數的名字,右側是函數的參數和參數類型.data Rval SDWORD ? Xval SDWORD 26 Yval SDWORD 30 Zval SDWORD 40 .code main PROC ;"主程序的名稱 PROC"開始,"相同的名稱 ENDP"作為結尾;主程序的名稱可以隨便起,本質是一個用符號表示的內存地址,它所定義的就是這個代碼段 ;的第一條指令的地址,是程序的入口點mov ax,1000h ; INC and DECinc ax ; 1001hdec ax ; 1000h; Expression: Rval = -Xval + (Yval - Zval)mov eax,Xvalneg eax ; EAX = -26mov ebx,Yval sub ebx,Zval ; EBX = -10add eax,ebxmov Rval,eax ; -36; Zero flag example:mov cx,1sub cx,1 ; ZF = 1mov ax,0FFFFh inc ax ; ZF = 1; Sign flag example:mov cx,0sub cx,1 ; SF = 1mov ax,7FFFh add ax,2 ; SF = 1; Carry flag examplemov al,0FFhadd al,1 ; CF = 1, AL = 00; Overflow flag examplemov al,+127 add al,1 ; OF = 1mov al,-128sub al,1 ; OF = 1INVOKE ExitProcess,0 ;32位的匯編最后都要調用操作系統結束當前進程的函數,該函數調用前要聲明原型;引入INVOKE偽指令使帶參數的函數在調用的時候可以一行寫完,后面跟函數名和參數main ENDP END main然后用Visual Studio演示了一下運行該程序
?
間接尋址
1.間接操作數(寄存器間接尋址)
把指針寄存器外加中括號。它其實就是一個指針,指向內存的某一個位置
;32位匯編:三個雙字相加.data arrayD DWORD 10000h,20000h,30000h .code mov esi,OFFSET arrayD ;OFFSET操作符取數組的首地址傳送到指針寄存器,引用數組名,數組名本質上是數組的首地址;指針寄存器里存的是32位的段內偏移,默認的段寄存器仍然是數據段寄存器DS,DS里存的是段表的行號,該指令執行時會根據DS的內容查段表對應的行,從該行得到段的起始地址。段的起始地址加指針寄存器的段內偏移,結果就是內存地址,然后訪問內存中的數組元素;代碼段并沒有初始化數據段寄存器,在32位保護模式下只有最高特權級的代碼,如操作系統內核才有權限修改段寄存器的內容,普通的應用程序運行在最低特權級3下沒有權限修改段寄存器的內容的;16位的匯編段寄存器要由程序自己管理mov eax,[esi] ;寄存器間接尋址,訪問數組的第0個元素add esi,4 ;數組元素32位占4個字節,指針+4指向數組的第1個元素 add eax,[esi] add esi,4 add eax,[esi] ;16位匯編:三個字相加.data arrayW WORD 1000h,2000h,3000h .code mov ax,@data mov ds,ax ;初始化數據段寄存器mov si,OFFSET arrayW ;OFFSET操作符取數組首地址,或者叫16位的段內偏移傳送到指針寄存器mov ax,[si] ;默認的段寄存器還是數據段寄存器,把數據段寄存器的內容段起始地址的高16位讀出,末尾添4個0構成20位的段起始地址,再加上指針寄存器里的16位段內偏移,結果為20位的內存地址。訪問內存剛好對應數組的第0個元素,從這個地址開始連續傳送2個字節至AXadd si,2 add ax,[si] add si,2 add ax,[si]?
間接操作數可以是任何用方括號括起來的32位通用寄存器(EAX,EBX,ECX,EDX,ESI,EDI,EBP,ESP)。實地址模式下只能用SI,DI,BX,BP。通常盡量避免使用BP(BP默認的段寄存器是SS,常用來尋址堆棧而不是數據段)
?
PTR與間接操作數的聯合使用
inc [esi] ; error: operand must have size. +1指令只有一個操作數,并且還是一個指針,指向一個內存位置,不知道存的是8位還是16位,32位inc BYTE PTR [esi] ; 認為是8位數據?
2.變址操作數(寄存器相對尋址)
.data arrayB BYTE 10h,20h,30h .code mov esi,0 ; 要訪問的數組元素編號作為偏移量放在指針寄存器 mov al,[arrayB + esi] ; AL = 10h mov al,arrayB[esi] ; 同上另一種格式 mov esi,OFFSET arrayB mov al,[esi] ; AL = 10h mov al,[esi+1] ; AL = 20h mov al,[esi+2] ; AL = 30h?
實模式下只能使用SI,DI,BX,BP寄存器。(盡量避免使用BP寄存器)
?
?
JMP和LOOP指令
1.JMP指令
top:...jmp top ;repeat the endless loop?
2.LOOP指令
格式:LOOP 目的地址
LOOP指令的執行:
在實地址模式下,用做默認循環計數器的是CX而不是ECX
?
mov ax,0mov ecx,5 L1: inc ax loop L1 ;循環體的第一條指令必須要給標號。循環體的最后一條指令要用loop,后面跟這個標號;循環結束時,AX=5 ECX=0?
循環的嵌套
.data count DWORD ? .codemov ecx,100 L1: push ecx ; 將循環計數器的內容,即外層循環的當前次數保存mov ecx,20 ; 重新設置循環計數器的內容 L2: ..loop L2pop ecx ; 恢復循環計數器原來的內容 loop L1?
;例子:整數數組求和; This program sums an array of words. (SumArray.asm) .386 .MODEL flat, stdcall .STACK 4096 ExitProcess PROTO,dwExitCode:DWORD .data intarray WORD 100h,200h,300h,400h .code main PROCmov edi,OFFSET intarray ; address of intarraymov ecx,LENGTHOF intarray ; loop countermov ax,0 ; zero the accumulator L1: add ax,[edi] ; add an integeradd edi,TYPE intarray ; point to next integerloop L1 ; repeat until ECX = 0INVOKE ExitProcess,0 main ENDP END main?
;例子:拷貝字符串; This program copies a string. (Copystr.asm) .386 .MODEL flat, stdcall .STACK 4096 ExitProcess PROTO,dwExitCode:DWORD .data source BYTE "This is the source string",0 target BYTE SIZEOF source DUP(0) .code main PROCmov esi,0 ; index registermov esi,SIZEOF source ; loop counter L1: mov al,source[esi] ; get a character from sourcetarget[esi],al ; store it in the targetinc esi ; move to next characterloop L1 ; repeat for entire stringINVOKE ExitProcess,0 main ENDP END main然后用Visual Studio演示了一下運行該程序
?
過程
與外部庫鏈接
鏈接庫如何工作?
假設程序要調用名為WriteString的過程在控制臺上顯示字符串,這個函數在另外一個庫里,并沒有在源代碼文件中,則:
1.程序中要用PROTO偽指令聲明要調用的程序
例:Irvine32.inc 中包含如下語句
WriteString PROTO2.用一條CALL指令執行WriteString過程
call WriteString3.當程序被編譯時,編譯器知道操作碼但不知道地址碼,因為這個函數的代碼不在當前文件中,它為CALL指令的目標地址留出空白,該空白將由鏈接器在鏈接時填充
4.鏈接器在鏈接庫中查找WriteString這個名字,從庫中提取函數代碼,跟編譯產生的目標文件鏈接到一起形成可執行文件,鏈接到一起之后這個函數的首地址邏輯地址才知道,再由鏈接器負責把WriteString的首地址填到CALL指令留的空白的位置
?
鏈接器的命令行選項
之前32位匯編的例子里結束當前程序要調用操作系統的函數,那個函數在kernel32.lib庫文件中
例:
Link32 HelloWin.obj user32.lib kernel32.lib/subsystem:windowsWindows下同一時刻可能有多個程序在運行,免不了要調用各種各樣Windows提供的函數,包括結束當前進程的函數,幾乎所有程序都要調用,每個程序里都會有很多這種重復的函數可執行代碼,實際上是沒有必要的,也浪費內存空間
所以實際在Windows系統里,這些庫文件里存的所謂的這些函數的代碼其實就是函數的鏈接而不是函數真正的代碼,可以理解成庫文件里跟那個函數所對應的代碼就是個跳轉指令,跳轉指令執行后才會真正地跳轉到那個函數的代碼去執行。那個函數的代碼包含在kernel32.dll動態鏈接庫文件中,重復的函數代碼在動態鏈接庫,在內存里只有一個副本
?
?
過程的定義和使用
1.過程的定義
子程序定義一般的結構:用PROC和ENDP偽指令來聲明,另外還必須給過程起一個名字(一個有效的標識符),中間寫代碼,代碼的最后一條指令必須得寫ret子程序返回指令
寫主程序的時候也可以用相同的結構來寫,主程序的結束如果是16位的匯編,通常調用DOS操作系統的4C號功能并且給操作系統返回0表示程序正常結束
如果是32位的匯編主程序結束一般結合INVOKE偽指令。INVOKE偽指令是微軟宏匯編6.0以上引入的,引入的目的就是為了帶參數的函數在調用的時候可以一行寫完,后面跟函數名和參數,多個參數用 ‘,’ 隔開
具體到這個例子,這條語句編譯之后會產生2條機器指令,首先會產生壓棧指令,把參數0壓入堆棧,然后再產生call指令調用子程序
?
2.例子:三個整數之和
SumOf PROCadd eax,ebxadd eax,ecxret SumOf ENDP?
3.為過程添加文檔
;------------------------------------------------------------------- SumOf PROC ; ;Calculates and returns the sum of three 32-bit integers. ;Receivers: EAX, EBX, ECX, the three integers. May be ; signed or unsigned ;Returns: EAX = sum, and the status flags (Carry, ; Overflow, etc.) are changed. ;-------------------------------------------------------------------add eax,ebxadd eax,ecxret SumOf ENDP?
4.向過程傳遞寄存器參數
.data theSum DWORD ? .code main PROCmov eax,10000h ; argumentmov ebx,20000h ; argumentmov ecx,30000h ; argumentcall Sumof ; EAX=(EAX+EBX+ECX)mov theSum,eax ; save the sum?
例子:對整數數組求和
; ArraySum 過程:;------------------------------------------------------------------- ArraySum PROC ; ; Calculates the sum of an array of 32-bit integers ; Receives: ESI points to the array, ECX = array size ; Returns: EAX = sum of the array elements ;-------------------------------------------------------------------push esi ; save ESI, ECXpush ecxmov eax,0 ; set the sum to zero L1:add eax,[esi] ; add each integer to sumadd esi,4 ; point to next integerloop L1 ; repeat for array sizepop ecx ; restore ECX, ESIpop esiret ; sum is in EAX ArraySum ENDP ; 調用 ArraySum.data array DWORD 10000h,20000h,30000h,40000h,50000h theSum DWORD ? .code main PROC mov esi,OFFSET array ; ESI points to arraymov ecx,LENGTHOF array ; ECX = array countcall ArraySum ; calculate the summov theSum,eax ; return in EAX......?
?
LEA DX,mess2和MOV DX,OFFSET mess2功能上等價,直接引用變量的名字,取該變量的地址而不是內容到DX寄存器。段內偏移放在DX寄存器,而段寄存器主程序已初始化好了
整個程序總的結尾用END跟這個程序的入口點,入口點應該是主程序的名字,本質上是主程序的第一條指令的地址。由于匯編語言用這種方式指明程序的入口點,所以寫的時候也可以把子程序寫在前面,主程序寫在后面
?
運行一下這個16位程序。在32位Windows下新建文本文件,后綴名改成.asm,打開把代碼粘貼進去。保存完之后再用最原始的方法在命令行方式下先后輸入編譯命令和鏈接命令,再輸入產生的可執行文件名字運行
會發現和想象的不太一樣,程序好像是個循環程序,循環到后面報錯了,程序被操作系統強行關閉。為什么會這樣?
問題出在子程序里面用壓棧指令保護現場,但是返回之前沒有出棧指令恢復現場,即返回指令之前少寫了POP DX
CALL指令執行的時候CPU首先會保護當前的斷點,把當前PC的內容壓入堆棧,8086的PC對應兩個寄存器CS和IP。這個程序CALL指令和RET指令默認都屬于靜調用和靜返回,靜調用和靜返回只需要保護IP,段寄存器的內容不需要保護,調用返回都是在同一個段里面。所以CALL指令執行時會首先把當前IP的內容壓入堆棧,它往堆棧里壓的這個地址應該是CALL指令順序的下一條指令的地址,再跳轉到子程序執行。子程序又會把DX原來的內容壓入堆棧,子程序返回指令會從堆棧段棧頂彈出數據到PC,對8086就是彈到IP,少寫POP指令會導致把一個錯的數彈到IP
OFFSET操作符取第一個字符串的首地址傳送到DX,第一個字符串是數據段里最開始定義的字符串,在它之前沒有任何東西,所以它的段內偏移應為0,DX的內容就是0。所以子程序執行到返回指令時,它會從堆棧里把0彈出賦值給IP。代碼段寄存器內容不變,但是段內偏移變為0,就會從主程序的第一條指令開始執行,所以會不停地來回循環,在屏幕上不停地顯示那兩條字符串
而且每調用一次子程序都在堆棧里留了一個數沒有彈出,棧只申請了256B的空間,增長到一定程度就會導致棧溢出,最終堆棧里的數據會覆蓋代碼段里的的指令,CPU再取指令譯碼發現是非法指令就產生中斷讓操作系統強行中止程序
?
條件處理
布爾和比較指令
1.AND指令
功能:在操作數的對應數據位之間執行布爾(位)”與“操作,并將結果保存在目的操作數中
格式:AND 目的操作數 源操作數
允許的操作數形式:
AND reg,reg AND reg,mem AND mem,reg AND reg,imm AND mem,imm兩個操作數可以是8、16或32位的,但它們的尺寸必須相同
影響的標志位:
- 總是清除OF和CF
- 根據結果修改SF、ZF、PF
主要用途:
-
對特定的位清”0“,同時保留其他的位
mov al,00111011b mov al,00001111b -
大寫字母與小寫字母的ASCII碼之間的關系:
‘a’:61h,即 01100001
‘A’:41h,即 01000001
;例:將字符轉換為大寫形式.data array BYTE 50 DUP(?) .codemov ecx,LENGTHOF arraymov esi,OFFSET array L1:and byte ptr [esi],11011111binc esiloop L1
?
2.OR指令
功能:按位取”或“
格式:與AND指令相同
主要用途:
-
對特定的位置置”1“,并保留其它位
mov al,00111011b or al,00001111b例:將0到9之間的整數轉換成對應的ASCII碼數字
方法:將位4和位5置為1
mov dl,5 ; 二進制值 or dl,30h ; 轉換到ASCII碼要調用操作系統的功能在屏幕上顯示必須得以ASCII碼的形式
也可以用add dl,30h或add dl,'0'
?
3.XOR指令
功能:按位取”異或“
格式:與AND及OR指令相同
XOR指令的用途:
-
對某些位取反,同時不影響其他的位
0跟一個數異或還是那個數,1跟一個數異或就是那個數的取反
-
判斷16位或32位值的奇偶性
mov ax,64C1h ; 0110 0100 1100 0001 xor ah,al ; PE,奇偶標志被設置奇偶標識位只反映運算結果低8位里1的個數是否是偶數個
-
簡單數據加密
將某個操作數與同樣的操作數執行兩次異或運算后,其值保持不變
(X⊕Y)⊕Y=X(X\oplus Y)\oplus Y=X (X⊕Y)⊕Y=X
4.NOT指令
功能:將操作數所有數據位取反,結果為反碼
格式:
NOT reg NOT mem例:
mov al,11110000b not al ; AL = 00001111bNOT指令不影響任何狀態標志
?
布爾和比較指令
1.TEST指令
功能:兩操作數按位“與”,根據結果設置標志位,但不回送結果(不修改目的操作數)
格式:與AND指令相同
用途:測試操作數的某一位是“0”還是“1”
例子:測試多個位
想知道AL中第0位、第3位是否同時為“0”
test al,00001001b ;test bits 0 and 3判斷ZF是否等于1
影響的標志:清除OF、CF;修改SF、ZF、PF
?
2.CMP指令
格式:與AND指令相同。cmp 目的操作數 源操作數
功能:與減法指令一樣執行減法操作,即目的操作數-源操作數,但不回送結果,只影響標志位
影響的標志:根據相減結果修改OF、SF、ZF、CF、AF、PF
?
無符號操作數的比較:
有符號操作數的比較:
例:
mov ax,5 cmp ax,10 ; CF = 1mov si,105 cmp si,0 ; ZF=0,CF=0mov ax,1000 mov cx,1000 cmp cx,ax ; ZF = 1?
條件跳轉
1.條件結構
條件分支的實現:
- 使用CMP、TEST、AND之類的指令修改CPU標志
- 使用條件跳轉指令測試標志值,以決定是否向新的分支轉移
例子:
cmp al,0jz L1 ;jump if ZF=1.. L1: and dl,10110000bjnz L2 ;jump if ZF=0.. L2:?
2.跳轉指令的類型
mov al,7Fh ; (7Fh or +127) cmp al,80h ; (80h or -128) ja IsAbove ; no: 7F not > 80h jg IsGreater ; yes: +127 > -128?
例:8位內存操作數status中存放著同接口卡相連的外設的狀態信息
1.bit5為“1”時外設處于脫機狀態,跳轉到某標號處
mov al,status test al.00100000b jnz EquipOffline2.bit0、bit1、bit4任何一位為“1”時跳轉到某標號處
mov al,status test al,00010011b jnz InputDataByte3.bit2、bit3、bit7全部為“1”時跳轉到某標號處
mov al,status and al,10001100b cmp al,10001100b jz ResetMachine?
例:比較V1、V2、V3三個無符號變量的值,將最小值送入AX寄存器
.data V1 WORD 23 V2 WORD 0 V3 WORD -1 .codemov ax,V1 ;assume V1 is smallestcmp ax,V2 ;if ax <= V2 thenjbe L1 ; jump to L1mov ax,V2 ;else move V2 to ax L1: cmp ax,V3 ;if ax <= V3 thenjbe L2 ; jump to L2mov ax,V3 ;else move V3 to ax L2: ......?
然后推薦了https://godbolt.org,可以直接輸入C語言代碼,即時查看反匯編結果
?
整數算數指令
移位 (Shifting) 指令:
記住以下單詞:Shift, Left, Right, Arithmetic, Rotate, Carry
-
SHL 邏輯左移
-
SHR 邏輯右移
-
SAL 算術左移
-
SAR 算術右移
-
ROL 循環左移
-
ROR 循環右移
-
RCL 帶進位的循環左移
-
RCR 帶進位的循環右移
上述移位指令影響OF、CF
?
1.SHL指令:邏輯左移
格式:SHL 目的操作數 移位位數
SHL reg,imm8 SHL mem,imm8 SHL reg,CL SHL mem,CL8088/8086要求imm8必須等于1。80286以上,imm8可為任意整數
CL方式可用于任何Intel x86處理器
上述格式也適用于SHR、SAL、SAR、ROR、ROL、RCR、RCL指令
源操作數可以是立即數,如果是8088/8086,立即數只能是1。如果想左移多位,得把移位的次數先放到CL寄存器
?
例:無符號數與2的整數次冪做快速乘法
mov dl,5 shl dl,1 ;5*2 = 10 mob dl,10 shl dl,2 ;10*4 = 40?
2.SAL指令:算術左移,與SHL指令等價
?
3.SHR指令:邏輯右移,最高位總是填0
格式:與SHL相同
?
例:無符號數與2的整數次冪做快速除法
mov dl,32 shr dl,1 ;32/2 = 16 mov dl,01000000b ;AL = 64 shr dl,3 ;64/8 = 8?
4.SAR指令:算術右移,最高位填充原來的符號位
例1:
mov al,0F0h ; AL = 11110000b (-16) sar al,1 ; AL = 11111000b (-8); CF = 0例2:有符號數的除法,-128/8=-16
mov dl,-128 ; DL = 10000000b sar dl,3 ; DL = 11110000b?
5.ROL指令:循環左移
特點:不丟失任何數據位
?
例1:
mov al,40h ;01000000 rol al,1 ;10000000, CF=0 rol al,1 ;00000001, CF=1 rol al,1 ;00000010, CF=0例2:將一個字節的低4位與高4位進行交換
mov al,26h rol al,4 ;AL = 62h?
6.ROR指令:循環右移
例:
mov al,01h ;00000001 ror al,1 ;10000000, CF=1 ror al,1 ;01000000, CF=0?
7.RCL和RCR
RCL指令:帶進位的循環左移
RCR指令:帶進位的循環右移
?
?
移位和循環移位的應用:
-
多雙字移位
在數據段里定義3個元素的數組,數組元素的類型是32位雙字,這3個元素連到一塊組成96位的數,小地址對應低位數據,大地址對應高位數據。希望將其整體邏輯右移1位
移位時從高位開始移。邏輯右移最高位永遠填0,最低位移走到CF。中間的數再移位就可以把剛才高位移走的數從CF再移到這個數里面去
-
二進制乘法:利用移位、相加實現
-
顯示二進制位
-
分離位串
例:MS-DOS的功能57h在DX中返回文件的修改時間,假設為2019年3月10日,則DX中的文件日期戳如下(年份是相對于1980年的):
提取月可以將這個數整體地邏輯右移5位,然后再用AND指令將高12位清0只保留低4位
?
乘法和除法指令
1.MUL指令
格式(操作數為乘數):操作數可以是寄存器也可以是內存變量,但不能是立即數。只跟一個操作數,另外一個操作數是隱含的或者說默認的
MUL r/m8 MUL r/m16 MUL r/m32功能:無符號乘法。將8位、16位或32位的操作數與AL、AX或EAX相乘
乘數和被乘數位數必須相等。兩個8位數相乘運算結果用16位表示,兩個16位數相乘結果是32位,高16位在DX,低16位在AX。32位數相乘同理
作為高級語言來講,32位變量做各種運算后運算結果也會存到一個32位變量里面,但這實際上有溢出的可能,用匯編語言來寫可以更完備。兩個8位數相乘,運算結果用8位表示不了,CF會置1。16位數和32位數相乘同理
例:
mov al,5h mov bl,10h mul bl ; CF = 0 ; 積在AX中,50h .data val1 WORD 2000h val2 WORD 0100h .code mov ax,val1 mul val2 ; CF = 1 ; 積在DX:AX中,00200000h?
2.IMUL指令
有符號乘法,格式與MUL指令相同。認為被乘數和乘數都是補碼,乘的結果也會用補碼來表示
如果積的高半部分不是低半部分的符號擴展,置CF和OF
例:
mov al,48 ; 48D = 30H mov bl,4 imul bl ; AX = 00C0h, OF = 1mov al,-4 mov bl,4 imul bl ; AX = FFF0h, OF = 0mov ax,48 mov bx,4 imul bx ; DX:AX = 000000C0h, OF = 0?
3.DIV指令
無符號除法
格式(操作數為除法):
DIV r/m8 DIV r/m16 DIV r/m32功能:執行8位、16位、32位無符號整數除法運算
除數是8位,被除數必須是16位,商跟余數也得是8位。除數是16位和32位同理
?
例:
; 8003h/100h mov dx,0 ; clear dividend, high mov ax,8003h ; dividend, low mov cx,100h ; divisor div cx ; AX = 0080h, DX = 0003h .data dividend QWORD 0000000800300020h divisor DWORD 00000100h .code mov edx,DWORD PTR dividend + 4 ; high doubleword mov eax,DWORD PTR dividend ; low doubleword div divisor ; EAX = 08003000h, EDX = 00000020h?
除法運算指令可能溢出,乘法兩個n位數相乘運算結果用2n位表示肯定不會溢出。如:
mov Ax,1000H mov BL,10H DIV BL結果商為100H,用8位寄存器AL無法表示,除法溢出在CPU內內部產生0號中斷
有符號數的除法運算指令為IDIV,下面幾個具體的例子會說到
?
例1:以匯編語言實現下面的C++語句(使用32位無符號整數):
var4 = (var1 + var2) * var3;?
mov eax,var1add eax,var2mul var3 ; EAX = EAX * var3jc tooBig ; unsigned overflow?mov var4,eaxjmp next tooBig: ; display error message?
?
例2:使用32位有符號整數實現下面C++語句:
var4 = (var1 * -5) / (-var2 % var3);?
mov eax,var2 ; begin right side neg eax ;有符號數,需將被除數符號擴展到EDX,然后用IDIV指令 cdq ; sign-extend dividend idiv var3 ; EDX = remainder mov ebx,edx ; EBX = right sidemov eax,-5 ; begin left side imul var1 ; EDX:EAX = left sideidiv ebx ; final division mov var4,eax ; quotientcdq 把EAX的最高位符號位擴展到EDX,x86還專門設計了類似的指令
cbw : 把AL寄存器里存的8位補碼擴展到16位,存到AX
cwd : 把AX寄存器里存的6位補碼擴展到32位。高16位存在DX,低16位存在AX
?
?
擴展加法和減法
思考:用C++如何實現兩個128位整數相加?
1.ADC:帶進位加
目的操作數+源操作數+進位標志→\to→目的操作數
ADC reg,reg ADC mem,reg ADC reg,mem ADC mem,imm ADC reg,imm?
2.SBB:帶進位減
目的操作數-源操作數-進位標志→\to→目的操作數
?
利用上述指令可方便地實現任意大小數字的減加法運算
?
?
?
高級過程
高級語言如C語言的編譯器把參數傳遞給子程序需要借助堆棧,子程序如何把參數從堆棧里取出來?
?
堆棧框架
內存模式:.MODEL偽指令
例:
.MODEL flat,stdcall保護模式程序使用平坦內存模式
?
STDCALL關鍵字:指定過程按照從右往左的順序壓入參數。例:
INVOKE ADDTwo,5,6將生成如下匯編語言代碼:
push 6 push 5 call AddTwo?
堆棧參數的顯式訪問:
.data sum DWORD ? .codepush 6 ; second argumentpush 5 ; first argumentcall AddTwo ; EAX = summov sum,eax ; save the sum...... AddTwo PROCpush ebp mov ebp,esp ; base of stack frame;把ESP的內容賦值給EBP,這樣EBP和ESP同時指向當前棧頂的位置。如果后面子程序里還有;PUSH,POP這樣對堆棧的操作只會影響ESP的內容,但EBP一旦賦值之后,在整個子程序范圍;內就不會再修改mov eax,[ebp + 12] ; second argumentadd eax,[ebp + 8] ; first argumentpop ebpret 8 ; clean up the stack;首先從堆棧中彈出棧頂元素至指令指針寄存器,然后把ESP的內容加上8,相當于把2個參數占;用的堆??臻g也釋放了 AddTwo ENDP?
?
子程序中定義的這些臨時變量存儲在堆棧中。在操作系統的角度來講,我們在C語言里寫的這個主程序也是子程序,所以主程序里定義的這些變量其實也是臨時變量,也存儲在堆棧中。這種情況下堆棧應該如何管理?
在主程序之外定義的變量不在堆棧中
?
創建局部變量:
C++例子
void MySub() {char X = 'X';int Y = 10;char name[20];name[0] = 'B';double Z = 1.2; }訪問這些變量都通過EBP相對尋址
為了對齊,盡管第一個變量X是8位數據,但實際上在堆棧里也會占用4個字節
?
用匯編語言實現
MySub PROCpush ebpmov ebp,espsub esp,36; create variablesmov BYTE PTR [ebp-4],'X' ; Xmov DWORD PTR [ebp-8],10 ; Ymov BYTE PTR [ebp-28],'B' ; name[0]mov DWORD PTR [ebp-32],3ff33333h ; Z(high)mov DWORD PTR [ebp-36],33333333h ; Z(low)…… ……mov esp,ebp ; destroy variablespop ebpret MySub ENDPz采用IEEE754標準存儲
講了下緩沖區溢出攻擊,并解釋了在https://godbolt.org中查看該例子時存在的一些不同
?
字符串和數組
基本字符串操作指令
5組處理字節、字和雙字數組的指令,成為基本字符串指令,但用法并不限于處理字符串數組
MOV : 數據傳送類 S : 串操作指令 B, W, D : 數組元素的類型是字節,字,雙字
?
使用重復前綴
例:
main PROCmov ax,@data ;get addr of data segmov ds,ax ;initialize DSmov es,ax ;initialize ES cld ; clear direction flag mov si,OFFSET string1 ; SI points to source mov di,OFFSET string2 ; DI points to target mov cx,10 ; set counter to 10 rep movsb ; move 10 bytes對于串操作指令。段寄存器必須用DS,指針寄存器必須用SI,這兩個寄存器指向源數組/源串。ES和DI指向目的數組/目的串
加了重復前綴rep后,這條指令會重復執行好多次,重復次數要放在CX/ECX。指令每執行一次首先會把CX/ECX的內容減1,如果不為0則重復執行這條指令。串操作指令從DS和SI指向的位置讀一個字節的數據然后把它寫入到ES和DI指向的位置。寫完了之后SI和DI的內容加還是減由方向標志位DF決定,DF=0則ESI、EDI自動增加,DF=1則ESI、EDI自動減少。加減多少取決于數組元素類型
數組元素的類型是字節,并且里面存的是ASCII碼,那就是字符串
方向標志可以通過CLD和STD指令改變:
CLD ; 清除方向標志 STD ; 設置方向標志?
?
二維數組
1.基址變址操作數(基址+變址尋址)
基址變址 (base-index) 操作數:將兩個寄存器的值相加(稱為基址寄存器、變址寄存器)來產生偏移地址
保護模式程序中,可使用任意32位通用寄存器
實地址模式下,基址寄存器訪問數據段最好用BX(也可以用BP,但是BP一般是用來訪問堆棧的,它的段寄存器默認是SS)。變址寄存器可以用SI和DI
.data array WORD 1000h,2000h,3000h .codemov ebx,OFFSET arraymov esi,2 mov ax,[ebx+esi] ; AX = 2000h?
表格的例子:
?
2.相對基址變址操作數(基址+變址+相對尋址)
相對基址變址操作數:有效地址偏移=偏移+基址寄存器+變址寄存器
幾種常見的格式:[base + index + displacement], displacement[base + index], displacement[base][index]
偏移 (displacement) : 變量的名字;常量表達式
基址、變址:保護模式為任意32位寄存器,實地址模式為BX、BP和SI、DI
?
表格的例子:
?
結構和宏
宏 (Macro)
命名的匯編語句塊。調用宏的時候,匯編語句塊的一份拷貝被直接插入到程序中
在程序代碼里要重復使用幾條指令,來來回回多次的寫會比較麻煩,就可以用宏來代替這幾條指令。在代碼里多次引用宏,編譯程序編譯到宏后會展開,用實際參數代替它的形式參數
宏的定義
macroname MACRO parameter-1,parameter-2...statement-list ENDM?
例1:
?
?
?
使用I/O端口控制硬件
x86屬于獨立編址,要訪問接口地址空間只能用IN指令和OUT指令。不管是8086還是32位、64位的CPU,接口地址都是16位的,端口地址范圍為0~\sim~?FFFFh
?
IN和OUT指令
IN指令:從端口輸入一個字節、字或雙字
OUT指令:向端口輸出一個字節、字或雙字
?
指令格式:
端口地址:0~\sim~FFh之間的一個常量(立即數),或是包含0~\sim~?FFFFh之間的值的DX寄存器
累加器:AL、AX或EAX
in al,3Ch ; input byte from port 003Ch out 3Ch,al ; output byte to port 003Ch mov dx,2A3Ch ; DX can contain a port number in ax,dx ; input word from port named in DX out dx,ax ; output word to the same port in eax,dx ; input doubleword from port out dx,eax ; output doubleword to same port?
然后講解了PC聲音程序和實時鐘RTC兩個例子,并現場演示了一下
?
32位/64位處理器擴展指令——多媒體/流媒體SIMD擴展指令集
多媒體擴展 (MMX) 指令集
SIMD : 單指令多數據 Single Instruction Multiple Data
Pentium Ⅱ : 引入MMX (Multiple Media Extensions) 指令集,實現64位并行處理。引入8個64位MMX寄存器mm0~\sim~mm7
Pentium Ⅲ : 引入SSE (Streaming SIMD Extensions) 指令集,實現128位并行浮點運算。引入8個128位MMX寄存器xmm0~\sim~xmm7
Pentium 4 : 引入SSE2指令集,實現128位并行定點運算
如果要處理的是32位數據,一個XXM寄存器可以存儲4個這樣的數據,1條SSE2指令可以同時處理4對操作數得到4個結果,這就是所謂的單指令流多數據流
?
SSE2指令集
SSE2數據類型
一個XXM寄存器可以存放4個單精度浮點數、2個雙精度浮點數、16個8位整數、8個16位整數、4個32位整數、2個64位整數。SSE和SSE2指令的操作數就是XXM寄存器,因此可以實現單指令流多數據流,具有高并行度
?
?
匯編語言與高級語言的接口
在C語言中嵌入匯編語言代碼
1.嵌入方法
//“__asm” 關鍵字//單句格式 main() {__asm MOV AH,2;__asm MOV BH,0;__asm MOV DL,20;__asm MOV DH,10; }//模塊格式 main() {__asm {MOV AH,2;MOV BH,0;MOV DL,20;MOV DH,10; } }?
在Visual C++中使用嵌入匯編的規定:
在用匯編編寫的函數中,不必保存EAX、EBX、ECX、EDX、ESI、EDI寄存器,但必須保存函數中使用的其他寄存器(如ESP、EBP、PSW等)
嵌入式匯編語言語句中,可以使用匯編語言格式表示整數常量(如378H),也可以采用C++的格式(如0x378)
嵌入式匯編中的標號和C++的標號相似,作用范圍是在定義它的函數中有效
?
?
2.程序舉例
例3:使用SSE2指令優化程序運行速度
已知矢量A=(a0,a1,...,aN?1)(a_0,a_1,...,a_{N-1})(a0?,a1?,...,aN?1?)?和矢量B=(b0,b1,...,bN?1)(b_0,b_1,...,b_{N-1})(b0?,b1?,...,bN?1?)?,兩個矢量的點乘定義為:A?B=∑i=0N?1aibiA\cdot B=\sum_{i=0}^{N-1}a_ib_iA?B=∑i=0N?1?ai?bi??
編寫采用SSE2指令實現求兩個矢量的點積的程序。設N=8N=8N=8?,矢量分量類型為字
采用SSE2指令中的PMADDWD(教材P83,表3.5)完成矢量的分類乘運算和部分和,然后利用移位運算一次求出最終結果
?
程序源代碼:
為了測試代碼運行時間,使用了標準C提供的CLOCK函數,該函數的原型和有關數據類型的聲明在 time.h 頭文件中。程序結尾希望調用操作系統的相關功能讓程序暫停,按任意鍵繼續,因此需要包含 Window.h 頭文件。用CLOCK函數測量時間,精度只能達到毫秒級,因此定義常量N讓被測試代碼執行2億次
主程序中,start, stop變量記錄時間,其數據類型在time.h頭文件中定義。變量X存儲兩個向量點積的最終結果。i, j用于循環計數。兩個數組每個8個元素,元素的數據類型為16位短整型,用一個128位的XXM寄存器可以存儲整個數組。然后顯示數組A和數組B的內容
調用CLOCK函數記錄開始時間
然后令求向量點積的代碼重復執行2億次。兩個下劃線跟asm關鍵字,大括號括起來的部分是匯編語言的代碼,這里大部分指令屬于SSE2指令集
分別取數組A和數組B的所有元素裝載至128位的XXM0和XXM1寄存器。PMADDWD乘法 - 累計指令的兩個操作數都是兩個128位的寄存器,也就是剛剛裝載的A和B兩個數組。這條指令的功能是兩個128位的XXM寄存器中相同位置的16位有符號整數相乘,相鄰的兩個乘積再求和。8對操作數經過一定的運算后得到4個結果。后面只需把這4個結果再求累加和即為2個向量的點積
將運算結果寄存器的低32位也就是4個結果中的一個傳送至EAX,再右移4個字節取第二個結果傳送至EBX。普通加法指令執行后EAX的內容為2個結果之和。右移32位,取第三個結果至EBX,再加到EAX中。右移32位,取第4個結果至EBX,再加到EAX中。此時EAX的內容即為兩個向量的點積,最終結果存入內存的X單元
調用CLOCK函數記錄結束時間
顯示點積結果和代碼運行2億次的時間(結束時間-開始時間除以1000,時間單位為秒)
?
以下部分則是用C語言實現向量的點積。最后屏幕暫停,按任意鍵繼續
?
測試結果及運行環境
可以發現同樣是求兩個向量的點積重復2億次,通過嵌入匯編使用SSE2指令集實現,可以針對特定的硬件,優化程序的性能,其速度是用C語言實現的6.6倍。測試計算機的CPU是2008年第三季度推出的2核2線程的酷睿移動處理器,主頻為2.26GHZ
換1臺計算機測試,這臺計算機的CPU是2007年第一季度推出的4核4線程的酷睿多面處理器,主頻為2.4GHZ,發現和前面的測試結果相差不大。同樣的程序為什么主頻差不多,在4核計算機上的運行速度沒有明顯的提高?
因為這是一個單線程的程序,線程是操作系統調度的最小單位,一個線程只能在一個內核上運行,其運行速度與CPU的總內核數無關
再換一臺計算機測試,這臺計算機是2016年第一季度推出的10核20線程的至強處理器,主頻為2.2GHZ,與前2臺測試機的主頻相差不大,但是程序的運行時間接近前2臺計算機運行時間的一半,這是CPU內部指令流水線進一步優化,引入了很多增強性能的新技術的結果。因此看一個CPU的單核性能,除了主頻之外,其體系結構方面的改進也非常關鍵
?
?
讓C語言從外部調用匯編
兩種實現方法:
1.嵌入匯編——在C語言中嵌入匯編語言代碼
優點:
- 簡單:無需考慮外部鏈接、命名、參數傳遞協議等問題
- 高效:不存在過程調用的開銷
缺點:不易維護,不方便移植
2.模塊連接——讓C語言從外部調用匯編
可以采用模塊化的程序設計思路,必須用匯編語言實現的部分可以設計成一個函數,存儲為后綴名為.asm的單獨文件,并將匯編程序文件加入到C語言項目中,在C語言代碼中直接調用這個用匯編語言實現的函數
?
例:
C++語言實現的主程序中需要調用外部的addem函數,這個函數需要三個整型參數,其功能為返回這三個參數之和。這里需要聲明一下這是一個外部的,C風格的函數,同時聲明了函數的原型
輸出字符串到屏幕,提示本程序的功能是求三個數之和。再調用addem函數,傳入三個參數,返回的累加和存入變量total。這條指令編譯后首先會產生3條壓棧指令,按照從右往左的順序分別把三個參數壓入堆棧,然后是一個子程序調用CALL指令調用函數addem,執行時先將當前指令指針寄存器的內容也就是返回地址壓入堆棧,然后跳轉到用32位匯編實現的addem函數去執行
.386偽指令告訴編譯程序產生32位的機器指令。.model偽指令定義內存模型為平坦型。.public跟函數名稱說明這個函數可以被外部的其他程序調用
然后是代碼段?!昂瘮得Q PROC”開始,“函數名稱 ENDP”結束,最后是END偽指令,不跟程序入口點。注意到這里的函數名稱比主程序中引用的多了個下劃線,因為Visual C++編譯器在編譯主程序代碼時會自動給addem函數名稱左側加一個下劃線
將EBP原來的值壓入堆棧,將ESP的值賦給EBP,此時EBP和ESP同時指向棧頂的位置。后面的代碼如果有壓棧出棧指令只會影響ESP的內容,EBP的內容就此固定。通過EBP相對尋址就可以訪問到堆棧中由主程序傳遞來的參數。取第一個參數至EAX,將第二和第三個參數加到EAX。最后恢復EBP原來的值,函數返回
在Visual C的函數調用規范中規定如果函數的返回值是一個32位整數就必須通過EAX返回
最后將函數的返回值,也就是三個參數之和顯示到屏幕上
?
通過程序調試界面的反匯編窗口,從中觀察到主程序的int total = addem( 19, 15, 25 )C++語句經過編譯后產生6條機器指令,包括3個壓棧指令、1個子程序調用指令、1個加法指令和1個數據傳送指令。當程序執行到addem函數內部求三個參數之和的代碼時,堆棧的內容如下圖所示。當執行出棧指令時EBP原來的值從堆棧中彈出,當執行子程序返回指令時返回地址被彈出,在執行加法指令之前,堆棧的棧頂位置如下圖所示。這條加法指令將ESP的內容加12,這就相當于回收了三個參數占用的空間,最后把函數的返回值由EAX傳送至內存的total變量
不同的C語言編譯器,其底層子程序調用的規范、參數傳遞的規則可能不同
?
高級語言編譯器在代碼優化方面是卓有成效的。有些編譯器針對特有的處理器進行了優化,可極大提高被編譯程序的執行速度
但是編譯器通常采用常規的優化處理手段,對個別的特殊應用和安裝的特殊硬件并不了解。而手動編寫的匯編語言代碼可充分利用計算機系統的某些硬件(比如視頻顯示卡、聲卡等)的特性。在某些場合,匯編語言可以很方便地實現高級語言不提供或較難實現的功能
?
?
C語言調用匯編的其他例子
判斷有符號整數加法溢出
盡管結果顯然是錯誤的,但C語言程序運行時沒有任何警告和提示。C編譯器不會做任何有關運算結果溢出的檢查??梢酝ㄟ^C嵌入匯編設計一個函數,來檢查兩個整型變量加法結果是否溢出。函數代碼如下:
函數接收兩個整型變量作為參數。相加后如果OF置位則跳轉返回1表示結果溢出。否則順序執行令EAX清0,也就是函數返回0表示相加結果不溢出
匯編語言實現分支功能,前面分支的末尾必須加跳轉指令跳過后面的分支
修改一下原來做加法的C程序,增加對剛才函數的調用。然后運行驗證
總線技術
總線概述
總線
連接兩個以上數字系統元件的公共的信息通路
這是一般性的概念,實際上還有一種總線叫專用總線,總線上可能只需要連接兩個數字系統或兩個模塊
計算機組件間、計算機間、計算機與設備間連接的信號線和通信的公共通路
?
?
總線的分類——按連接的層次
CPU內部有很多種不同的總線結構,比如說單總線、雙總線、甚至是三總線結構。這種芯片內部的總線就叫片內總線
一般用來連接電路板上各個芯片之間的非標準的總線叫元件級總線。比如說早期的計算機內存芯片是直接焊接到計算機主板上的,連接內存芯片跟CPU芯片之間的這種總線就可以叫元件級總線
當然最新的計算機內存都是以內存條的形式插到計算機主板上的,內存條跟計算機主板之間的總線就屬于系統總線,指的是連接計算機系統內部各個模塊之間,或者說用來連接計算機主板跟各種插件板之間的標準的總線。因為系統總線通常在主機箱的內部,所以也叫內總線
通信總線用來連接計算機系統跟外設,這種總線一般在主機箱外部,所以也叫外總線
也有一些特例,比如現在最普及的PCIe總線
現在CPU內部通常會集成很多個CPU的內核,還集成了內存的控制器,包括一些高速的接口控制器。微處理器芯片內部CPU內核和內存控制器還有高速的總線控制器之間的互聯通常也會采用PCIe總線。盡管最新的微處理器芯片內部也會有PCIe總線,從制定PCIe總線規范的初衷來講,PCIe應該屬于系統總線
輔存,如硬盤一般也會歸到外設里面?,F在比較流行的固態硬盤有多種連接方式,有一種叫M.2和U.2接口,底層的通信協議仍然采用PCIe
?
?
總線的分類——按數據傳輸位數
1.并行總線
采用多條數據線對數據各位進行同時傳輸
僅適宜計算機內部高速部件近距離連接。高頻率使發送端原本同步的數據到達接收端時出現時鐘偏移,也會加劇空間距離很近的多條數據線間的串擾
典型的并行PCI總線,一般時鐘頻率就是33MHZ。后來在服務器或者工作站上采用66MHZ,頻率再高,以上這些問題要解決起來就非常復雜
通信距離較長則信號線線數比較多,也會增加總線的長度進而增加總線的成本
?
2.串行總線
采用一條數據線逐位傳輸各位數據
無需考慮時鐘偏移和串擾問題,故可通過提高頻率來提高數據傳輸率
數據二進制位采用差分的形式來傳遞的話就可以避免線跟線之間,或者說外界對數據線的干擾
常用于長距離通信及計算機網絡
在短距離應用中性能也已超過并行總線
?
?
系統總線 (PCI, PCIe) 和通信總線 (USB) 一般都會有標準化的組織為它制定相應的規范
標準化總線
由IEEE(美國電氣電子工程師協會)或計算機廠商聯盟制定。對總線插槽尺寸、引腳分布、信號分布、時序控制、數據傳輸率、總線通信協議等有明確規定
顯卡電路板跟計算機主板之間相連一般采用標準的PCIe$\times$16系統總線
?
總線標準化的優點:
1.簡化軟硬件設計,簡化系統結構 →\to→模塊化
2.使系統易于擴展、便于更新
3.便于調試、維修 →\to→各插件板分別調試;一級維修
?
典型的標準化總線——內總線
ISA總線
1981年,IBM PC/XT (8位ISA總線,基于8088)
內存直接焊接在計算機主板上。要對計算機進行功能擴展,可以基于8位的ISA總線來擴展插件板,把設計的板卡直接插到計算機主板8位的ISA總線插槽上,就相當于對計算機系統的硬件進行了一定的擴展
1984年,IBM PC/AT (16位ISA總線,基于80286)
16位的ISA總線為了跟原來的8位兼容,在原來的8位ISA總線基礎上后面增加了一些相應的信號。數據線從原來的8位擴展到了16位,地址線從原來的20位擴展到了24位
即設計的板卡如果基于8位的ISA總線,實際上也可以插,只不過只用到了插槽的前面這一半
1988年,以康柏 (Compaq) 公司為首,EISA (Extended ISA),32位
?
ISA總線可以簡單地理解成就是8088/8086系統總線的延伸,16位的ISA總線也可以理解成是80286系統總線的延伸。常用的讀寫信號,如地址線、數據線、讀寫控制信號ISA總線上面應該都能找得到,電源、時鐘信號、跟中斷有關的信號也有
下圖的兩部分應該在一條上,底下一截平移到了右邊顯示
?
?
PCI總線
特點:
1.不依賴于處理器
PCI總線在制定規范時則并沒有參考任何一款處理器
ISA總線在制定規范的時候針對的就是Intel從8086到80286這一系列的微處理器,基于這樣的微處理器的系統總線信號定義到ISA總線上
?
2.擴充性好
可以通過相應的橋芯片擴展出多條PCI總線,連接更多的外設部件
?
3.支持自動配置
基于PCI總線的設備或板卡插到總線的擴展插槽上,計算機啟動時會自動識別插接到擴展插槽上的設備并且對設備內部的各種資源比如占用的接口地址、內存范圍進行自動配置
ISA總線通常不支持。事先要把ISA總線的板卡(上面一般會有一些撥動開關,開關撥到不同狀態占用的接口地址范圍是不同的)插接到計算機的主板上加電看能不能正常啟動。不能則說明ISA總線的板卡占用的資源跟主板資源沖突
?
4.支持多主控設備
如果某一個PCI總線上的設備成為主控設備之后,實際上它可以主動去訪問這個總線上的其他設備,甚至可以直接主動去訪問主存。不經過CPU能夠實現板卡與板卡之間,設備與設備之間或者說設備與內存之間的直接數據傳送
DMA就是基于這種所謂的多主控設備方式來實現的
?
PCI總線上究竟有哪些總線信號,這些總線信號通過什么方式來實現PCI總線上兩個設備之間的可靠數據通信?下面通過時序來說明,這是傳統的并行PCI總線上常用的信號
PCI總線屬于同步總線,所以總線信號里肯定應該有一個CLK時鐘信號
PCI總線上的仲裁方式采用的是集中式的獨立請求方式。假設現在PCI總線上的某一個設備取得了總線的使用權,成為了PCI總線上的主設備,想要選擇某一個從設備傳輸數據,要從從設備里的存儲器里讀一些數據
主設備先把FRAME ̄\overline{FRAME}FRAME信號置低電平,告知總線上的其他設備做好準備自己要進行通信,同時會把它要訪問的內存地址放在[AD…0] 這32根地址數據復用的信號線上。在C/BE ̄[3..0]C/\overline{BE}[3..0]C/BE[3..0]這4根線上放一個命令,0110表示內存讀命令。此時掛接在PCI總線上的其他設備要檢查一下主設備要訪問的內存地址是否落在自己的管轄范圍內。被選中的從設備會把設備被選中信號DEVSEL ̄\overline{DEVSEL}DEVSEL置為低電平,告知主設備自己在哪并且已經準備就緒
實際上計算機的最大內存尋址空間通常比較大。比如32位系統下內存地址是32位,最大的內存地址空間應該是4GB,但實際上這4GB不可能全部分配給內存條來用。通常在高端的地址要保留一部分給外設來用,相當一部分要保留給PCI設備來用,所以PCI設備內部存儲器通常容量也比較大,一般也會把它映射到內存地址空間
中間隔一個時鐘周期實現總線傳輸信號方向的轉變以避免總線競爭。經過一個總線轉換周期后,后面理論上來講只要主設備和從設備都處于就緒狀態,則每一個時鐘周期時鐘信號的上升沿就可以傳遞一個32位的數據。當然傳遞的過程主設備和從設備內部的地址會自增,所以地址只需要傳一次,后面可以連續傳輸很多的數據,這種方式叫多發數據傳送方式
如果主設備或者從設備內部有時可能內部來不及處理需要等待一下呢?IRDY ̄\overline{IRDY}IRDY是主設備準備好的信號,TRDY ̄\overline{TRDY}TRDY是從設備準備好的信號,這2個信號同時為低電平就可以傳輸一個數據,如在時鐘周期5時鐘信號的上升沿,從設備沒有準備好,此時就不會傳輸數據
主設備把FRAME ̄\overline{FRAME}FRAME?信號置高電平,后面再傳輸一個數據,整個傳輸過程就可以結束
?
通過這個時序可以了解到PCI總線一個時鐘周期就可以傳輸一個32位的數據,通過這點可以算傳統的PCI總線理想情況下的多發數據傳輸率。如33MHZ PCI對應數據傳輸率為33×106×32÷8=132MB/s33\times10^6\times 32\div8=132MB/s33×106×32÷8=132MB/s??
?
PCI總線的局限:
總線上的所有設備共享帶寬,只能分時使用
用并行單端信號,一般通過提高總線位寬和頻率的方法增加總線帶寬。提高總線位寬會增加芯片引腳,影響芯片生產成本。提高總線頻率會產生時鐘偏移和串擾問題,還會影響總線負載能力
高端的工作站或者服務器上PCI總線的數據線的位數一般相比個人計算機更大
?
PCIe總線
PCIe在計算機里面首先會有一個總的控制器根組件,根組件內部通常會集成一個PCIe總線控制器和存儲器控制器,存儲器控制器主要用來實現PCIe總線地址空間跟內存地址空間之間的映射,或者說轉換。同樣根組件內部還提供了多個PCIe端口
如果需要用到傳統的并行的PCI總線可以再接一個PCIe-PCI橋。橋可以實現兩種不同總線協議之間的轉換
如果需要接更多的基于PCIe總線的外設,可以通過一個交換器(和網絡上的交換機類似)擴展更多的端口
?
PCIe總線的特點:
1.串行差分,一次只傳輸一個二進制位
避免了時鐘偏移和串擾問題,總線可達到很高的頻率,從而進一步提高總線帶寬
每一條PCIe數據通路 (Lane) 由兩組串行差分信號(4根信號線,這4根線組成一個通道)構成,可進行全雙工數據傳輸,發送和接收各是一對信號線,發送和接收并行工作
?
2.直流平衡、內嵌時鐘
PCIe1.0和PCIe2.0采用8/10b編碼,將每8位數據編碼成10位傳輸,編碼開銷占總帶寬的20%開銷
串行傳輸的信號如果傳輸的時候連續0和連續1太多,電平長時間沒有變化,很難從信號里把時鐘提取出來。8/10b編碼就是用10個二進制位,從1024種組合里找出256種組合,這256種組合連續0和連續1的個數不會太多,且0的個數和1的個數基本均衡,用它來表示相應的一個字節的信息
PCIe3.0采用128/130b編碼提高了帶寬利用率,編碼開銷占總帶寬的1.538%開銷
總線頻率2.5GHz則每秒鐘可以傳輸2500M個二進制位,10個二進制位用來表示一個字節,所以×1\times1×1單向帶寬為250MB/s (1GT/s=1Gbit/s)。如果發送的同時還在接收的話,則總的帶寬在此基礎上可以再乘2
?
3.多通道
一條PCIe鏈路可以由多條數據通路組成,多通道并行工作提高總線帶寬
目前PCIe鏈路可以支持1、2、4、8、12、16和32個數據通路,即×1、×2、×4、×8、×16、×32\times1、\times2、\times4、\times8、\times16、\times32×1、×2、×4、×8、×16、×32 數據帶寬
如獨立顯卡一般會通過PCIe×\times×?16的總線連接到主板上,指的就是有16個通道
通道之間不需要同步。比如說這次要傳輸一大批數據,采用PCIe$\times$16的總線傳輸。可以把這一大批數據平均分成16等份,每一等份用一個通道來傳輸,底層會一個個數據包打包之后再傳輸。具體數據包之間按照什么順序來組合內部都有編號。接收方接收到這些數據之后按照數據包內部規定跟順序有關的信息接收方再重新拼裝成原始數據
?
4.端到端(交換結構),實現多模塊同時通信
傳統并行PCI總線所有設備共享帶寬。PCIe總線采用端到端(交換結構)的連接方式,每一條PCIe鏈路中只連接兩個設備
?
5.軟件向下兼容
不管是PCI還是PCIe總線對掛接在這個總線上的設備進行讀寫和控制,軟件的接口是完全一樣的
?
下面這個主板上有很多PCI總線的插槽。為了兼容老的基于并行PCI總線的設備或者說插卡,仍然保留了傳統的32位PCI總線
典型的標準化總線——外總線(通信總線)
通常把這種總線也叫做接口,比如說USB接口
?
RS-232C串行通信接口
打開設備管理器,在端口這一欄有一個通信端口 (COM1),這就是傳統的RS232串行接口。最新的計算機主板可能并沒有把這個接口作為一個插座引出,但是這個接口的資源在計算機主板上仍然存在。打開COM1端口的屬性還可以設置這個端口在通信時的一些參數
早期國際互聯網還沒有出現時想實現遠程的不同地方的計算機之間的通信要借助電話線,電話線上傳輸的是模擬信號,調制解調器把計算機能夠接收的TTL電平轉換為模擬信號,而調制解調器就是通過RS-232C連接到計算機上的
現在如果提到DTE,就理解成計算機。提到DCE就理解成外設
?
?
1.信號
傳送信息信號:
RS-232C能夠實現全雙工的通信,所以數據線有2根,一根用來發送一根用來接收
- TxD : 發送數據線 (DTE →\to→ DCE)
- RxD : 接收數據線 (DTE ←\leftarrow← DCE)
?
聯絡信號
- RTS : 請求發送,計算機告訴外設要發送數據 (DTE →\to→? DCE)
- CTS : 清除發送,外設告訴計算機可以接收數據 (DTE ←\leftarrow←?? DCE)
- DTR : DTE就緒,計算機告訴外設已經準備就緒 (DTE →\to→ DCE)
- DSR : DCE就緒,外設告訴計算機已經準備就緒 (DTE ←\leftarrow← DCE)
- DCD : 數據載波檢測。調制解調器如果從電話線上接收到有數據來了就會把數據載波檢測置成有效告訴計算機 (DTE ←\leftarrow←? DCE)
- RI (22) : 振鈴提示,跟電話線有關 (DTE ←\leftarrow← DCE)
?
2.電平
?
3.與TTL電平轉換
計算機希望接收發送的信號都是TTL電平,比如低電平就是0V,高電平就是5V。所以中間需要電平轉換
MAX232芯片只需要一個單5V電源供電,單5V電源內部經過升壓之后產生±10V\pm 10V±10V?的電壓。下圖即為該芯片的具體引腳
增壓電路需要一些電容,電容很難集成到芯片內部,特別是一些容量大的電容,需要外接。計算機發送出來的TTL電平信號經過它轉換成RS-232C串行接口所需要的±10V\pm 10V±10V的電平。從串行接口來的±10V\pm 10V±10V的電平信號通過它轉換成計算機能夠接受的TTL電平
?
接下來看一下RS-232串行接口具體的通信格式
首先總線處于空閑狀態,一個啟動位,接下來先傳低位后傳高位,最后是奇偶校驗位和2個停止位
RS-232串行接口有的時候叫異步串行通信。因為它沒有時鐘信號,所以不是同步總線而是異步總線。并且通信之前雙方事先要約定通信的波特率,兩邊設置的參數包括數據的格式必須要一樣,然后才可以通信
比如說發送方要發送數據,必須得從啟動位開始,接收方檢測到啟動位就會從這個位置開始計時,因為雙方約定了數據傳輸的波特率,相鄰兩個數據之間的間隔可以計算得到。從這個啟動位開始,接收方每固定一段時間采樣一次數據線得到一位的二進制數據
當然發送方和接收方具體的定時不可能毫無誤差,但只要保證這一個數據(通常最多只傳輸一個字節)在傳輸的過程中哪怕每一次采樣時稍微有一點點錯位,傳輸到最后一個數據能夠正常接收就可以了。傳輸下一個數據時又會再來一個啟動位重新對齊
?
應用:
1.使用Modem連接
電纜信號一一對應,共10線。
(保護地、信號地、…)
?
2.直接連接:計算機 (DTE) ?\leftrightarrow? 計算機 (DTE)
RS-232串行接口最大的優點在于實現起來非常方便,成本也低。最簡單的實現方式是三線經濟方式 (三線制 RS-232)
計算機跟外設之間首先要共地。發送一根數據線,接收一根數據線,兩邊交叉著直接連
?
3.軟硬件系統調試:控制臺、超級終端
RS-232串行接口相對數據傳輸率較低,現在用的不是很多。最典型的應用主要是硬件調試
比如說現在有一塊嵌入式的開發板,這個電路板在設計的時候為了簡化問題,可能沒有那么多輸入輸出設備可以連接
為了觀察到電路板內部CPU執行程序的過程,或者說發命令來控制它的工作過程,可以通過最簡單的三根線實現的RS-232串行接口實現接口來和計算機連接。在計算機上運行超級終端軟件,通過計算機的鍵盤在超級終端軟件里面直接輸入命令,那個命令就會通過RS-232串行接口傳輸到電路板內部。電路板內部接收到命令,做一定的運算操作之后會把可能的相應輸出信息的ASCII碼再通過串行接口傳輸到計算機上運行的超級終端軟件里面。超級終端軟件會把相應的接收到的字符串顯示到計算機屏幕上
這樣其實就相當于把計算機的顯示器和鍵盤給開發板來用
?
USB總線
特點:
每個USB總線支持127個外設
整個USB系統只用一個端口(接口地址)、一個中斷 →\to→ 節省系統資源
支持熱插拔、動態加載驅動程序:PnP,自動配置。帶電拔出后自動回收資源
傳輸距離:3~5米,電壓:5V
?
通過信號的名字可以觀察到USB總線采用串行差分的方式來傳送數據。USB1.0和USB2.0只有一對數據線,只能工作在半雙工狀態下,不能同時接收和發送
USB各版本向下兼容。USB3.0為了和USB2.0兼容,原來的+5V電源、+5V地、兩根差分形式傳輸的數據線仍然保留,另外又新增了兩對數據線。USB3.0如果工作在超速模式下,發送和接收分別各用一對,采用差分的形式來傳輸信號,支持全雙工的方式來進行工作
?
?
Low-Speed 低速模式,Full-Speed 全速模式,High-Speed 高速模式,SuperSpeed 超速模式
現在一些低速的外設,比如說USB接口的鍵盤和鼠標,工作在低速模式下應該也完全夠用
USB總線可以通過總線來給外設提供電源,即能夠實現給外設直接供電。USB3.1從第2代開始還支持快充模式,可以提供更大功率的輸出
再來看一下USB具體物理上的接口,計算機端和外設端有A、B、C類型。C類型接口從使用的角度來講最大的方便之處在于不分正反,正著插和反著插都可以。通過信號的分布會發現所有的這些總線信號都是根據中心點鏡像對稱的,不管是正插還是反插對應的都是同一個位置
TX1+和TX1-是一對用來發送的差分信號線,RX1+和RX1-是一對用來接收的信號線
?
Type-C有兩個高速數據通道, (TX1+/TX1-,RX1+/RX1-) 和 (TX2+/TX2-,RX2+/RX2-)
USB3.1僅使用其中一個通道,另一個通道可用于備用模式
USB3.2可以充分使用這兩個高速通道,雙通道20Gbps。要能夠實現最高的數據傳輸率必須得用C型接口
如果這兩個高速通道都用于備用模式,則速度就會下降成USB2.0的High-Speed
?
?
ATA總線
ATA總線連接存儲設備和計算機。早期采用并行的方式傳輸數據,PATA即并行ATA接口,其總線電纜是40芯或者80芯的扁平電纜,線比較寬占主機箱空間,也影響散熱,也不可能過長。后來出現了串行ATA接口,叫SATA或者SATA總線
?
ATA總線設計之初就是用來連接硬盤的,所以它在具體通信時的通信格式,包括邏輯塊地址用多少個二進制位來表示在它的總線規范里有具體表示。比如最早的ATA總線,它在通信時規定邏輯塊地址用22位來表示,這樣硬盤的邏輯塊最多有222=4M2^{22}=4M222=4M?個,一個邏輯塊對應一個扇區,而通常情況下一個扇區是512B,這樣就能算出理論上能支持的最大磁盤容量
1TB=1024TB,1TB=1024GB
?
SATA具有速度高、電壓低(功耗低)、線纜窄、支持熱拔插、傳輸距離長的特點,現已取代PATA
服務器一般配置好,通常是24小時不斷電工作。如果服務器磁盤陣列里的某一個硬盤出問題會希望服務器不要關機,直接帶電將其拆下來,這就要求硬盤的接口必須支持熱插拔
?
總線的驅動與控制
總線的競爭與負載
?
總線競爭:同一總線上,同一時刻,有兩個或兩個以上的器件輸出其狀態
1.對TTL門
有兩個門同時將狀態輸出到信號線上,一個輸出低電平一個輸出高電平,此時總線上會是一種不高不低的非TTL電平,可能會丟失狀態。并且這種情況下可能有一個非常大的電流灌進輸出低電平的引腳,嚴重時會燒壞電路
照著具體的門電路內部看一下。如果這兩個門的輸出引腳接到一起,實際上就相當于兩個門同時把狀態輸出到這一根線上
右邊的與非門只畫了輸出的這部分電路
比如一個輸出低電平一個輸出高電平,輸出低電平的引腳底下的晶體管處于飽和狀態,即集電極和發射極之間導通。輸出高電平的引腳對應的這個晶體管截止,集電級跟發射極之間斷開,上面上拉用的晶體管是導通的。這樣如下圖所示,從正電源到輸出低電平的輸出引腳,內部的晶體管一直到地會有一個相對比較大的電流。長時間維持這種狀態,電流大功耗大,發熱熱量達到一定程度有可能會把這兩個器件的輸出引腳,即它內部對應的晶體管燒壞
這根線上究竟是高還是低呢?
實際上只要有一個引腳輸出為低電平,就相當于把這根線通過這個輸出引腳內部的下拉晶體管直接接地,所以上這根線上呈現的應該是類似低電平的狀態
?
2.特例:集電極開路/漏極開路輸出
OC: Open Collector
有一種門電路,它的輸出引腳集電極開路/漏極開路,相當于輸出引腳上面上拉的部分沒有,輸出引腳到正電源之間什么都沒連,處于開路狀態
下圖只畫了輸出引腳有關的晶體管,內部的其他部分省略
這樣的輸出引腳是可以接到一起的,外面必須加一個阻值相對較大的上拉電阻。如果這幾個引腳都想輸出高電平,每一個晶體管都處于截止狀態,集電極與發射極之間不導通,線上呈現高電平。若某一個輸出引腳想要輸出低電平,其三極管處于飽和狀態,相當于把這根線通過晶體管接地,會產生如下圖所示的電流,但因為上拉電阻阻值較大,所以這個電流比較小,不會燒壞器件,但B輸出的“1”信息會丟失
在數字電路中三極管當作電子開關來用,要么導通要么斷開
只有所有的輸出引腳都輸出電平時,這根線才會是高電平,任何一個輸出引腳只要輸出低電平,這根線就呈現低電平狀態,這就是線與邏輯
?
?
3.用三態電路,嚴格控制邏輯
這根線接了兩個負載也就是兩個輸出引腳,都具有三態功能,可以控制是否能輸出。三態門的輸出狀態有高電平、低電平、高阻三種。高阻相當于電阻無窮大,相當于輸出引腳跟總線之間脫開
?
?
總線的負載
1.直流負載
如果驅動門輸出高電平時,沒有能力提供負載需要吸收的電流,會導致高電平電壓高不上去。輸出低電平時如果吸收電流的能力不夠,會導致低電平電壓低不下來。都會造成邏輯上的問題
2.交流負載
對MOS電路,IILI_{IL}IIL?、IIHI_{IH}IIH?很小 →\to→ 主要考慮電容負載
所有的這些負載輸入引腳對地之間都有一個分布電容。接的負載多了后,這些電容是并聯關系,一端同時接到線上,另外一端同時接地,總電容越來越大,會產生較大的電容負載,濾掉高頻分量
且如果想從低電平變到高電平,電容電壓無法突變,充電需要過程,電容越大過程越緩慢,從高電平變到低電平也一樣。一些窄的正脈沖可能會被濾掉,產生邏輯問題
?
所有的負載輸入引腳到地到會有一個電容。信號線在電路板上走,信號線本身到地之間也會有電容,這是元件級總線、內總線帶來的電容。信號線要從一個電路板引到另外一個電路板上,或者連接到某一個外設上,傳輸線長了也會用更大的分布電容
故要求輸出門的負載電容CPC_PCP?滿足如下條件:
?
驅動門和負載的原始參數都可以通過查芯片手冊得到
?
總線驅動設計
單向總線信號的驅動設計
如果連接到總線上的模塊很多,也就是總線的負載很重,就需要加驅動器。對于單向的總線控制信號,比如地址、讀寫控制信號,情況相對簡單。雙向數據總線如果要加驅動,稍有不慎就會引起總線競爭
?
比如要設計一個由兩塊電路板(主板CPU板,擴展板電路板1)組成的產品。擴展板上需要從CPU板引過來很多信號,包括地址線的最低位A0。地址線實際上需要接很多負載,比如CPU板需要擴展內存,主板上可能會有很多內存芯片,每個內存芯片都有地址線,可能都要跟CPU的地址線相連,A0這根線本身在CPU板上就要接很多負載?,F在這根線要引出來到擴展板上,擴展板很多地方也要用到這個信號,又要有很多負載
最好不要直接把這根線引出來而是加一個驅動器。經過驅動器再輸出,不管它輸出之后后面接了多少個負載,對CPU來講也只是增加了1個負載而已
把該信號引入后最好也不要直接用而是加緩沖器。下圖從驅動器的角度來講它后面只接了3個負載,每個緩沖器后面又加了5個負載
緩沖器和驅動器功能類似,只是叫法不一樣
驅動器和緩沖器也能起到緩沖作用,保護硬件
?
74LS244可以當驅動器或者說緩沖器來用,它主要用來實現單向的總線信號驅動。其內部有8個三態門,4個一組,總共2組,每組三態門的控制端接到一起引出來,低電平有效。把允許信號,即三態門的控制端接地令其永遠有效,這樣8個輸入和8個輸出永遠一樣
?
雙向總線信號的驅動設計
雙向數據總線驅動器可以選擇74LS245芯片實現,通過控制允許信號和方向控制信號來避免總線競爭。允許信號低電平,A邊和B邊導通。然后方向控制信號給高電平,信號傳遞的方向從A到B,給低電平則從B到A
注意74LS245不是一個開關,它是把一邊接收的信號原封不動地復制到另一邊輸出
?
例3:某微型機電路板上有內存C0000H~\sim~EFFFFH和接口A000H~\sim~BFFFH,試畫出該電路板板內雙向數據總線驅動與控制電路
1.防止總線競爭原則:只有當CPU讀板內內存或讀板內接口時,才允許雙向數據驅動器指向系統總線的三態門是導通的
2.地址分析(內存地址、接口地址)
設計的插件板板卡內部有很多存儲器,插在計算機主板插槽上占用資源,希望占用一部分的內存地址空間和接口地址空間。計算機主板CPU運行某一程序時訪問內存或接口,訪問的內存或接口地址落在這個范圍之內,訪問板卡內部的存儲器,驅動器74LS245的允許信號就應該給低電平讓兩邊接通,然后根據讀板卡還是寫板卡控制方向信號。設計雙向數據總線驅動時,要設計相應的內存和接口地址譯碼電路判斷計算機主板要訪問的內存和接口是否在范圍內
C0000H~\sim~EFFFFH低4位全0到全F所有的組合都包含在內,故只需分析從C到E的最高位。觀察高位地址發現A18、A17、A16是4、5、6這3種組合之一,如果該譯碼電路用3-8譯碼器實現,那Y4、Y5、 Y6任何一個是低電平,這三根線肯定就是這三種狀態之一。此外A19必須是1。
對A000H~\sim~???BFFFH的分析過程也類似,高三位地址只要是“101”地址就落在范圍內
3.畫驅動與控制電路
74LS138是3-8譯碼器
內存地址譯碼電路限定了高位地址A19必須是1,A18、A17、A16必須是4、5、6三種組合之一,CPU此時正在訪問內存的條件
接口地址譯碼電路限定了A15、A14、A13分別是高電平、低電平、高電平,CPU此時正在訪問接口地址空間,CPU此時正在控制總線不是DMA狀態(AEN信號給低電平)的條件
這兩部分任何一個條件全部滿足74LS245的兩邊就應該接通
方向控制信號用讀信號,內存讀和接口讀信號任何一個給低電平經過與門輸出就是低,方向從B到A。寫時讀信號肯定無效,兩個信號都是高電平,經過與門輸出高電平,方向從A到B
存儲技術
常用存儲器芯片及連接使用
?
靜態隨機讀/寫存儲器 (SRAM) 及接口設計
SRAM的優點是可以直接連接到系統總線上,有獨立的地址線,數據線,讀信號,寫信號和片選,并且讀寫速度都很快。是易失性存儲器,掉電之后內容就沒了,剛一加電內容是隨機的
?
用單個SRAM芯片構成8088的內存
可以認為這些信號都是8088工作在最大模式下的系統總線信號,或者認為這些信號都是8位ISA總線的信號,反正都一樣
?
有的電路高位地址會留幾根不連,這樣連線較少但會浪費內存地址空間
?
用多片SRAM芯片構成8088的內存
存儲器的字擴展:
1.地址線并聯
2.數據線并聯
3.OE― 并 → MEMR―,WE― 并 → MEMW―
4.CS― → 地址譯碼器(高位地址譯碼)的不同輸出
8088系統總線數據線有8根,要求內存是8位的
如果SRAM芯片只有4根數據線,則不管構成什么內存都要進行位擴展,因為CPU訪問內存最小內存就是8位,不可能一次訪問4位的數據
?
存儲器的位擴展:
1.地址線并聯
2.數據線:1號芯片D0~D7,2號芯片D8~D15
3.OE― 并 → MEMR―,WE― 并 → MEMW―
4.CS―并聯 → 地址譯碼器(高位地址譯碼)
8086要求內存必須是16位的,下圖中SRAM芯片是8位的,要進行位擴展
連接地址線時,SRAM芯片的A0~A10要連接到系統總線的A1~A11。因為2個8位存儲器位擴展之后構成1個16位存儲器,給16位存儲器地址要給字地址。x86按字節編址,如果給字的地址要從A1開始。這兩個芯片可以同時被選中,同時被選中時CPU就對內存進行16位讀寫
?
若現在想用匯編語言編一個程序訪問它里面的某一個單元,如從80002H地址讀一個16位數據:
先初始化DS,將段的起始地址高16位傳送到DS。再將16位段內偏移送到指針寄存器BX
然后寄存器間接尋址從指向的內存單元讀一個數。默認段寄存器為DS,CPU會從DS讀出段起始地址的高16位,末尾添4個0,再加上BX里存的16位段內偏移,加的結果就是要訪問的內存地址
MOV AX,8000H MOV DS,AX MOV BX,2 MOV AX,[BX]?
如要讀一個8位數據,只需要把目標寄存器改為8位
從偶地址讀BHE―為高電平,A0為低電平。從奇地址讀BHE―為低電平,A0為高電平
?
如要讀一個16位數據,目的寄存器就應該寫16位
從偶地址開始讀,BHE―和A0均為低電平,連續讀2個字節傳送到目的寄存器,2個字節占2個地址,小地址傳送到低8位,大地址傳送到高8位
從奇地址開始讀,必須分兩次來讀,先讓BHE―引腳為低電平,A0為高電平,讀8位數據傳送到目的寄存器的低8位。再讓BHE―為高電平,A0為低電平,讀8位數據傳送到目的寄存器的高8位
?
數據在內存中要對齊存放,如果按字節編址,16位數據起始地址是2的整數倍,32位數據起始地址是4的整數倍,64位數據起始地址是8的整數倍
如果不對齊存放,像x86處理器,如8086,16位數據從奇地址開始放照樣可以處理,只是速度會變慢,如果對齊一個總線周期就能夠完成,如果不對齊得花兩個總線周期。時間變長,但是功能照樣能實現
這里也體現出CISC結構處理器對軟件要求不是很高,硬件能干的活盡量硬件都干了,有一些RISC結構的處理器就要求數據在內存里面必須對齊存放,不對齊指令就無法執行,不同的CPU處理方式不一樣
現在計算機內存并不是用SRAM來構成的,包括最早的個人計算機內存也是用DRAM構成的。講課用SRAM是因為這個簡單,因為它從信號上來講有獨立的地址線數據線讀寫信號和片選,可以直接連接到系統總線,電路包括控制方式最簡單
用SRAM構成單片機內存完全夠用。如51單片機芯片內部會集成一定容量的內存,就存儲一些臨時變量用的,如果太小外面可能還要再擴展內存,擴展內存一般不大
?
如果用DRAM構成內存,進行一次讀寫地址得分兩次來送,先送一個行地址,再送一個列地址,然后才能讀寫,還有定時刷新。DRAM后來發展出現了SDRAM(同步動態隨機訪問存儲器),再發展出現了雙倍速的SDRAM就是DDR的SRAM,現在主流用的比較多的是DDR4
這種雙倍速的SDRAM在讀寫時時序更復雜,對它進行讀寫首先要發激活命令,同時送行地址。發激活命令相當于把它里面的某一行打開,然后把這一行的所有內容讀到行緩沖區中,然后再給它發相應的讀寫命令,同時給它列地址,對打開的行緩沖區進行讀寫。在訪問其他行之前要把當前這一行關閉,也就是把行緩沖區的內容回寫到原來的位置,這要給它發一個預充電命令
SDRAM存儲器在控制時涉及到很多比較復雜的命令,沒有辦法直接連接到系統總線。一般要設計一個比較復雜的電路現在都會有一些現成的芯片,或者說現成的IP核可以直接用。你可以理解成CPU系統總線上必須要通過一個SDRAM控制器間接地才可以訪問到SDAM。從這個角度來講,現在計算機的內存其實和外設很像,CPU通過相應的接口訪問,而不是直接訪問,實現的電路非常復雜
現在的內存,如DDR的SDAM在進行讀寫時采用突發的讀寫方式,一次突發讀寫傳送幾個數據,并不是隨機訪問。CPU內核要取得某一個數據最終從CPU內部的Cache得到,數據如果在內存中對齊存放,最終以塊為單位裝載到Cache內,它在Cache內也是對齊存放的。CPU內核可以直接訪問Cache,Cache就是用SRAM實現的
?
計算機的存儲器現在分層較細,如果只算寄存器組,控制器和運算器之外的那些存儲器,最頂層的是用SRAM構成,可以直接訪問的Cache,再往下是用DDR的SRAM實現的主存,再往下有一些計算機有如Intel的奧騰存儲器的介于主存和固態硬盤之間的層次,再往下是固態硬盤,再往下是機械硬盤
?
?
除了用基本的邏輯門和3-8譯碼器,也可以用ROM作譯碼器。ROM能根據輸入選中對應存儲單元并輸出該存儲單元的內容
?
現在要用4片6264構成一個存儲容量為32KB的8位存儲器(字擴展),其地址空間為E0000H~E7FFFH。用一塊512×4(即512個存儲單元,每個存儲單元為4位)的PROM芯片63S241作為ROM譯碼器
給單向信號(A0~A12,MEMR―,MEMW―)和雙向數據信號(D0~D7)加驅動
CPU沒有訪問內存時MEMR―,MEMW―均為高電平,經過與門之后輸出是高。ROM芯片片選和讀信號都是高電平,數據線輸出是高阻。高阻就是電阻無窮大,相當于4片6264芯片的片選信號懸空哪都沒有接,輸入引腳是高電平還是低電平是不定的。加上拉電阻能保證這4根線輸出高阻時仍然會呈現高電平狀態
?
?
也可以用數據比較器作譯碼器
兩路8位輸入數據相等,輸出低電平。否則輸出高電平
?
一路輸入直接接開關置于固定狀態。開關斷開時由于上拉電阻呈現高電平,閉合時相當于把這根線通過開關接地,這根線就是低電平
另一路接到系統總線的高位,不用的線也直接接高電平。高位地址和開關撥的狀態一樣時才會輸出低電平選中這個芯片
數據線還是要加雙向驅動
?
也可以用PLD作譯碼器。知道有這種辦法就可以了
?
SRAM設計舉例1:
現有容量為 8K×8bit 和 4K×8bit 的SRAM芯片。在8086系統中,
1.利用這樣的芯片構成地址范圍為C2000H~C7FFFH的內存,畫出最大模式下包括總線驅動在內的此芯片與系統總線的連接圖(譯碼器件自行選擇,盡量選擇容量大的芯片)
2.試編寫8086匯編語言程序,從地址C2000H開始,依次寫入數據,直到地址C7FFFH。要求數據從0開始,每寫入一個字節后數據加1,即寫入數據依次為:00H、01H、02H、03H、… 、FEH、FFH、00H、01H、…… 。然后逐個單元讀出比較,若有錯,則在DL中寫入01H,退出檢測;若每個單元均對,則在DL寫入00H
順便介紹了下檢查內存的幾種方法和原理
?
?
首先確定要用多少片芯片
C8000H-C2000H=6000H,即24K
盡量選擇容量大的芯片,首先要用8K×8bit的芯片。但考慮到要構成8086的內存,如果用8位的SRAM必須是偶數片,2片8位的SRAM位擴展之后才能夠構成1個16位的存儲器。折中一下:
需要8K×8bit芯片2片、4K×8bit芯片2片
8K×8bit芯片地址線:A0~A12,數據線:D0~D7
4K×8bit芯片地址線:A0~A11,數據線:D0~D7
?
考慮內存地址譯碼電路應該有幾個輸出
作為8086的內存來講,每2片8位芯片位擴展之后構成1個16位的存儲器,各對應地址譯碼電路的1個輸出。所以應該有2個輸出
?
然后根據地址范圍分析內存地址譯碼電路應該怎么設計
C8000H-C2000H 16進制地址的低3位從全0到全F,所有組合都有,可以不用考慮,直接分析高8位地址C2~C7
先看經過位擴展之后構成一個16位存儲器的2片8K×8bit的芯片,它的內部地址應該是什么
8K×8bit芯片地址線是A0~A12,16位存儲器要給字的地址。所以連系統總線時從A1開始連,連接系統總線的A1~A13。下圖的表格中每一行低12位地址A0~A11是任意的,對應4KB,2片8KB的芯片位擴展之后總容量是16KB,對應前4行。前4行對應內存地址譯碼電路的1個輸出,用來選擇這2片8KB的芯片
后2行對應另外一個輸出,選擇的是2片4KB芯片位擴展之后的16位存儲器
內存地址譯碼電路的輸入應該是高位地址,A13雖然是SRAM內部地址的高位,也要參與地址譯碼,這樣才能保證不參與地址譯碼的A0~A12能包含全0到全1的所有組合
?
然后畫具體電路
首先畫SRAM芯片
先畫2片8KB的芯片。第一片8KBSRAM芯片的引腳首先是數據線D0~D7,這8根線直接連8086系統總線的低8位D0′~D7′,這樣連它就是偶地址存儲體。然后地址線A0~A12連8086的系統總線A1′~A13′(注意從A1′開始連)。讀信號OE―連內存讀MEMR′―,寫信號WE―連內存寫MEMW′―。最后一個是片選CS―,偶地址存儲體要被選中應該有一個或門,或門的輸出是它的片選,或門的一個輸入是偶地址存儲體的選擇信號A0′,另外一根線接地址譯碼電路的第一個輸出
片選和地址譯碼電路的輸出有關,可以留到最后畫
第二片8KBSRAM芯片作為奇地址存儲體,或門的輸出是它的片選,或門的輸入是奇地址存儲體的選擇信號BHE′―和和第一片相同的地址譯碼電路輸出,數據線D0~D7連接系統總線數據線高8位D8′~D15′,地址線,讀信號OE―,寫信號WE―的連法和第一片一樣
因為2個芯片是位擴展的,這2個芯片受地址譯碼電路同一個輸出的控制。所以為了畫起來方便下圖把這個芯片的片選畫在上面2個挨著
再看2片4KB的芯片,畫法幾乎一樣。同樣數據線8根線D0~D7一個作為偶地址存儲體連系統總線的低8位D0′~D7′,一個作為奇地址存儲體連系統總線的高8位D8′~D15′。地址線12根線A0~A11連系統總線A1′~A12′。讀信號OE―和寫信號WE―所有芯片的連法都一樣。片選的套路也一樣,用或門輸出,輸入端是A0(偶地址存儲體)或BHE′―(奇地址存儲體)和地址譯碼電路的另外一路輸出
?
再畫內存地址譯碼電路
限定高位地址要滿足的規律。按照剛才的分析,內存地址譯碼電路的輸入應該從A13′開始一直到A19
上表中A15,A14,A13′這3根線一共就3種組合,用3-8譯碼器74LS138實現較方便,分別接C,B,A這3根線。2種組合對應Y1―和Y2―,Y1―和Y2―任何一個輸出低電平,地址譯碼電路的第一路輸出都應該是低令2片8KB芯片的片選有效,故Y1―和Y2―這2根線經過一個與門之后合成一個信號輸出。Y3―是另外一路輸出,用來選中2片4KB的芯片
畫的時候如果不希望線連的太長,可以給輸出信號起名字(如下圖的CS8K表示8KB芯片的片選信號),另一邊起相同的名字,默認兩根連在一起
高4位地址A16~A19限定上表的條件就可以。高2位地址A19,A18都得是高電平,用一個與門接到高有效的允許G1。A17,A16都得是低電平,用或門接到低有效的允許G2A。此外這是一個內存地址譯碼電路,只有當CPU訪問內存時它輸出才有可能是低電平,此時內存讀MEMR′―和內存寫MEMW′―必然有一個是低電平,經過一個與門接到低有效的允許G2B
不用的Y―可以不畫
?
如果要求畫驅動,則還需要再增加一些東西驅動那些接了好幾處的信號
驅動單向信號A0~A13,BHE―,MEMR―,MEMW―用單向驅動器74LS244,允許接地令其永遠有效
驅動雙向數據總線D0~D7用雙向驅動器74LS245。需要2片,一片A邊接系統總線低8位D0~D7,B邊是驅動后的D0′~D7′,把需要通過低8位數據線傳輸數據的芯片的片選信號CS1―和CS3―加一個與門接到其輸出允許OE―。一片A邊接系統總線高8位D8~D15,B邊是驅動后的D8′~D15′,把需要通過高8位數據線傳輸數據的芯片的片選信號CS2―和CS4―加一個與門接到其輸出允許OE―。方向控制DIR都接內存讀MEMR′―
經過驅動的信號可以加一撇
?
在這個基礎上編寫匯編程序
如果按8位字節來寫和比較。首先初始化段寄存器DS和指針寄存器DI,循環次數放到CX,每次寫入的數據內容放在AL,初值為0。然后循環,每寫一個數據DI和AL的內容加1指向下一個字節。再循環比較,循環之前的準備基本和上面一樣,初始化指針寄存器SI,循環次數放到SI,比較的數據內容放在AL。比較過程中如果發現不一樣就跳轉到錯誤程序,如果一樣則AL和SI加1繼續循環,如果循環正常退出說明所有單元正確,置一個正確標志后跳到最后
如果按16位字,在剛才的基礎上稍微改一點。循環次數減半,每次寫入的數據內容放在AX,初始低8位是全0高8位是全1。每次循環寫入后DI加2指向下一個字,AX低8位和高8位分別加2。比較時也類似
?
?
?
只讀存儲器 (ROM) 及接口設計
非易失性存儲器,掉電之后內容不丟失
?
紫外線可擦除只讀存儲器EPROM
這里對一些應該不太重要的硬件細節記得比較簡略
?
若利用全地址譯碼將EPROM2764接在8088內存首地址為A0000H的內存區,試畫連接圖
擦除時用紫外線照石英窗口15~20min,擦干凈時每單元里內容均為FFH,編程寫入時把期望變成0的那些位變成0
?
?
例:利用2732(8K×8bit SRAM,A12~A0)和6264(4K×8bit EPROM,A11~A0)構成從00000H~02FFFH的ROM存儲區和從03000H~06FFFH的RAM存儲區。畫出與8088系統總線的連接圖。(不考慮板內總線驅動)
?
ROM區:(2FFF+1) / 400 = C,共12KB → 需3片
RAM區:4000 / 400 = 10,共16KB → 需2片
?
如果將SRAM芯片改成8K×4bit,則
?
?
電可擦除只讀存儲器EEPROM (E2PROM)
?
98C64寫之前不需要擦除,直接給寫的時序就能夠把原來的內容覆蓋掉寫入新的數據
讀的時序和SRAM,EPROM都一樣,主要看怎么寫入
下圖是一個寫的時序,在整個寫的過程中讀信號OE―必須無效,首先A12~A0這13根地址線給地址選中內部的某一單元,整個過程中片選信號CE―有效選中該芯片。然后D7~D0數據線上給要寫的數據,地址數據穩定后在寫信號WE―上給負脈沖,該負脈沖的最小寬度是100ns。在負脈沖的上升沿把地址還有數據寫入到芯片內部的一個緩存,上升沿結束之后,芯片內部才真正開始把數據寫入到指定的單元,這個寫的過程是比較慢的,大概需要5~10ms,忙信號READY/BUSY―會輸出低電平,READY/BUSY―再次變成無效時數據才真正寫完
?
?
?
用98C64構成8088的內存
把芯片內部所有的單元寫入55H
?
如果想要提高效率,可以設計一個輸入接口讀READY/BUSY―信號當前的狀態,如果為低電平忙則等待,如果為高電平不忙就馬上寫下一個數據。把READY/BUSY―信號通過一個三態門連接到數據線,CPU要查詢這根線時三態門才打開把狀態輸出到數據線上,以防止總線競爭
控制端控制這個三態門什么時候該打開,什么時候該關閉。如果把這個輸入接口映射到內存地址空間就給內存地址譯碼電路,要映射到接口地址空間就給接口地址譯碼電路。下圖顯然是接口地址譯碼電路
READY/BUSY―是一個漏極開路的引腳,輸出應該加上拉電阻。此外下圖還加了一個不太重要的濾波電容
這里和下一章有點關系??梢韵瓤春竺?#xff0c;回頭再看會比較好理解
這樣原來的延時程序就可以改成查詢方式。通過IN指令從內存地址7000H讀進8位數據到AL,最低位即反映READY/BUSY―信號的狀態。再用TEST指令將AL高7位清0(只影響標志位,不回送結果),若結果為0,說明READY/BUSY―信號為低電平,芯片正忙,則跳轉繼續循環等待。直到芯片真正把數據寫入到對應單元,READY/BUSY―變為高電平,結果為1時才不再跳轉順序執行下面的指令
然后講了下為什么要把忙信號設計成漏極開路
?
?
?
閃速E2PROM : Flash
對于E2PROM,需要快寫必須一次寫一頁,按字節寫速度慢。編程時間長、容量小
Flash : 容量大,編程速度快。成本低、密度大。應用廣泛
本章后面的內容都是了解性的,可以自己看對應的錄播(10.18后半節)和課件
?
輸入/輸出技術
程序查詢 I/O 方式
無條件傳送方式
有一些最簡單的外設不需要查詢,可以隨時給它發命令,隨時讀它的狀態,這種外設可以采用無條件傳送的方式
?
輸入:開關、…
輸出:發光二極管、繼電器、步進電機、…
?
輸入設備
下圖中外設是開關,10K的上拉電阻經過開關接地,電容起到消抖的作用。要讀該外設的狀態,不能直接連接到系統總線,要經過三態門以避免總線競爭
CPU要訪問該外設時才允許把這根線的狀態輸入到系統總線的某一根數據線上,否則這根線應該和系統總線沒有任何關系
74LS244有8個三態門,我們只需要接到其中某一個的輸入,該三態門的輸出接到系統總線數據線的最低位D0
三態門的控制端,允許信號OE―接一個或門。或門的一個輸入端接接口讀信號IOR―,另外兩個輸入端接譯碼的輸出,譯碼的輸入是低16位地址線
下圖連的是8088的總線。但不管是8086還是8088訪問接口都只需要用到低16位地址線
這里簡化了AEN信號,AEN應為低電平表示CPU在控制總線而不是處在DMA狀態
?
不難得接口地址是FFF7H。有了該接口地址之后可以寫一段匯編程序。首先接口地址FFF7H傳送到DX,因為通常不管是IN指令還是OUT指令都要求接口地址得在DX,然后執行IN指令,從地址為FFF7H的接口讀8位數據到AL,AL的最低位反映的就是開關的狀態。接下來用AND指令將高7位清0,如果結果為0說明A開關閉合,跳轉到ON標號執行,否則說明開關斷開,跳轉到OFF標號執行
?
輸出設備
下圖中反相器加發光二極管加限流電阻是一個外設,總共有8個外設。雖然直接連到系統總線的數據線不會造成總線競爭,但是系統總線是分時使用的公共信號線,故必須要接具有存儲能力的鎖存器
鎖存器74LS273的輸出Q0~Q7接8個外設,輸入D0~D7分別接系統總線數據線的D0~D7
鎖存信號(寫信號)CLK接一個或門?;蜷T的一個輸入端接接口寫信號IOW―,另外兩個輸入端接接譯碼的輸出,譯碼的輸入是低16位地址線
?
不難得接口地址是0000H。具體的控制程序先把接口地址送到DX,然后把要往接口寫的數據送到AL,再執行OUT指令把數據寫入鎖存器,其輸出在下次改寫前都保持不變
?
剛才針對的都是8088的8位總線,現在要設計16位總線的8086的接口
下圖中有16個外設,用2片74LS273構成一個16位的接口
上面的74LS273作為偶地址存儲體,輸入D0~D7連系統總線數據線的低8位D0~D7,譯碼時加A0必須為低電平的條件
下面的74LS273作為奇地址存儲體,輸入D0~D7連系統總線的高8位D8~D15,譯碼時加BHE―必須為低電平的條件
用3-8譯碼器做接口地址譯碼,輸入為地址A1~A15和IOW―
?
不難得偶地址存儲體的地址為3804H,奇地址存儲體的地址為3805H。程序首先做偶地址8位寫。把偶地址存儲體的地址放在DX,然后把要寫入的數據放在AL。再執行OUT指令,該指令執行時用的是8位的AL,是8位接口寫,并且接口地址是偶地址,會把數據寫入到上面的74LS273,不會影響到下面的74LS273
然后做奇地址8位寫和16位寫
?
?
?
查詢方式
查詢方式的基礎是必須要有能讀取外設當前狀態的輸入接口,根據外設的不同狀態決定應該對外設采取什么樣的操作
?
下圖中外設引腳有8根數據線D0~D7,鎖存信號(寫信號)STB―,忙信號BUSY。忙信號為高電平則等待,忙信號為低電平則把要寫入的下一個數據放在8根數據線上,當數據穩定之后在STB―給一個寬度大于100μs的負脈沖,在負脈沖的上升沿把數據寫入到外設。寫入的數據內部需要花一定時間處理,一旦寫完則BUSY馬上變為高電平。當忙信號再次變為低電平,說明上一個數據已經處理完,然后再重復上面的過程
?
現在要設計基于該外設的接口
要設計輸出接口來給外設提供8位數據和鎖存信號,輸入接口讀外設BUSY信號當前的狀態。輸出接口用鎖存器實現,輸入接口用三態門實現
第一個輸出接口要提供8位的數據,需要一個74LS273,一邊D0~D7接系統總線數據線D0~D7,一邊Q0~Q7接外設,CLK接一個或門,或門的一個輸入端接IOW―,另外一端接地址譯碼電路的一個輸出
另一個輸出接口的74LS273只用到其中的一位,其輸入接某一根系統總線數據線,輸出接STB―,CLK接一個或門,或門的一個輸入端接IOW―,另外一端接地址譯碼電路的一個輸出
輸入接口的74LS244只用到其中的一個三態門,其輸入接BUSY,輸出接某一根系統總線數據線。OE―接一個或門,或門的一個輸入端接IOR―,另外一端接地址譯碼電路的一個輸出
再用3-8譯碼器實現接口地址譯碼電路,需要的3個輸出為Y0―~Y2―,輸入為低16位地址A0~A15
?
不難得Y0―~Y2―對應的接口地址分別為02F8H,02F9H,02FAH
?
編程首先把段的起始地址的高16位D200H通過通用寄存器傳到DS,然后用寄存器間接尋址就可以訪問到內存從這個單元開始的這些數據。指針寄存器可以是BX,SI,DI,這里用BX,將BX初值置為0。循環次數1000放在CX
初始化STB―,STB―寫時給負脈沖,不寫時應該是高電平。OUT指令把AL的內容01H寫入到接口地址02F9H對應的中間的74LS273令Q0=1,將STB―置為高電平為后面寫數據做準備
判斷BUSY的狀態。IN指令讀地址為02FA的接口,讀取的數據放在AL。再用AND指令將低7位清0只保留最高位。如果結果不為0,說明BUSY為高電平,就跳轉回上面再讀,一直循環到結果為0,BUSY變為低電平才順序執行后面的指令
數據輸出。利用寄存器間接尋址,DS為默認段寄存器,BX存段內偏移,從內存D2000H取數據放在AL。OUT指令將該數據寫入接口地址02F8H對應的上面的74LS273,給外設提供數據
數據已經準備好,將STB―置為低電平,延時100μs后再將STB―置為高電平。在STB―的上升沿將數據寫入到外設
寫完之后BX內容加1指向內存的下一個單元,然后繼續循環。總共循環1000次,每次都先判斷BUSY忙則等待,不忙再寫下一個數據
然后引申講了下輸出接口和輸入接口占用同一個接口地址等簡化軟硬件的方法
?
多外設的查詢控制
這種方式不好。因為如果某一個外設壞了永遠處于沒準備好的狀態,CPU就會老是等待,其他外設也永遠得不到服務
?
這種方式有優先級的概念,會偏向認為先查詢的外設更重要
?
如果不需要優先級,機會均等。采用這種方式
?
中斷方式
x86處理器處理中斷的過程在第二章講CPU引腳時已經介紹過,可以查閱當時的筆記。這里介紹來自INTR引腳的中斷請求,一般對應的都是外設或接口的中斷請求
?
?
假設CPU執行到某條指令,通過INTR引腳來一個中斷請求。這種情況下若滿足中斷響應的條件:標志寄存器中中斷允許標志位IF為1處于開中斷狀態,當前這條指令執行完才會響應中斷請求
首先要保護當前的斷點,依次將Flags,CS,IP的內容壓入堆棧,并將標志寄存器中的中斷允許標志位IF和陷阱標志位IF清0關中斷,這樣再從這個引腳來中斷請求CPU就不會響應了
標志寄存器教材叫程序狀態字PSW,即下圖的Flags。子程序調用則不需要保護Flags的內容
1413:0105表示內存地址,左側是段寄存器的內容,右側是段內偏移
然后CPU會通過INTA―連續送出兩個負脈沖。8259接收到第一個負脈沖就知道CPU正在響應剛才的中斷請求,它會準備好發出中斷請求的中斷源的8位中斷向量碼,等第二個負脈沖來時將其放在低8位數據線上讓CPU采樣得到
CPU會根據中斷向量查從內存地址0開始放的中斷向量表的對應行,獲取對應的中斷服務程序的入口地址。中斷向量表每一行4個字節,前2個字節存段內偏移賦值給IP,后2個字節存段寄存器的內容賦值給CS,下一條指令就會跳轉到從中斷服務程序的第一條指令的地址開始取并執行
注意是小端存儲,小地址對應低字節,大地址對應高字節
中斷服務程序的結構和子程序很像,也要保護現場,將要修改的控制寄存器的內容先壓入堆棧,用完之后再恢復現場,然后再中斷返回。執行中斷返回指令IRET時會從堆棧彈出3個數恢復Flags,CS,IP,返回到剛才被打斷的指令的下一條指令繼續執行
CPU硬件在響應中斷時會自動關中斷。如果中斷服務程序允許中斷嵌套,在保護現場之后加開中斷指令STI,恢復現場之前再用關中斷指令CLI
這里內部中斷理解成中斷源在CPU內部,跟調試無關的那些中斷,如除法錯誤中斷。跟調試有關的那些中斷,如單步中斷和斷點中斷通常優先級最低
?
?
?
可編程中斷控制器8259
可對8個中斷源實現優先級控制
支持多片級聯,一個主片可以有8個從片,可擴展至對64個中斷源實現優先級控制
可編程設置不同工作方式
根據中斷源向x86提供不同中斷類型碼
可編程就是該芯片內部有一些寄存器,往寄存器里面寫入不同的內容就可以具有不同的功能,可以工作在不同的模式下
?
8259內部結構
8259的引腳:
中斷源中斷請求輸入IR0~IR7
雙向數據總線D0~D7
中斷請求INTR,中斷應答INTA―
讀信號RD―,寫信號WR― : 要對內部寄存器進行讀寫,讀寫時傳數據通過8根數據線
片選信號CS― : 支持多片級聯,要看訪問哪一片
地址A0 : 8259只有一根地址線,只需要占用2個接口地址
上圖中還有4個引腳,這幾個引腳和級聯有關,如果只是單片使用用不上
?
需要了解3個寄存器:
中斷請求寄存器IRR (INTERRUPT REQUEST REG):保存從IR0~IR7來的中斷請求信號,某位=1表示對應的IRi有中斷請求
中斷屏蔽寄存器IMR (INTERRUPT MASK REG):存放中斷屏蔽字,某位=1表示對應的IRi輸入被屏蔽
中斷服務寄存器ISR (IN - SERVICE REG):保存正在服務的中斷源,某位=1表示對應的IRi中斷正在被服務
?
中斷優先權判別電路 (PRIORITY RESOLVER): 確定是否向CPU發出中斷請求,中斷響應時確定ISR的哪位應置位及把相應中斷的類型碼放到數據總線上
?
?
下圖中即表示8086/8088 CPU正在執行IR6和IR4兩個引腳所對應的中斷源的中斷服務程序,CPU同時執行兩個中斷源的中斷服務程序,很顯然發生了中斷嵌套。引腳IR2,IR3,IR5同時發中斷請求,作為8259如果是固定優先級,編號小的優先級高??紤]到IR2被屏蔽,且IR3優先級高于IR6和IR4,8259會通過INT向CPU發一個IR3的中斷請求
CPU通過一系列過程響應中斷,再通過INTA―給8259送連續2個負脈沖。8259接收到第一個負脈沖后,因為CPU馬上要執行到IR3對應的中斷源的中斷服務程序,會將ISR的第3位置1,IRR的第3位清0
?
CPU在中斷服務程序執行完之后通知8259可以將ISR的相應位清0。在中斷服務程序的最后恢復現場之前要給8259發一個EOI中斷結束命令。要實現EOI功能可能需要3條指令,先將8259的偶地址(8259只有一根地址線,地址線給0是偶地址,給1是奇地址)放到DX,要寫入的內容放在AL,再用OUT指令把AL的內容寫入到DX所指向的接口
MOV DX,8259偶地址 MOV AL,20H OUT DX,AL寫的數是20H,是一般EOI命令。允許中斷嵌套并且是正常優先級的情況下就會在在中斷服務程序的末尾向8259發一個一般EOI命令。8259接收到命令后將ISR中優先級最高的那一位清0,因為有正常優先級概念的情況下,如果允許中斷嵌套,最先執行完的中斷服務程序肯定是優先級最高的
?
8259工作方式
緩沖方式可以設成非緩沖和緩沖。如果設置成工作在緩沖方式,雙向數據線需要加一個74LS245驅動器,其OE―信號由8259的SP―/EN―作為輸出引腳提供。如果沒有工作在緩沖模式下,SP―/EN―作為輸入引腳讓多片級聯時8259可以知道自己是主片還是從片,其接到高電平為主片,接地為從片
書上后面的例子都沒有讓8259工作在緩沖模式下
?
中斷是否允許嵌套即中斷程序是否允許被優先級更高的中斷源打斷。如果不允許中斷嵌套就設置中斷結束方式為自動EOI方式。在接收到中斷應答的第二個負脈沖時就會將ISR的內容清0,此時可以簡單地認為ISR的內容總是0,不會記錄CPU正在執行哪一個中斷源的中斷服務程序,所以也就無法實現帶優先級的各種中斷嵌套。這樣在編寫中斷服務程序時就不用加STL指令,末尾也不需要向8259發EOI命令
?
屏蔽方式可以設成一般屏蔽和特殊屏蔽。一般屏蔽就是正常優先級的概念,即如果CPU正在執行某一個中斷源的中斷服務程序,同級的和優先級更低的中斷源是不能打斷當前的中斷服務程序的,這樣在中斷服務程序的末尾要向8259發一個一般EOI命令。如果設置成特殊屏蔽方式就沒有優先級的概念,只要不是同級的,任何一個其他的中斷源都可以打斷當前的服務程序,究竟哪一個中斷服務程序最先執行完不一定, 這樣在中斷服務程序的末尾要向8259發一個特殊EOI命令,里面包含清0的那一位的編號,明確告訴8259應該把ISR的哪一位清0
教材上后面的例子都讓8259工作在一般屏蔽方式
?
剛才舉的這些例子都默認8259工作在固定優先級,優先級順序從高到低為IR0~ IR7,還可以設置成循環優先級
自動循環優先級:
中斷源輪流處于最高優先級,即自動中斷級循環
初始優先級順序可由編程改變
某中斷請求IRi被處理后,其優先級別自動降為最低,原來比它低一級的優先級上升為最高級
指定循環優先級:
優先級排列順序可編程改變
加電后8259A的默認方式,默認優先級順序從高到低為IR0~IR7
?
最后看8259是否需要多片級聯。如果不需要級聯單片工作設置成一般嵌套方式。如果是多片級聯主片設置成特殊嵌套方式,從片設置成一般嵌套方式
8259級聯:
單片8259A可支持8個中斷源
采用多片8259A級連,可最多支持64個中斷源。n片8259A可支持7n+1 (n≤9) 個中斷源
級聯時只有一片8259A為從片,其余的均為從片。只有主片有能力向CPU發中斷請求。從片的中斷請求連到主片的某一個IRi引腳,從片所有引腳發的中斷請求都得從該引腳走,從片再向主片發中斷請求,主片再向CPU發中斷請求
涉及到的8259A引腳包括:CAS0?CAS2,SP―/EN―,IRi,INT
?
通過SP―/EN―引腳8259得知自己是主片還是從片,其接高電平是主片,接地是從片。ICW3寫什么內容和級聯狀態下怎么連有關系,通過初始化過程向ICW3寫入適當的內容,讓主片知道IRi引腳接的是從片還是中斷源,讓從片知道接的是主片的IRi引腳
8259有4個初始化命令字ICW1~ICW4,還有3個操作命令字OCW1~OCW3。OCW1其實就是IMR,所以8259內部總共有9個寄存器
連電路時INTA―接主片和從片,所有8259都能接收到CPU從INTA―發來的兩個連續負脈沖。主片通過INTA―接收到第一個負脈沖后會對ISR和IRR置位。主片替IRi向CPU發中斷請求,就會通過CAS0~CAS2這3根線送級聯地址i給兩個從片,兩個從片在中斷應答的第一個負脈沖會檢查這3根線的狀態與ICW3內容作比較,一致則對ISR和IRR置位,并在第二個負脈沖來時提供中斷向量
一般嵌套方式與特殊全嵌套方式的區別:
主片能夠區分引腳哪一個接中斷源,哪一個接從片。只有接從片的引腳才允許同級的中斷源打斷當前的中斷服務程序
?
?
8259編程使用
內部寄存器的尋址方式:
需要CS―,A0,RD―,WR―和D4,D3的配合。內部寄存器的訪問方法如下表
?
為了能夠區分內部究竟訪問的是哪一個寄存器,寫入數據的某一位在8259內部也是當地址來用的
如8259要能正常工作必須要有初始化的過程,往4個ICW里寫入事先設計好的內容。首先要寫ICW1,ICW1對應的是8259的偶地址,即A0=0的情況。所以如果想要初始化8259,第一步就要往8259的偶地址里寫入D4位為1的一個數據
Icw1的結構如下。D2和D5~D7這幾位沒有用到,LTIM表示IR0~IR7這8根線是高電平還是上升沿有效,SNGL表示是單片工作(級聯控制字ICW3不需要寫)還是多片級聯,IC4表示是否要寫ICW4
一旦ICW1被寫就啟動了一個初始化的過程,緊接著要馬上寫后面這幾個ICW,往奇地址里面連續寫入若干個數:
若現在寫ICW1,將其配置成多片級聯并且要寫ICW4就要寫3個數,分別自動寫入到ICW2~ICW4
如果寫的內容規定是單片工作且要寫ICW4,就要寫2個數,分別自動寫入到ICW2和ICW4
如果寫的內容規定是單片工作且不要寫ICW4,就要寫1個數,到ICW2
一旦初始化的過程完成之后,后面這些OCW隨時都可以寫,按什么順序寫都無所謂,寫不寫都可以
電路連好后怎么檢查8259能否正常工作,或者說它的寄存器能否正常讀寫?
OCW1是8259內部唯一一個既可以寫又可以讀的寄存器,它對應奇地址
初始化完之后往8259的奇地址里面寫入全0,再讀出來比較一下是否為全0。再寫入全1,讀出來比較一下是否為全1。如果都對就認為8259內部的這些寄存器能夠正常寫入,數據線和地址線的連法應該也沒有問題
?
ICW1:初始化字
上面已經進行了說明
ICW2:中斷向量碼
ICW3:級聯控制字
ICW4:中斷結束方式字
?
OCW1:中斷屏蔽字
OCW2:中斷結束和優先級循環
OCW3:屏蔽方式和讀出控制字
?
?
中斷方式實現方法
1.8259連接(硬件)
若要連接到8086的系統總線。8259地址線只有一根,占用2個接口地址,如果想把它映射到偶地址上,或者說作為一個偶地址存儲體,其8根數據線要連接到系統總線數據線的低8位。A0是偶地址存儲體的選擇信號,地址線從A1開始,故這塊應該連到A1上,A1作為芯片的內部地址,高位地址從A2開始
?
如果要映射到偶地址最好讓A0也參與譯碼,限定A0是0。下圖中因為A0沒有參與譯碼其實應該占用4個接口地址FF00H~FF03H,但實際上如FF00H和FF01H這兩個地址中只能用FF00H,因為FF01H對8086來講是奇地址,8086不管是訪問內存還是接口,如果訪問的是奇地址就會通過高8位的數據線來傳遞數據。所以如果你用FF01H這個地址讀8259的某一個寄存器,IN指令執行時8086只會采樣數據線的高8位,雖然8259芯片已經輸出了寄存器的內容,但它放在了數據線的低8位,得不到數據
所以我們在連數據線時這8根線連接到系統總線的低8位,8086在訪問該8259芯片時就必須要用偶地址,用奇地址訪問不到芯片內部的寄存器。FF01H和FF03H這2個接口地址雖然被占用但是不能用,如果不想浪費,就應該加限定A0是0的條件,這樣電路就只占用2個接口地址,對于8086來講這兩個都是偶地址,對于8259來講,FF00H是8偶地址,FF02H是奇地址
如果計算機中有DMA存在,應該有限定AEN―是低電平的條件
?
2.編寫初始化程序:8259初始化,設置中斷向量表
在有了硬件的基礎上。計算機剛一加電啟動的過程中,通常由BIOS程序負責對8259的ICW寫入事先設計好的內容來進行初始化讓它工作在期望的模式下
初始化8259:
SET59A:MOV DX,0FF00H ;8259的地址A0=0MOV AL,13H ;寫ICW1,邊沿觸發,單片,需要ICW4OUT DX,ALMOV DX,0FF02H ;8259的地址A0=1MOV AL,48H ;寫ICW2,設置中斷向量碼OUT DX,ALMOV AL,03H ;寫ICW4,8086/88模式,自動EOI;非緩沖,一般嵌套OUT DX,ALMOV AL,0E0H ;寫OCW1,屏蔽IR5、IR6、IR7;(假定這3個中斷輸入未用)OUT DX,AL?
下例檢查硬件電路能否正常工作,可以認為和前面的初始化沒有關系
;將00H與FFH分別寫入IMR,并將其讀出比較 MOV DX,0FF02H MOV AL,0 OUT DX,AL ;寫OCW1,將00H寫入IMR IN AL,DX ;讀IMR OR AL,AL ;判斷IMR內容為00H否 JNZ IMRERR MOV AL,0FFH OUT DX,AL IN AL,DX ADD AL,1 JNZ IMRERR?
設置中斷碼:
中斷向量碼初始化位48H,中斷處理程序入口地址的標號為CLOCK,設置中斷向量表
法1 直接寫中斷向量表
INTITB: MOV AX,0MOV DS,AX ;將內存段設置在最低端MOV SI,0120H ;n=48H, 4×n=120HMOV AX,OFFSET CLOCK ;獲取中斷處理程序首地址的段內偏移地址MOV [SI],AX ;段內偏移地址寫入中斷向量表4×n地址處MOV AX,SEG CLOCK ;獲取中斷處理程序首地址的段地址MOV [SI+2],AX ;段地址寫入中斷向量表4×n+2地址處?
法2 利用DOS功能調用
MOV AH,25H ;功能號 MOV AL,48H ;中斷向量碼 MOV DX,SEG CLOCK MOV DS,DX ;段地址→DS MOV DX,OFFSET CLOCK ;偏移地址→DX INT 21H?
3.編寫中斷處理程序
中斷嵌套的實現:
保護現場 (PUSH reg’s)
開中斷 (STI)
中斷服務
…
中斷服務
關中斷 (CLI)
產生EOI命令
恢復現場 (POP reg’s)
中斷返回
?
產生EOI命令:
上例初始化時設置成自動EOI模式,所以中斷服務程序的末尾沒有必要向8259發EOI命令。如果允許中斷嵌套,工作在一般EOI方式,往8259偶地址中寫入20H相當于發一個一般EOI命令,8259會把ISR置1位中優先級最高的那一位清0
中斷方式實現示例
初始化8259:SET59A
初始化中斷向量表:INTITB
?
中斷處理程序:CLOCK
假設在內存中有連續這么幾個字節的數據,第一個字節由標號TIMER指向,存1/50秒的信息,下一個字節存秒的信息,再下一個字節存分鐘的信息,最后一個字節存小時的信息。并且后三個字節的內容采用壓縮的BCD碼的形式存,1/50秒直接用二進制的形式存
DAA指令會根據當前標志寄存器里面的一些信息決定要不要加6修正
?
DMA的過程之前講過,DMA控制器8237不具體講
可編程8237芯片有4個通道,可以實現內存到接口,內存到內存之間的數據傳送,但是不能實現接口到接口之間的數據傳送
?
到此這章的內容就結束了。后面還講了下TSR時鐘顯示程序和比較新的計算機如何實現中斷和DMA
常用接口器件
思考:計算機和外設之間如何通過接口傳送數據(非DMA)?
最簡單的方式是上一章講的程序查詢或者說直接控制
想讀輸入設備當前的狀態,通過三態門實現接口。給三態門接口地址,只有用IN指令讀地址為這個的接口時,三態門才打開把外設的狀態輸出到系統總線數據線,IN指令最后采樣數據線把外設的狀態讀到CPU內部寄存器
用OUT指令輸出數據,數據只在OUT指令執行的瞬間呈現在數據線上,所以輸出接口需要用鎖存器實現存儲功能
下圖中輸入接口只能讀不能寫,只需要IOR―信號參與地址譯碼。輸出接口只能寫不能讀,只需要IOW―信號參與地址譯碼
?
這種方式CPU利用率較低,大部分時間都花在不停地循環查詢外設。如果想要提高CPU的利用率,外設或者說接口能夠引入中斷功能,外設不忙需要CPU參與時再給CPU發中斷請求
輸出接口里有可以暫存數據的緩沖寄存器,可以判斷該寄存器內容是否為空,即數據是否已被外設取走。如果為空應該向通過INTR―信號向CPU發中斷請求通知CPU緩存已空,能否寫入新的數據。CPU經過一定過程響應中斷,最終進入中斷服務程序,中斷服務程序把要傳輸給外設的下一個數據寫入到接口內部的緩沖寄存器,寫完直接返回。接口檢測到緩沖寄存器寫入新數據,將OBF―信號置低通知外設拿走數據,外設采樣數據線拿走數據處理完后通過ACK―信號發一個負脈沖通知接口數據已被取走。接口將OBF―信號置高,同時再次向CPU發中斷請求重復剛才的過程
輸入接口里也有可以暫存數據的寄存器,可以通知外設緩沖寄存器內容為空可以寫下一個數據。外設通過數據線提供數據,數據穩定后通過STB―信號發一個負脈沖,在負脈沖的上升沿把數據寫入到接口的暫存器中。接口寫入新數據后把IBF信號置高表示輸入緩沖區滿,通過INTR―信號向CPU發中斷請求,CPU響應中斷進入到中斷服務程序,從接口把數據讀走。接口將IBF信號置低,重復剛才的過程
ACK―和STB―不是狀態信號而是脈沖信號
設計雙向的具有中斷功能的輸入/輸出接口,結合上述兩種情況
輸入/輸出接口內部有2個寄存器,從CPU角度一個只能寫不能讀一個只能讀不能寫,可以占用同一個寄存器地址。CPU通過讀狀態寄存器得知產生中斷的原因并做相應處理
?
要實現上述接口,可以直接用可編程并行接口8255
8255一共有3種工作方式:
工作在方式0的輸入相當于三態門,工作在方式0的輸出相當于鎖存器
工作在方式1的輸出相當于具有中斷功能的輸出接口,工作在方式1的輸入相當于具有中斷功能的輸入接口
工作在方式2相當于雙向的輸入/輸出接口
?
8255:可編程并行接口
內部結構及外部引線
8255有A,B,C三個并行接口
每個并行接口都有8根線,A口為PA7~PA0,B口為PB7~PB0,C口分成相互獨立,可以分別配置的高4位PC7~PC4和低4位PC3~PC0
任何一個口都可以工作在方式0,A口還可以工作在方式1和方式2,B口還可以工作在方式1。方式0和方式1可以配置成輸入也可以配置成輸出
工作在方式1和方式2需要借用C口的信號線
若配置8255的A口工作在方式2。PA7~PA0這8根線跟外設之間是雙向的,此外構成其他的一些握手信號STB―,IBF,ACK―,OBF―和INTR―還需要分別借用C口的信號線PC4~PC7和PC3。如果B口剛好工作在方式1,還可以借用C口的另外3根線
A口,B口,C口各有1個8位的緩沖寄存器,此外還有1個控制字,加電啟動后通過寫控制字可以初始化8255,配置具體的哪一個口應該工作在哪種方式下。對這些寄存器進行讀寫和總線間要有地址線A0~A1,數據線D0~D7和讀信號RD―,寫信號WR―,片選信號CS―
另外還多了1個復位信號RESET,直接接到計算機主板的復位電路引腳。計算機剛一加電后復位電路工作產生負脈沖,將8255內部的控制字自動置成事先設置好的狀態即A,B,C口均為輸入以避免總線競爭
?
8255的方式控制字及狀態字
8255的地址線有2根,A,B,C三個口的緩沖寄存器和控制字分別占用地址00,01,10和11
寫地址11,最高位用來區分現在是寫控制字還是將C口寄存器的某一位清0或置1
C口寄存器具體的某一位跟C口的引腳之間一般有對應關系。如C口寄存器的第3位置1,則PC3輸出引腳直接輸出高電平
?
8255的工作方式
?
?
?
8255的尋址和連接使用
方式0:
易得A,B,C口寄存器和控制字的具體接口地址分別是380H,381H,382H,383H
看地址線A0,A1怎么連確定連的是8086還是8088。8259的A0,A1連系統總線的A0,A1連的是8088的系統總線。A0,A1連系統總線的A1,A2連的是8086的系統總線,數據線如果連到系統總線低8位必須要給偶地址,地址譯碼電路加偶地址存儲體選擇信號A0必須是低電平的條件
?
?
8255初始化及應用舉例:方式0 - 打印機接口
除了數據線,外設還要求有鎖存信號STROBE―,要用B口或C口的某一根線提供(負脈沖寬度要求≥1us,無法直接通過較快的總線信號提供)。對外設來講STOROBE―信號是輸入,BUSY信號是輸出,而C口較為靈活,高4位和低4位可以分別配置。可以讓C口的高4位配置成輸出,只能寫不能讀,再隨便選一根線如PC6,C口的低4位配置成輸入,只能讀不能寫,再隨便選一根線如PC1,剛好既可以判斷BUSY信號的狀態,又可以給它送STROBE―信號
?
8255地址:380H~383H
初始化程序:
?
利用8255方式0以程序控制(查詢)方式實現打印機接口
?
?
8255初始化及應用舉例:方式1 - 打印機接口
單穩觸發器將下降沿轉換為寬度為1us的負脈沖
同樣這種情況下PC6輸入引腳和C口寄存器的第6位沒有關系,C口寄存器的第6位是中斷允許位
8255地址:380H~383H
初始化程序:
?
利用8255方式1程序控制(查詢)方式實現打印機接口:
?
利用8255方式1以中斷方式實現打印機接口:
8253:可編程定時器
外部引線及功能
頻率已知的時鐘信號,對時鐘信號進行計數就能達到定時的效果,所以所謂的定時器還有另外一個名字:計數器。8253芯片內有3個計數器,要求寫入計數初值,開始計數后每來一個時鐘信號的下降沿計數值減1
?
3個計數器都有相應的16位寄存器用于寫入計數初值。8根數據線D0~D7寫計數初值時分兩次寫,先寫的對應低8位,后寫的對應高8位
要區分讀或寫的是哪一個寄存器還要有地址線A0~A1。系統總線可見或者說可以訪問的寄存器還有控制字,往控制字寫入不同的內容就可以讓每一個計數器工作在不同的模式下。4個寄存器用2位地址訪問。2根地址線4種組合。00,01,10,11分別對應計數器0,1,2的1計數值寄存器和控制字。地址0,1,2這3個寄存器可寫可讀,地址3寄存器可寫不可讀
此外8253和系統總線這邊的信號還有讀寫信號RD―,WR―,片選信號CS―
?
計數器i有時鐘信號輸入引腳CLKi,用于通知計數值減到0的輸出引腳OUTi,用于暫停計數的門控信號GATEi。GATEi高電平正常計數,低電平暫停計數)
?
?
工作方式
每一個計數器可以工作在6種不同的模式下
?
方式0和方式1產生一個負脈沖
方式0:計數結束產生中斷
寫控制字規定某計數器工作在方式0下后OUT引腳馬上就變為低電平。寫入計數初值n后開始定時計數,計數值減到0后OUT引腳變為高電平。其他方式只要寫控制字規定工作方式后OUT引腳馬上變為高電平
可將其接到8259的一個中斷請求輸入引腳,定時向CPU發出中斷請求
?
方式1:可編程單穩
與方式0不同,方式1寫完計數初值后要等到GATE信號來一個上升沿后才開始計數
可將一個上升沿轉換成一個寬度可編程的負脈沖
?
?
方式2和方式3產生周期信號,方式2產生周期性負脈沖。方式3產生方波
方式2:頻率發生器
方式3:方波發生器
?
?
方式4和方式5隔一定時間后產生寬度為一個時間周期的負脈沖
方式4:軟件觸發旁通
方式5:硬件觸發旁通
?
?
8253的控制字
開始計數時,第一個時鐘下降沿會把計數值從計數值寄存器傳送到減1寄存器,此時不會判斷計數值是否為0。在下一個時鐘周期下降沿才把減1寄存器的內容減1,然后判斷是否為0。故計數初值如果寫0對應的是最大計數值
減1計數器的每次改變都會同步到計數鎖存器,正常工作時這兩個寄存器的內容總是一樣的。計數值寄存器和計數鎖存器占用同一個地址,寫這個地址將計數初值寫入計數值寄存器,讀這個地址讀當前計數鎖存器的內容。減1計數器則不能讀也不能寫。讀之前要給計數器發鎖存命令 ,一旦發鎖存命令后減1計數器和計數鎖存器切斷聯系,計數鎖存器內容不再變化
從系統總線的角度來講,每個計數器好像只有1個相應的16位寄存器,但其實有3個
8253的地址3對應控制字,看起來8253內部好像也只有一個控制字,但實際上內部對應每一個計數器都有其專有的控制字
故每一個計數器內部都有4個寄存器:計數值寄存器,減1計數器,計數鎖存器和控制字
?
8255的尋址及連接
?
8253的初始化及應用
然后講了下以前CPU為8088時計算機主板上跟8253有關的部分電路和電源掉電檢測的例子
?
可編程定時/計數器8253的串聯使用
單個定時/計數器的限制:
?
多個定時/計數器串聯使用:
要求輸出為指定寬度的周期性負脈沖,如何設計?
方法1:
方法2:
?
基于總線的I/O接口設計
略
總結
以上是生活随笔為你收集整理的西电计科院微机原理与系统设计课程笔记(车向泉版)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 前端学习(1814):前端调试之css
- 下一篇: 前端学习(1973)vue之电商管理系统