系统信息获取:CPU信息、内存状态、操作系统版本、环境变量
系统信息获取,说白了就是让你的程序知道自己「跑在什么机器上」。
我刚开始做跨平台开发时,觉得这功能可有可无。直到有一次,我的程序在客户服务器上莫名其妙崩溃——后来发现是内存不足,而我的代码根本没做检测。嗯,从那以后,我把系统信息获取列入了每个项目的「基础设施清单」。
为什么需要系统信息?
你想想看,一个程序要稳定运行,至少得知道三件事:
- CPU够不够快——决定要不要启用多线程
- 内存够不够用——决定缓存策略和内存分配
- 系统是什么版本——决定调用哪些API、避开哪些坑
环境变量则是「运行时配置」的最后一公里。很多隐蔽的bug,其实就是环境变量没设对。
跨平台获取CPU信息
CPU信息包括:核心数、架构、频率、缓存大小。不同平台获取方式完全不同。
| 平台 | 获取方式 | 关键API/文件 |
|---|---|---|
| Windows | 注册表 + Win32 API | GetSystemInfo, RegQueryValueEx |
| Linux | 读取 /proc/cpuinfo | fopen + 解析文本 |
| macOS | sysctl 系统调用 | 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"。建议解析时做大小写不敏感匹配。
内存状态获取
内存信息包括:总物理内存、可用内存、已用内存、交换分区大小。
| 平台 | 方式 | 关键点 |
|---|---|---|
| Windows | GlobalMemoryStatusEx | 返回结构体包含所有信息 |
| Linux | /proc/meminfo | 文本解析,单位是kB |
| macOS | host_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。
操作系统版本检测
这个坑最多。不同平台、不同版本号格式,甚至同一个平台的不同发行版,版本号含义都不一样。
| 平台 | 方法 | 示例输出 |
|---|---|---|
| Windows | RtlGetVersion | 10.0.19041 |
| Linux | /etc/os-release 或 uname | Ubuntu 22.04 LTS |
| macOS | sw_vers 或 Gestalt | 14.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。
| 操作 | Windows | Linux/macOS |
|---|---|---|
| 读取 | GetEnvironmentVariable | getenv |
| 设置 | SetEnvironmentVariable | setenv / 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。
知识体系总览
下面这张图概括了系统信息获取的核心逻辑:
封装策略总结
做了这么多年跨平台封装,我总结出三条铁律:
- 接口统一——上层只看到
get_cpu_info()、get_memory_info(),看不到任何平台宏 - 实现隔离——每个平台一个 .cpp 文件,用构建系统选择编译哪个
- 错误处理——系统调用可能失败,返回 bool 或 optional,不要抛异常
核心要点:
- CPU信息:核心数、架构、频率,三平台三套实现
- 内存状态:总内存和可用内存是基本需求,注意 Linux 的 MemAvailable
- 操作系统版本:优先使用结构化文件(/etc/os-release),回退到 uname
- 环境变量:getenv/setenv 三平台通用,Windows 注意缓冲区大小
系统信息获取看起来琐碎,但它是程序稳定运行的「眼睛」。我见过太多线上事故,追根溯源都是「没检查系统资源」或者「环境变量没传对」。把这些基础能力封装好,你的程序就能在各种环境下从容应对。