「这是我参与2022首次更文挑战的第20天,活动详情查看:2022首次更文挑战」。
前言
本文主要介绍defer如何在堆中存储。
堆分配
在go1.14版本中,堆分配主要分为deferproc,和deferreturn。 其中deferproc主要分为以下几步:
- 计算出堆栈指针寄存器,程序计数器的寄存器值以及函数参数放在栈中的位置
- 在堆内存中分配一个新的_defer结构体,并将这个结构体插入当前协程记录_defer结构体的链表的头部如下图
- 将堆栈指针寄存器,程序计数器的寄存器值记录到新的_defer结构体中,并将栈上的参数(指defer调用的参数,这些参数会存到栈上)复制到堆区
在函数退出前,调用了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