明天,咱们将认真钻研其 Func
构造,并探讨无关垃圾回收在 Go 中如何工作的一些细节。
这篇文章是“Golang 外部常识,第 3 局部:链接器和 Go 对象文件”的持续,并应用雷同的示例程序。因而,咱们强烈建议您在持续之前先浏览上一部分。
函数元数据的构造
重定位背地的次要思维应该从第 3 局部中分明地理解。当初,让咱们看一下 Func
该main
办法的构造
Func: &goobj.Func{
Args: 0,
Frame: 8,
Leaf: false,
NoSplit: false,
Var: { },
PCSP: goobj.Data{Offset:255, Size:7},
PCFile: goobj.Data{Offset:263, Size:3},
PCLine: goobj.Data{Offset:267, Size:7},
PCData: {{Offset:276, Size:5},
},
FuncData: {
{Sym: goobj.SymID{Name:"gclocals·3280bececceccd33cb74587feedb1f9f", Version:0},
Offset: 0,
},
{Sym: goobj.SymID{Name:"gclocals·3280bececceccd33cb74587feedb1f9f", Version:0},
Offset: 0,
},
},
File: {"/home/adminone/temp/test.go"},
},
您能够将此构造视为编译器在指标文件中收回并由 Go 运行时应用的函数元数据。该文章介绍了在不同畛域的具体格局和意义Func
。当初,咱们将尝试向您展现如何在运行时中应用此元数据
在运行时包外部,此元数据映射在以下构造上
type _func struct {
entry uintptr // start pc
nameoff int32 // function name
args int32 // in/out args size
frame int32 // legacy frame size; use pcsp if possible
pcsp int32
pcfile int32
pcln int32
npcdata int32
nfuncdata int32
}
您能够看到并非指标文件中的所有信息都已间接映射。某些字段仅由链接器应用。不过,这里最乏味的是 pcsp
,pcfile
和pcln
畛域,当其应用程序计数器相应地转换成堆栈指针,文件名和行号。
例如,这在 panic
产生时是必须的。在那一刻,运行时仅晓得已触发的以后汇编指令的程序计数器 panic
。因而,运行时应用该计数器来获取以后文件,行号和残缺的堆栈跟踪。应用pcfile
和pcln
字段间接解析文件和行号。应用递归解决堆栈跟踪pcsp
。
当初咱们有了程序计数器,问题是,如何取得相应的行号?要答复这个问题,您须要浏览汇编代码并理解行号如何存储在指标文件中。
0x001a 00026 (test.go:4) MOVQ $1,(SP)
0x0022 00034 (test.go:4) PCDATA $0,$0
0x0022 00034 (test.go:4) CALL ,runtime.printint(SB)
0x0027 00039 (test.go:5) ADDQ $8,SP
0x002b 00043 (test.go:5) RET ,
咱们能够看到,程序计数器从 26 到 38(包含端点)对应于第 4 行,而计数器从 39next_function_program_counter - 1
对应于第 5 行。为了节俭空间,存储上面的映射就足够了。
26 - 4
39 - 5
…
这简直就是编译器所做的。该 pcln
字段指向映射中与以后性能的第一个程序计数器绝对应的特定偏移量。在晓得此偏移量以及下一个性能的第一个程序计数器的偏移量后,运行时能够应用二进制搜寻来找到与给定程序计数器绝对应的行号。
在 Go 中,这个想法是狭义的。不仅行号或堆栈指针能够映射到程序计数器,而且任何整数值都能够映射。这是通过 PCDATA
指令实现的。每次链接器都会找到以下指令。
(这个货色是用来辅助 GC 的,在 go 源码中搜寻 pcdatavalue
能够看出具体是如何应用的。参考 scanframeworker)(https://zhuanlan.zhihu.com/p/…
它不会生成任何理论的汇编程序指令。相同,它将此指令的第二个参数存储在具备以后程序计数器的映射中,而第一个参数批示应用的是哪个映射。应用第一个参数,咱们能够轻松地增加新映射,这对于编译器和运行时是已知的,但对于链接程序却是不通明的。
垃圾收集器如何应用函数元数据
函数元数据中依然须要廓清的最初一件事是 FuncData
数组。它蕴含垃圾收集所需的信息。Go 应用分阶段运行的标记革除垃圾收集器(GC)。在第一阶段(标记),它将遍历所有仍在应用的对象并将它们标记为可达到。在第二(扫描)阶段中,所有未标记的对象都将被删除。
因而,垃圾收集器首先在几个已知的地位中查找可拜访的对象,例如全局变量,处理器寄存器,堆栈帧和已达到对象中的指针。然而,如果认真考虑一下,则在堆栈帧中查找指针绝非易事。因而,当运行时执行垃圾回收时,如何辨别堆栈中的变量是指针还是属于非指针类型?这就是 FuncData
发挥作用的中央。
对于每个函数,编译器都会创立两个变量。一个蕴含用于堆栈帧的参数区域的位图矢量。另一个蕴含帧其余部分的位图,该位图蕴含函数中定义的指针类型的所有局部变量。这些变量中的每一个都通知垃圾收集器,指针在堆栈帧中的确切地位,并且该信息足以实现其工作。
这也是值得一提的喜爱 PCDATA
,FUNCDATA
也由伪围棋汇编指令产生的。
0x001a 00026 (test.go:3) FUNCDATA $0,gclocals·3280bececceccd33cb74587feedb1f9f+0(SB)`
该指令的第一个自变量示意这是自变量的函数数据还是局部变量区域。第二个实际上是对蕴含 GC 掩码的暗藏变量的援用。
在接下来的文章中,咱们将钻研 Go 疏导过程,这是理解 Go 运行时如何工作的要害。