v74.01 鸿蒙内核源码分析(编码方式篇) | 机器指令是如何编码的 | 百篇博客分析OpenHarmony源码
本篇关键词:指令格式、条件域、类型域、操作域、数据指令、访存指令、跳转指令、SVC(软件中断)
内核汇编相关篇为:
- v74.01 鸿蒙内核源码分析(编码方式) | 机器指令是如何编码的
- v75.03 鸿蒙内核源码分析(汇编基础) | CPU上班也要打卡
- v76.04 鸿蒙内核源码分析(汇编传参) | 如何传递复杂的参数
- v77.01 鸿蒙内核源码分析(可变参数) | 正在制作中 ...
- v78.01 鸿蒙内核源码分析(开机启动) | 正在制作中 ...
- v79.01 鸿蒙内核源码分析(进程切换) | 正在制作中 ...
- v80.03 鸿蒙内核源码分析(任务切换) | 看汇编如何切换任务
- v81.05 鸿蒙内核源码分析(中断切换) | 系统因中断活力四射
- v82.06 鸿蒙内核源码分析(异常接管) | 社会很单纯 复杂的是人
- v83.01 鸿蒙内核源码分析(缺页中断) | 正在制作中 ...
本篇说清楚 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
汇编代码对应的机器指令如下图所示:
便于后续分析,将以上代码整理成如下表格
汇编代码 | 机器指令(十六进制表示) | 机器指令(二进制表示) |
---|---|---|
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 和浮点指令。
操作域
操作域是因类型变化而变化的二级格式 ,作用于保留位。包含
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
指向程序运行栈当前位置
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
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
保存的就是那个位置,从哪里来就回到哪里去。
百文说内核 | 抓住主脉络
- 百文相当于摸出内核的肌肉和器官系统,让人开始丰满有立体感,因是直接从注释源码起步,在加注释过程中,每每有心得处就整理,慢慢形成了以下文章。内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感,容易理解记忆。说别人能听得懂的话很重要! 百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思。更希望让内核变得栩栩如生,倍感亲切。
- 与代码需不断
debug
一样,文章内容会存在不少错漏之处,请多包涵,但会反复修正,持续更新,v**.xx
代表文章序号和修改的次数,精雕细琢,言简意赅,力求打造精品内容。 - 百文在 < 鸿蒙研究站 | 开源中国 | 博客园 | 51cto | csdn | 知乎 | 掘金 > 站点发布,鸿蒙研究站 | weharmonyos 中回复 百文 可方便阅读。
按功能模块:
百万注源码 | 处处扣细节
百万汉字注解内核目的是要看清楚其毛细血管,细胞结构,等于在拿放大镜看内核。内核并不神秘,带着问题去源码中找答案是很容易上瘾的,你会发现很多文章对一些问题的解读是错误的,或者说不深刻难以自圆其说,你会慢慢形成自己新的解读,而新的解读又会碰到新的问题,如此层层递进,滚滚向前,拿着放大镜根本不愿意放手。
< gitee | github | coding | gitcode > 四大码仓推送 | 同步官方源码,鸿蒙研究站 | weharmonyos 中回复 百万 可方便阅读。
据说喜欢点赞分享的,后来都成了大神。
v74.01 鸿蒙内核源码分析(编码方式篇) | 机器指令是如何编码的 | 百篇博客分析OpenHarmony源码的更多相关文章
- v87.01 鸿蒙内核源码分析 (内核启动篇) | 从汇编到 main () | 百篇博客分析 OpenHarmony 源码
本篇关键词:内核重定位.MMU.SVC栈.热启动.内核映射表 内核汇编相关篇为: v74.01 鸿蒙内核源码分析(编码方式) | 机器指令是如何编码的 v75.03 鸿蒙内核源码分析(汇编基础) | ...
- v75.01 鸿蒙内核源码分析(远程登录篇) | 内核如何接待远方的客人 | 百篇博客分析OpenHarmony源码
子曰:"不学礼,无以立 ; 不学诗,无以言 " <论语>:季氏篇 百篇博客分析.本篇为: (远程登录篇) | 内核如何接待远方的客人 设备驱动相关篇为: v67.03 ...
- v82.01 鸿蒙内核源码分析 (协处理器篇) | CPU 的好帮手 | 百篇博客分析 OpenHarmony 源码
本篇关键词:CP15 .MCR.MRC.ASID.MMU 硬件架构相关篇为: v65.01 鸿蒙内核源码分析(芯片模式) | 回顾芯片行业各位大佬 v66.03 鸿蒙内核源码分析(ARM架构) | A ...
- v86.01 鸿蒙内核源码分析 (静态分配篇) | 很简单的一位小朋友 | 百篇博客分析 OpenHarmony 源码
本篇关键词:池头.池体.节头.节块 内存管理相关篇为: v31.02 鸿蒙内核源码分析(内存规则) | 内存管理到底在管什么 v32.04 鸿蒙内核源码分析(物理内存) | 真实的可不一定精彩 v33 ...
- 鸿蒙内核源码分析(文件系统篇) | 用图书管理说文件系统 | 百篇博客分析OpenHarmony源码 | v63.01
百篇博客系列篇.本篇为: v63.xx 鸿蒙内核源码分析(文件系统篇) | 用图书管理说文件系统 | 51.c.h.o 文件系统相关篇为: v62.xx 鸿蒙内核源码分析(文件概念篇) | 为什么说一 ...
- 鸿蒙内核源码分析(文件概念篇) | 为什么说一切皆是文件 | 百篇博客分析OpenHarmony源码 | v62.01
百篇博客系列篇.本篇为: v62.xx 鸿蒙内核源码分析(文件概念篇) | 为什么说一切皆是文件 | 51.c.h.o 本篇开始说文件系统,它是内核五大模块之一,甚至有Linux的设计哲学是" ...
- 鸿蒙内核源码分析(编译过程篇) | 简单案例窥视GCC编译全过程 | 百篇博客分析OpenHarmony源码| v57.01
百篇博客系列篇.本篇为: v57.xx 鸿蒙内核源码分析(编译过程篇) | 简单案例窥视编译全过程 | 51.c.h.o 编译构建相关篇为: v50.xx 鸿蒙内核源码分析(编译环境篇) | 编译鸿蒙 ...
- 鸿蒙内核源码分析(进程镜像篇)|ELF是如何被加载运行的? | 百篇博客分析OpenHarmony源码 | v56.01
百篇博客系列篇.本篇为: v56.xx 鸿蒙内核源码分析(进程映像篇) | ELF是如何被加载运行的? | 51.c.h.o 加载运行相关篇为: v51.xx 鸿蒙内核源码分析(ELF格式篇) | 应 ...
- 鸿蒙内核源码分析(重定位篇) | 与国际接轨的对外部发言人 | 百篇博客分析OpenHarmony源码 | v55.01
百篇博客系列篇.本篇为: v55.xx 鸿蒙内核源码分析(重定位篇) | 与国际接轨的对外部发言人 | 51.c.h.o 加载运行相关篇为: v51.xx 鸿蒙内核源码分析(ELF格式篇) | 应用程 ...
随机推荐
- 序列化和反序列化&持久化
java序列化与反序列化全讲解 之前一知半解的,对于序列化的概念,为啥用,哪里用也不清楚,现在深入了解协议,先把序列化这个这个概念和和使用场景搞清楚
- 请说说你对Struts2的拦截器的理解?
Struts2拦截器是在访问某个Action或Action的某个方法,字段之前或之后实施拦截,并且Struts2拦截器是可插拔的,拦截器是AOP的一种实现. 拦截器栈(Interceptor Stac ...
- MySQL 获取每月多少日的sql写法
# 写法1 指定 年月 的共有多少日 select DATEDIFF(DATE_ADD(CONCAT( 2020, '-', '03','-','01'),INTERVAL 1 MONTH),CONC ...
- SpringDataJdbc命名查询
当使用SpringDataJdbc命名查询时,需要配置几处:官方文档:https://docs.spring.io/spring-data/jdbc/docs/2.0.4.RELEASE/refere ...
- 为什么线程通信的方法 wait(), notify()和 notifyAll()被定 义在 Object 类里?
Java 的每个对象中都有一个锁(monitor,也可以成为监视器) 并且 wait(),notify() 等方法用于等待对象的锁或者通知其他线程对象的监视器可用.在 Java 的线程中 并没有可供任 ...
- Spring 由哪些模块组成?
以下是 Spring 框架的基本模块:第 393 页 共 485 页 Core module Bean module Context module Expression Language module ...
- 学习Apache(六)
Apache 是一款使用量排名第一的 web 服务器,LAMP 中的 A 指的就是它.由于其开源.稳定.安全等特性而被广泛使用.下边记录了使用 Apache 以来经常用到的功能,做此梳理,作为日常运维 ...
- 模拟web服务器http请求应答
我们在浏览器打开网页,其实是向远端服务器提出页面发送请求,远端服务器在接到请求后,就开始执行请求页面的程序文件,然后将执行结果通过html格式,发送到你的浏览器,再显示出来.以下用百度(www.bai ...
- 使用滑模控制对sin(t)曲线追踪
结合:[Matlab]简单的滑模控制程序及Simulink仿真本片文章观看,此篇文章是在这篇文章的基础上进行修改的 输出u的推导过程 如果不明白控制量输出u的推到过成请看:[控制理论]滑模控制最强解析 ...
- vue H5 超简单的swiper制作抖音上拉切换视频播放
-----html部分------ <swiper vertical :style="{height: windowheight+'px',width:375+'px'}" ...