v74.01 鴻蒙內核源碼分析(編碼方式篇) | 機器指令是如何編碼的 | 百篇部落格分析OpenHarmony源碼

本篇關鍵詞:指令格式、條件域、類型域、操作域、數據指令、訪存指令、跳轉指令、SVC(軟體中斷)

內核彙編相關篇為:

本篇說清楚 ARM指令是如何被編碼的,機器指令由哪些部分構成,指令有哪些類型,每種類型的語法又是怎樣的 ?

程式碼案例 | C -> 彙編 -> 機器指令

看一段C語言編譯(clang)成的最後的機器指令(armv7)

int main(){
    int a = 0;
    if( a != 1) 
        a = 2*a + 1;
    return a;
}

 生成彙編程式碼如下:

    main:
60c: sub	sp, sp, #8
610: mov	r0, #0
614: str	r0, [sp, #4]
618: str	r0, [sp]
61c: ldr	r0, [sp]
620: cmp	r0, #1
624: beq	640 <main+0x34>
628: b	62c <main+0x20>
62c: ldr	r1, [sp]
630: mov	r0, #1
634: orr	r0, r0, r1, lsl #1
638: str	r0, [sp]
63c: b	640 <main+0x34>
640: ldr	r0, [sp]
644: add	sp, sp, #8
648: bx	lr

彙編程式碼對應的機器指令如下圖所示:

圖(1)

便於後續分析,將以上程式碼整理成如下表格

彙編程式碼 機器指令(十六進位表示) 機器指令(二進位表示)
sub sp, sp, #8 e24dd008 1110 0010 0100 1101 1101 0000 0000 1000
mov r0, #0 e3a00000 1110 0011 1010 0000 0000 0000 0000 0000
str r0, [sp, #4] e58d0004 1110 0101 1000 1101 0000 0000 0000 0100
str r0, [sp] e58d0000 1110 0101 1000 1101 0000 0000 0000 0000
ldr r0, [sp] e59d0000 1110 0101 1001 1101 0000 0000 0000 0000
cmp r0, #1 e3500001 1110 0011 0101 0000 0000 0000 0000 0001
beq 640 <main+0x34> 0a000005 0000 1010 0000 0000 0000 0000 0000 0101
b 62c <main+0x20> eaffffff 1110 1010 1111 1111 1111 1111 1111 1111
ldr r1, [sp] e59d1000 1110 0101 1001 1101 0001 0000 0000 0010
mov r0, #1 e3a00002 1110 0011 1010 0000 0000 0000 0000 0001
orr r0, r0, r1, lsl #1 e1800081 1110 0001 1000 0000 0000 0000 1000 0001
str r0, [sp] e58d0000 1110 0101 1000 1101 0000 0000 0000 0000
b 640 <main+0x34> eaffffff 1110 1010 1111 1111 1111 1111 1111 1111
ldr r0, [sp] e59d1000 1110 0101 1001 1101 0001 0000 0000 0000
add sp, sp, #8 e28dd008 1110 0010 1000 1101 1101 0000 0000 1000
bx lr e12fff1e 1110 0001 0010 1111 1111 1111 0001 1110

CPSR暫存器

在理解本篇之前需了解下CPSR暫存器的高4[31,28] 表達的含義。關於暫存器的詳細介紹可翻看 系列篇的 (暫存器篇)
圖(2)

N、Z、C、V均為條件碼標誌位。它們的內容可被算術或邏輯運算的結果所改變,並且可以決定某條指令是否被執行!意義重大!

  • CPSR的第31位是 N,符號標誌位。它記錄相關指令執行後,其結果是否為負。
    如果為負 N = 1,如果是非負數 N = 0
  • CPSR的第30位是Z0標誌位。它記錄相關指令執行後,其結果是否為0
    如果結果為0。那麼Z = 1。如果結果不為0,那麼Z = 0
  • CPSR的第29位C,進位標誌位(Carry)。一般情況下,進行無符號數的運算。
    加法運算:當運算結果產生了進位時(無符號數溢出),C=1,否則C=0
    減法運算(包括CMP):當運算時產生了借位時(無符號數溢出),C=0,否則C=1
  • CPSR的第28位是V,溢出標誌位(Overflow)。在進行有符號數運算的時候,
    如果超過了機器所能標識的範圍,稱為溢出。

指令格式

ARM 指令流是一連串的字對齊的四位元組指令流。每個 ARM 指令是一個單一的 32 位字(4位元組),如圖(3)
圖(3)

解讀
圖為ARM指令的編碼一級格式,所有的指令都必須符合一級格式,分成三部分:

  • 條件域: cond[31:28]表示,條件域會影響CPSR的條件碼N、Z、C、V標誌位。
  • 類型域: op1[27:25]op[4]arm將指令分成了六大類型 。
  • 操作域: 剩下的[24:5][4:0] 即圖中的空白位/保留位,這是留給下級自由發揮的,不同的類型對這些保留位有不同的定義。可以理解為因類型變化而變化的二級格式。
  • 那有了二級格式會不會有三級格式 ? 答案是必須有, 二級格式只會對保留位定義部分位,會留一部分給具體的指令格式自由發揮。
  • 一定要理解這種層次結構才能理解ARM指令集的設計總思路,因為RISC(精簡指令集) 的指令長度是固定的16/32/64位,以32位為例,所有的指令設計必須全用32位來表示,如果只有一層結構是難以滿足眾多的指令設計需求的,要靈活有包容就得給適當的空間發揮。

條件域

cond 為條件域,每一條可條件執行的條件指令都有4位的條件位域,2^4能表示16種條件

cond 助記符 含義(整型) 含義(浮點型) 條件標誌
0000 EQ 相等 相等 Z == 1
0001 NE 不等 不等或無序 Z == 0
0010 CS 進位 大於等於或無序 C == 1
0011 CC 進位清除 小於 C == 0
0100 MI 減、負數 小於 N == 1
0101 PL 加、正數或 0 大於等於或無序 N == 0
0110 VS 溢出 無序 V == 1
0111 VC 未溢出 有序 V == 0
1000 HI 無符號大於 大於或無序 C == 1 and Z == 0
1001 LS 無符號小於或等於 小於或等於 C == 0 or Z == 1
1010 GE 有符號大於或等於 大於或等於 N == V
1011 LT 有符號小於 小於或無序 N != V
1100 GT 有符號大於 大於 Z == 0 and N ==V
1101 LE 有符號大於或等於 小於等於或無序 Z == 1 or N != V
1110 無條件 無條件 任何
  • 大部分的指令都是 1110 = e,無條件執行指令,只要看到 e開頭的機器指令都屬於這類
  • beq 640 <main+0x34>	// 機器碼 0a000005 <=>	0000 1010 0000 0000 0000 0000 0000 0101
                                                    0000	EQ	Equal(相等)	Z == 1
    

類型域

圖(3)op1 域位於 bits[27:25],佔三位;op 域位於 bit[4],佔一位。它們的取值組合在一起,決定指令所屬的分類(Instruction Class),其值對應的關係如下

op1    op    指令類型
00x    -     數據處理以及雜項指令
010    -     load/store word類型 或者 unsigned byte
011    0     同上
011    1     媒體介面指令
10x    -     跳轉指令和塊數據操作指令,塊數據操作指令指 STMDA 這類,連續記憶體操作。
11x    -     協處理器指令和 svc 指令,包括高級的 SIMD 和浮點指令。

操作域

操作域是因類型變化而變化的二級格式 ,作用於保留位。包含

00x | 數據處理類指令

  • 上圖為涉及數據處理指令的對應編碼,由 op[佔5位]op2[佔2位]兩項來確定指令的唯一性
  • 一般情況下只需op指定唯一性,圖中 SUB指令對應為 0010x,而程式碼案例中的第一句
    sub	sp, sp, #8  // 機器碼 e24dd008 <=> 1110 001`0 0100` 1101 1101 0000 0000 1000
    

    對應[24:20]位就是0 0100,從而CPU在解碼階段將其解析為SUB指令執行

  • 需要用到op2的是 MOV系列指令,包括邏輯/算術左移右移,例如:
    mov r0, #0	//e3a00000 <=> 1110 0011 1010 0000 0000 0000 0000 0000
    

    中的op = 1 1010op2 = 00 對應 MOV(register,ARM) on page A8-489
    00x中的x表示數據處理分兩種情況

    • 000 無立即數參與(暫存器之間) ,圖A5.2.1 表示了這種情況 [27:25]= 000
    • 001 有立即參與的運算,例如 mov r0, #0 中的 [27:25]= 001,此處未展示圖,可前往 ARM體系結構參考手冊.pdf 翻看

010 | 載入存儲指令

  • Load/store是一組記憶體訪問指令,用來在ARM暫存器和記憶體之間進行數據傳送,ARM指令中有3種基本的數據傳送指令

    • 單暫存器 Load/Store 記憶體訪問指令(single register):這些指令為ARM暫存器和存儲器提供了更靈活的單數據項傳送方式。數據可以使位元組,16位半字或32位字
    • 多暫存器 Load/Store 記憶體訪問指令:可以實現大量數據的同時傳送,主要用於進程的進入和退出、保存和恢復工作暫存器以及複製暫存器中的一片(一塊)數據
    • 暫存器交換指令(single register swap): 實現暫存器數據和記憶體數據進行交換,而且是在一條指令中完成,執行過程中不會受到中斷干擾
  • 出現在程式碼案例中的

    str r0, [sp, #4] //  機器碼 e58d0004 <=>	1110 0101 1000 1101 0000 0000 0000 0100
    str r0, [sp]	 //  機器碼 e58d0000 <=>	1110 0101 1000 1101 0000 0000 0000 0000
                         將r0中的字數據寫入以SP為地址的存儲器中
    ldr r0, [sp]	 //  機器碼 e59d0000 <=>	1110 0101 1001 1101 0000 0000 0000 0000
                         存儲器地址地址為SP的數據讀入r0 暫存器
    

    [27:25] = 010說明都屬於這類指令,完成對記憶體的讀寫,包括 LDRLDRBLDRHSTRSTRBSTRH六條指令。
    ldr 為載入指令,但是載入到記憶體還是暫存器,這該怎麼記 ? 因為主角是CPU,載入有進來的意思,將內容載入至暫存器中。STR有出去的意思,將內容保存到記憶體里。
    [sp]相當於C語言的 *spsp 指向程式運行棧當前位置

  • 具體可看 >> ARM的六條訪存指令集—LDR、LDRB、LDRH、STR、STRB、STRH

010 | 多媒體指令

多媒體指令使用較少,但是它涉及指令卻很多

10x | 跳轉/分支/塊數據處理 指令

  • 出現在程式碼案例中的
    beq 640 <main+0x34>	// 機器碼 0a000005 <=> 0000 1010 0000 0000 0000 0000 0000 0101
    b 62c <main+0x20>	// 機器碼 eaffffff <=> 1110 1010 1111 1111 1111 1111 1111 1111
    

    [27:25] = 101說明都屬於這類指令

  • 聽得很多的poppush也屬於這類,成塊的數據操作,例如push常用於將函數的所有參數一次性入棧。
  • 記憶體 <> 暫存器 批量數據搬運指令 STMDA (STMED) LDMDA/LDMF

11x | 軟中斷/協處理器 指令

  • 其中最有名的就是svc 0,在系列篇中曾多次提及它,此處詳細說下 svcsvc全稱是 Supervisor CallSupervisorCPU的管理模式,svc導致處理器進入管理模式,很多人問的系統調用底層是怎麼實現的? svc就是答案。
  • 例如 printf是個標準庫函數,在標準庫的底層程式碼中會調用 svc 0,導致用戶態的 ARM 程式通常將系統調用號傳入 R7 暫存器(也被鴻蒙內核使用),然後用 SVC 指令調用 0 號中斷來直接執行系統調用,
  • 在以前的ARM架構版本中,SVC指令被稱為SWI,軟體中斷。
  • 描述svc功能的詳細偽程式碼如下,請嘗試讀懂它
      The TakeSVCException() pseudocode procedure describes how the processor takes the exception:
      // TakeSVCException()
      // ==================
      TakeSVCException()
      // Determine return information. SPSR is to be the current CPSR, after changing the IT[]
      // bits to give them the correct values for the following instruction, and LR is to be
      // the current PC minus 2 for Thumb or 4 for ARM, to change the PC offsets of 4 or 8
      // respectively from the address of the current instruction into the required address of
      // the next instruction, the SVC instruction having size 2bytes for Thumb or 4 bytes for ARM.
      ITAdvance();
      new_lr_value = if CPSR.T == '1' then PC-2 else PC-4;
      new_spsr_value = CPSR;
      vect_offset = 8;
      // Check whether to take exception to Hyp mode
      // if in Hyp mode then stay in Hyp mode
      take_to_hyp = (HaveVirtExt() && HaveSecurityExt() && SCR.NS == '1' && CPSR.M == '11010');
      // if HCR.TGE is set to 1, take to Hyp mode through Hyp Trap vector
      route_to_hyp = (HaveVirtExt() && HaveSecurityExt() && !IsSecure() && HCR.TGE == '1'
      && CPSR.M == '10000'); // User mode
      // if HCR.TGE == '1' and in a Non-secure PL1 mode, the effect is UNPREDICTABLE
      
      preferred_exceptn_return = new_lr_value;
      if take_to_hyp then
      EnterHypMode(new_spsr_value, preferred_exceptn_return, vect_offset);
      elsif route_to_hyp then
      EnterHypMode(new_spsr_value, preferred_exceptn_return, 20);
      else
      // Enter Supervisor ('10011') mode, and ensure Secure state if initially in Monitor
      // ('10110') mode. This affects the Banked versions of various registers accessed later
      // in the code.
      if CPSR.M == '10110' then SCR.NS = '0';
      CPSR.M = '10011';
      // Write return information to registers, and make further CPSR changes: IRQs disabled,
      // IT state reset, instruction set and endianness set to SCTLR-configured values.
      SPSR[] = new_spsr_value;
      R[14] = new_lr_value;
      CPSR.I = '1';
      CPSR.IT = '00000000';
      CPSR.J = '0'; CPSR.T = SCTLR.TE; // TE=0: ARM, TE=1: Thumb
      CPSR.E = SCTLR.EE; // EE=0: little-endian, EE=1: big-endian
      // Branch to SVC vector.
      BranchTo(ExcVectorBase() + vect_offset);
    
  • 這部分內容在系列篇 (暫存器篇)(系統調用篇)(標準庫篇) 中都有提及。

具體指令

細看幾條程式碼案例出現的常用指令

sub sp, sp, #8

sub	sp, sp, #8  // 機器碼 e24dd008 < = > 1110 0010 0100 1101 1101 0000 0000 1000

是減法操作指令,減法編碼格式為

圖中除了給出格式語法還有一段偽程式碼用於描述指令的使用條件

  • sp13號暫存器, lr14號暫存器 ,pc15號暫存器。

  • 如果是PC暫存器(Rn = 15)S等於0 查看 ADR指令。。

  • 如果是SP暫存器(Rn = 13)SUB(申請棧空間)。

  • 如果是PC暫存器(Rd = 15)S等於1 。查看 subs pc lr相關指令

  • 套用格式結合源碼

    cond op1 操作碼 S Rn Rd imm12(立即數)
    1110 001 0010 0 1101 1101 0000 0000 1000
    無條件執行 表示數據處理 SUB sp sp 8

mov r0, #0

mov r0, #0	//e3a00000	1110 0011 1010 0000 0000 0000 0000 0000

bx lr

bx lr	e12fff1e	1110 0001 0010 1111 1111 1111 0001 1110
  • Rm = 1110 對應 lr 暫存器 ,其相當於高級語言的 return,函數執行完了需切回到調用它的函數位置繼續執行,lr保存的就是那個位置,從哪裡來就回到哪裡去。

百文說內核 | 抓住主脈絡

  • 百文相當於摸出內核的肌肉和器官系統,讓人開始豐滿有立體感,因是直接從注釋源碼起步,在加註釋過程中,每每有心得處就整理,慢慢形成了以下文章。內容立足源碼,常以生活場景打比方儘可能多的將內核知識點置入某種場景,具有畫面感,容易理解記憶。說別人能聽得懂的話很重要! 百篇部落格絕不是百度教條式的在說一堆詰屈聱牙的概念,那沒什麼意思。更希望讓內核變得栩栩如生,倍感親切。
  • 與程式碼需不斷debug一樣,文章內容會存在不少錯漏之處,請多包涵,但會反覆修正,持續更新,v**.xx 代表文章序號和修改的次數,精雕細琢,言簡意賅,力求打造精品內容。
  • 百文在 < 鴻蒙研究站 | 開源中國 | 部落格園 | 51cto | csdn | 知乎 | 掘金 > 站點發布,鴻蒙研究站 | weharmonyos 中回復 百文 可方便閱讀。

按功能模組:

基礎知識 進程管理 任務管理 記憶體管理
雙向鏈表
內核概念
源碼結構
地址空間
計時單位
宏的使用
鉤子框架
點陣圖管理
POSIX
main函數
調度故事
進程式控制制塊
進程空間
線性區
紅黑樹
進程管理
Fork進程
進程回收
Shell編輯
Shell解析
任務控制塊
並發並行
就緒隊列
調度機制
任務管理
用棧方式
軟體定時器
控制台
遠程登錄
協議棧
記憶體規則
物理記憶體
虛擬記憶體
虛實映射
頁表管理
靜態分配
TLFS演算法
記憶體池管理
原子操作
圓整對齊
通訊機制 文件系統 硬體架構 內核彙編
通訊總覽
自旋鎖
互斥鎖
快鎖使用
快鎖實現
讀寫鎖
訊號量
事件機制
訊號生產
訊號消費
消息隊列
消息封裝
消息映射
共享記憶體
文件概念
文件故事
索引節點
VFS
文件句柄
根文件系統
掛載機制
管道文件
文件映射
寫時拷貝
晶片模式
ARM架構
指令集
協處理器
工作模式
暫存器
多核管理
中斷概念
中斷管理
編碼方式
彙編基礎
彙編傳參
可變參數
開機啟動
進程切換
任務切換
中斷切換
異常接管
缺頁中斷
編譯運行 調測工具
編譯過程
編譯構建
GN語法
忍者無敵
ELF格式
ELF解析
靜態鏈接
重定位
動態鏈接
進程映像
應用啟動
系統調用
VDSO
模組監控
日誌跟蹤
系統安全
測試用例

百萬注源碼 | 處處扣細節

  • 百萬漢字註解內核目的是要看清楚其毛細血管,細胞結構,等於在拿放大鏡看內核。內核並不神秘,帶著問題去源碼中找答案是很容易上癮的,你會發現很多文章對一些問題的解讀是錯誤的,或者說不深刻難以自圓其說,你會慢慢形成自己新的解讀,而新的解讀又會碰到新的問題,如此層層遞進,滾滾向前,拿著放大鏡根本不願意放手。

  • < gitee | github | coding | gitcode > 四大碼倉推送 | 同步官方源碼,鴻蒙研究站 | weharmonyos 中回復 百萬 可方便閱讀。

據說喜歡點贊分享的,後來都成了大神。😃