本篇部落格試圖對cpu執行的細節進行一次全景式的描述,以彌補寫version1.0時的缺憾。此文以《深入理解計算機系統》的第四章「處理器體系結構」為藍本,結合從《編碼》中得到的知識,力圖把執行一條指令時,每一步中cpu到底都幹了些什麼描述清楚。
由於這個執行過程與所使用的指令集密不可分,所以首先擺上《系統》中用到的isa:
這是一套不定長的指令集,指令之間的長度並不一致,直覺上這比定長指令要麻煩些(麻煩的是硬體的設計)。大體上,這些指令分為三部分:第一部分是指令指示符,佔乙個位元組;第二部分是暫存器指示符,也就是暫存器位址,也佔乙個位元組;第三部分是立即數,表示乙個位址或者運算元,四個位元組。其中第一部分是必不可少的,剩餘的兩部分根據指令型別不同「酌情刪減」。
可以看到,第乙個位元組和第二個位元組還可以繼續細分:乙個位元組有8位,它們被平均分成高四位與低四位,裡面存放著不同的**。
暫存器指示符位元組的兩個部分意義很簡單,它們分別代表兩個暫存器位址,一般來講(好吧是我瞎猜)ra代表源暫存器,而rb代表目的暫存器。如果乙個暫存器位址被設為0xf,則意味著不需要訪問暫存器。原來暫存器也是有位址的,表面上我們看到的是%eax,%ebx這個字元,但這只是為了方便人類的閱讀,計算機內部還是要把字元翻譯成數字的。請看下圖:
指令指示符位元組的兩個部分分別稱為code和function,code表示乙個大類,如果該類指令下有許多指令,則需要用function來標號不同的指令,沒有的話就設為0(注:此句說法有誤。應該是,此類指令只有一條的話,function值是0,若有其他指令,則在0的後面遞增)。可以看到,code的值是從0到0xb,代表12類不同的指令,而2、6、7號指令下面有再細分,function用fn代表,不再詳細論述。
那麼問題來了:如何識別以及利用這些**呢?比如0號指令代表halt指令,電腦怎麼知道?畢竟目前這只是停留在紙面上。我的意思是,必須有個翻譯電路,把這些代號翻譯成相應的控制訊號(以後會在我的文章經常看到「翻譯」這個詞,我覺得計算機中經常會用到這個「方法」,比如ip系統)。《系統》中有這麼一句話:每條指令的第乙個位元組有唯一的code和function組合,給定這個位元組我們就可以決定所有其他附加位元組的長度和含義。下面我們就來具體解釋一下這句話的含義:
由圖可以看到,每條指令被執行之前,其起始位址就已經被載入到pc中,新的時鐘週期一旦開始,在控制訊號的作用下,各個相關的」門「就會被開啟,於是記憶體中相應位址中的指令就會被瞬間傳輸到指令暫存器中(好吧,這裡其實有個問題我也沒搞清楚:就是如何確定指令的長度?因為這裡的說法似乎暗示指令的所有位元組是一次性取出的,但是那句話的暗示感覺相反:即先取出第乙個位元組才能確定總指令長度),然後指令暫存器中的指令被分拆:
第乙個位元組被送到split單元,並在那裡被拆分成code和function,然後分別送往icode和ifun兩個部件(目前我們只可以把它們視為黑盒子,不需要了解內部電路的實現),icode根據code值計算出三個訊號instr_valid、need_regids、need_valc,其含義是指令合法性、是否需要訪問暫存器、有無立即數。
剩餘的位元組被送往align單元,align會根據need_regids訊號和暫存器指示符位元組產生暫存器的位址訊號(這個訊號應該被送往暫存器的位址選擇器),具體來說,如果need_regids=0,那表示不需要訪問暫存器,那麼位址訊號就會被align設為0xf;如果need_regids=1,那表示要訪問暫存器,那麼產生的位址訊號其實直接等於從記憶體中讀取的**,align不會做什麼改變,於是這個時候如果只需要乙個暫存器的話,另乙個暫存器位址必須要攜程0xf,而且是由指令本身提供的,而不是由align設定。
另外align還要產生立即數,它需要兩個資訊,即need_regids、need_valc:如果need_valc=0,那就不需要了;如果need_valc=1,那還要看need_regids的值,以便確定第二個位元組是不是valc的起始位元組。
寫到這裡,我越來越覺得,應該不會一次性(即在乙個時鐘週期內)把指令的全部位元組讀入指令儲存器,而是先讀入第乙個,然後根據需要再讀入後續位元組。比如,有的指令本身只有乙個位元組,第二個位元組根本不存在,那還讀入幹嘛?甚至第二個位元組本身可能是下一條指令的首位元組,這樣的話,cpu處理下一條指令的時候會把其他位元組當成指令指示符,然後可以預見整個程式就會崩潰。
不過接著我讀到這句話,又徹底推翻了剛才的結論:pc增加器單元根據當前的pc以及need_regids、need_valc產生valp,即下一條指令的位址。
計算方式為:p+1+r+4i,其中p為當前指令的其起始位址,1代表指令指示符,r是need_regids的值,而i是need_valc的值。這個公式很簡單對不?但是我從中看到了剛才的邏輯裡面致命的乙個缺點,即只要有valp,不管現在這條指令有沒有讀入下一條指令的位元組,總能確定下一條指令的起始位址。還有,指令從記憶體到指令暫存器的過程是乙個複製的過程,而不是剪下。。。所以,cpu的邏輯就是:不管你這條指令有多長,我先一次性讀入六個位元組再說,後面的位元組用到了最好,用不到也不妨礙下一條指令的執行。(不得不說,這個發現是在我寫的過程中發現的,前幾天想了好久也沒想出來,看來「寫是為了更好的思考」這句話真的很對)
我想,寫到這裡基本上就可以結束了,最關鍵的那部分就只有這麼多了。剩下的部分無非就是alu、暫存器、記憶體等部件在由icode和ifun產生的控制訊號的控制下,開啟或關閉各種「門」,讓資料按照指定的管道流動,非常瑣碎。可能alu的實現稍微複雜一些,但今天就先寫這麼多吧。有時間再補充。
指令與CPU的執行探微 二)
這篇部落格主要將介紹cpu硬體層面上的一些細節,但不會涉及全部的元件,只描述我個人認為重要的幾個點而已,更具體詳盡的描述要看前面那篇文章中介紹的兩本書。1 位址與定址 這在之前是個非常折磨我的問題,我實在想象不出如何根據乙個數字去尋找記憶體中相應的單元,難不成每個單元上面都貼著標籤?直到我看到下面所...
CPU指令亂序執行問題
cpu為了提高指令執行效率,會在一條指令執行過程中 比如去記憶體讀資料 慢100倍 去同時執行另一條指令,前提是,兩條指令沒有依賴關係 寫操作也可以進行合併 硬體記憶體屏障 x86 sfence store 在sfence指令前的寫操作當必須在sfence指令後的寫操作前完成。lfence load...
8086的復位與啟動 CPU執行指令的步驟
東北大學 計算機硬體技術基礎 取指令將cs和ip的內容通過位址加法器得到指令的實體地址,經位址解碼器選址後將指定單元中的指令取入cpu的ir當中。解碼id對ir中的指令進行解碼,分析指令的操作碼 執行什麼操作 和運算元 具體數,存放位置 以及操作結果的存放位置,並由控制器向儲存器,運算器等有關部件發...