我有一个用 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 只是一个虚拟内存,实际上它并没有被进程使用”形式的答案。
最佳答案
我可以做出有根据的猜测。
您可能知道,默认情况下,“引用”Go 实现(历史上称为“gc”;可从 the main site 下载)的编译器生成静态链接的二进制文件。这意味着,此类二进制文件仅依赖于操作系统内核提供的所谓“系统调用”,而不依赖于操作系统(或第 3 方)提供的任何共享库。
在基于 Linux 的平台上,这并不完全正确:在默认设置(在 Linux 上为 Linux 构建,即不交叉编译)中,生成的二进制文件实际上与 libc
链接
和 libpthread
(间接地,通过 libc
)。
这种“扭曲”来自 Go 标准库与操作系统交互的两个需求:
net
包需要。 os
包所需要的。 这里的问题有两个:
这是“名称服务开关”¹。
NSS 提供了可插拔模块,可以服务于
作为提供特定类型查询的数据库:DNS、用户/组数据库等(例如“服务”等的知名名称)。一个相当普遍的例子
用户/组数据库的非标准提供程序是本地的
联系 LDAP 服务器的服务。
在典型的基于 GNU/Linux 的操作系统上,NSS 由
libc
(在不太典型的系统上,它可能由单独的共享库,但这并没有太大变化)。
因为 - 再次,通常, -
libc
是一个相当稳定的就其 API 而言的库(它甚至提供版本化符号
为了面向 future ),Go 作者正确地决定链接
libc
以导入最小的符号子集(主要是 getaddrinfo
、 getnameinfo
、 getpwnam_r
等)是可以的默认情况下完成,因为它在 99% 的情况下都是安全的,
如果不是,那些必须处理这些案件的人通常
无论如何都知道该怎么做。
因此,默认情况下
cgo
已启用并用于使用 NSS 实现这些查找。如果
cgo
被禁用,Go 编译器会改为链接自己的回退实现,试图模仿什么的一个子集
完整的 NSS 实现(即解析
/etc/resolv.conf
并使用其中的信息直接查询此处列出的 DNS 服务器;解析 /etc/passwd
和 /etc/group
以服务于用户/组数据库查询)。如您所见,在默认情况下,
libc
被映射,例如明显缓存 NSS 调用返回的数据。
相反,在禁用
cgo
的情况下,则不会发生上述两种情况。您有更多静态链接的 stdlib 代码,但看起来默认情况仅在总体累积 RSS 使用方面胜过后者。考虑研究输出
this query
为了额外的乐趣;-)
¹ 不要与 Mozilla 的
libnss
混淆。