鸿蒙内核源码分析(用栈方式篇) | 程序运行场地谁提供的

2024-04-30 00:04

本文主要是介绍鸿蒙内核源码分析(用栈方式篇) | 程序运行场地谁提供的,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

精读内核源码就绕不过汇编语言,鸿蒙内核有6个汇编文件,读不懂它们就真的很难理解以下问题.

1.系统调用是如何实现的?

2.CPU是如何切换任务和进程上下文的?

3.硬件中断是如何处理的?

4.main函数到底是怎么来的?

5.开机最开始发生了什么?

6.关机最后的最后又发生了什么?

以下是一个很简单的C文件编译成汇编代码后的注解. 读懂这些注解会发现汇编很可爱,甚至还会上瘾,并没有想象中的那么恐怖,读懂它会颠覆你对汇编和栈的认知.

#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 = 1;for(int a = 0;a < 100; a++){sum = sum + fp(a);}return sum;
}

//编译器: armv7-a clang (trunk)
square(int, int):sub     sp, sp, #8     @sp减去8,意思为给square分配栈空间,只用2个栈空间完成计算str     r0, [sp, #4]   @第一个参数入栈str     r1, [sp]       @第二个参数入栈ldr     r1, [sp, #4]   @取出第一个参数给r1ldr     r2, [sp]       @取出第二个参数给r2mul     r0, r1, r2     @执行a*b给R0,返回值的工作一直是交给R0的add     sp, sp, #8     @函数执行完了,要释放申请的栈空间bx      lr             @子程序返回,等同于mov pc,lr,即跳到调用处
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=1str     r0, [sp]       @再把1也保存在SP的位置ldr     r0, [sp]       @把SP的值给R0ldr     r1, [sp, #4]   @把SP+4的值给R1add     r1, r0, r1     @执行r1=a+bmov     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:push    {r11, lr}      @r11(fp)/lr入栈,保存调用者的位置mov     r11, sp        @r11用于保存sp值,函数栈开始位置sub     sp, sp, #16    @sp减去8,意思为给main分配栈空间,只用2个栈空间完成计算mov     r0, #0         @初始化r0str     r0, [r11, #-4] @作用是保存SUM的初始值 str     r0, [sp, #8]   @sum将始终占用SP+8的位置str     r0, [sp, #4]   @a将始终占用SP+4的位置b       .LBB1_1        @跳到循环开始位置
.LBB1_1:                       @循环开始位置入口ldr     r0, [sp, #4]   @取出a的值给r0cmp     r0, #99        @跟99比较bgt     .LBB1_4        @大于99,跳出循环 mov pc .LBB1_4b       .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,保存到r1ldr     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的值,赋给r0add     r0, r0, #1     @r0增加1str     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

这个简单的汇编并不是鸿蒙的汇编,只是先打个底,由浅入深, 但看懂了它基本理解鸿蒙汇编代码没有问题, 后续将详细分析鸿蒙内核各个汇编文件的作用.
开始分析上面的汇编代码.

第一: 上面的代码和鸿蒙内核用栈方式一样,都采用了递减满栈的方式, 什么是递减满栈? 递减指的是栈底地址高于栈顶地址,满栈指的是SP指针永远在栈顶.一定要理解递减满栈,否则读不懂内核汇编代码.举例说明:

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

首句汇编的含义就是申请栈空间, sp = sp - 8 ,一个栈内单元(栈空间)占4个字节,申请2个栈空间搞定函数的计算,仔细看下代码除了在函数的末尾 sp = sp + 8 又恢复在之前的位置的中间过程,SP的值是没有任务变化,它的指向是不动的, 这跟很多人对栈的认知是不一样的,它只是被用于计算,例如
ldr r1, [sp, #4] 的意思是取出SP+4这个虚拟地址的值给r1寄存器,SP的值并没有改变的,为什么要+呢,因为SP是指向栈顶的,地址是最小的. 满栈就是用栈过程中对地址的操作不能超过SP,所以你很少在计算过程中看到 把sp-4地址中的值给某个寄存器, 除非是特别的指令,否则不可能有这样的指令.

第二: sub sp, sp, #8 和 add sp, sp, #8 是成对出现的,这就跟申请内存,释放内存的道理一样,这是内核对任务的运行栈管理方式,一样用多少申请多少,用完释放.空间大小就是栈帧,这是栈帧的本质含义.

第三: push {r11, lr} 和 pop {r11, lr} 也是成对出现的,主要是用于函数调用,例如 A -> B, B要保存A的栈帧范围和指令位置, lr保存是是A函数执行到哪个指令的位置, r11干了fp的工作,其实就是指向 A的栈顶位置,如此B执行完后return回A的时候,先mov pc,lr 内核就知道改执行A的哪条指令了,同时又知道了A的栈顶位置.

第四: 频繁出现的R0寄存器的作用用于传参和返回值, A调用B之前,假如有两个参数,就把参数给r0 ,r1记录,充当了A的变量, 到了B中后,先让 r0,r1入栈,目的是保存参数值, 因为 B中要用r0,r1 ,他们变成B的变量用了. 返回值都是默认统一给r0保存. B中将返回值给r0,回到A中取出R0值对A来说这就是B的返回值.

这是以上为汇编代码的分析,追问两个问题

第一:如果是可变参数怎么办? 100个参数怎么整, 通过寄存器总共就12个,不够传参啊
第二:返回值可以有多个吗?

也为了积极培养鸿蒙生态人才,让大家都能学习到鸿蒙开发最新的技术,针对一些在职人员、0基础小白、应届生/计算机专业、鸿蒙爱好者等人群,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线。大家可以进行参考学习:https://qr21.cn/FV7h05

①全方位,更合理的学习路径
路线图包括ArkTS基础语法、鸿蒙应用APP开发、鸿蒙能力集APP开发、次开发多端部署开发、物联网开发等九大模块,六大实战项目贯穿始终,由浅入深,层层递进,深入理解鸿蒙开发原理!

②多层次,更多的鸿蒙原生应用
路线图将包含完全基于鸿蒙内核开发的应用,比如一次开发多端部署、自由流转、元服务、端云一体化等,多方位的学习内容让学生能够高效掌握鸿蒙开发,少走弯路,真正理解并应用鸿蒙的核心技术和理念。

③实战化,更贴合企业需求的技术点
学习路线图中的每一个技术点都能够紧贴企业需求,经过多次真实实践,每一个知识点、每一个项目,都是码牛课堂鸿蒙研发团队精心打磨和深度解析的成果,注重对学生的细致教学,每一步都确保学生能够真正理解和掌握。

为了能让大家更好的学习鸿蒙(HarmonyOS NEXT)开发技术,这边特意整理了《鸿蒙开发学习手册》(共计890页),希望对大家有所帮助:https://qr21.cn/FV7h05

《鸿蒙开发学习手册》:https://qr21.cn/FV7h05

如何快速入门:

  1. 基本概念
  2. 构建第一个ArkTS应用
  3. ……

开发基础知识:https://qr21.cn/FV7h05

  1. 应用基础知识
  2. 配置文件
  3. 应用数据管理
  4. 应用安全管理
  5. 应用隐私保护
  6. 三方应用调用管控机制
  7. 资源分类与访问
  8. 学习ArkTS语言
  9. ……

基于ArkTS 开发:https://qr21.cn/FV7h05

  1. Ability开发
  2. UI开发
  3. 公共事件与通知
  4. 窗口管理
  5. 媒体
  6. 安全
  7. 网络与链接
  8. 电话服务
  9. 数据管理
  10. 后台任务(Background Task)管理
  11. 设备管理
  12. 设备使用信息统计
  13. DFX
  14. 国际化开发
  15. 折叠屏系列
  16. ……

鸿蒙开发面试真题(含参考答案):https://qr21.cn/FV7h05

大厂鸿蒙面试题::https://qr18.cn/F781PH

鸿蒙开发面试大盘集篇(共计319页):https://qr18.cn/F781PH

1.项目开发必备面试题
2.性能优化方向
3.架构方向
4.鸿蒙开发系统底层方向
5.鸿蒙音视频开发方向
6.鸿蒙车载开发方向
7.鸿蒙南向开发方向

这篇关于鸿蒙内核源码分析(用栈方式篇) | 程序运行场地谁提供的的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/947498

相关文章

Linux之systemV共享内存方式

《Linux之systemV共享内存方式》:本文主要介绍Linux之systemV共享内存方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、工作原理二、系统调用接口1、申请共享内存(一)key的获取(二)共享内存的申请2、将共享内存段连接到进程地址空间3、将

Maven中引入 springboot 相关依赖的方式(最新推荐)

《Maven中引入springboot相关依赖的方式(最新推荐)》:本文主要介绍Maven中引入springboot相关依赖的方式(最新推荐),本文给大家介绍的非常详细,对大家的学习或工作具有... 目录Maven中引入 springboot 相关依赖的方式1. 不使用版本管理(不推荐)2、使用版本管理(推

Python中的Walrus运算符分析示例详解

《Python中的Walrus运算符分析示例详解》Python中的Walrus运算符(:=)是Python3.8引入的一个新特性,允许在表达式中同时赋值和返回值,它的核心作用是减少重复计算,提升代码简... 目录1. 在循环中避免重复计算2. 在条件判断中同时赋值变量3. 在列表推导式或字典推导式中简化逻辑

C#使用StackExchange.Redis实现分布式锁的两种方式介绍

《C#使用StackExchange.Redis实现分布式锁的两种方式介绍》分布式锁在集群的架构中发挥着重要的作用,:本文主要介绍C#使用StackExchange.Redis实现分布式锁的... 目录自定义分布式锁获取锁释放锁自动续期StackExchange.Redis分布式锁获取锁释放锁自动续期分布式

快速修复一个Panic的Linux内核的技巧

《快速修复一个Panic的Linux内核的技巧》Linux系统中运行了不当的mkinitcpio操作导致内核文件不能正常工作,重启的时候,内核启动中止于Panic状态,该怎么解决这个问题呢?下面我们就... 感谢China编程(www.chinasem.cn)网友 鸢一雨音 的投稿写这篇文章是有原因的。为了配置完

Java对象转换的实现方式汇总

《Java对象转换的实现方式汇总》:本文主要介绍Java对象转换的多种实现方式,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录Java对象转换的多种实现方式1. 手动映射(Manual Mapping)2. Builder模式3. 工具类辅助映

Spring Boot读取配置文件的五种方式小结

《SpringBoot读取配置文件的五种方式小结》SpringBoot提供了灵活多样的方式来读取配置文件,这篇文章为大家介绍了5种常见的读取方式,文中的示例代码简洁易懂,大家可以根据自己的需要进... 目录1. 配置文件位置与加载顺序2. 读取配置文件的方式汇总方式一:使用 @Value 注解读取配置方式二

JAVA保证HashMap线程安全的几种方式

《JAVA保证HashMap线程安全的几种方式》HashMap是线程不安全的,这意味着如果多个线程并发地访问和修改同一个HashMap实例,可能会导致数据不一致和其他线程安全问题,本文主要介绍了JAV... 目录1. 使用 Collections.synchronizedMap2. 使用 Concurren

C# foreach 循环中获取索引的实现方式

《C#foreach循环中获取索引的实现方式》:本文主要介绍C#foreach循环中获取索引的实现方式,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录一、手动维护索引变量二、LINQ Select + 元组解构三、扩展方法封装索引四、使用 for 循环替代

将Java程序打包成EXE文件的实现方式

《将Java程序打包成EXE文件的实现方式》:本文主要介绍将Java程序打包成EXE文件的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录如何将Java程序编程打包成EXE文件1.准备Java程序2.生成JAR包3.选择并安装打包工具4.配置Launch4