我们知道 arm 体系或 x86 体系中(我比较关心 arm ),在进行函数调用时,会区分 Caller saved 和 Callee saved (就是把通用寄存器划分为两个范围)。
软件行为我是指:我在汇编代码中能够看到保存的过程(就是入栈和出栈)。比如在被调用的函数( Callee )里如果改变了 Callee saved 寄存器,那么在 这个函数里的开头结尾,就会分别出一个 入栈保存和出栈恢复 的操作(如果没有改变 Callee saved 寄存器,那么就不会多这两个操作)。
硬件行为我是指:是 CPU 自动做的,不是我在汇编代码里面能看到的指令。
1
thinkm 2023-06-23 19:32:14 +08:00
函数调用是硬件做的,例如像是 RTOS 之类的操作系统入栈出栈是软件做的。
多年没搞了,记忆中是这样 |
2
leonshaw 2023-06-23 19:34:49 +08:00
软件行为,caller 自己才知道哪些需要保存
|
3
bugu1986 2023-06-23 19:46:19 +08:00 1
os 做的
|
4
bugu1986 2023-06-23 19:48:02 +08:00
trap 把执行权从硬件给软件
|
5
feather12315 2023-06-23 19:52:25 +08:00 via Android
一部分硬件,一部分软件。
准确地说: 通用寄存是是软件行为,特定的寄存器是硬件行为。 比如函数调用是软件行为,中断异常的寄存器保存( amd64 下的 rip 、返回地址)是硬件行为 |
6
feather12315 2023-06-23 19:53:47 +08:00 via Android
arm 的话,返回地址是 r13 还是 r12 。
这个要查手册页了,programming manual 有详细的介绍( arm 的手册很多,要仔细找找,有个 guide 简述了上述过程 |
7
amiwrong123 OP @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 |
8
amiwrong123 OP @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. 文档原话如上。 |
9
leonshaw 2023-06-23 20:35:26 +08:00
@amiwrong123 对,不用就不需要保存,callee-saved 如果不去动它也不需要保存。区别是 callee-saved 在入口和出口保存 /恢复一次,caller-saved 每次调用前后都要保存 /恢复。寄存器分配算法一般会尽量避免这个开销。
|
10
amiwrong123 OP |
11
feather12315 2023-06-23 21:17:22 +08:00 via Android
@amiwrong123 #8 right 准确地来讲这是被优化后的代码,如果禁止优化的话 O0 应该就不这样了(没测试,可以看看)
@amiwrong123 #10 是的。这个要查手册页才能确定,而且贼琐碎。 |
12
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 的时候软件做太慢了,但这种都是私有实现 |
13
detached 2023-06-23 22:44:43 +08:00
肯定是软件做的。这个问题主要出在汇编里面的函数之间相互调用。硬件是没有办法知道的,只能是软件。这里的软件具体是编译器,在生成代码的时候写入对应的汇编语句。
如果是你自己写的汇编程序,是不需要考虑这个问题的,因为你作为 programmer ,是明确知道哪些寄存器的值是不能改变的,哪些是临时的可以被服用。 简而言之,caller save or callee save 都是 convention ,目的是为了让不同的汇编程序之间相互兼容,对硬件是完全透明的。 |
14
detached 2023-06-23 22:47:38 +08:00
反驳一下 os 做的这个说法。
os 只有在 context switch 的时候才会保存现场,也就是将所有寄存器的值存在某一块内存( kernel 的栈上)。这里寄存器的保存会发生在同一个进 /线程的函数调用中,与 context switch ,即从一个进 /线程切换到另一个中是两个不同的概念,前者只是正常的函数调用,后者需要 system call trap 到 kernel 中。 |
15
bugu1986 2023-06-24 10:19:09 +08:00
@amiwrong123 可选,但为了函数调用一定得加
|
16
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 的结果。 |