保存 Caller saved 寄存器 的动作,是硬件行为,还是软件行为?

2023-06-23 19:22:36 +08:00
 amiwrong123

我们知道 arm 体系或 x86 体系中(我比较关心 arm ),在进行函数调用时,会区分 Caller saved 和 Callee saved (就是把通用寄存器划分为两个范围)。

软件行为我是指:我在汇编代码中能够看到保存的过程(就是入栈和出栈)。比如在被调用的函数( Callee )里如果改变了 Callee saved 寄存器,那么在 这个函数里的开头结尾,就会分别出一个 入栈保存和出栈恢复 的操作(如果没有改变 Callee saved 寄存器,那么就不会多这两个操作)。

硬件行为我是指:是 CPU 自动做的,不是我在汇编代码里面能看到的指令。

1489 次点击
所在节点    程序员
16 条回复
thinkm
2023-06-23 19:32:14 +08:00
函数调用是硬件做的,例如像是 RTOS 之类的操作系统入栈出栈是软件做的。
多年没搞了,记忆中是这样
leonshaw
2023-06-23 19:34:49 +08:00
软件行为,caller 自己才知道哪些需要保存
bugu1986
2023-06-23 19:46:19 +08:00
os 做的
bugu1986
2023-06-23 19:48:02 +08:00
trap 把执行权从硬件给软件
feather12315
2023-06-23 19:52:25 +08:00
一部分硬件,一部分软件。

准确地说:
通用寄存是是软件行为,特定的寄存器是硬件行为。
比如函数调用是软件行为,中断异常的寄存器保存( amd64 下的 rip 、返回地址)是硬件行为
feather12315
2023-06-23 19:53:47 +08:00
arm 的话,返回地址是 r13 还是 r12 。
这个要查手册页了,programming manual 有详细的介绍( arm 的手册很多,要仔细找找,有个 guide 简述了上述过程
amiwrong123
2023-06-23 20:11:39 +08:00
@thinkm
@leonshaw
@bugu1986
@feather12315
之所以我能确信,保存 Callee saved 寄存器的行为,是一个软件行为。是因为我在编译后查看汇编文件,发现大多数函数的实现,在开头部分有 PUSH {r4-r11, lr},在结束部分有 POP {r4-r11, PC}。——即我发现了汇编中的 保存 Callee saved 寄存器的行为。

但我没有找到汇编中,保存 Caller saved 寄存器的行为。虽然我也认为,应该是一个软件行为(最起码在函数调用中,应该是的。在响应中断时,就得另当别论了)。有找到两处 POP {r0}的用法( r0 是 Caller save 的),但也不是正常的函数调用时在用(我希望是调用了 POP {r0}后,马上调用函数,但并不是这么用的),其中一个用的地方是__rt_exit 和_aeabi_uldivmod
amiwrong123
2023-06-23 20:22:11 +08:00
@thinkm
@leonshaw
@bugu1986
@feather12315

另外,我猜是不是应该这么理解?

保存 Caller saved 寄存器的行为是一个可选项,如果在函数调用后 不需要使用到 Caller saved 寄存器,那么在调用之前,也就不需要有保存 Caller saved 寄存器的行为了。

Caller saved registers–– If the
data in these registers needs to be used after a C function call, the caller needs to save it
before calling a C function.
文档原话如上。
leonshaw
2023-06-23 20:35:26 +08:00
@amiwrong123 对,不用就不需要保存,callee-saved 如果不去动它也不需要保存。区别是 callee-saved 在入口和出口保存 /恢复一次,caller-saved 每次调用前后都要保存 /恢复。寄存器分配算法一般会尽量避免这个开销。
amiwrong123
2023-06-23 20:36:53 +08:00
@feather12315
特定的寄存器是硬件行为
=======
确实,汇编中找不到更新 LR 寄存器的指令(即函数调用前,把函数调用的下一个指令 作为返回地址)。但这个事应该是硬件来做的。
feather12315
2023-06-23 21:17:22 +08:00
@amiwrong123 #8 right 准确地来讲这是被优化后的代码,如果禁止优化的话 O0 应该就不这样了(没测试,可以看看)


@amiwrong123 #10 是的。这个要查手册页才能确定,而且贼琐碎。
churchmice
2023-06-23 21:50:38 +08:00
你都说堆栈了,这个东西硬件是无法帮你保存的,save/restore 都是软件来做的,这个就是俗称的 context switch 了

硬件的话有一种叫做 banking 机制,单这个东西更多的是中断处理相关,比如 cpu 的 r0,r1...r15 寄存器,硬件可以帮你做两套,典型的在 FIQ 发生的时候,硬件进 FIQ 的时候有单独的 r0-r15 寄存器,所以软件就无需自己把 r0-r15 save 到堆栈上面

总结一下
硬件何德何能能知道你的堆栈开在什么地方,在堆栈上的 save/restore 都是需要软件调用指令来完成的,硬件唯一能做的是 banking,在进 FIQ 的时候能让 sw 不用把 r0-r15 存到堆栈上面去

当然,的确也有人做 hw based save/restore,主要是嫌弃 context switch 的时候软件做太慢了,但这种都是私有实现
detached
2023-06-23 22:44:43 +08:00
肯定是软件做的。这个问题主要出在汇编里面的函数之间相互调用。硬件是没有办法知道的,只能是软件。这里的软件具体是编译器,在生成代码的时候写入对应的汇编语句。
如果是你自己写的汇编程序,是不需要考虑这个问题的,因为你作为 programmer ,是明确知道哪些寄存器的值是不能改变的,哪些是临时的可以被服用。
简而言之,caller save or callee save 都是 convention ,目的是为了让不同的汇编程序之间相互兼容,对硬件是完全透明的。
detached
2023-06-23 22:47:38 +08:00
反驳一下 os 做的这个说法。
os 只有在 context switch 的时候才会保存现场,也就是将所有寄存器的值存在某一块内存( kernel 的栈上)。这里寄存器的保存会发生在同一个进 /线程的函数调用中,与 context switch ,即从一个进 /线程切换到另一个中是两个不同的概念,前者只是正常的函数调用,后者需要 system call trap 到 kernel 中。
bugu1986
2023-06-24 10:19:09 +08:00
@amiwrong123 可选,但为了函数调用一定得加
mengzhuo
2023-06-25 09:51:16 +08:00
clobber 是软件行为,楼上说的 os 是不对的。

op 你说的是 ps ABI 的行为,手册在这 https://github.com/ARM-software/abi-aa/releases/download/2023Q1/aapcs32.pdf (仔细读 6.3 章) 。说大家都一样的,你看看隔壁 Go 用不用这个 ABI ?

而且多看看历史就知道为啥了,早期比如 6065 就 1 个累加寄存器( ALU ) 2 个辅助寄存器,SP 、PC 、FLAG 一共 6 个寄存器,各种数据就尽量塞内存上的,比如栈地址,程序返回地址什么的。
后来寄存器越来越多才导致了部分数据可以直接放寄存器上,渐渐才有了今天这样 ps ABI 的结果。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/951071

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX