15、系统信息获取:操作系统版本、CPU信息、内存信息的跨平台获取
说实话,系统信息获取这事儿,看着简单,写起来全是坑。
我早年做嵌入式监控软件时,就吃过这个亏。在Windows上跑得好好的代码,一放到Linux服务器上,直接编译不过。后来我学乖了——跨平台获取系统信息,本质上就是跟三个不同的操作系统内核打交道。你得知道它们各自藏在哪里,怎么敲门,以及门开了之后拿到的数据长什么样。
15.1 为什么系统信息获取不能写死?
你想想看,一个程序如果连自己跑在什么系统上都不知道,那它怎么自适应?怎么动态调整内存池大小?怎么根据CPU核数开线程?
我见过不少新手,直接在代码里写死内存大小,结果部署到不同配置的机器上就崩。说白了,系统信息获取是跨平台程序的第一道防线。
核心原则:运行时检测,而非编译时假设。编译时用宏判断平台,运行时用API获取具体数值。
15.2 操作系统版本获取
每个操作系统都有自己的版本号体系。Windows喜欢用大版本.小版本.构建号,Linux用内核版本+发行版名称,macOS则是达尔文内核版本那一套。
15.2.1 Windows 平台
Windows上获取版本信息,我建议用 RtlGetVersion 而不是 GetVersionEx。为什么?因为 GetVersionEx 在Windows 8.1之后就被微软“阉割”了——它会返回虚假的版本号,除非你专门配置清单文件。我曾经被这个坑过,排查了半天才发现是API本身的问题。
#ifdef _WIN32
#include <windows.h>
typedef LONG (WINAPI *RtlGetVersionPtr)(PRTL_OSVERSIONINFOW);
void get_windows_version() {
HMODULE hMod = GetModuleHandleW(L"ntdll.dll");
if (!hMod) return;
RtlGetVersionPtr RtlGetVersion = (RtlGetVersionPtr)GetProcAddress(hMod, "RtlGetVersion");
if (!RtlGetVersion) return;
RTL_OSVERSIONINFOW osvi = {0};
osvi.dwOSVersionInfoSize = sizeof(osvi);
if (RtlGetVersion(&osvi) == 0) {
printf("Windows %lu.%lu (Build %lu)\n",
osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber);
}
}
#endif
15.2.2 Linux 平台
Linux下获取系统版本,最靠谱的方式是读取 /etc/os-release 文件。别去解析 /proc/version,那个只告诉你内核版本,不告诉你发行版信息。
#ifdef __linux__
#include <stdio.h>
#include <string.h>
void get_linux_version() {
FILE *fp = fopen("/etc/os-release", "r");
if (!fp) {
// 回退方案:读取 /etc/issue
fp = fopen("/etc/issue", "r");
if (!fp) return;
}
char line[256];
while (fgets(line, sizeof(line), fp)) {
if (strncmp(line, "PRETTY_NAME=", 12) == 0) {
// 去掉引号和换行符
char *start = strchr(line, '"');
char *end = strrchr(line, '"');
if (start && end && end > start) {
*end = '\0';
printf("OS: %s\n", start + 1);
}
break;
}
}
fclose(fp);
}
#endif
我的习惯:在Linux上总是准备两套方案。主方案读 /etc/os-release,备方案读 /etc/issue 或 uname -a。有些精简版容器镜像可能没有 /etc/os-release。
15.3 CPU 信息获取
CPU信息包括:架构(x86、ARM、RISC-V)、核心数、频率、缓存大小。不同平台获取方式天差地别。
15.3.1 跨平台获取CPU核心数
这个需求最常见——开线程池时要用。好消息是,C++11之后有标准方法,但咱们讲C语言,还是得用平台API。
| 平台 | API / 方法 | 头文件 |
|---|---|---|
| Windows | GetSystemInfo() → dwNumberOfProcessors |
<windows.h> |
| Linux | sysconf(_SC_NPROCESSORS_ONLN) |
<unistd.h> |
| macOS | sysctl() → HW_NCPU |
<sys/sysctl.h> |
#include <stdio.h>
#ifdef _WIN32
#include <windows.h>
#elif defined(__linux__)
#include <unistd.h>
#elif defined(__APPLE__)
#include <sys/sysctl.h>
#endif
int get_cpu_cores() {
#ifdef _WIN32
SYSTEM_INFO sysinfo;
GetSystemInfo(&sysinfo);
return sysinfo.dwNumberOfProcessors;
#elif defined(__linux__)
return (int)sysconf(_SC_NPROCESSORS_ONLN);
#elif defined(__APPLE__)
int count;
size_t size = sizeof(count);
if (sysctlbyname("hw.ncpu", &count, &size, NULL, 0) == 0)
return count;
return 1;
#else
return 1; // 未知平台,保守返回1
#endif
}
注意:_SC_NPROCESSORS_ONLN 返回的是当前在线的核心数,不是物理核心数。如果你需要物理核心数(比如做CPU亲和性绑定),Linux下得解析 /proc/cpuinfo 中的 core id 字段。
15.3.2 获取CPU架构名称
这个更多是编译时判断,但运行时也有用——比如你要动态加载不同架构的优化库。
const char* get_cpu_arch() {
#if defined(__x86_64__) || defined(_M_X64)
return "x86_64";
#elif defined(__i386__) || defined(_M_IX86)
return "x86";
#elif defined(__aarch64__) || defined(_M_ARM64)
return "ARM64";
#elif defined(__arm__) || defined(_M_ARM)
return "ARM";
#elif defined(__riscv)
return "RISC-V";
#else
return "Unknown";
#endif
}
15.4 内存信息获取
内存信息包括总物理内存、可用内存、已用内存。这个在实现内存池、缓存淘汰策略时特别重要。
15.4.1 Windows 内存信息
#ifdef _WIN32
#include <windows.h>
void get_windows_memory() {
MEMORYSTATUSEX statex;
statex.dwLength = sizeof(statex);
GlobalMemoryStatusEx(&statex);
printf("Total RAM: %llu MB\n", statex.ullTotalPhys / (1024 * 1024));
printf("Available RAM: %llu MB\n", statex.ullAvailPhys / (1024 * 1024));
printf("Usage: %.1f%%\n",
100.0 * (1.0 - (double)statex.ullAvailPhys / statex.ullTotalPhys));
}
#endif
15.4.2 Linux 内存信息
Linux下内存信息在 /proc/meminfo 里。别用 sysinfo(),那个结构体里的字段单位在不同内核版本上不一致,我踩过这个坑。
#ifdef __linux__
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void get_linux_memory() {
FILE *fp = fopen("/proc/meminfo", "r");
if (!fp) return;
long total = 0, available = 0;
char line[128];
while (fgets(line, sizeof(line), fp)) {
if (sscanf(line, "MemTotal: %ld kB", &total) == 1) continue;
if (sscanf(line, "MemAvailable: %ld kB", &available) == 1) break;
}
fclose(fp);
printf("Total RAM: %ld MB\n", total / 1024);
printf("Available RAM: %ld MB\n", available / 1024);
}
#endif
避坑指南:我曾经在解析 /proc/meminfo 时,直接用 fscanf 按顺序读字段。结果某个内核版本新增了一行,顺序全乱了。后来我改成逐行匹配字段名,再也没出过问题。
15.5 跨平台封装:统一接口设计
说了这么多平台差异,最终我们要封装成一个统一的接口。我个人习惯用结构体+函数指针的方式,这样调用方完全不用关心底层实现。
typedef struct {
char os_name[64];
char os_version[64];
char cpu_arch[16];
int cpu_cores;
long long total_memory; // 字节
long long available_memory; // 字节
} SystemInfo;
int get_system_info(SystemInfo *info); // 跨平台实现
这个 get_system_info 函数内部根据平台宏调用不同的实现。调用方只需要包含头文件,链接对应的平台源文件即可。
15.6 知识体系总览
下面这张图总结了系统信息获取的跨平台架构。你可以看到,应用层通过统一接口调用,底层按平台分流,各自实现细节被完全隔离。
15.7 实战建议
最后说几点我在项目中积累的经验:
- 缓存结果:系统信息在程序运行期间基本不变。我习惯在初始化时一次性获取,存到全局结构体里,避免反复调用系统API。
- 降级策略:如果某个API调用失败(比如
/proc/meminfo被容器限制),要有合理的默认值。我一般用编译时宏定义的保守值作为兜底。 - 日志输出:程序启动时打印系统信息到日志,方便后期排查问题。这个习惯帮我省了不少调试时间。
- 单元测试:写一个测试程序,在不同平台上运行,验证返回值是否合理。比如内存不能为0,CPU核心数不能超过256等。
核心思想:跨平台不是把代码写死成多份,而是设计一个优雅的抽象层,让平台差异被隔离在底层。上层代码只关心“我要什么”,不关心“从哪里拿”。
嗯,系统信息获取就讲到这里。这部分代码量不大,但设计思路很重要。你写跨平台程序时,多想想“如果换一个操作系统,这段代码还能跑吗?”——有这个意识,你就已经超过大多数开发者了。
公众号:蓝海资料掘金营,微信deep3321