You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在x64-64 Linux中用/proc接口编程计算指定进程页表总大小

用C语言通过/proc接口计算进程页表相关内存大小

首先需要明确两个容易混淆的概念:

  1. 进程映射的物理内存总大小:进程实际使用的物理页总容量,可通过统计有效页表项数量乘以页大小得到。
  2. 页表自身占用的内存大小: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 16:15:13