如何在x64-64 Linux中用/proc接口编程计算指定进程页表总大小
用C语言通过/proc接口计算进程页表相关内存大小
首先需要明确两个容易混淆的概念:
- 进程映射的物理内存总大小:进程实际使用的物理页总容量,可通过统计有效页表项数量乘以页大小得到。
- 页表自身占用的内存大小:Linux多级页表结构(如x86_64的PGD/PUD/PMD/PTE)本身消耗的内存,通过
/proc/pid/maps和/proc/pid/pagemap无法直接精准计算,通常可通过/proc/pid/statm的第5项(页表大小,单位为页)或/proc/pid/smaps中的PageTables字段获取。
根据你的代码逻辑,你需要的应该是第一种——进程实际使用的物理内存总大小,以下是修正并完善后的代码:
问题修正与代码优化
你的原代码存在几个关键问题:
- 每次循环重复打开
pagemap文件,效率极低,应只打开一次。 - 未初始化统计变量,会导致计算结果错误。
- 未判断页表项的有效性(仅当
pagemap项的第63位为1时,该页才实际存在于物理内存)。
完善后的代码:
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <stdint.h> #include <string.h> #include <errno.h> #define PAGE_SIZE 4096 #define PAGEMAP_LENGTH 8 #define MAPS_FILE "/proc/%d/maps" #define PAGEMAP_FILE "/proc/%d/pagemap" // 从pagemap项中提取页框号,并判断页是否存在 int get_pfn_and_present(uint64_t entry, uint64_t *pfn) { // 第63位是存在位(1表示页在物理内存中) if (!(entry & (1ULL << 63))) { return 0; // 页不存在 } // 页框号是低55位(x86_64架构) *pfn = entry & ((1ULL << 55) - 1); return 1; } void alltablesize(int pid) { char file_path[50]; sprintf(file_path, MAPS_FILE, pid); FILE *maps = fopen(file_path, "r"); if (maps == NULL) { perror("Failed to open /proc/pid/maps"); return; } // 提前打开pagemap文件,避免重复IO操作 char pagemap_path[50]; sprintf(pagemap_path, PAGEMAP_FILE, pid); int pagemap_fd = open(pagemap_path, O_RDONLY); if (pagemap_fd == -1) { perror("Failed to open /proc/pid/pagemap"); fclose(maps); return; } char line[256]; uint64_t start, end; uint64_t vpn, pfn; uint64_t valid_entries = 0; uint64_t total_phy_mem_size = 0; while (fgets(line, sizeof(line), maps) != NULL) { char fname[256]; // 解析maps行,提取虚拟地址区间 if (sscanf(line, "%lx-%lx %*s %*lx %*x:%*x %*d %s", &start, &end, fname) < 2) { continue; // 解析失败跳过该行 } // 遍历该区间内的每个虚拟页号 for (vpn = start / PAGE_SIZE; vpn < end / PAGE_SIZE; vpn++) { uint64_t entry; off_t offset = vpn * PAGEMAP_LENGTH; // 定位到对应页表项的位置 if (lseek(pagemap_fd, offset, SEEK_SET) == -1) { fprintf(stderr, "lseek failed: %s\n", strerror(errno)); goto cleanup; } // 读取页表项 if (read(pagemap_fd, &entry, sizeof(uint64_t)) != sizeof(uint64_t)) { fprintf(stderr, "read pagemap entry failed: %s\n", strerror(errno)); goto cleanup; } // 判断页是否存在,并统计有效页 if (get_pfn_and_present(entry, &pfn)) { valid_entries++; total_phy_mem_size += PAGE_SIZE; } } } // 输出计算结果 printf("进程%d的有效页表项数量: %lu\n", pid, valid_entries); printf("进程%d映射的物理内存总大小: %lu bytes (%.2f MB)\n", pid, total_phy_mem_size, (double)total_phy_mem_size / (1024 * 1024)); cleanup: close(pagemap_fd); fclose(maps); } int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "Usage: %s <pid>\n", argv[0]); return 1; } int pid = atoi(argv[1]); alltablesize(pid); return 0; }
关于页表自身大小的获取
如果确实需要获取页表结构自身占用的内存大小,可直接读取/proc/pid/statm文件的第5个字段,该字段表示页表占用的页数,乘以页大小即可得到字节数:
uint64_t get_page_table_size(int pid) { char statm_path[50]; sprintf(statm_path, "/proc/%d/statm", pid); FILE *statm = fopen(statm_path, "r"); if (!statm) { perror("Failed to open /proc/pid/statm"); return 0; } unsigned long size, resident, shared, text, lib, data, dt; fscanf(statm, "%lu %lu %lu %lu %lu %lu %lu", &size, &resident, &shared, &text, &lib, &data, &dt); fclose(statm); // dt就是页表占用的页数,乘以页大小得到总字节数 return dt * PAGE_SIZE; }
内容的提问来源于stack exchange,提问作者aebosnak
相关产品推荐
相关产品推荐

