admin管理员组文章数量:1033144
OpenHarmony 内核源码分析 (编码方式篇)
本篇说清楚 ARM
指令是如何被编码的,机器指令由哪些部分构成,指令有哪些类型,每种类型的语法又是怎样的 ?
代码案例 | C -> 汇编 -> 机器指令
看一段C语言编译(clang)成的最后的机器指令(armv7)
代码语言:c代码运行次数:0运行复制int main(){
int a = 0;
if( a != 1)
a = 2*a + 1;
return a;
}
生成汇编代码如下:
代码语言:c代码运行次数:0运行复制 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
汇编代码对应的机器指令如下图所示:
便于后续分析,将以上代码整理成如下表格
汇编代码 | 机器指令(十六进制表示) | 机器指令(二进制表示) |
---|---|---|
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]
表达的含义。关于寄存器的详细介绍可翻看 系列篇的 (寄存器篇)
N、Z、C、V
均为条件码标志位。它们的内容可被算术或逻辑运算的结果所改变,并且可以决定某条指令是否被执行!意义重大!
CPSR
的第31
位是N
,符号标志位。它记录相关指令执行后,其结果是否为负。 如果为负N = 1
,如果是非负数N = 0
。CPSR
的第30
位是Z
,0
标志位。它记录相关指令执行后,其结果是否为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):
解读
图为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 和浮点指令。
DD一下:欢迎大家关注公众号<程序猿百晓生>,可以了解到以下知识点。
代码语言:erlang复制`欢迎大家关注公众号<程序猿百晓生>,可以了解到以下知识点。`
1.OpenHarmony开发基础
2.OpenHarmony北向开发环境搭建
3.鸿蒙南向开发环境的搭建
4.鸿蒙生态应用开发白皮书V2.0 & V3.0
5.鸿蒙开发面试真题(含参考答案)
6.TypeScript入门学习手册
7.OpenHarmony 经典面试题(含参考答案)
8.OpenHarmony设备开发入门【最新版】
9.沉浸式剖析OpenHarmony源代码
10.系统定制指南
11.【OpenHarmony】Uboot 驱动加载流程
12.OpenHarmony构建系统--GN与子系统、部件、模块详解
13.ohos开机init启动流程
14.鸿蒙版性能优化指南
.......
操作域
操作域是因类型变化而变化的二级格式 ,作用于保留位。包含
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 1010
,op2 = 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
说明都属于这类指令,完成对内存的读写,包括 LDR
、LDRB
、LDRH
、STR
、STRB
、STRH
六条指令。
ldr
为加载指令,但是加载到内存还是寄存器,这该怎么记 ? 因为主角是CPU
,加载有进来的意思,将内容加载至寄存器中。STR
有出去的意思,将内容保存到内存里。
[sp]
相当于C
语言的 *sp
,sp
指向程序运行栈当前位置
- 具体可看 >> 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
说明都属于这类指令
- 听得很多的
pop
,push
也属于这类,成块的数据操作,例如push
常用于将函数的所有参数一次性入栈。 - 内存 <> 寄存器 批量数据搬运指令
STMDA (STMED)
LDMDA/LDMF
。
11x | 软中断/协处理器 指令
- 其中最有名的就是
svc 0
,在系列篇中曾多次提及它,此处详细说下svc
,svc
全称是Supervisor Call
,Supervisor
是CPU
的管理模式,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
代码语言:c代码运行次数:0运行复制sub sp, sp, #8 // 机器码 e24dd008 < = > 1110 0010 0100 1101 1101 0000 0000 1000
是减法操作指令,减法编码格式为
图中除了给出格式语法还有一段伪代码用于描述指令的使用条件
sp
为13
号寄存器,lr
为14
号寄存器 ,pc
为15
号寄存器。- 如果是
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
保存的就是那个位置,从哪里来就回到哪里去。
写在最后
如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
- 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力;
- 关注小编,同时可以期待后续文章ing
OpenHarmony 内核源码分析 (编码方式篇)
本篇说清楚
ARM
指令是如何被编码的,机器指令由哪些部分构成,指令有哪些类型,每种类型的语法又是怎样的 ?代码案例 | C -> 汇编 -> 机器指令
看一段C语言编译(clang)成的最后的机器指令(armv7)
代码语言:c代码运行次数:0运行复制int main(){ int a = 0; if( a != 1) a = 2*a + 1; return a; }
生成汇编代码如下:
代码语言:c代码运行次数:0运行复制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
汇编代码对应的机器指令如下图所示:
便于后续分析,将以上代码整理成如下表格
汇编代码
机器指令(十六进制表示)
机器指令(二进制表示)
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]
表达的含义。关于寄存器的详细介绍可翻看 系列篇的 (寄存器篇)N、Z、C、V
均为条件码标志位。它们的内容可被算术或逻辑运算的结果所改变,并且可以决定某条指令是否被执行!意义重大!CPSR
的第31
位是N
,符号标志位。它记录相关指令执行后,其结果是否为负。 如果为负N = 1
,如果是非负数N = 0
。CPSR
的第30
位是Z
,0
标志位。它记录相关指令执行后,其结果是否为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):解读
图为
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) 的
代码语言:c代码运行次数:0运行复制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 和浮点指令。
DD一下:
代码语言:erlang复制欢迎大家关注公众号<程序猿百晓生>,可以了解到以下知识点。
`欢迎大家关注公众号<程序猿百晓生>,可以了解到以下知识点。` 1.OpenHarmony开发基础 2.OpenHarmony北向开发环境搭建 3.鸿蒙南向开发环境的搭建 4.鸿蒙生态应用开发白皮书V2.0 & V3.0 5.鸿蒙开发面试真题(含参考答案) 6.TypeScript入门学习手册 7.OpenHarmony 经典面试题(含参考答案) 8.OpenHarmony设备开发入门【最新版】 9.沉浸式剖析OpenHarmony源代码 10.系统定制指南 11.【OpenHarmony】Uboot 驱动加载流程 12.OpenHarmony构建系统--GN与子系统、部件、模块详解 13.ohos开机init启动流程 14.鸿蒙版性能优化指南 .......
操作域
操作域是因类型变化而变化的二级格式 ,作用于保留位。包含
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 1010
,op2 = 00
对应 MOV(register,ARM) on page A8-48900x
中的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
说明都属于这类指令,完成对内存的读写,包括LDR
、LDRB
、LDRH
、STR
、STRB
、STRH
六条指令。ldr
为加载指令,但是加载到内存还是寄存器,这该怎么记 ? 因为主角是CPU
,加载有进来的意思,将内容加载至寄存器中。STR
有出去的意思,将内容保存到内存里。[sp]
相当于C
语言的*sp
,sp
指向程序运行栈当前位置- 具体可看 >> 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
说明都属于这类指令- 听得很多的
pop
,push
也属于这类,成块的数据操作,例如push
常用于将函数的所有参数一次性入栈。 - 内存 <> 寄存器 批量数据搬运指令
STMDA (STMED)
LDMDA/LDMF
。
11x | 软中断/协处理器 指令
- 其中最有名的就是
svc 0
,在系列篇中曾多次提及它,此处详细说下svc
,svc
全称是Supervisor Call
,Supervisor
是CPU
的管理模式,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
代码语言:c代码运行次数:0运行复制sub sp, sp, #8 // 机器码 e24dd008 < = > 1110 0010 0100 1101 1101 0000 0000 1000
是减法操作指令,减法编码格式为
图中除了给出格式语法还有一段伪代码用于描述指令的使用条件
sp
为13
号寄存器,lr
为14
号寄存器 ,pc
为15
号寄存器。- 如果是
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
代码语言:c代码运行次数:0运行复制 mov r0, #0 //e3a00000 1110 0011 1010 0000 0000 0000 0000 0000
bx lr
代码语言:c代码运行次数:0运行复制 bx lr e12fff1e 1110 0001 0010 1111 1111 1111 0001 1110
Rm = 1110
对应lr
寄存器 ,其相当于高级语言的return
,函数执行完了需切回到调用它的函数位置继续执行,lr
保存的就是那个位置,从哪里来就回到哪里去。
写在最后
如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
- 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力;
- 关注小编,同时可以期待后续文章ing
本文标签: OpenHarmony 内核源码分析 (编码方式篇)
版权声明:本文标题:OpenHarmony 内核源码分析 (编码方式篇) 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://it.en369.cn/jiaocheng/1748007784a2240695.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论