前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >OpenHarmony内核源码分析(汇编基础篇) | CPU在哪里打卡上班

OpenHarmony内核源码分析(汇编基础篇) | CPU在哪里打卡上班

原创
作者头像
小帅聊鸿蒙
发布2025-03-11 17:36:32
发布2025-03-11 17:36:32
2900
代码可运行
举报
文章被收录于专栏:鸿蒙开发笔记鸿蒙开发笔记
运行总次数:0
代码可运行

本篇通过拆解一段很简单的汇编代码来快速认识汇编,为读懂鸿蒙汇编打基础.系列篇后续将逐个剖析鸿蒙的汇编文件.

汇编很简单

  • 第一: 要认定汇编语言一定是简单的,没有高深的东西,无非就是数据的搬来搬去,运行时数据主要待在两个地方:内存和寄存器。寄存器是CPU内部存储器,离运算器最近,所以最快.
  • 第二: 运行空间(栈空间)就是CPU打卡上班的地方,内核设计者规定谁请CPU上班由谁提供场地,用户程序提供的场地叫用户栈,敏感工作CPU要带回公司做,公司提供的场地叫内核栈,敏感工作叫系统调用,系统调用的本质理解是CPU要切换工作模式即切换办公场地。
  • 第三:CPU的工作顺序是流水线的,它只认指令,而且只去一个地方(指向代码段的PC寄存器)拿指令运算消化。指令集是告诉外界我CPU能干什么活并提供对话指令,汇编语言是人和CPU能愉快沟通不拧巴的共识语言。一一对应了CPU指令,又能确保记性不好的人类能模块化的设计idea, 先看一段C编译成汇编代码再来说模块化。

square(c -> 汇编)

代码语言:c
代码运行次数:0
复制
//编译器: armv7-a clang (trunk)
//++++++++++++ square(c -> 汇编)++++++++++++++++++++++++
int square(int a,int b){
    return a*b;
}
square(int, int):
        sub     sp, sp, #8     @sp减去8,意思为给square分配栈空间,只用2个栈空间完成计算
        str     r0, [sp, #4]   @第一个参数入栈
        str     r1, [sp]       @第二个参数入栈
        ldr     r1, [sp, #4]   @取出第一个参数给r1
        ldr     r2, [sp]       @取出第二个参数给r2
        mul     r0, r1, r2     @执行a*b给R0,返回值的工作一直是交给R0的
        add     sp, sp, #8     @函数执行完了,要释放申请的栈空间
        bx      lr             @子程序返回,等同于mov pc,lr,即跳到调用处

fp(c -> 汇编)

代码语言:c
代码运行次数:0
复制
//++++++++++++ fp(c -> 汇编)++++++++++++++++++++++++
int fp(int b)
{
    int a = 1;
    return square(a+b,a+b);
}
fp(int):
        push    {r11, lr}      @r11(fp)/lr入栈,保存调用者main的位置
        mov     r11, sp        @r11用于保存sp值,函数栈开始位置 
        sub     sp, sp, #8     @sp减去8,意思为给fp分配栈空间,只用2个栈空间完成计算
        str     r0, [sp, #4]   @先保存参数值,放在SP+4,此时r0中存放的是参数
        mov     r0, #1         @r0=1
        str     r0, [sp]       @再把1也保存在SP的位置
        ldr     r0, [sp]       @把SP的值给R0
        ldr     r1, [sp, #4]   @把SP+4的值给R1
        add     r1, r0, r1     @执行r1=a+b
        mov     r0, r1         @r0=r1,用r0,r1传参
        bl      square(int, int)@先mov lr, pc 再mov pc square(int, int)   
        mov     sp, r11        @函数执行完了,要释放申请的栈空间 
        pop     {r11, lr}      @弹出r11和lr,lr是专用标签,弹出就自动复制给lr寄存器
        bx      lr             @子程序返回,等同于mov pc,lr,即跳到调用处

main(c -> 汇编)

代码语言:c
代码运行次数:0
复制
//++++++++++++ main(c -> 汇编)++++++++++++++++++++++++
int main()
{
    int sum = 0;
    for(int a = 0;a < 100; a++){
        sum = sum + fp(a);
    }
    return sum;
}
main:
        push    {r11, lr}      @r11(fp)/lr入栈,保存调用者的位置
        mov     r11, sp        @r11用于保存sp值,函数栈开始位置
        sub     sp, sp, #16    @sp减去16,意思为给main分配栈空间,只用4个栈空间完成计算
        mov     r0, #0         @初始化r0
        str     r0, [r11, #-4] @执行sum = 0
        str     r0, [sp, #8]   @sum将始终占用SP+8的位置
        str     r0, [sp, #4]   @a将始终占用SP+4的位置
        b       .LBB1_1        @跳到循环开始位置
.LBB1_1:                       @循环开始位置入口
        ldr     r0, [sp, #4]   @取出a的值给r0
        cmp     r0, #99        @跟99比较
        bgt     .LBB1_4        @大于99,跳出循环 mov pc .LBB1_4
        b       .LBB1_2        @继续循环,直接 mov pc .LBB1_2
.LBB1_2:                       @符合循环条件入口
        ldr     r0, [sp, #8]   @取出sum的值给r0,sp+8用于写SUM的值
        str     r0, [sp]       @先保存SUM的值,SP的位置用于读SUM值
        ldr     r0, [sp, #4]   @r0用于传参,取出A的值给r0作为fp的参数
        bl      fp(int)        @先mov lr, pc再mov pc fp(int)
        mov     r1, r0         @fp的返回值为r0,保存到r1
        ldr     r0, [sp]       @取出SUM的值
        add     r0, r0, r1     @计算新sum的值,由R0保存
        str     r0, [sp, #8]   @将新sum保存到SP+8的位置
        b       .LBB1_3        @无条件跳转,直接 mov pc .LBB1_3
.LBB1_3:                       @完成a++操作入口
        ldr     r0, [sp, #4]   @SP+4中记录是a的值,赋给r0
        add     r0, r0, #1     @r0增加1
        str     r0, [sp, #4]   @把新的a值放回SP+4里去
        b       .LBB1_1        @跳转到比较 a < 100 处
.LBB1_4:                       @循环结束入口
        ldr     r0, [sp, #8]   @最后SUM的结果给R0,返回值的工作一直是交给R0的
        mov     sp, r11        @函数执行完了,要释放申请的栈空间
        pop     {r11, lr}      @弹出r11和lr,lr是专用标签,弹出就自动复制给lr寄存器
        bx      lr             @子程序返回,跳转到lr处等同于 MOV PC, LR

代码有点长,都加了注释,如果能直接看懂那么恭喜你,鸿蒙内核的6个汇编文件基于也就懂了。这是以下C文件全貌

文件全貌

代码语言:c
代码运行次数:0
复制
#include <stdio.h>
#include <math.h>

int square(int a,int b){
    return a*b;
}

int fp(int b)
{
    int a = 1;
    return square(a+b,a+b);
}

int main()
{
    int sum = 0;
    for(int a = 0;a < 100; a++){
        sum = sum + fp(a);
    }
    return sum;
}

代码很简单谁都能看懂,代码很典型,具有代表性,有循环,有判断,有运算,有多级函数调用。编译后的汇编代码基本和C语言的结构差不太多,区别是对循环的实现用了四个模块,四个模块也好理解:

一个是开始块(LBB1_1), 一个符合条件的处理块(LBB1_2),一个条件发生变化块(LBB1_3),最后收尾块(LBB1_4).

按块逐一剖析.

先看最短的那个

代码语言:c
代码运行次数:0
复制
int square(int a,int b){
    return a*b;
}
//编译成
square(int, int):
        sub     sp, sp, #8     @sp减去8,意思为给square分配栈空间,只用2个栈空间完成计算
        str     r0, [sp, #4]   @第一个参数入栈
        str     r1, [sp]       @第二个参数入栈
        ldr     r1, [sp, #4]   @取出第一个参数给r1
        ldr     r2, [sp]       @取出第二个参数给r2
        mul     r0, r1, r2     @执行a*b给R0,返回值的工作一直是交给R0的
        add     sp, sp, #8     @函数执行完了,要释放申请的栈空间
        bx      lr             @子程序返回,等同于mov pc,lr,即跳到调用处

首先上来一句 sub sp, sp, #8 等同于 sp = sp - 8 ,CPU运行需要场地,这个场地就是栈 ,SP是指向栈的指针,表示此时用栈的刻度. 代码和鸿蒙内核用栈方式一样,都采用了递减满栈的方式(FD).

什么是递减满栈? 递减指的是栈底地址高于栈顶地址,栈的生长方向是递减的, 满栈指的是SP指针永远指向栈顶. 每个函数都有自己独立的栈底和栈顶,之间的空间统称栈帧.可以理解为分配了一块

区域给函数运行,sub sp, sp, #8 代表申请2个栈空间,一个栈空间按四个字节算.

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.鸿蒙版性能优化指南
.......

用完要不要释放?当然要,add sp, sp, #8 就是释放栈空间. 是一对的,减了又加回去,空间就归还了.

ldr r1, sp, #4 的意思是取出SP+4这个虚拟地址的值给r1寄存器,而SP的指向并没有改变的,还是在栈顶, 为什么要+呢, +就是往回数, 定位到分配的栈空间上.

一定要理解递减满栈,这是关键! 否则读不懂内核汇编代码.

入参方式

一般都是通过寄存器(r0..r10)传参,fp调用square之前会先将参数给(r0..r10)

代码语言:c
代码运行次数:0
复制
        add     r1, r0, r1     @执行r1=a+b
        mov     r0, r1         @r0=r1,用r0,r1传参
        bl      square(int, int)@先mov lr, pc 再mov pc square(int, int) 

到了square中后,先让 r0,r1入栈,目的是保存参数值, 因为 square中要用r0,r1 ,

代码语言:c
代码运行次数:0
复制
        str     r0, [sp, #4]   @先入栈保存第一个参数
        str     r1, [sp]       @再入栈保存第二个参数
        ldr     r1, [sp, #4]   @再取出第一个参数给r1,(a*b)中a值
        ldr     r2, [sp]       @再取出第二个参数给r2,用于计算 (a*b)中b值

是不是感觉这段汇编很傻,直接不保存计算不就完了吗,这个是流程问题,编译器统一先保存参数,至于你想怎么用它不管,也管不了.

另外返回值都是默认统一给r0保存. square中将(a*b)的结果给了r0,回到fp中取出R0对fp来说这就是square的返回值,这是规定.

函数调用

main 和 fp 中都需要调用其他函数,所以都出现了

代码语言:c
代码运行次数:0
复制
        push    {r11, lr}
        //....
        pop     {r11, lr}

这哥俩也是成对出现的,这是函数调用的必备装备,作用是保存和恢复调用者的现场,例如 main -> fp, fp要保存main的栈帧范围和指令位置, lr保存的是main函数执行到哪个指令的位置, r11的作用是指向main的栈顶位置,如此fp执行完后return回main的时候,先mov pc,lr, PC寄存器的值一变, 表示执行的代码就变了,又回到了main的指令和栈帧继续未完成的事业.

内存和寄存器数据怎么搬?

数据主要待在两个地方:内存和寄存器. 寄存器<->寄存器 , 内存<->寄存器 , 内存<->内存 搬运指令都不一样.

代码语言:c
代码运行次数:0
复制
        str     r1, [sp]       @ 寄存器->内存
        ldr     r1, [sp, #4]   @ 内存->寄存器

这又是一对,用于 内存<->寄存器之间,熟知的 mov r0, r1 用于 寄存器<->寄存器

追问三个问题

第一:如果是可变参数怎么办? 100个参数怎么整, 通过寄存器总共就12个,不够传参啊

第二:返回值可以有多个吗?

第三:数据搬运可以不经过CPU吗?

写在最后

如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:

  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力;
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识;
  • 想要获取更多完整鸿蒙最新学习知识点,可关注B站:码牛课堂;

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 汇编很简单
    • square(c -> 汇编)
    • fp(c -> 汇编)
  • main(c -> 汇编)
    • 文件全貌
      • DD一下:欢迎大家关注公众号<程序猿百晓生>,可以了解到一下知识点。
    • 入参方式
  • 写在最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档