系统信息获取:CPU信息、内存状态、操作系统版本、环境变量

系统信息获取,说白了就是让你的程序知道自己「跑在什么机器上」。

我刚开始做跨平台开发时,觉得这功能可有可无。直到有一次,我的程序在客户服务器上莫名其妙崩溃——后来发现是内存不足,而我的代码根本没做检测。嗯,从那以后,我把系统信息获取列入了每个项目的「基础设施清单」。

为什么需要系统信息?

你想想看,一个程序要稳定运行,至少得知道三件事:

  • CPU够不够快——决定要不要启用多线程
  • 内存够不够用——决定缓存策略和内存分配
  • 系统是什么版本——决定调用哪些API、避开哪些坑

环境变量则是「运行时配置」的最后一公里。很多隐蔽的bug,其实就是环境变量没设对。

跨平台获取CPU信息

CPU信息包括:核心数、架构、频率、缓存大小。不同平台获取方式完全不同。

平台获取方式关键API/文件
Windows注册表 + Win32 APIGetSystemInfo, RegQueryValueEx
Linux读取 /proc/cpuinfofopen + 解析文本
macOSsysctl 系统调用sysctlbyname

我个人习惯封装一个 CPUInfo 结构体,然后针对每个平台写一个实现文件。这样上层代码完全不用关心底层差异。

// cpu_info.h
struct CPUInfo {
    int logical_cores;
    int physical_cores;
    std::string architecture;  // x86_64, arm64...
    double max_freq_mhz;
};

bool get_cpu_info(CPUInfo& info);

Linux下的实现大概是这样的:

// cpu_info_linux.cpp
bool get_cpu_info(CPUInfo& info) {
    FILE* fp = fopen("/proc/cpuinfo", "r");
    if (!fp) return false;

    char line[256];
    int core_count = 0;
    while (fgets(line, sizeof(line), fp)) {
        if (strncmp(line, "processor", 9) == 0) core_count++;
        // 解析架构、频率等字段...
    }
    fclose(fp);

    info.logical_cores = core_count;
    // 物理核心数需要额外解析 "cpu cores" 字段
    return true;
}
注意:/proc/cpuinfo 的格式在不同Linux发行版之间略有差异。我曾经在某个嵌入式Linux上遇到字段名大小写不一致的问题——"cpu cores" 变成了 "CPU cores"。建议解析时做大小写不敏感匹配。

内存状态获取

内存信息包括:总物理内存、可用内存、已用内存、交换分区大小。

平台方式关键点
WindowsGlobalMemoryStatusEx返回结构体包含所有信息
Linux/proc/meminfo文本解析,单位是kB
macOShost_statistics需要mach API

Linux下读取内存信息,我习惯这样写:

bool get_memory_info(uint64_t& total_mb, uint64_t& free_mb) {
    FILE* fp = fopen("/proc/meminfo", "r");
    if (!fp) return false;

    char line[128];
    uint64_t total_kb = 0, free_kb = 0;
    while (fgets(line, sizeof(line), fp)) {
        if (sscanf(line, "MemTotal: %lu kB", &total_kb) == 1) continue;
        if (sscanf(line, "MemAvailable: %lu kB", &free_kb) == 1) break;
    }
    fclose(fp);

    total_mb = total_kb / 1024;
    free_mb  = free_kb / 1024;
    return total_mb > 0;
}
小技巧:Linux 2.6.27+ 内核提供了 MemAvailable 字段,比单纯用 MemFree 更准确,因为它考虑了缓存和缓冲区可以回收的部分。如果你的目标系统较老,可能需要自己计算:MemAvailable = MemFree + Buffers + Cached。

操作系统版本检测

这个坑最多。不同平台、不同版本号格式,甚至同一个平台的不同发行版,版本号含义都不一样。

平台方法示例输出
WindowsRtlGetVersion10.0.19041
Linux/etc/os-release 或 unameUbuntu 22.04 LTS
macOSsw_vers 或 Gestalt14.3.1

我个人推荐优先读取 /etc/os-release,因为它结构清晰,是 freedesktop.org 标准。如果文件不存在,再回退到 uname

std::string get_os_version() {
    FILE* fp = fopen("/etc/os-release", "r");
    if (!fp) return "unknown";

    char line[256];
    std::string name, version;
    while (fgets(line, sizeof(line), fp)) {
        if (sscanf(line, "PRETTY_NAME=\"%[^\"]\"", ...)) break;
        // 解析 NAME= 和 VERSION_ID=
    }
    fclose(fp);
    return name + " " + version;
}
避坑:Windows上不要用 GetVersionEx,它在 Windows 8.1 之后被废弃,返回的版本号可能不准确。一定要用 RtlGetVersion 或者直接读取注册表 HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion

环境变量操作

环境变量是跨平台开发中最容易被忽视的「隐形配置」。我见过太多因为环境变量没设对导致的诡异bug。

操作WindowsLinux/macOS
读取GetEnvironmentVariablegetenv
设置SetEnvironmentVariablesetenv / putenv
删除SetEnvironmentVariable(name, NULL)unsetenv

封装一个跨平台的接口其实很简单:

std::string get_env(const std::string& name) {
#ifdef _WIN32
    char buf[4096];
    DWORD ret = GetEnvironmentVariable(name.c_str(), buf, sizeof(buf));
    return (ret > 0 && ret < sizeof(buf)) ? std::string(buf) : "";
#else
    const char* val = getenv(name.c_str());
    return val ? std::string(val) : "";
#endif
}

bool set_env(const std::string& name, const std::string& value) {
#ifdef _WIN32
    return SetEnvironmentVariable(name.c_str(), value.c_str()) != 0;
#else
    return setenv(name.c_str(), value.c_str(), 1) == 0;
#endif
}
经验之谈:Windows下环境变量缓冲区建议用 4096 字节,这是 Windows 环境变量的最大长度。Linux 下 getenv 返回的指针指向进程内部数据,不要修改它——要修改就用 setenv。

知识体系总览

下面这张图概括了系统信息获取的核心逻辑:

系统信息获取 CPU信息 核心数 / 架构 / 频率 Win: GetSystemInfo Linux: /proc/cpuinfo macOS: sysctl 内存状态 总内存 / 可用 / 已用 Win: GlobalMemoryStatusEx Linux: /proc/meminfo macOS: host_statistics 操作系统版本 版本号 / 发行版名称 Win: RtlGetVersion Linux: /etc/os-release macOS: sw_vers 环境变量 读取 / 设置 / 删除 Win: Get/SetEnvironmentVariable Linux: getenv / setenv macOS: 同Linux 核心原则:统一接口 + 平台隔离

封装策略总结

做了这么多年跨平台封装,我总结出三条铁律:

  1. 接口统一——上层只看到 get_cpu_info()get_memory_info(),看不到任何平台宏
  2. 实现隔离——每个平台一个 .cpp 文件,用构建系统选择编译哪个
  3. 错误处理——系统调用可能失败,返回 bool 或 optional,不要抛异常

核心要点:

  • CPU信息:核心数、架构、频率,三平台三套实现
  • 内存状态:总内存和可用内存是基本需求,注意 Linux 的 MemAvailable
  • 操作系统版本:优先使用结构化文件(/etc/os-release),回退到 uname
  • 环境变量:getenv/setenv 三平台通用,Windows 注意缓冲区大小

系统信息获取看起来琐碎,但它是程序稳定运行的「眼睛」。我见过太多线上事故,追根溯源都是「没检查系统资源」或者「环境变量没传对」。把这些基础能力封装好,你的程序就能在各种环境下从容应对。

公众号:蓝海资料掘金营,微信deep3321