在 Go 语言中通过 os/exec 启动子进程之后,准确掌握其内存使用情况是写监控工具或资源限制模块时的常见需求。由于操作系统对内存的抽象比较复杂,只看进程启动时的参数或粗略的命令输出往往得不到真实数据。

为什么需要专门测量子进程内存
很多初学者会用 exec.Command 启动程序,然后调用系统命令如 ps 或 top 来查看内存。这种做法有两个问题:一是外部命令本身有开销且解析文本容易出错;二是默认显示的 RSS(常驻内存)会重复计算共享库,导致多个进程加起来超过物理内存。
利用 procfs 读取子进程状态
在 Linux 系统上,每个进程对应 /proc/<pid> 目录。其中 smaps 和 status 文件包含了详细的内存统计。Go 可以直接读取这些文件,不必依赖外部命令。
通过 Status 文件获取 RSS
status 文件中的 VmRSS 行给出了常驻内存大小,单位是 kB。下面的示例展示如何在 Go 中启动子进程并读取其 VmRSS。
package main
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"strings"
"strconv"
)
func main() {
// 启动一个子进程,这里用 sleep 做示例
cmd := exec.Command("sleep", "30")
cmd.Start()
pid := cmd.Process.Pid
// 读取 /proc/<pid>/status 文件
data, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/status", pid))
if err != nil {
fmt.Println("读取失败:", err)
return
}
// 解析 VmRSS 行
for _, line := range strings.Split(string(data), "n") {
if strings.HasPrefix(line, "VmRSS:") {
fields := strings.Fields(line)
// fields[1] 是数值,fields[2] 通常是 kB
rssKB, _ := strconv.ParseInt(fields[1], 10, 64)
fmt.Printf("子进程 %d 的 RSS 为 %d kBn", pid, rssKB)
}
}
cmd.Process.Kill()
cmd.Wait()
os.Exit(0)
}
使用 PSS 获得更准确的数据
如果子进程使用了共享库,RSS 会虚高。smaps 中的 PSS(按比例占用物理内存)把共享部分均摊,更适合统计。我们可以用类似方式解析 /proc/<pid>/smaps。
package main
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"strings"
"strconv"
)
func main() {
cmd := exec.Command("sleep", "30")
cmd.Start()
pid := cmd.Process.Pid
// 读取 smaps 统计 PSS
data, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/smaps", pid))
if err != nil {
fmt.Println("读取 smaps 失败:", err)
return
}
var totalPSS int64
for _, line := range strings.Split(string(data), "n") {
if strings.HasPrefix(line, "Pss:") {
fields := strings.Fields(line)
v, _ := strconv.ParseInt(fields[1], 10, 64)
totalPSS += v
}
}
fmt.Printf("子进程 %d 的 PSS 为 %d kBn", pid, totalPSS)
cmd.Process.Kill()
cmd.Wait()
os.Exit(0)
}
跨平台与权限注意
上述方法仅适用于 Linux。在 macOS 或 Windows 上,需要使用 syscall 包调用对应系统的 API,例如 Windows 的 GetProcessMemoryInfo。另外,读取 proc 文件需要目标进程的同用户权限或 root,否则会报权限错误。
小结
在 Go 中准确测量子进程内存,推荐直接读取 /proc 下对应 pid 的 status 与 smaps 文件,用 PSS 替代 RSS 可避免共享内存导致的统计偏差。将读取逻辑封装成函数,就能在监控循环中稳定获取子进程的真实内存占用。