本篇关键词:指令格式、条件域、类型域、操作域、数据指令、访存指令、跳转指令、SVC(软件中断)

内核汇编相关篇为:
本篇说清楚 ARM指令是如何被编码的,机器指令由哪些部分构成,指令有哪些类型,每种类型的语法又是怎样的 ?
看一段C语言编译(clang)成的最后的机器指令(armv7)
int main(){ int a = 0; if( a != 1) a = 2*a + 1; return a;} 生成汇编代码如下: main:60c: sub sp, sp, #8610: mov r0, #0614: str r0, [sp, #4]618: str r0, [sp]61c: ldr r0, [sp]620: cmp r0, #1624: beq 640 <main+0x34>628: b 62c <main+0x20>62c: ldr r1, [sp]630: mov r0, #1634: orr r0, r0, r1, lsl #1638: str r0, [sp]63c: b 640 <main+0x34>640: ldr r0, [sp]644: add sp, sp, #8648: 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寄存器的高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 byte011 0 同上011 1 媒体接口指令10x - 跳转指令和块数据操作指令,块数据操作指令指 STMDA 这类,连续内存操作。11x - 协处理器指令和 svc 指令,包括高级的 SIMD 和浮点指令。操作域是因类型变化而变化的二级格式 ,作用于保留位。包含

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]= 000001 有立即参与的运算,例如 mov r0, #0 中的 [27:25]= 001,此处未展示图,可前往 ARM体系结构参考手册.pdf 翻看
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 0100str 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

多媒体指令使用较少,但是它涉及指令却很多

beq 640 <main+0x34> // 机器码 0a000005 <=> 0000 1010 0000 0000 0000 0000 0000 0101b 62c <main+0x20> // 机器码 eaffffff <=> 1110 1010 1111 1111 1111 1111 1111 1111[27:25] = 101说明都属于这类指令pop,push也属于这类,成块的数据操作,例如push常用于将函数的所有参数一次性入栈。STMDA (STMED) LDMDA/LDMF。
svc 0,在系列篇中曾多次提及它,此处详细说下 svc, svc全称是 Supervisor Call, Supervisor是CPU的管理模式,svc导致处理器进入管理模式,很多人问的系统调用底层是怎么实现的? svc就是答案。printf是个标准库函数,在标准库的底层代码中会调用 svc 0,导致用户态的 ARM 程序通常将系统调用号传入 R7 寄存器(也被鸿蒙内核使用),然后用 SVC 指令调用 0 号中断来直接执行系统调用,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 // 机器码 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 //e3a00000 1110 0011 1010 0000 0000 0000 0000 0000
bx lr e12fff1e 1110 0001 0010 1111 1111 1111 0001 1110Rm = 1110 对应 lr 寄存器 ,其相当于高级语言的 return,函数执行完了需切回到调用它的函数位置继续执行,lr保存的就是那个位置,从哪里来就回到哪里去。debug一样,文章内容会存在不少错漏之处,请多包涵,但会反复修正,持续更新,v**.xx 代表文章序号和修改的次数,精雕细琢,言简意赅,力求打造精品内容。
按功能模块:
| 基础知识 | 进程管理 | 任务管理 | 内存管理 |
|---|---|---|---|
| 双向链表 内核概念 源码结构 地址空间 计时单位 宏的使用 钩子框架 位图管理 POSIX main函数 | 调度故事 进程控制块 进程空间 线性区 红黑树 进程管理 Fork进程 进程回收 Shell编辑 Shell解析 | 任务控制块 并发并行 就绪队列 调度机制 任务管理 用栈方式 软件定时器 控制台 远程登录 协议栈 | 内存规则 物理内存 虚拟内存 虚实映射 页表管理 静态分配 TLFS算法 内存池管理 原子操作 圆整对齐 |
| 通讯机制 | 文件系统 | 硬件架构 | 内核汇编 |
| 通讯总览 自旋锁 互斥锁 快锁使用 快锁实现 读写锁 信号量 事件机制 信号生产 信号消费 消息队列 消息封装 消息映射 共享内存 | 文件概念 文件故事 索引节点 VFS 文件句柄 根文件系统 挂载机制 管道文件 文件映射 写时拷贝 | 芯片模式 ARM架构 指令集 协处理器 工作模式 寄存器 多核管理 中断概念 中断管理 | 编码方式 汇编基础 汇编传参 可变参数 开机启动 进程切换 任务切换 中断切换 异常接管 缺页中断 |
| 编译运行 | 调测工具 | ||
| 编译过程 编译构建 GN语法 忍者无敌 ELF格式 ELF解析 静态链接 重定位 动态链接 进程映像 应用启动 系统调用 VDSO | 模块监控 日志跟踪 系统安全 测试用例 |
百万汉字注解内核目的是要看清楚其毛细血管,细胞结构,等于在拿放大镜看内核。内核并不神秘,带着问题去源码中找答案是很容易上瘾的,你会发现很多文章对一些问题的解读是错误的,或者说不深刻难以自圆其说,你会慢慢形成自己新的解读,而新的解读又会碰到新的问题,如此层层递进,滚滚向前,拿着放大镜根本不愿意放手。
< gitee | github | coding | gitcode > 四大码仓推送 | 同步官方源码,鸿蒙研究站 | weharmonyos 中回复 百万 可方便阅读。
据说喜欢点赞分享的,后来都成了大神。??