【Linux 從頭學】是什么這兩年多以來,我的本職工作重心一直是在 x86 Linux 系統(tǒng)這一塊,從驅動到中間層,再到應用層的開發(fā)。
隨著內(nèi)容的不斷擴展,越發(fā)覺得之前很多基礎的東西都差不多忘記了
描述Linux 系統(tǒng)中幾個段描述符信息。
數(shù)據(jù)段和代碼段,仔細看一下相關書籍就知道這些描述符代表什么意思,但是:
為什么這幾個段的 Base 地址都是 0x00000000?
為什么 Limit 都是 0xfffff?
為什么它們的 Type 類型和優(yōu)先級 DPL 又各不相同?
如果沒有對 x86 平臺的一些基礎知識的理解,要啃完這本書真的是挺費力氣的!
更要命的是,隨著 Linux 內(nèi)核代碼的體積不斷膨脹,最新的 5.13 版本壓縮檔已經(jīng)是一百多兆了:
這么一個龐然大物,如何下手才能真正的學好 Linux 呢?!
即便是從 Linux 0.11 版本開始,其中的很多代碼看起來也是非常費勁的!
Linux 系統(tǒng)中的很多關于分段、內(nèi)存、寄存器相關的設計,都可以在這些書籍中找到基礎支撐。
于是乎,我就有了一個想法:是否可以把這些書籍中,與 Linux 系統(tǒng)相關的內(nèi)容進行一次重讀和整理,但絕不是簡單的知識搬運。
考慮了一下,大概有下面幾個想法:
先確定最終目標的目標:學習 Linux 操作系統(tǒng);
這幾本書寫的都是匯編語言,以及比較基礎的底層知識。我們會淡化匯編語言部分,把重點放在與 Linux 操作系統(tǒng)有關聯(lián)的原理部分;
不會嚴格按照書中的內(nèi)容、順序來輸出文章,而是把幾本書中內(nèi)容相關的部分放在一起學習、討論;
有些內(nèi)容,可以與 Linux 2.6 版本中的相關部分進行對比分析,這樣的話在以后學習 Linux 內(nèi)核部分時,可以找到底層的支撐;
最后,希望我自己能堅持這個系列,也算是給自己的一個梳理吧。
一句話:以基礎知識為主!
作為開篇第一章,本文將會描述執(zhí)行步驟:
現(xiàn)在就開始吧!
古老的 Intel8086 處理器8086 是 Intel 公司的第一款 16 位處理器,誕生于 1978 年,應該比各位小伙伴的年齡都大一些。
在 Intel 公司的所有處理器中,它占有很重要的地位,是整個 Intel 32 位架構處理器(IA-32)的開山鼻祖。
那么,問題來了,什么叫 16 位的處理器?
有些人會把處理器的位數(shù)與地址總線的位數(shù)搞混在一起!
我們知道,CPU 在訪問內(nèi)存的時候,是通過地址總線來傳送物理地址的。
8086 CPU 有 20 位的地址線,可以傳送 20 位地址。
每一根地址線都表示一個 bit,那么 20 個 bit 可以表示的最大值就是 2 的 20 次方。
也就是說:最大可以定位到 1M 地址的內(nèi)存,這稱作 CPU 的尋址能力。
但是,8086 處理器卻是 16 位的,因為:
運算器一次最多可以處理 16 位的數(shù)據(jù);
寄存器的最大寬度為 16 位;
寄存器和運算器之間的通路為 16 位;
也就是說:在 8086 處理器的內(nèi)部,能夠一次性處理、傳輸、暫時存儲的最大長度是 16 位,因此,我們說它是 16 位結構的 CPU。
主存儲器是什么?計算機的本質(zhì)就是對數(shù)據(jù)的存儲和處理,那么參與計算的數(shù)據(jù)是從哪里來的呢?那就是一個稱作 存儲器(Storage 或 Memory)的物理器件。
從廣義上來說,只要能存儲數(shù)據(jù)的器件都可以稱作存儲器,比如:硬盤、U盤等。
但是,在計算機內(nèi)部,有一種專門與 CPU 相連接,用來存儲正在執(zhí)行的程序和數(shù)據(jù)的存儲器,一般稱作內(nèi)存儲器或者主存儲器,簡稱:內(nèi)存或主存。
內(nèi)存按照字節(jié)來組織,單次訪問的最小單位是 1 個字節(jié),這是最基本的存儲單元。
每一個存儲單元,也就是一個字節(jié),都對應著一個地址。
CPU 就通過地址總線來確定:對內(nèi)存中的哪一個存儲單元中的數(shù)據(jù)進行訪問。
第 1 個字節(jié)的地址是 0000H,第 2 個字節(jié)的地址是 0001H,后面以此類推。
圖中的這個內(nèi)存,最大存儲單元的地址是 FFFFH,換算成十進制就是 65535,因此這個內(nèi)存的容量是 65536 字節(jié),也就是 64 KB。
這里有一個原子操作的問題可以考慮一下。
在 Linux 內(nèi)核代碼中,很多地方使用了原子操作,比如:互斥鎖的實現(xiàn)代碼。
為什么原子操作需要對變量的類型限制為 int 型呢?這就涉及到對內(nèi)存的讀寫操作了。
盡管內(nèi)存的最小組成單位是字節(jié),但是,經(jīng)過精心的設計和安排,不同位數(shù)的 CPU,能夠按照字節(jié)、字、雙字進行訪問。
換句話說,僅通過單次訪問,16 位處理器就能處理 16 位的二進制數(shù),32 位處理器就能處理 32 位的二進制數(shù)。
寄存器是什么?在 CPU 內(nèi)部,一些都是代表 0 或 1 的電信號,這些二進制數(shù)字的一組電信號出現(xiàn)在處理器內(nèi)部線路上,它們是一排高低電平的組合,代表著二進制數(shù)中的每一位。
在處理器內(nèi)部,必須用一個稱為寄存器的電路把這些數(shù)據(jù)鎖存起來。
因此,寄存器本質(zhì)上也屬于存儲器的一種。只不過它們位于處理器的內(nèi)部,CPU 訪問寄存器比訪問內(nèi)存的速度更快。
處理器總是很忙的,在它操作的過程中,所有數(shù)據(jù)在寄存器里面只能是臨時存在一小會,然后再被送往別處,這就是為什么它被叫做“寄存器”。
8086 中的寄存器都是 16 位的,可以存放 2 個字節(jié),或者說 1 個字。高字節(jié)在前(bit8 ~ bit15),低字節(jié)在后(bit0 ~ bit7)。
剛才說了,這些寄存器都是 16 位的。由于需要與以前更古老的處理器兼容,其中的 4 個寄存器:AX、BX、CX、DX 還可以當成 2 個 8 位的寄存器來使用。
比如:AX 代表一個 16 位的寄存器,AH、AL 分別代表一個 8 位的寄存器。
mov AX, 5D 表示把 005D 送入 AX 寄存器(16 位)
mov AL, 5D 表示把 5D 送入 AL 寄存器(8 位)
三個總線當我們啟動一個應用程序的時候,這個程序的代碼和數(shù)據(jù)都被加載到物理內(nèi)存中。
CPU 無論是讀取指令,還是操作數(shù)據(jù),都需要與內(nèi)存進行信息的交互:
確定存儲單元的地址(地址信息);
器件的選擇,讀或寫的命令(控制信息);
讀或寫的數(shù)據(jù)(數(shù)據(jù)信息);
在計算機中,有專門連接 CPU 和其他芯片的數(shù)據(jù),稱為總線。
從邏輯上來分類,包括下面 3 種總線:
地址總線:用來確定存儲單元的地址;
控制總線: CPU 對外部期間進行控制;
數(shù)據(jù)總線: CPU 與內(nèi)存或其他器件之間傳送數(shù)據(jù);
8086 有 20 根地址線,稱作地址總線的寬度,它可以尋址 2 的 20 次方個內(nèi)存單元。
同樣的道理,8086 數(shù)據(jù)總線的寬度是 16,也就是一次性可以傳送 16 bit 的數(shù)據(jù)。
控制總線決定了 CPU 可以對外進行多少種控制,決定了 CPU 對外部器件的控制能力。
CPU 如何對內(nèi)存進行尋址?在 Linux 2.6 內(nèi)核代碼中,編譯器產(chǎn)生的地址叫做虛擬地址(也稱作:邏輯地址),這個邏輯地址經(jīng)過段轉換之后,變成線性地址,線性地址再經(jīng)過分頁轉換,就得到最終物理內(nèi)存上的物理地址。
還記得文章開頭的那張段描述符的表格嗎?
其中的代碼段和數(shù)據(jù)段描述符的起始地址都是 0x00000000,也就是說: 在數(shù)值上虛擬地址和轉換后的線性地址是相等的(稍后就會明白為什么是這樣)。
我們再來看看一下 8086 中更簡單的地址轉換。
剛才說到,內(nèi)存是一個線性的存儲器件,CPU 依賴地址來定位每一個存儲單元。
對于 8086 CPU 來說,它有 20 根地址線,可以傳送 20 位地址,達到 1MB 的尋址能力。
但是 8086 又是 16 位的結構,在內(nèi)部一次性處理、傳輸、暫時存儲的地址只有 16 位。
從內(nèi)部結構來看,如果將地址從內(nèi)部簡單的發(fā)出到地址總線上,只能送出 16 位的地址,這樣的話,尋址能力只有 64KB。
那么應該怎么才能充分利用 20 根地址線呢?
8086 CPU 采用: 在內(nèi)部使用兩個 16 位地址合成的方法,來形成一個 20 位的物理地址。
第一個 16 位的地址稱為段地址,第二個 16 位的地址稱為偏移地址。
地址加法器采用下面的這個公式,來“合成”得到一個 20 位的物理地址:
物理地址 = 段地址 x 16 + 偏移地址
例如:我們編寫的程序,在加載到內(nèi)存中之后,放在一個內(nèi)存空間中。
CPU 在執(zhí)行這些指令的時候,把 CS 寄存器當做段寄存器,把 IP 寄存器當做偏移寄存器,然后計算 CS x 16 + IP 的值,就得到了指令的物理地址。
從以上的描述中可以看出:8086 CPU 似乎是因為寄存器無法直接輸出 20 位的物理地址,不得已才使用這樣的地址合成方式。
其實更本質(zhì)的原因是:8086 CPU 就是想通過 基地址 + 偏移量 的方式來對內(nèi)存進行尋址(這里的基地址,就是段地址左移 4 位)。
也就是說,即使 CPU 有能力直接輸出一個 20 位的地址,它仍然可能會采用 基地址 + 偏移量的方式來進行內(nèi)存尋址。
想一下:我們在 Linux 系統(tǒng)中編譯一個庫文件的時候,一般都會在編譯選項中添加 -fPIC 選項,表示編譯出來的動態(tài)庫是地址無關的,在被加載到內(nèi)存時需要被重定位。
而基地址+偏移量的尋址模式,就為重定位提供了底層支撐。
我們是如何控制 CPU 的?CPU 其實是一個很純粹、很呆板的一個東西,它唯一做的事情就是:到 CS:IP 這兩個寄存器指定的內(nèi)存單元中取出一條指令,然后執(zhí)行這條指令。
當然了,還需要預先定義一套指令集,在內(nèi)存中的指令區(qū)中,存儲的都必須是合法的指令,否則 CPU 就不認識了。
每一條指令都是用某些特定的數(shù)(指令碼)來指示 CPU 進行特定的操作。
CPU 認識這些指令,一看到這些指令碼,CPU 就知道這個指令碼后面還有幾個字節(jié)的操作數(shù)、需要進行什么樣的操作。
例如:指令碼 F4H 表示讓處理器停機,當 CPU 執(zhí)行這條指令的時候,就停止工作。
(其實這里說 CPU 已經(jīng)有點不準確了,因為 CPU 是囊括了很多器件的一個整體,也許這里說 CPU 中的執(zhí)行單元會更準確些。)
另外有一點可以提前說一下:內(nèi)存中的一切都是數(shù)據(jù),至于把其中的哪一部分數(shù)據(jù)當做指令來執(zhí)行,哪一部分數(shù)據(jù)當做被指令操作的“變量”,這完全是由操作系統(tǒng)的設計者來規(guī)劃的。
在 8086 處理器的層面來說,只要是 CS:IP “指向”的內(nèi)存區(qū)域,都被當做指令來執(zhí)行。
從以上描述可以看出:在 CPU 中,程序員能夠用指令讀寫的器件只有寄存器,我們可以通過改變寄存器中的內(nèi)容,來實現(xiàn)對 CPU 的控制。
更直白的說就是:我們可以通過改變 CS、IP 寄存器中的內(nèi)容,來控制 CPU 執(zhí)行目標指令。
作為一名合格的嵌入式開發(fā)者,大家估計都配置過一些單片機里的寄存器,以達到一些功能定義、端口復用的目的,其實這些操作,都可以看做是我們對 CPU 的控制。
如果把 CPU 比作木偶,那么 寄存器就是控制木偶的繩索。
我們再把 CPU 與 工控領域的 PLC 編程進行類比一下。
我們在拿到一個新的 PLC 設備之后,其中只有一個運行時(runtime),這個運行時執(zhí)行的本職工作就是:
掃描所有的輸入端口,鎖存在輸入映象區(qū);
執(zhí)行一個運算、控制邏輯,得到一些列輸出信號,鎖存到輸出映象區(qū);
把輸出映象區(qū)的信號,刷新到輸出端口;
在一個全新的 PLC 中,其中第 2 個步驟中需要的運算、控制邏輯可能就不存在。
因此,單單一個 runtime,PLC 是無法完成一件有意義的工作的。
為了讓 PLC 完成一個具體的控制目標,我們還需要利用 PLC 廠家提供的上位機編程軟件,開發(fā)一個運算、控制邏輯程序,編程語言一般都是梯形圖居多。
當這個程序被下載到 PLC 中之后,它就可以控制運行時來做一些有意義的工作了。
我們可以簡單的認為:梯形圖就是用來控制 PLC 的運行時。
對于 CPU 來說,想讓它執(zhí)行某個內(nèi)存單元的指令,只要修改寄存器 CS 和 IP 即可。
換句話說:只要對一個程序的內(nèi)存布局足夠的清楚,可以把 CPU 玩弄于股掌之間,讓它執(zhí)行哪里的代碼都可以。
CPU 執(zhí)行指令流程現(xiàn)在我們已經(jīng)明白了地址轉換、內(nèi)存的尋址,距離 CPU 執(zhí)行一條指令需要的最小單元還剩下:指令緩沖區(qū)和控制電路。
簡單來說:指令緩沖區(qū)用來緩存從內(nèi)存中讀取的指令,控制電路用來協(xié)調(diào)各種器件對總線等資源的使用。
對于下面這張圖來說,它一共有 4 條指令:
以第一條指令來舉例,它一共經(jīng)過 5 個步驟:
把 CS:IP 內(nèi)容送入地址加法器,計算得到 20 位的物理地址 20000H;
控制電路把 20 位的地址,送入到地址總線;
內(nèi)存中 20000H 單元處的指令 B8 23 01,經(jīng)過數(shù)據(jù)總線被送到指令緩沖區(qū);
指令偏移寄存器 IP 的值要加 3,指向下一條等待被執(zhí)行的偏移地址(因為指令碼 B8 代表當前指令的長度是 3 個字節(jié));
執(zhí)行指令緩沖區(qū)中的指令: 把數(shù)值 0123H 送入寄存器 AX 中;
以上就是一條指令的執(zhí)行最基本步驟,當然,現(xiàn)代處理器的指令執(zhí)行流程,比這里的要復雜的多得多。
編輯:jq
-
寄存器
+關注
關注
31文章
5584瀏覽量
128974 -
cpu
+關注
關注
68文章
11213瀏覽量
222737 -
代碼
+關注
關注
30文章
4940瀏覽量
73066 -
LINUX內(nèi)核
+關注
關注
1文章
318瀏覽量
23024
原文標題:Linux 從頭學 01:CPU 是如何執(zhí)行一條指令的?
文章出處:【微信號:zhuyandz,微信公眾號:FPGA之家】歡迎添加關注!文章轉載請注明出處。
發(fā)布評論請先 登錄
riscv實現(xiàn)自定義指令并用qemu運行
指令集測試的一種糾錯方法
探索CPU架構的奧秘,揭秘高性能計算的隱形引擎
功率半導體究竟是什么
FOC電機控制究竟該如何學?
CPU的各種指令和執(zhí)行流程
工程師在產(chǎn)品選型的時究竟是選CAN還是CANFD接口卡呢?
一條光纖怎么分兩條
室內(nèi)導航究竟是如何實現(xiàn)的
ADS1298R PACE_OUT1和PACE_OUT2這兩條引腿究竟是輸入還是輸出?有什么用?怎樣使用?
CPU(中央處理器)的概念、結構特點和在系統(tǒng)中的地位
LoRa數(shù)據(jù)究竟是如何傳輸?shù)模?/a>


解析CPU究竟是怎么執(zhí)行一條指令的?
評論