愛了愛了,這篇寄存器講的有點意思

點擊藍色「程序員cxuan 」關注我喲程序員

加個「星標」,歡迎來撩web


這是程序員cxuan的第 41期原創分享


下面咱們就來介紹一下關於寄存器的相關內容。咱們知道,寄存器是 CPU 內部的構造,它主要用於信息的存儲。除此以外,CPU 內部還有運算器,負責處理數據;控制器控制其餘組件;外部總線鏈接 CPU 和各類部件,進行數據傳輸;內部總線負責 CPU 內部各類組件的數據處理。
編程

那麼對於咱們所瞭解的彙編語言來講,咱們的主要關注點就是 寄存器數組

爲何會出現寄存器?由於咱們知道,程序在內存中裝載,由 CPU 來運行,CPU 的主要職責就是用來處理數據。那麼這個過程勢必涉及到從存儲器中讀取和寫入數據,由於它涉及經過控制總線發送數據請求並進入存儲器存儲單元,經過同一通道獲取數據,這個過程很是的繁瑣而且會涉及到大量的內存佔用,並且有一些經常使用的內存頁存在,實際上是沒有必要的,所以出現了寄存器,存儲在 CPU 內部。緩存

認識寄存器

寄存器的官方叫法有不少,Wiki 上面的叫法是 Processing Register, 也能夠稱爲 CPU Register,計算機中常常有一個東西多種叫法的狀況,反正你知道都說的是寄存器就能夠了。微信

認識寄存器以前,咱們首先先來看一下 CPU 內部的構造。數據結構

CPU 從邏輯上能夠分爲 3 個模塊,分別是控制單元、運算單元和存儲單元,這三部分由 CPU 內部總線鏈接起來。架構

幾乎全部的馮·諾伊曼型計算機的 CPU,其工做均可以分爲5個階段:「取指令、指令譯碼、執行指令、訪存取數、結果寫回」併發

  • 取指令階段是將內存中的指令讀取到 CPU 中寄存器的過程,程序寄存器用於存儲下一條指令所在的地址
  • 指令譯碼階段,在取指令完成後,立馬進入指令譯碼階段,在指令譯碼階段,指令譯碼器按照預約的指令格式,對取回的指令進行拆分和解釋,識別區分出不一樣的指令類別以及各類獲取操做數的方法。
  • 執行指令階段,譯碼完成後,就須要執行這一條指令了,此階段的任務是完成指令所規定的各類操做,具體實現指令的功能。
  • 訪問取數階段,根據指令的須要,有可能須要從內存中提取數據,此階段的任務是:根據指令地址碼,獲得操做數在主存中的地址,並從主存中讀取該操做數用於運算。
  • 結果寫回階段,做爲最後一個階段,結果寫回(Write Back,WB)階段把執行指令階段的運行結果數據寫回到 CPU 的內部寄存器中,以便被後續的指令快速地存取;

計算機架構中的寄存器

寄存器是一塊速度很是快的計算機內存,下面是現代計算機中具備存儲功能的部件比對,能夠看到,寄存器的速度是最快的,同時也是造價最高昂的。app

咱們以 intel 8086 處理器爲例來進行探討,8086 處理器是 x86 架構的前身。在 8086 後面又衍生出來了 8088 。

在 8086 CPU 中,地址總線達到 20 根,所以最大尋址能力是 2^20 次冪也就是 1MB 的尋址能力,8088 也是如此。

在 8086 架構中,全部的內部寄存器、內部以及外部總線都是 16 位寬,能夠存儲兩個字節,由於是徹底的 16 位微處理器。8086 處理器有 14 個寄存器,每一個寄存器都有一個特有的名稱,即

「AX,BX,CX,DX,SP,BP,SI,DI,IP,FLAG,CS,DS,SS,ES」

這 14 個寄存器有可能進行具體的劃分,按照功能能夠分爲三種

  • 通用寄存器
  • 控制寄存器
  • 段寄存器

下面咱們分別介紹一下這幾種寄存器

通用寄存器

通用寄存器主要有四種 ,即 「AX、BX、CX、DX」 一樣的,這四個寄存器也是 16 位的,能存放兩個字節。AX、BX、CX、DX 這四個寄存器通常用來存放數據,也被稱爲 數據寄存器。它們的結構以下

8086 CPU 的上一代寄存器是 8080 ,它是一類 8 位的 CPU,爲了保證兼容性,8086 在 8080 上作了很小的修改,8086 中的通用寄存器 AX、BX、CX、DX 均可以獨立使用兩個 8 位寄存器來使用。

在細節方面,AX、BX、CX、DX 能夠再向下進行劃分

  • AX(Accumulator Register) :累加寄存器,它主要用於輸入/輸出和大規模的指令運算。
  • BX(Base Register):基址寄存器,用來存儲基礎訪問地址
  • CX(Count Register):計數寄存器,CX 寄存器在迭代的操做中會循環計數
  • DX(data Register):數據寄存器,它也用於輸入/輸出操做。它還與 AX 寄存器以及 DX 一塊兒使用,用於涉及大數值的乘法和除法運算。

這四種寄存器能夠分爲上半部分和下半部分,用做八個 8 位數據寄存器

  • 「AX 寄存器能夠分爲兩個獨立的 8 位的 AH 和 AL 寄存器;」
  • 「BX 寄存器能夠分爲兩個獨立的 8 位的 BH 和 BL 寄存器;」
  • 「CX 寄存器能夠分爲兩個獨立的 8 位的 CH 和 CL 寄存器;」
  • 「DX 寄存器能夠分爲兩個獨立的 8 位的 DH 和 DL 寄存器;」

除了上面 AX、BX、CX、DX 寄存器之外,其餘寄存器均不能夠分爲兩個獨立的 8 位寄存器

以下圖所示。

合起來就是

AX 的低位(0 - 7)位構成了 AL 寄存器,高 8 位(8 - 15)位構成了 AH 寄存器。AH 和 AL 寄存器是可使用的 8 位寄存器,其餘同理。

在認識了寄存器以後,咱們經過一個示例來看一下數據的具體存儲方式。

好比數據 19 ,它在 16 位存儲器中所存儲的表示以下

寄存器的存儲方式是先存儲低位,若是低位知足不了就存儲高位,若是低位可以知足,高位用 0 補全,在其餘低位能知足的狀況下,其他位也用 0 補全。

8086 CPU 能夠一次存儲兩種類型的數據

  • 字節(byte):一個字節由 8 bit 組成,這是一種恆定不變的存儲方式
  • 字(word):字是由指令集或處理器硬件做爲單元處理的固定大小的數據,對於 intel 來講,一個字長就是兩個字節,字是計算機一個很是重要的特徵,針對不一樣的指令集架構來講,計算機一次處理的數據也是不一樣的。也就是說,針對不一樣指令集的機器,一次能處理不用的字長,有字、雙字(32位)、四字(64位)等。

AX 寄存器

咱們上面探討過,AX 的另一個名字叫作累加寄存器或者簡稱爲累加器,其能夠分爲 2 個獨立的 8 位寄存器 AH 和 AL;在編寫彙編程序中,AX 寄存器能夠說是使用頻率最高的寄存器。

下面是幾段彙編代碼

mov ax,20   /* 將 20 送入寄存器 AX*/
mov ah,80 /* 將 80 送入寄存器 AH*/
add ax,10 /* 將寄存器 AX 中的數值加上 8 */

這裏注意下:上面代碼中出現的是 ax、ah ,而註釋中確是 AX、AH ,其實含義是同樣的,不區分大小寫。

AX 相比於其餘通用寄存器來講,有一點比較特殊,AX 具備一種特殊功能的使用,那就是使用 DIV 和 MUL 指令式使用。

DIV 是 8086 CPU 中的除法指令。

MUL 是 8086 CPU 中的乘法指令。

BX 寄存器

BX 被稱爲數據寄存器,即代表其可以暫存通常數據。一樣爲了適應之前的 8 位 CPU ,而能夠將 BX 當作兩個獨立的 8 位寄存器使用,即有 BH 和 BL。BX 除了具備暫存數據的功能外,還用於 尋址,即尋找物理內存地址。BX 寄存器中存放的數據通常是用來做爲偏移地址 使用的,由於偏移地址固然是在基址地址上的偏移了。偏移地址是在段寄存器中存儲的,關於段寄存器的介紹,咱們後面再說。

CX 寄存器

CX 也是數據寄存器,可以暫存通常性數據。一樣爲了適應之前的 8 位 CPU ,而能夠將 CX 當作兩個獨立的 8 位寄存器使用,即有 CH 和 CL。除此以外,CX 也是有其專門的用途的,CX 中的 C 被翻譯爲 Counting 也就是計數器的功能。當在彙編指令中使用循環 LOOP 指令時,能夠經過 CX 來指定須要循環的次數,每次執行循環 LOOP 時候,CPU 會作兩件事

  • 一件事是計數器自動減 1

  • 還有一件就是判斷 CX 中的值,若是 CX 中的值爲 0 則會跳出循環,而繼續執行循環下面的指令,

    固然若是 CX 中的值不爲 0 ,則會繼續執行循環中所指定的指令 。

DX 寄存器

DX 也是數據寄存器,可以暫存通常性數據。一樣爲了適應之前的 8 位 CPU ,DX 的用途其實在前面介紹 AX 寄存器時便已經有所介紹了,那就是支持 MUL 和 DIV 指令。同時也支持數值溢出等。

段寄存器

CPU 包含四個段寄存器,用做程序指令,數據或棧的基礎位置。實際上,對 IBM PC 上全部內存的引用都包含一個段寄存器做爲基本位置。

段寄存器主要包含

  • CS(Code Segment) :代碼寄存器,程序代碼的基礎位置
  • DS(Data Segment):數據寄存器,變量的基本位置
  • SS(Stack Segment):棧寄存器,棧的基礎位置
  • ES(Extra Segment):其餘寄存器,內存中變量的其餘基本位置。

索引寄存器

索引寄存器主要包含段地址的偏移量,索引寄存器主要分爲

  • BP(Base Pointer):基礎指針,它是棧寄存器上的偏移量,用來定位棧上變量
  • SP(Stack Pointer): 棧指針,它是棧寄存器上的偏移量,用來定位棧頂
  • SI(Source Index): 變址寄存器,用來拷貝源字符串
  • DI(Destination Index): 目標變址寄存器,用來複制到目標字符串

狀態和控制寄存器

就剩下兩種寄存器還沒聊了,這兩種寄存器是指令指針寄存器和標誌寄存器:

  • IP(Instruction Pointer):指令指針寄存器,它是從 Code Segment 代碼寄存器處的偏移來存儲執行的下一條指令
  • FLAG : Flag 寄存器用於存儲當前進程的狀態,這些狀態有
    • 位置 (Direction):用於數據塊的傳輸方向,是向上傳輸仍是向下傳輸
    • 中斷標誌位 (Interrupt) :1 - 容許;0 - 禁止
    • 陷入位 (Trap) :肯定每條指令執行完成後,CPU 是否應該中止。1 - 開啓,0 - 關閉
    • 進位 (Carry) : 設置最後一個無符號算術運算是否帶有進位
    • 溢出 (Overflow) : 設置最後一個有符號運算是否溢出
    • 符號 (Sign) : 若是最後一次算術運算爲負,則設置  1 =負,0 =正
    • 零位 (Zero) : 若是最後一次算術運算結果爲零,1 = 零
    • 輔助進位 (Aux Carry) :用於第三位到第四位的進位
    • 奇偶校驗 (Parity) : 用於奇偶校驗

物理地址

咱們你們都知道, CPU 訪問內存時,須要知道訪問內存的具體地址,內存單元是內存的基本單位,每個內存單元在內存中都有惟一的地址,這個地址便是 物理地址。而 CPU 和內存之間的交互有三條總線,即數據總線、控制總線和地址總線。

CPU 經過地址總線將物理地址送入存儲器,那麼 CPU 是如何造成的物理地址呢?這將是咱們接下來的討論重點。

如今,咱們先來討論一下和 8086 CPU 有關的結構問題。

cxuan 和你聊了這麼久,你應該知道 8086 CPU 是 16 位的 CPU 了,那麼,什麼是 16 位的 CPU 呢?

你可能大體聽過這個回答,16 位 CPU 指的是 CPU 一次能處理的數據是 16 位的,能回答這個問題表明你的底層還不錯,可是不夠全面,其實,16 位的 CPU 指的是

  • CPU 內部的運算器一次最多能處理 16 位的數據

運算器其實就是 ALU,運算控制單元,它是 CPU 內部的三大核心器件之一,主要負責數據的運算。

  • 寄存器的最大寬度爲 16 位

這個寄存器的最大寬度值就是通用寄存器能處理的二進制數的最大位數

  • 寄存器和運算器之間的通路爲 16 位

這個指的是寄存器和運算器之間的總線,一次能傳輸 16 位的數據

好了,如今你應該知道爲何叫作 16 位 CPU 了吧。

在你知道上面這個問題的答案以後,咱們下面就來聊一聊如何計算物理地址。

8086 CPU 有 20 位地址總線,每一條總線均可以傳輸一位的地址,因此 8086 CPU 能夠傳送 20 位地址,也就是說,8086 CPU 能夠達到 2^20 次冪的尋址能力,也就是 1MB。8086 CPU 又是 16 位的結構,從 8086 CPU 的結構看,它只能傳輸 16 位的地址,也就是 2^16 次冪也就是 64 KB,那麼它如何達到 1MB 的尋址能力呢?

原來,8086 CPU 的內部採用兩個 16 位地址合成的方式來傳輸一個 20 位的物理地址,以下圖所示

敘述一下上圖描述的過程

CPU 相關組件提供兩個地址:段地址和偏移地址,這兩個地址都是 16 位的,他們經由地址加法器變爲 20 位的物理地址,這個地址便是輸入輸出控制電路傳遞給內存的物理地址,由此完成物理地址的轉換。

地址加法器採用 「物理地址 = 段地址 * 16 + 偏移地址」 的方法用段地址和偏移地址合成物理地址。

下面是地址加法器的工做流程

其實段地址 * 16 ,就是左移 4 位。在上面的敘述中,物理地址 = 段地址 * 16 + 偏移地址,其實就是「基礎地址 + 偏移地址 = 物理地址」 尋址模式的一種具體實現方案。基礎地址其實就等於段地址 * 16。

你可能不太清楚 的概念,下面咱們就來探討一下。

什麼是段

段這個概念常常出如今操做系統中,好比在內存管理中,操做系統會把不一樣的數據分紅 來存儲,好比 「代碼段、數據段、bss 段、rodata 段」 等。

可是這些的劃分並非內存乾的,cxuan 告訴你是誰幹的,這實際上是幕後 Boss CPU 搞的,內存看成了聲討的對象。

其實,內存沒有進行分段,分段徹底是由 CPU 搞的,上面聊過的經過基礎地址 + 偏移地址 = 物理地址的方式給出內存單元的物理地址,使得咱們能夠分段管理 CPU。

如圖所示

這是兩個 16 KB 的程序分別被裝載進內存的示意圖,能夠看到,這兩個程序的段地址的大小都是 16380。

這裏須要注意一點, 8086 CPU 段地址的計算方式是段地址 * 16,因此,16 位的尋址能力是 2^16 次方,因此一個段的長度是 64 KB。

段寄存器

cxuan 在上面只是簡單爲你介紹了一下段寄存器的概念,介紹的有些淺,並且介紹段寄存器不介紹段也有「不知廬山真面目」的感受,如今爲你詳細的介紹一下,相信看完上面的段的概念以後,段寄存器也是手到擒來。

咱們在合成物理地址的那張圖提到了 相關部件 的概念,這個相關部件其實就是段寄存器,即 「CS、DS、SS、ES」 。8086 的 CPU 在訪問內存時,由這四個寄存器提供內存單元的段地址。

CS 寄存器

要聊 CS 寄存器,那麼 IP 寄存器是你繞不過去的曾經。CS 和 IP 都是 8086 CPU 很是重要的寄存器,它們指出了 CPU 當前須要讀取指令的地址。

CS 的全稱是 Code Segment,即代碼寄存器;而 IP 的全稱是 Instruction Pointer ,即指令指針。如今知道這兩個爲何一塊兒出現了吧!

在 8086 CPU 中,由 CS:IP 指向的內容看成指令執行。以下圖所示

說明一下上圖

在 CPU 內部,由 CS、IP 提供段地址,由加法器負責轉換爲物理地址,輸入輸出控制電路負責輸入/輸出數據,指令緩衝器負責緩衝指令,指令執行器負責執行指令。在內存中有一段連續存儲的區域,區域內部存儲的是機器碼、外面是地址和彙編指令。

上面這幅圖的段地址和偏移地址分別是 2000 和 0000,當這兩個地址進入地址加法器後,會由地址加法器負責將這兩個地址轉換爲物理地址

而後地址加法器負責將指令輸送到輸入輸出控制電路中

輸入輸出控制電路將 20 位的地址總線送到內存中。

而後取出對應的數據,也就是 「B八、2三、01」,圖中的 B八、BB 都是操做數。

控制輸入/輸出電路會將 B8 23 01 送入指令緩存器中。

此時這個指令就已經具有執行條件,此時 IP 也就是指令指針會自動增長。咱們上面說到 IP 其實就是從 Code Segment 也就是 CS 處偏移的地址,也就是偏移地址。它會知道下一個須要讀取指令的地址,以下圖所示

在這以後,指令執行執行取出的 B8 23 01 這條指令。

而後下面再把 2000 和 0003 送到地址加法器中再進行後續指令的讀取。後面的指令讀取過程和咱們上面探討的一模一樣,這裏 cxuan 就再也不贅述啦。

經過對上面的描述,咱們能總結一下 8086 CPU 的工做過程

  • 段寄存器提供段地址和偏移地址給地址加法器
  • 由地址加法器計算出物理地址經過輸入輸出控制電路將物理地址送到內存中
  • 提取物理地址對應的指令,經由控制電路取回並送到指令緩存器中
  • IP 繼續指向下一條指令的地址,同時指令執行器執行指令緩衝器中的指令

什麼是 Code Segment

Code Segment 即代碼段,它就是咱們上面聊到就是 CS 寄存器中存儲的基礎地址,也就是段地址,段地址其本質上就是一組內存單元的地址,例如上面的 「mov ax,0123H 、mov bx, 0003H」。咱們能夠將長度爲 N 的一組代碼,存放在一組連續地址、其實地址爲 16 的倍數的內存單元中,咱們能夠認爲,這段內存就是用來存放代碼的。

DS 寄存器

CPU 在讀寫一個內存單元的時候,須要知道這個內存單元的地址。在 8086 CPU 中,有一個 DS 寄存器,一般用來存放訪問數據的段地址。若是你想要讀取一個 10000H 的數據,你可能會須要下面這段代碼

mov bx,10000H
mov ds,bx
mov a1,[0]

上面這三條指令就把 10000H 讀取到了 a1 中。

在上面彙編代碼中,mov 指令有兩種傳送方式

  • 一種是把數據直接送入寄存器
  • 一種是將一個寄存器的內容送入另外一個寄存器

可是不只僅如此,mov 指令還具備下面這幾種表達方式

描述 舉例
mov 寄存器,數據 好比:mov ax,8
mov 寄存器,寄存器 好比:mov ax,bx
mov 寄存器,內存單元 好比:mov ax,[0]
mov 內存單元,寄存器 好比:mov[0], ax
mov 段寄存器,寄存器 好比:mov ds,ax

棧我相信大部分小夥伴已經很是熟悉了,是一種具備特殊的訪問方式的存儲空間。它的特殊性就在於,先進入棧的元素,最後纔出去,也就是咱們常說的 先入後出

它就像一個大的收納箱,你能夠往裏面放相同類型的東西,好比書,最早放進收納箱的書在最下面,最後放進收納箱的書在最上面,若是你想拿書的話, 必須從最上面開始取,不然是沒法取出最下面的書籍的。

棧的數據結構就是這樣,你把書籍壓入收納箱的操做叫作壓入(push),你把書籍從收納箱取出的操做叫作彈出(pop),它的模型圖大概是這樣

入棧至關因而增長操做,出棧至關因而刪除操做,只不過叫法不同。棧和內存不一樣,它不須要指定元素的地址。它的大概使用以下

// 壓入數據
Push(123);
Push(456);
Push(789);

// 彈出數據
j = Pop();
k = Pop();
l = Pop();

在棧中,LIFO 方式表示棧的數組中所保存的最後面的數據(Last In)會被最早讀取出來(First Out)。

棧和 SS 寄存器

下面咱們就經過一段彙編代碼來描述一下棧的壓入彈出的過程

8086 CPU 提供入棧和出棧指令,最基本的兩個是 PUSH(入棧)POP(出棧)。好比 push ax 會把 ax 寄存器中的數據壓入棧中,pop ax 表示從棧頂取出數據送入 ax 寄存器中。

這裏注意一點:8086 CPU 中的入棧和出棧都是以字爲單位進行的。

我這裏首先有一個初始的棧,沒有任何指令和數據。

而後咱們向棧中 push 數據後,棧中數據以下

涉及的指令有

mov ax,2345H
push ax

注意,數據會用兩個單元存放,高地址單元存放高 8 位地址,低地址單元存放低 8 位。

再向棧中 push 數據

其中涉及的指令有

mov bx,0132H
push bx

如今棧中有兩條數據,如今咱們執行出棧操做

其中涉及的指令有

pop ax
/* ax = 0132H */

再繼續取出數據

涉及的指令有

pop bx
/* bx = */

完整的 push 和 pop 過程以下

如今 cxuan 問你一個問題,咱們上面描述的是 10000H ~ 1000FH 這段空間來做爲 push 和 pop 指令的存取單元。可是,你怎麼知道這個棧單元就是 10000H ~ 1000FH 呢?也就是說,你如何選擇指定的棧單元進行存取?

事實上,8086 CPU 有一組關於棧的寄存器 SSSP。SS 是段寄存器,它存儲的是棧的基礎位置,也就是棧頂的位置,而 SP 是棧指針,它存儲的是偏移地址。在任意時刻,SS:SP 都指向棧頂元素。push 和 pop 指令執行時,CPU 從 SS 和 SP 中獲得棧頂的地址。

如今,咱們能夠完整的描述一下 push 和 pop 過程了,下面 cxuan 就給你推導一下這個過程。

上面這個過程主要涉及到的關鍵變化以下。

當使用 「PUSH」 指令向棧中壓入 1 個字節單元時,SP = SP - 1;即棧頂元素會發生變化;

而當使用 「PUSH」 指令向棧中壓入 2 個字節的字單元時,SP = SP – 2 ;即棧頂元素也要發生變化;

當使用 「POP」 指令從棧中彈出 1 個字節單元時, SP = SP + 1;即棧頂元素會發生變化;

當使用 「POP」 指令從棧中彈出 2 個字節單元的字單元時, SP = SP + 2 ;即棧頂元素會發生變化;

棧頂越界問題

如今咱們知道,8086 CPU 可使用 SS 和 SP 指示棧頂的地址,而且提供 PUSH 和 POP 指令實現入棧和出棧,因此,你如今知道了如何可以找到棧頂位置,可是你如何能保證棧頂的位置不會越界呢?棧頂越界會產生什麼影響呢?

好比以下是一個棧頂越界的示意圖

第一開始,SS:SP 寄存器指向了棧頂,而後向棧空間 push 必定數量的元素後,SS:SP 位於棧空間頂部,此時再向棧空間內部 push 元素,就會出現棧頂越界問題。

棧頂越界是危險的,由於咱們既然將一塊區域空間安排爲棧,那麼在棧空間外部也可能存放了其餘指令和數據,這些指令和數據有多是其餘程序的,因此如此操做會讓計算機懵逼

咱們但願 8086 CPU 能本身解決問題,畢竟 8086 CPU 已是個成熟的 CPU 了,要學會本身解決問題了。

然鵝(故意的),這對於 8086 CPU 來講,這多是它一生的 夙願 了,真實狀況是,8086 CPU 不會保證棧頂越界問題,也就是說 8086 CPU 只會告訴你棧頂在哪,並不會知道棧空間有多大,因此須要程序員本身手動去保證。。。


  



往期精選

對不起,學會這些 Linux 知識後,我有點飄

cxuan 又瞎 TM 寫了兩本 PDF。

程序員 cxuan 的時間管理???

哦!這該死的 C 語言!

我工做三年了,該懂併發了(乾貨)

另外,cxuan 肝了六本 PDF,公號回覆 cxuan ,領取做者所有 PDF 。

本文分享自微信公衆號 - 程序員cxuan(cxuangoodjob)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索