当前位置: 首页 > news >正文

74、【OS】【Nuttx】【启动】深入理解 caller-saved 和 callee-saved(下)

【声明】本博客所有内容均为个人业余时间创作,所述技术案例均来自公开开源项目(如Github,Apache基金会),不涉及任何企业机密或未公开技术,如有侵权请联系删除

背景

接上篇 blog
【OS】【Nuttx】【启动】深入理解 caller-saved 和 callee-saved(上)
分析了易失性寄存器(volatile registers)和非易失性寄存器(non-volatile registers)两个重要概念,现在回过头来再看 caller-saved 和 callee-saved

caller-saved 和 callee-saved

首先看 caller-saved ,调用者保存

caller-saved(调用者保存)

综合之前 blog 分析的,调用者保存的,应该是易失性的寄存器(范围 r0-r3),这些寄存器用于临时数据或参数传递,比如下列流程:

  • 函数 A 调用函数 B
  • 如果 A 想在调用 B 后还能继续使用某个寄存器的值,它有两种选择,一种选择是将该内容放到非易失性寄存器里(比如 r4-r8,r10-r11),另一种选择是在调用前把该寄存器压栈保存,把数据放到 ram 上
    在这里插入图片描述
  • B 可以修改这些寄存器,而不用负责恢复它们的值

比如下面这段汇编代码演示的

; 函数 A 要调用函数 B 了,想要存下 r0 的值,那就先压到栈上
push {r0}        ; 入栈保存
bl function_b	 ; 调用函数 B
pop {r0}         ; 出栈恢复

callee-saved(被调用者保存)

同样按照之前分析的,被调用者保存的,应该是非易失性的寄存器(范围 r4-r8,r10-r11),这些寄存器可以用来长期存储局部变量等,比如下面流程

  • 函数 B 被调用时,如果要用到某个非易失性寄存器(比如 r4),那它必须先把该寄存器压栈保存
  • 返回函数 A 之前再恢复这个寄存器的值,如下图
    在这里插入图片描述
  • 这样做可以不让调用者(函数 A)担心非易失性寄存器被破坏

比如下面汇编代码演示的

function_b:push {r4} ; 保存 r4...		  ; 对 r4 做一些事情pop {r4}  ; 恢复 r4

AAPCS

这种规则是 arm 官方定义的调用约定,也就是一直提到的 AAPCS(ARM Architecture Procedure Call Standard),这样做有这么些好处:

  • 提高效率:caller-saved 寄存器可以让函数调用更轻量,只有真正需要保留时才压栈(callee-saved 寄存器都用满了,然后还不够,还有数据要保存),而 callee-saved 寄存器可以避免频繁压栈(被调用函数要用到的时候才需要压栈),适合长期使用的变量
  • 统一接口:所有编译器都遵守同样的规则,不同模块之间可以安全交互
  • 便于优化:编译器可以根据这些规则进行寄存器分配、内联优化等

提高效率便于优化好理解,这里再着重强调下统一接口这个好处

统一接口

这也是 AAPCS 的核心价值之一,只要两个模块(A 和 B)都遵循 AAPCS 调用约定,并且目标平台一致(如都是 Cortex-M4),即使它们是用不同的编译器(比如 gcc、clang、armcc、iar 等)编译的,也可以安全地链接在一起形成一个可执行文件, 这就是调用约定存在的意义,可以看作是编程世界的交通规则通信协议

想象一下没有 AAPCS 的情况:

  • 编译器 1 在编译模块 A 时,把第一个参数放在 r0;
  • 编译器 2 在编译模块 B 时,把第一个参数放在栈偏移 +8
  • 最后链接成一个可执行文件后,模块 A 和模块 B 之间的函数调用就会出错

所以为了确保不同编译器之间兼容,C 和汇编混合编程可行,动态库,静态库,裸机程序,RTOS 任务之间互操作,就需要有一个统一的标准来定义

  • 参数怎么传:通过 r0~r3,再加栈
  • 返回值放哪:r0(≤ 4字节时)
  • 哪些寄存器必须保存: r4~r11
  • 栈增长方向:向下增长

等等,这就是 AAPCS 存在的意义

再举个例子,假设有两个模块 A 和 B,其中

  • 模块 A:用 gcc 编写并编译;
  • 模块 B:用 iar 编写并编译;
  • 两者都针对 Cortex-M4 平台,且都遵守 AAPCS;

那么就可以把它们分别编译成 .o,或者 .so 文件,然后用链接器将它们链接为一个完整的 elf 可执行文件,最后在 stm32f4 上正常运行, 因为它们都遵守相同的调用规则,函数之间可以互相调用,不会出错

调用约定就分析到这,下篇继续

http://www.lryc.cn/news/587714.html

相关文章:

  • 游戏框架笔记
  • 网络准入控制系统的作用解析,2025年保障企业入网安全第一道防线
  • 在 Azure Linux 上安装 RustFS
  • 使用 pytest 测试框架构建自动化测试套件之一
  • LightGBM 在处理**不均衡二分类任务**时,能在 **AUC 和 Accuracy** 两个指标上表现良好
  • SQL性能调优经验总结
  • 【Linux】基本指令详解(一) 树状文件结构、家目录、绝对/相对路径、linux文件类型
  • 1.2.1 面向对象详解——AI教你学Django
  • 【世纪龙科技】迈腾B8汽车整车检测与诊断仿真实训系统
  • 波兰无人机具身导航基准测试与最新进展!FlySearch:探索视觉语言模型的探索能力
  • 用Spring Boot逻辑删除(isDelete)优雅守护你的数据资产:告别物理删除的烦恼
  • 第十二批深度合成算法备案情况
  • [源力觉醒 创作者计划]_文心大模型4.5开源部署指南:从技术架构到实战落地
  • C++动态数组vector
  • JavaScript数据交互:现代Web应用的核心引擎
  • Redis技术笔记-主从复制、哨兵与持久化实战指南
  • 【MySQL】剖析InnoDB存储引擎
  • FBRT-YOLO: Faster and Better for Real-Time Aerial Image Detection论文精读(逐段解析)
  • Spring原理揭秘--初识AOP
  • openEuler系统串口文件手法压力测试及脚本使用说明
  • 11.设置 Python 3 和 pip 3 为默认版本
  • 从零构建搜索引擎 build demo search engine from scratch
  • 如何单独安装设置包域名
  • PostgreSQL ExecInitIndexScan 函数解析
  • Cesium源码打包
  • MyBatis 在执行 SQL 时找不到名为 name 的参数
  • 项目进度压缩影响质量,如何平衡进度与质量
  • 多模态数据处理新趋势:阿里云ODPS技术栈深度解析与未来展望
  • 【Echarts】 电影票房汇总实时数据横向柱状图比图
  • 【PostgreSQL异常解决】`PostgreSQL`异常之类型转换错误