V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
amiwrong123
V2EX  ›  程序员

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

  •  
  •   amiwrong123 · 2023-06-23 19:22:36 +08:00 · 1477 次点击
    这是一个创建于 504 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

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

    • 然后我现在理解,保存 Callee saved 寄存器的行为,应该是一个软件行为。
    • 但,保存 Caller saved 寄存器的行为,是什么行为呢?——如果能附上 arm 文档链接和原话就更好了。
    16 条回复    2023-06-25 09:51:16 +08:00
    thinkm
        1
    thinkm  
       2023-06-23 19:32:14 +08:00
    函数调用是硬件做的,例如像是 RTOS 之类的操作系统入栈出栈是软件做的。
    多年没搞了,记忆中是这样
    leonshaw
        2
    leonshaw  
       2023-06-23 19:34:49 +08:00
    软件行为,caller 自己才知道哪些需要保存
    bugu1986
        3
    bugu1986  
       2023-06-23 19:46:19 +08:00   ❤️ 1
    os 做的
    bugu1986
        4
    bugu1986  
       2023-06-23 19:48:02 +08:00
    trap 把执行权从硬件给软件
    feather12315
        5
    feather12315  
       2023-06-23 19:52:25 +08:00 via Android
    一部分硬件,一部分软件。

    准确地说:
    通用寄存是是软件行为,特定的寄存器是硬件行为。
    比如函数调用是软件行为,中断异常的寄存器保存( amd64 下的 rip 、返回地址)是硬件行为
    feather12315
        6
    feather12315  
       2023-06-23 19:53:47 +08:00 via Android
    arm 的话,返回地址是 r13 还是 r12 。
    这个要查手册页了,programming manual 有详细的介绍( arm 的手册很多,要仔细找找,有个 guide 简述了上述过程
    amiwrong123
        7
    amiwrong123  
    OP
       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
        8
    amiwrong123  
    OP
       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
        9
    leonshaw  
       2023-06-23 20:35:26 +08:00
    @amiwrong123 对,不用就不需要保存,callee-saved 如果不去动它也不需要保存。区别是 callee-saved 在入口和出口保存 /恢复一次,caller-saved 每次调用前后都要保存 /恢复。寄存器分配算法一般会尽量避免这个开销。
    amiwrong123
        10
    amiwrong123  
    OP
       2023-06-23 20:36:53 +08:00
    @feather12315
    特定的寄存器是硬件行为
    =======
    确实,汇编中找不到更新 LR 寄存器的指令(即函数调用前,把函数调用的下一个指令 作为返回地址)。但这个事应该是硬件来做的。
    feather12315
        11
    feather12315  
       2023-06-23 21:17:22 +08:00 via Android
    @amiwrong123 #8 right 准确地来讲这是被优化后的代码,如果禁止优化的话 O0 应该就不这样了(没测试,可以看看)


    @amiwrong123 #10 是的。这个要查手册页才能确定,而且贼琐碎。
    churchmice
        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 的时候软件做太慢了,但这种都是私有实现
    detached
        13
    detached  
       2023-06-23 22:44:43 +08:00
    肯定是软件做的。这个问题主要出在汇编里面的函数之间相互调用。硬件是没有办法知道的,只能是软件。这里的软件具体是编译器,在生成代码的时候写入对应的汇编语句。
    如果是你自己写的汇编程序,是不需要考虑这个问题的,因为你作为 programmer ,是明确知道哪些寄存器的值是不能改变的,哪些是临时的可以被服用。
    简而言之,caller save or callee save 都是 convention ,目的是为了让不同的汇编程序之间相互兼容,对硬件是完全透明的。
    detached
        14
    detached  
       2023-06-23 22:47:38 +08:00
    反驳一下 os 做的这个说法。
    os 只有在 context switch 的时候才会保存现场,也就是将所有寄存器的值存在某一块内存( kernel 的栈上)。这里寄存器的保存会发生在同一个进 /线程的函数调用中,与 context switch ,即从一个进 /线程切换到另一个中是两个不同的概念,前者只是正常的函数调用,后者需要 system call trap 到 kernel 中。
    bugu1986
        15
    bugu1986  
       2023-06-24 10:19:09 +08:00
    @amiwrong123 可选,但为了函数调用一定得加
    mengzhuo
        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 的结果。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2886 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 34ms · UTC 02:35 · PVG 10:35 · LAX 18:35 · JFK 21:35
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.