重學電腦組成原理(五)- "旋轉跳躍"的指令實現

来源:https://www.cnblogs.com/JavaEdge/archive/2019/08/14/11349512.html
-Advertisement-
Play Games

CPU執行的也不只是一條指令,一般一個程式包含很多條指令 因為有if…else、for這樣的條件和迴圈存在,這些指令也不會一路平直執行下去。 一個電腦程式是怎麼被分解成一條條指令來執行的呢 1 CPU如何執行指令 CPU里差不多幾百億個晶體管 實際上,一條條電腦指令執行起來非常複雜 好在CPU在 ...


CPU執行的也不只是一條指令,一般一個程式包含很多條指令

因為有if…else、for這樣的條件和迴圈存在,這些指令也不會一路平直執行下去。

一個電腦程式是怎麼被分解成一條條指令來執行的呢

1 CPU如何執行指令

CPU里差不多幾百億個晶體管

實際上,一條條電腦指令執行起來非常複雜

好在CPU在軟體層面已經為我們做好了封裝

對於程式員來說,我們只要知道,寫好的代碼變成了指令之後,是一條一條順序執行

不管幾百億的晶體管的背後是怎麼通過電路運轉起來的

邏輯上,我們可以認為,CPU其實就是由一堆寄存器組成的

而寄存器就是CPU內部,由多個觸發器(Flip-Flop)或者鎖存器(Latches)組成的簡單電路。

觸發器和鎖存器,其實就是兩種不同原理的數字電路組成的邏輯門
如果想要深入學習的話,可以學習數字電路的相關課程

N個觸發器或者鎖存器,就可以組成一個N位(Bit)的寄存器,能夠保存N位的數據

比方說,我們用的64位Intel伺服器,寄存器就是64位的

CPU里有很多種不同功能的

1.1 寄存器

寄存器(Register),是中央處理器內的其中組成部分。寄存器是有限存貯容量的高速存貯部件,它們可用來暫存指令、數據和地址。在中央處理器的控制部件中,包含的寄存器有指令寄存器(IR)和程式計數器。在中央處理器的算術及邏輯部件中,包含的寄存器有累加器。

在電腦體繫結構里,處理器中的寄存器是少量且速度快的電腦存儲器,藉由提供快速共同地訪問數值來加速電腦程式的運行:典型地說就是在已知時間點所作的之計算中間的數值。

寄存器是存儲器層次結構中的最頂端,也是系統操作數據的最快速途徑。寄存器通常都是以他們可以保存的比特數量來估量,舉例來說,一個8位寄存器或32位寄存器。寄存器現在都以寄存器數組的方式來實現,但是他們也可能使用單獨的觸發器、高速的核心存儲器、薄膜存儲器以及在數種機器上的其他方式來實現出來。

這個名詞通常都用來意指由一個指令之輸出或輸入可以直接索引到的寄存器組群。更適當的是稱他們為“架構寄存器”。例如,x86指令集定義八個32位寄存器的集合,但一個實現x86指令集的CPU可以包含比八個更多的寄存器。

1.1.1 PC寄存器(Program Counter Register)

亦稱指令地址寄存器(Instruction Address Register)

存放下一條需要執行的電腦指令的記憶體地址

1.1.2 指令寄存器(Instruction Register)

存放當前正在執行的指令

1.1.3 條件碼寄存器(Status Register)

用裡面的一個一個標記位(Flag),存放CPU進行算術或者邏輯計算的結果

CPU裡面還有更多用來存儲數據和記憶體地址的寄存器

這樣的寄存器通常一類裡面不止一個

通常根據存放的數據內容來給它們取名字,比如

  • 常量寄存器
    用來持有隻讀的數值(例如0、1、圓周率等等)。由於“其中的值不可更改”這一特殊性質,這些寄存器未必會有實體的硬體電路相對應,例如將從零常數寄存器讀的操作實現為接通目標寄存器的下拉電阻。
    一般而言,即使真正在硬體中放置常數寄存器也未必會是出於體繫結構理論上的考慮,而很可能是由硬體描述語言為了簡化操作而自動生成的電路
  • 整數寄存器
    用來存儲整數數字(參考以下的浮點寄存器)。在某些簡單(或舊)的CPU,特別的數據寄存器是累加器,作為數學計算之用。
  • 浮點數寄存器(FPRs)
    用來存儲浮點數字。
  • 向量寄存器
    用來存儲由向量處理器運行SIMD指令所得到的數據。
  • 地址寄存器
    持有存儲器地址,以及用來訪問存儲器。在某些簡單/舊的CPU里,特別的地址寄存器是索引寄存器(可能出現一個或多個)。

有些寄存器既可以存放數據,又能存放地址,我們就叫它通用寄存器(GPRs)。

程式執行的時候,CPU會

  1. 根據PC寄存器里的地址
  2. 從記憶體裡面把需要執行的指令讀取到指令寄存器裡面執行
  3. 然後根據指令長度自增
  4. 開始順序讀取下一條指令

可以看到,一個程式的一條條指令,在記憶體里是連續保存的,也會一條條順序載入

而有些特殊指令,比如上一講我們講到J類指令,也就是跳轉指令,會修改PC寄存器裡面的地址值

這樣,下一條要執行的指令就不是從記憶體裡面順序載入的了

事實上,這些跳轉指令的存在,也是我們可以在寫程式的時候,使用

  • if…else條件語句
  • while/for迴圈語句

的原因

2 從if/else看程式的執行和跳轉

我們現在就來看一個包含if…else的簡單程式。

  • test.c

用rand生成了一個隨機數r(0/1)

  • 當r是0,我們把之前定義的變數a設成1
  • 不然就設成2

我們把這個程式編譯成彙編代碼。你可以忽略前後無關的代碼,只關註於這裡的if…else條件判斷語句

  • 對應的彙編代碼是這樣的

對於r == 0的條件判斷,被編譯成了cmp和jne兩條指令。

  • cmp指令比較了前後兩個操作數的值
    DWORD PTR 代表操作的數據類型是32位的整數
    rbp-0x4則是一個寄存器的地址
    第一個操作數就是從寄存器里拿到的變數r的值
    第二個操作數0x0就是我們設定的常量0的16進位表示

cmp指令的比較結果,會存入到條件碼寄存器

狀態寄存器又名條件碼寄存器,它是電腦系統的核心部件——運算器的一部分
狀態寄存器用來存放兩類信息:
一類是體現當前指令執行結果的各種狀態信息(條件碼),如有無進位(CF位)、有無溢出(OF位)、結果正負(SF位)、結果是否為零(ZF位)、奇偶標誌位(P位)等
另一類是存放控制信息(PSW:程式狀態字寄存器),如允許中斷(IF位)、跟蹤標誌(TF位)等
有些機器中將PSW稱為標誌寄存器FR(Flag Register)。

如果比較結果 True,即 r == 0,就把零標誌條件碼(對應的條件碼是ZF,Zero Flag)設置為1

條件碼是CPU根據運算結果由硬體設置的位,體現當前指令執行結果的各種狀態信息
例如:算術運算產生的正、負、零或溢出等的結果。條件碼可被測試,作為分支運算的依據,此外,有些條件碼可被設置,例如對於最高位進位標誌C,可用指令對它置位和複位。

Intel的CPU下還有

  • 進位標誌(CF,Carry Flag)
    最近的操作使最高位產生了進位。可以用來檢查無符號操作數據的溢出。
  • 符號標誌(SF,Sign Flag)
    最近的操作得到的結果為負數。
  • 溢出標誌(OF,Overflow Flag)
    最近的操作導致一個補碼溢出--正溢出或負溢出

用在不同的判斷條件下。

cmp指令執行完成之後,PC寄存器會自增,開始執行下一條jne的指令

跟著的jne指令(jump if not equal),它會查看對應的零標誌位

如果為0,會跳轉到後面跟著的操作數4a的位置

4a,對應彙編代碼的行號,也就是else條件里的第一條指令

當跳轉發生,PC寄存器不再是自增變成下一條指令的地址,而被直接設置4a這個地址

這個時候,CPU再把4a地址里的指令載入到指令寄存器執行。

跳轉到執行地址為4a的指令,實際是一條mov指令

第一個操作數和前面的cmp指令一樣,是另一個32位整型的寄存器地址,以及對應的2的16進位值0x2

mov指令把2設置到對應的寄存器里去,相當於一個賦值操作

然後,PC寄存器里的值繼續自增,執行下一條mov指令。

這條mov指令的第一個操作數eax,代表累加寄存器

在中央處理器中,累加器 (accumulator) 是一種寄存器,用來儲存計算產生的中間結果。如果沒有像累加器這樣的寄存器,那麼在每次計算 (加法,乘法,移位等等) 後就必須要把結果寫回到 記憶體,也許馬上就得讀回來。然而存取主存的速度是比從算術邏輯單元到有直接路徑的累加器存取更慢。

第二個操作數0x0則是16進位的0的表示。這條指令其實沒有實際的作用,它的作用是一個占位符

if條件如果滿足,在賦值的mov指令執行完成之後,有一個jmp的無條件跳轉指令

跳轉的地址就是這一行的地址51

我們的main函數沒有設定返回值,而mov eax, 0x0 其實就是給main函數生成了一個預設的為0的返回值到累加器裡面

if條件裡面的內容執行完成之後也會跳轉到這裡,和else里的內容結束之後的位置是一樣的。

上一講我們講打孔卡的時候說到,讀取打孔卡的機器會順序地一段一段地讀取指令,然後執行。

執行完一條指令,它會自動地順序讀取下一條指令

如果執行的當前指令帶有跳轉的地址,比如往後跳10個指令,那麼機器會自動將卡片帶往後移動10個指令的位置,再來執行指令

同樣的,機器也能向前移動,去讀取之前已經執行過的指令

這也就是我們的while/for迴圈實現的原理。

如何通過if…else和goto來實現迴圈?

我們再看一段簡單的利用for迴圈的程式。我們迴圈自增變數i三次,三次之後,i>=3,就會跳出迴圈。整個程式,對應的Intel彙編代碼就是這樣的:

可以看到,對應的迴圈也是用1e這個地址上的cmp比較指令

和緊接著的jle條件跳轉指令來實現的

主要的差別在於,這裡的jle跳轉的地址,在這條指令之前的地址14,而非if…else編譯出來的跳轉指令之後

往前跳轉使得條件滿足的時候,PC寄存器會把指令地址設置到之前執行過的指令位置,重新執行之前執行過的指令,直到條件不滿足,順序往下執行jle之後的指令,整個迴圈才結束。

如果你看一長條打孔卡的話,就會看到卡片往後移動一段,執行了之後,又反向移動,去重新執行前面的指令。

jle和jmp指令,有點像程式語言裡面的goto命令,直接指定了一個特定條件下的跳轉位置

雖然我們在用高級語言開發程式的時候反對使用goto,但是實際在機器指令層面,無論是if…else…也好,還是for/while也好,都是用和goto相同的跳轉到特定指令位置的方式來實現的。

3 總結

學習了程式里的多條指令,究竟是怎麼樣一條一條被執行的

除了簡單地通過PC寄存器自增的方式順序執行外

條件碼寄存器會記錄下當前執行指令的條件判斷狀態

然後通過跳轉指令讀取對應的條件碼

修改PC寄存器內的下一條指令的地址

最終實現if…else以及for/while這樣的程式控制流程。

雖然我們可以用高級語言,可以用不同的語法,比如 if…else 這樣的條件分支,或者 while/for 這樣的迴圈方式,來實現不用的程式運行流程

但是回歸到電腦可以識別的機器指令級別,其實都只是一個簡單的地址跳轉而已,也就是一個類似於goto的語句。

想要在硬體層面實現這個goto語句,除了本身需要用來保存下一條指令地址,以及當前正要執行指令的PC寄存器、指令寄存器外

我們只需要再增加一個條件碼寄存器,來保留條件判斷的狀態。這樣簡簡單單的三個寄存器,就可以實現條件判斷和迴圈重覆執行代碼的功能。

4 推薦閱讀

  • 《深入理解電腦系統》的第3章
    詳細講解了C語言和Intel CPU的彙編語言以及指令的對應關係,以及Intel CPU的各種寄存器和指令集。

Intel指令集相對於之前的MIPS指令集要複雜一些

  • 所有的指令是變長的
    從1個位元組到15個位元組不等
  • 即使是彙編代碼,還有很多針對操作數據的長度不同有不同的尾碼

參考


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • #環境 :內核的版本必須大於3.10 #安裝docker #配置文件 #啟動 加入開機啟動 #創建啟動使用容器 ...
  • 什麼是 strace strace是Linux環境下的一款程式調試工具,用來監察一個應用程式所使用的系統調用。 Strace是一個簡單的跟蹤系統調用執行的工具。在其最簡單的形式中,它可以從開始到結束跟蹤二進位的執行,併在進程的生命周期中輸出一行具有系統調用名稱,每個系統調用的參數和返回值的文本行。 ...
  • 本人用的觸摸屏IC是FocalTech公司的ft5306,是一款i2c的電容屏多點觸控晶元。對於它的整體驅動官方已經給了,我們就觸摸屏和按鍵部分的代碼做相關說明。說明其中應該註意的地方。 對於所有的input設備,報告input事件時候都分這麼幾部分,首先在probe文件中設置設備發送的事件類型、按 ...
  • 有時候,當電腦有兩個網卡時;一個網卡 連接免費網路,一個網卡連接收費網路。這樣當你想使用免費網路與遠程伺服器建立連接,使用諸如scp命令或者 ssh 隧道之類傳輸大文件。這時候你需要指定特定的特定的網卡來建立連接了。 ssh 中 有一個選項可以綁定特定的interface 我們使用 man ssh ...
  • 1、Centos7下載 http://isoredirect.centos.org/centos/7/isos/x86_64/CentOS-7-x86_64-Minimal-1810.iso 2、推薦設置VM NAT模式 3、VM安裝Centos7,適用推薦安裝即可。 4、安裝完畢後首先進行一些常用 ...
  • 在有大量圖片的頁面中,為了避免頁面載入完圖片還未載入完成,我們通常會使用js的圖片預載入。 這是一個預載入的demo: 首先把圖片放入到一個類名為imgSrcArr的變數當中: var imgSrcArr = [ ‘./imgs/01.png’, ‘./imgs/02.png’, ‘./imgs/0 ...
  • 最近,一臺虛擬機是從外網下載的,然後導入本地測試環境使用。 發現一個奇怪的問題:修改了 /etc/sysconfig/network-scripts/ifcfg-eth0 保存後, 重啟網路服務( systemctl restart network)是有效的。但是重啟系統之後就失效了。 ifcfg- ...
  • iotop的簡介: iotop是一款開源、免費的用來監控磁碟I/O使用狀況的類似top命令的工具,iotop可以監控進程的I/O信息。它是Python語言編寫的,與iostat工具比較,iostat是系統級別的IO監控,而iotop是進程級別IO監控。目前最新的版本為iotop 0.6。其官方網址h... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...