go语言defer系列(三)—— 堆分配

468 阅读2分钟

「这是我参与2022首次更文挑战的第20天,活动详情查看:2022首次更文挑战」。

前言

本文主要介绍defer如何在堆中存储。

堆分配

在go1.14版本中,堆分配主要分为deferproc,和deferreturn。 其中deferproc主要分为以下几步:

  • 计算出堆栈指针寄存器,程序计数器的寄存器值以及函数参数放在栈中的位置
  • 在堆内存中分配一个新的_defer结构体,并将这个结构体插入当前协程记录_defer结构体的链表的头部如下图
  • 将堆栈指针寄存器,程序计数器的寄存器值记录到新的_defer结构体中,并将栈上的参数(指defer调用的参数,这些参数会存到栈上)复制到堆区

image.png

在函数退出前,调用了runtime.deferreturn,deferreturn的任务是逐个调用deferproc存在协程链表中的函数。这个过程中,除了在链表遍历结束后终止,还有一种终止条件:

判断defer结构体的SP属性值和deferreturn的调用者堆栈指针寄存器值不同时,此时说明这个defer并不是该调用者添加进协程链表的。

在执行defer结构体中的fn函数时,需要将所需参数再次传入栈中,然后调用freedefer销毁当前的结构体,并将链表指向下一个_defer结构体。做完这些后,就可以调用就就jmpdefer运行fn函数了。

jmpdefer的源码如下

TEXT runtime·jmpdefer(SB), NOSPLIT, $0-16
   MOVQ   fv+0(FP), DX   // fn
   MOVQ   argp+8(FP), BX // caller sp
   LEAQ   -8(BX), SP // caller sp after CALL
   MOVQ   -8(SP), BP // restore BP as if deferreturn returned (harmless if framepointers not in use)
   SUBQ   $5, (SP)   // return to CALL again
   MOVQ   0(DX), BX
   JMP    BX // but first run the deferred function

可以看到jumpdefer会调整堆栈指针寄存器的值和基址寄存器的值,并重新调用CALL,也就是会重新执行deferreturn