本節書摘來自華章計算機《c語言程式設計魔法書:基于c11标準》一書中的第2章,第2.1節,作者: 陳轶 更多章節内容可以通路雲栖社群“華章計算機”公衆号檢視。
我們在第1章已經大緻介紹了c語言的概念以及編譯、連接配接流程。我們知道c語言是進階語言中比較偏硬體底層的程式設計語言,是以對于用c語言的程式設計人員而言,了解一些關于處理器架構方面的知識是很有必要的,對于嵌入式系統開發的程式員而言更是如此了。
另外,c語言中有很多按位計算以及邏輯計算,是以對于初學者來說,如果對整數編碼方式等計算機基礎知識不熟悉,那麼對這些操作的了解也會變得十分困難。是以,本章将主要給c語言初學者、同時也是計算機程式設計初學者,提供計算機程式設計中會涉及的基本知識,這樣,在本書後面講解到一系列相關概念時,初學者也不會感到陌生。

一個簡單的計算機系統包含了中央處理器(cpu)以及存儲器和其他外部裝置。而在cpu内部則由計算單元、通用目的寄存器、程式序列器、資料位址生成器等部件構成。下面我們将從外到内分别簡單地介紹這些元件。
2.1.1 貯存器
貯存器(storage)盡管在圖2-1中沒有表示出來,但我們對它一定不會陌生,比如我們在pc上使用的硬碟(hard disk)就是一種貯存器。貯存器是一種存儲器,不過它可用于持久儲存資料而不丢失。是以我們通常把具有可持久儲存的存儲器統稱為貯存器。現在pc上用得比較現代化的貯存器就是ssd(solid-state disk)了,俗稱固态硬碟。當然,貯存器就其存儲媒體來說屬于rom(read-only memory),即隻讀存儲器。這類存儲器的特點是資料能持久保留,比如我們pc上的檔案,即便在關閉計算機之後也一直會儲存在你的硬碟上,而且pc上的軟體往往也是以可執行檔案的形式儲存在硬碟上的。但是它的讀寫速度非常緩慢,尤其是老式的sata磁盤,寫操作則更慢。因為通常對rom的資料修改都要通過先讀取某段資料所在的扇區,然後對該資料進行修改,再擦除所涉及的扇區,最後把修改好的資料所包含的扇區再寫回去。而對于rom來說,其扇區是有寫入次數限制的,是以寫入次數越多,損耗就越大。當我們發現一個硬碟通路很慢的時候,通常就是其扇區(或磁道)已經破損嚴重了,這是在不斷糾錯并交換良好的扇區所引發的延遲。在嵌入式系統中,我們用的rom一般是eprom、eeprom、flash rom等。這些硬體的詳細資料各位可以從網上輕易獲得,這裡不再贅述。
2.1.2 存儲器
存儲器(memory)一般是指我們通常所說的記憶體或主存(main memory)。其存儲媒體屬于ram(random access memory),即随機通路存儲器。它的特點是通路速度快,可對單個位元組進行讀寫,這與rom需要擦除整個扇區再對整個扇區寫入的方式有所不同,是以更高效、靈活。但是ram的資料無法持久化,掉電之後就會消失。此外,ram的成本也比rom高昂得多,我們對比一下16gb的記憶體條與256gb ssd的價格就能知道。然而正因為ram的通路速度快,并且離cpu更近,是以在許多系統中都是将程式代碼與資料先讀取到ram中之後再讓cpu去執行處理的。當然,在一些嵌入式系統中也有讓cpu直接執行rom中的代碼并通路讀rom中常量資料的情況,因為這類系統中總線頻率以及cpu頻率都相對較低,并且rom也是與cpu以soc(system-on-chip,系統級晶片)的方式整合在一塊晶片上的,是以通路成本要低很多。而有些環境對rom的讀取速度甚至比讀取ram還更快些。
注意: 在本書中所出現的“存儲器”均表示記憶體,即ram。而将可持久儲存資料的存儲器都一律稱為“貯存器”。了解了這些概念後,我們在國外網站購買mac或pc時,看到相關的術語就不會手足無措了。
2.1.3 寄存器
寄存器是在cpu核心中的、用于暫存資料的存儲單元。一般處理器内部對資料的算術邏輯計算往往都需要通過寄存器(register),而不是直接對外部存儲器進行操作。是以,如果我們要計算一個加法或乘法計算,需要先把相關資料從外部存儲器讀到處理器自己的通用目的寄存器中,然後對寄存器做計算操作,再将計算結果也放入寄存器,最後将結果寄存器中的資料再寫入外部存儲器。寄存器的通路速度非常快,它是這三種存儲媒體中速度最快的,但是數量也是最少的。像在傳統的32位x86處理器體系結構下,程式員一般能直接用的通用目的寄存器隻有eax、ebx、ecx、edx、esi、edi、ebp這7個。還有一個esp用于操作堆棧,往往無法用來處理通用計算。
2.1.4 計算單元
計算單元一般由算術邏輯單元(alu)、乘法器、移位器構成。當然,像一般進階點的處理器還包含除法器,以及用于做浮點數計算的浮點處理單元(fpu)。它們一般都直接對寄存器進行操作。而涉及資料讀寫的指令會由專門的加載、存儲處理單元進行操作。
2.1.5 程式執行流程
處理器在執行一段程式時,通常先從外部存儲器取得指令,然後對指令進行譯碼處理,轉換為相關的一系列操作。這些操作可能是對寄存器的算術邏輯運算,也可能是對存儲器的讀寫操作,然後執行相關計算。最後把計算結果寫回寄存器或寫回到存儲器。不過處理器在執行一系列指令的時候并不是每條指令都必須先經過上面所描述的整個過程才能執行下一條,而是采用流水線的方式執行,如圖2-2所示。
圖2-2展現了一個簡單的處理器執行完一條指令的完整過程。我們這裡假設從第一個取指令階段到最後的寫回階段,這5個階段均花費1個周期,倘若不是采用流水線的方式,而是每完成一條指令的執行再執行下一條指令,那麼每條指令的處理都需要5個周期。而一旦采用流水線方式處理,那麼我們可以看到,在第一條指令執行到譯碼階段時,處理器可以對第二條指令做取指令操作;當第一條指令執行到執行階段時,第二條指令執行到了譯碼階段,此時第三條指令開始做取指令階段,然後以此類推。這樣,當整條流水線填充滿之後,即執行到了第5條指令,那麼對于後續指令而言,處理每一條指令的時間均隻需要一個周期。
這裡需要注意的是,并不是每條指令都需要訪存操作,隻有當需要對外部存儲器做讀寫操作時才會動用訪存執行單元。然而大部分指令都需要寫回寄存器操作,即便像一條用于比較大小的指令,或一條系統中斷指令,它們也會影響狀态寄存器。當然,很多處理器會有空操作(nop)指令,它僅僅占用一個時鐘周期,而不會對除了指令指針寄存器以外的任何寄存器産生影響。