为什么CGO_ENABLE对虚拟内存有这种影响?

时间:2019-01-31 08:19:33

标签: go

我有一个用Golang编写的小守护程序,该守护程序循环运行并完成一些工作。我发现,在使用CGO_ENABLE = 1或CGO_ENABLED = 0进行编译时,守护程序的行为会有所不同。例如,在CGO_ENABLE = 1(默认设置)的情况下,程序的VSZ在短时间内(一小时内)膨胀到1-2GB。当CGO_ENABLED = 0时,VSZ在很长一段时间(几天内)都相同。看下面的数字:

CGO_ENABLED = 1(守护进程已运行5分钟)

$ grep -E 'VmSize|VmRSS' /proc/14916/status
VmSize:    1084052 kB
VmRSS:       12524 kB

CGO_ENABLED = 0(守护进程已运行约30个小时)

$ grep -E 'VmSize|VmRSS' /proc/15160/status
VmSize:    110232 kB
VmRSS:       9756 kB

不使用依赖于CGO的程序包或函数。其他Go编写的程序显示相同的行为。我知道VSZ和RSS之间的区别,而且有趣的是,这种行为的本质是什么?为什么用CGO_ENABLED = 1编译的程序要求从内核提供这么多的内存?

我希望答案不是“不要担心,VSZ只是一个虚拟内存,实际上它没有被进程使用”的形式。

1 个答案:

答案 0 :(得分:2)

我可以做出有根据的猜测。

您可能已经知道,“参考” Go实现的编译器(历史上称为“ gc”;该编译器可从the main site下载)默认情况下会生成静态链接的二进制文件。这意味着这些二进制文件仅依赖于OS内核提供的所谓“系统调用”,而不依赖于OS(或第3方)提供的任何共享库。

在基于Linux的平台上,这并非完全正确:在默认设置(在Linux for Linux上构建,即非交叉编译)中,生成的二进制文件实际上与libc链接 并通过libpthread(通过libc间接)。

“扭曲”来自Go标准库必须与操作系统进行交互的两个需求:

  1. DNS解析,net程序包需要。
  2. os软件包所需的用户和组查找。

这里的问题有两个:

  • Linux 本身(即内核,而不是整个操作系统)不提供任何手段来执行这些任务。

  • 从此以后,任何典型的类似UNIX的系统都会使用称为“ NSS”的特殊功能来提供这两项任务, 这就是“名称服务切换”¹。

    NSS提供可服务的可插拔模块 例如提供特定类型查询的数据库:DNS,用户/组数据库等(例如“服务”的知名名称等)。一个据称相当普遍的例子 用户/组数据库的非标准提供程序是本地 与LDAP服务器联系的服务。

在典型的基于GNU / Linux的操作系统上,NSS通过以下方式实现 libc(在不太典型的系统上,它可能是由 单独的共享库,但这并没有太大变化。

由于—通常,— libc是相当稳定的 就其API而言,库(甚至提供版本化的符号 以确保面向未来),Go作者正确地认为,针对libc进行链接以导入符号的最小子集(主要是getaddrinfogetnameinfogetpwnam_r等) 默认情况下可以完成,因为在99%的情况下都是安全的, 而如果不是,那些通常要处理这些情况的人 知道该怎么做。

因此,默认情况下cgo已启用并用于以使用NSS实施这些查找。

如果cgo被禁用,Go编译器将自己链接 后备实现,它们试图模仿一个 NSS的全面实现(例如,解析/etc/resolv.conf并使用其中的信息直接查询此处列出的DNS服务器;解析/etc/passwd/etc/group来为用户/组数据库查询提供服务)

如您所见,在最糟糕的情况下,

  • libc被映射,并且
  • 已被初始化 并根据自身需要使用了一些内存- 例如NSS调用返回的明显缓存数据。

相反,在禁用cgo的情况下,上述两件事不会发生。您有更多的stdlib代码以静态方式链接,但是看起来在默认情况下,就整体RSS累积使用量而言,默认情况仅胜于后一种情况。

考虑研究输出 this query 以获得更多乐趣;-)

希望有帮助。


¹不要与Mozilla的libnss混淆。