虚拟地址映射

1.设置一直运行的进程

#include <iostream>
#include <unistd.h>   // for fork(), sleep()
#include <sys/types.h>
#include <sys/wait.h> // for wait()
#include <cstdlib>    // for malloc(), free()
#include <cstring>    // for memset()
#include <chrono>     // for time measurement

#define MEM_SIZE 100 * 1024 * 1024  // 500MB

void allocate_and_compute() {
    // 打印进程号
    std::cout << "Process " << getpid() << " started." << std::endl;

    // 分配500MB内存
    char *buffer = (char *)malloc(MEM_SIZE);
    if (buffer == nullptr) {
        std::cerr << "Memory allocation failed!" << std::endl;
        exit(EXIT_FAILURE);
    }

    // 将内存初始化为0,以确保它实际被分配
    memset(buffer, 0, MEM_SIZE);

    // 记录开始时间
    auto start_time = std::chrono::steady_clock::now();

    // 进行计算以保持进程活跃
    unsigned long long sum = 0;
    while (true) {
        for (unsigned long long i = 0; i < 1000000; ++i) {
            sum += i;
        }

        // 每隔一段时间输出一次进程状态
        auto current_time = std::chrono::steady_clock::now();
        std::chrono::duration<double> elapsed = current_time - start_time;
        if (elapsed.count() >= 60) { // 运行10分钟
            break;
        }
        // std::cout << "Process " << getpid() << " is working... sum = " << sum << std::endl;
    }

    // 释放内存
    free(buffer);

    // 打印进程号,表示进程结束
    std::cout << "Process " << getpid() << " finished." << std::endl;
}

int main() {
    const int num_processes = 5;  // 要创建的进程数
    pid_t pids[num_processes];

    for (int i = 0; i < num_processes; ++i) {
        pids[i] = fork();
        if (pids[i] < 0) {
            std::cerr << "Fork failed for process " << i << "!" << std::endl;
            return 1;
        } else if (pids[i] == 0) {
            // 子进程中执行任务
            allocate_and_compute();
            return 0;
        }
    }

    // 父进程等待所有子进程完成
    for (int i = 0; i < num_processes; ++i) {
        waitpid(pids[i], nullptr, 0);
    }

    std::cout << "All processes completed." << std::endl;

    return 0;
}

2.查看当前运行的进程

3.批量将当前进程,物理地址虚拟地址映射

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

虚拟地址范围(如 00400000-00452000):进程在内存中的虚拟地址区间。

访问权限(如 r-xp):代表读(r)、写(w)、执行(x)权限,以及是否是私有的(p)或共享的(s)。

偏移量(如 00000000):文件偏移。

设备ID(如 08:02):设备号(主设备号:次设备号)。

节点号(如 787339):文件的 inode 号,表示文件在设备中的唯一标识。

路径(如 /bin/cat):映射到该内存区域的文件的路径。如果是 [heap][stack],表示进程的堆和栈区域。

4. 如何获得虚拟地址对应的pfn

在 Linux 系统中,获得一个进程的虚拟地址对应的物理帧号(Page Frame Number, PFN)可以通过以下步骤来实现。具体步骤如下:

步骤 1: 获取进程的虚拟地址

首先,通过 /proc/[pid]/maps 文件获取进程的虚拟地址范围。假设你已经知道特定的虚拟地址或者已经获取了虚拟地址范围。

步骤 2: 获取页表信息

接下来,可以通过 /proc/[pid]/pagemap 文件来查询虚拟地址对应的物理帧号(PFN)。

访问 pagemap 文件

/proc/[pid]/pagemap 文件保存了用户空间虚拟内存的页表信息,通过这个文件可以找到虚拟地址对应的物理地址(PFN)。pagemap 文件是一个二进制文件,每 8 字节表示一个页的信息。

步骤 3: 使用脚本或程序解析 pagemap 文件

可以编写一个脚本或小程序来解析 /proc/[pid]/pagemap 文件,以下是一个简单的 Python 脚本示例,来获取给定虚拟地址的 PFN。

import os
import struct

def get_pfn(pid, vaddr):
    pagesize = os.sysconf("SC_PAGE_SIZE")  # 获取系统页面大小,通常是4096字节
    pagemap_entry_size = 8  # 每个条目大小为8字节

    # 计算虚拟地址对应的页号
    vpn = vaddr // pagesize

    # 读取 pagemap 文件
    with open(f"/proc/{pid}/pagemap", "rb") as f:
        # 定位到虚拟地址对应的页表条目
        f.seek(vpn * pagemap_entry_size)
        entry = f.read(pagemap_entry_size)
        entry = struct.unpack("Q", entry)[0]

        # 检查页是否存在
        if entry & (1 << 63):
            # PFN 是低 55 位
            pfn = entry & ((1 << 55) - 1)
            return pfn
        else:
            return None

# 示例用法
pid = 1234  # 替换为实际的进程ID
vaddr = 0x7f8fbc7e8000  # 替换为实际的虚拟地址

pfn = get_pfn(pid, vaddr)
if pfn is not None:
    print(f"虚拟地址 0x{vaddr:x} 对应的 PFN 是 {pfn}")
else:
    print(f"虚拟地址 0x{vaddr:x} 不存在或未映射")

解释

  1. 页面大小os.sysconf("SC_PAGE_SIZE") 用于获取系统的页面大小(通常是 4096 字节)。

  2. 虚拟页号 (VPN):通过将虚拟地址除以页面大小,获得虚拟页号。

  3. 定位 pagemap 条目:使用 f.seek(vpn * pagemap_entry_size) 定位到对应的 pagemap 条目。

  4. 解析 pagemap 条目

    • 页表条目的最高位(第 63 位)指示页是否有效(即是否映射到物理内存)。
    • 低 55 位包含 PFN(Page Frame Number),即对应的物理帧号。

步骤 4: 验证物理帧号

使用获得的 PFN 可以进一步计算物理地址,或者通过 /proc/kpageflags 等文件查看该 PFN 对应的更多信息。

注意事项

  • 需要有权限访问 /proc/[pid]/pagemap 文件,通常需要 root 权限。
  • 如果进程内存较大,解析 pagemap 文件可能会涉及大量计算,可能需要优化或处理多个虚拟地址。

通过这个方法,你可以获取 Linux 中任意进程的虚拟地址对应的物理帧号,并进一步分析或调试内存管理问题。

5. 用C++实现获取linux某个进程对应所有的虚拟地址,并获得对应的映射物理地址pfn,将所有的虚拟地址和物理地址入vector<pair<uint64,uint64>>addrPfnVec中

要在 C++ 中实现获取 Linux 某个进程的所有虚拟地址,并获取其对应的物理地址 (Page Frame Number, PFN),然后将这些地址和 PFN 存入 std::vector> addrPfnVec 中,可以参考以下代码实现。

#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <utility>
#include <cstdint>
#include <unistd.h>
#include <sys/types.h>

#define PAGE_SIZE 4096
#define PFN_MASK ((1ULL << 55) - 1)

bool getPfn(uint64_t vaddr, pid_t pid, uint64_t &pfn) {
    std::ifstream pagemap;
    std::string pagemapPath = "/proc/" + std::to_string(pid) + "/pagemap";
    pagemap.open(pagemapPath, std::ios::in | std::ios::binary);
    if (!pagemap.is_open()) {
        std::cerr << "Failed to open " << pagemapPath << std::endl;
        return false;
    }

    uint64_t vpn = vaddr / PAGE_SIZE;
    pagemap.seekg(vpn * sizeof(uint64_t));
    
    uint64_t entry;
    pagemap.read(reinterpret_cast<char*>(&entry), sizeof(entry));

    if (entry & (1ULL << 63)) {  // Page is present in memory
        pfn = entry & PFN_MASK;
        return true;
    }
    return false;
}

void getVirtualAndPhysicalAddresses(pid_t pid, std::vector<std::pair<uint64_t, uint64_t>> &addrPfnVec) {
    std::ifstream maps;
    std::string mapsPath = "/proc/" + std::to_string(pid) + "/maps";
    maps.open(mapsPath);
    if (!maps.is_open()) {
        std::cerr << "Failed to open " << mapsPath << std::endl;
        return;
    }

    std::string line;
    while (std::getline(maps, line)) {
        std::istringstream iss(line);
        std::string addrRange;
        iss >> addrRange;

        // Split the address range into start and end
        size_t dashPos = addrRange.find('-');
        uint64_t startAddr = std::stoull(addrRange.substr(0, dashPos), nullptr, 16);
        uint64_t endAddr = std::stoull(addrRange.substr(dashPos + 1), nullptr, 16);

        for (uint64_t vaddr = startAddr; vaddr < endAddr; vaddr += PAGE_SIZE) {
            uint64_t pfn;
            if (getPfn(vaddr, pid, pfn)) {
                addrPfnVec.emplace_back(vaddr, pfn);
            }
        }
    }
}

int main() {
    pid_t pid = 1234;  // 替换为目标进程的 PID
    std::vector<std::pair<uint64_t, uint64_t>> addrPfnVec;

    getVirtualAndPhysicalAddresses(pid, addrPfnVec);

    // 打印结果
    for (const auto &entry : addrPfnVec) {
        std::cout << "Virtual Address: 0x" << std::hex << entry.first
                  << ", PFN: 0x" << entry.second << std::dec << std::endl;
    }

    return 0;
}

代码说明:

  1. getPfn 函数

    • 该函数负责读取 /proc/[pid]/pagemap 文件并获取给定虚拟地址的物理帧号(PFN)。
    • 它通过计算虚拟页号 (VPN) 并在 pagemap 文件中查找相应条目。
    • 如果该页存在,则返回 true 并将 PFN 存储在 pfn 中。
  2. getVirtualAndPhysicalAddresses 函数

    • 该函数读取 /proc/[pid]/maps 文件,提取所有虚拟地址范围。
    • 对于每个地址范围中的每一页,调用 getPfn 函数获取相应的 PFN。
    • 如果获取成功,将虚拟地址和对应的 PFN 存储在 addrPfnVec 中。
  3. main 函数

    • 这是程序的入口。你需要将 pid 设置为你希望分析的进程 ID。
    • 调用 getVirtualAndPhysicalAddresses 函数来填充 addrPfnVec,然后可以打印或进一步处理这些地址信息。

    运行效果:

    程序会输出指定进程的每个虚拟地址及其对应的物理帧号 (PFN),并存储在 addrPfnVec 中。输出格式类似:

    Virtual Address: 0x7f8fbc7e8000, PFN: 0x1c0000
    Virtual Address: 0x7f8fbc7e9000, PFN: 0x1c0001
    ...
    
    

    这些信息可用于内存调试和分析进程的内存布局。

6.给以上代码注释一下

#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <utility>
#include <cstdint>
#include <unistd.h>
#include <sys/types.h>

#define PAGE_SIZE 4096            // 定义页面大小,通常为 4096 字节(4KB)
#define PFN_MASK ((1ULL << 55) - 1)  // 定义 PFN 掩码,用于从 pagemap 条目中提取 PFN

// 函数:getPfn
// 功能:从 pagemap 文件中获取给定虚拟地址的物理帧号 (PFN)
// 参数:
//   - vaddr: 虚拟地址
//   - pid: 进程 ID
//   - pfn: 用于存储返回的 PFN 值
// 返回值:
//   - true: 如果该页存在并且 PFN 成功获取
//   - false: 如果该页不存在或获取失败
bool getPfn(uint64_t vaddr, pid_t pid, uint64_t &pfn) {
    std::ifstream pagemap;
    std::string pagemapPath = "/proc/" + std::to_string(pid) + "/pagemap";
    pagemap.open(pagemapPath, std::ios::in | std::ios::binary);
    if (!pagemap.is_open()) {
        std::cerr << "Failed to open " << pagemapPath << std::endl;
        return false;
    }

    // 计算虚拟页号 (VPN)
    uint64_t vpn = vaddr / PAGE_SIZE;
    
    // 定位到 pagemap 文件中对应的页表条目位置
    pagemap.seekg(vpn * sizeof(uint64_t));
    
    // 读取页表条目
    uint64_t entry;
    pagemap.read(reinterpret_cast<char*>(&entry), sizeof(entry));

    // 检查页是否在内存中
    if (entry & (1ULL << 63)) {  // 页存在 (Present bit 被设置)
        pfn = entry & PFN_MASK;  // 提取物理帧号 (PFN)
        return true;
    }
    return false;  // 如果页不存在,返回 false
}

// 函数:getVirtualAndPhysicalAddresses
// 功能:获取进程的所有虚拟地址,并获取它们对应的物理帧号 (PFN),存入 addrPfnVec 向量中
// 参数:
//   - pid: 进程 ID
//   - addrPfnVec: 用于存储虚拟地址和物理帧号 (PFN) 的向量
void getVirtualAndPhysicalAddresses(pid_t pid, std::vector<std::pair<uint64_t, uint64_t>> &addrPfnVec) {
    std::ifstream maps;
    std::string mapsPath = "/proc/" + std::to_string(pid) + "/maps";
    maps.open(mapsPath);
    if (!maps.is_open()) {
        std::cerr << "Failed to open " << mapsPath << std::endl;
        return;
    }

    std::string line;
    // 逐行读取 /proc/[pid]/maps 文件
    while (std::getline(maps, line)) {
        std::istringstream iss(line);
        std::string addrRange;
        iss >> addrRange;  // 获取地址范围字符串

        // 将地址范围字符串拆分为起始地址和结束地址
        size_t dashPos = addrRange.find('-');
        uint64_t startAddr = std::stoull(addrRange.substr(0, dashPos), nullptr, 16);
        uint64_t endAddr = std::stoull(addrRange.substr(dashPos + 1), nullptr, 16);

        // 遍历地址范围内的每一页
        for (uint64_t vaddr = startAddr; vaddr < endAddr; vaddr += PAGE_SIZE) {
            uint64_t pfn;
            if (getPfn(vaddr, pid, pfn)) {
                // 将虚拟地址和对应的 PFN 存入向量
                addrPfnVec.emplace_back(vaddr, pfn);
            }
        }
    }
}

// 主函数
int main() {
    pid_t pid = 1234;  // 替换为目标进程的 PID
    std::vector<std::pair<uint64_t, uint64_t>> addrPfnVec;

    // 获取虚拟地址和对应的物理帧号,并存入 addrPfnVec 向量
    getVirtualAndPhysicalAddresses(pid, addrPfnVec);

    // 打印结果
    for (const auto &entry : addrPfnVec) {
        std::cout << "Virtual Address: 0x" << std::hex << entry.first
                  << ", PFN: 0x" << entry.second << std::dec << std::endl;
    }

    return 0;
}

代码说明:

  1. calculateTotalPages 函数
    • 该函数读取 /proc/[pid]/maps 文件,遍历所有虚拟地址范围,计算出该进程的总页面数。
    • 计算方法是将每个地址范围的大小除以页面大小 (PAGE_SIZE)。
  2. 预分配容量
    • 在主函数中,使用 calculateTotalPages 函数计算出精确的页面数量,并使用 addrPfnVec.reserve(total_pages)addrPfnVec 向量分配精确的容量。

优化效果:

通过这次优化,程序能够准确预估并分配所需的内存空间,减少动态内存分配带来的开销,从而提高了处理效率。这在处理大型进程的内存映射时尤其有用。

7. 怎么确保addrPfnVec在插入的时候不会溢出

在 C++ 中,std::vector 是一个动态数组,它会自动管理其容量。如果你在向 std::vector 中插入元素时,容量不足,vector 会自动扩展以容纳新元素。因此,在一般情况下,std::vector 是不会出现溢出的问题的。

然而,为了提高效率和确保安全性,仍然有一些最佳实践可以遵循,以避免潜在的性能问题和确保程序的稳定性。

1. 预分配足够的空间

在向 std::vector 中插入大量元素之前,如果你能够预估或计算出所需的容量,最好使用 reserve 方法预先分配足够的空间。这样可以避免多次动态分配内存,提高性能。

addrPfnVec.reserve(total_pages);

total_pages 是根据进程的内存页面数量计算得出的,如果 total_pages 足够准确,这将确保 addrPfnVec 不需要在插入元素时频繁扩展。

2. 检查插入操作的有效性

虽然 std::vector 会自动扩展,但在极端情况下,内存不足可能会导致分配失败。因此,尽可能避免在低内存情况下执行大规模的内存操作。

如果你对 std::vector 的插入操作非常敏感,可以在程序中添加异常处理,以捕获可能的内存分配失败的异常:

try {
    addrPfnVec.reserve(total_pages);
} catch (const std::bad_alloc& e) {
    std::cerr << "Memory allocation failed: " << e.what() << std::endl;
    // 采取适当的措施,如释放资源或退出程序
    exit(EXIT_FAILURE);
}

3. 使用 emplace_back 而不是 push_back

在插入元素时,使用 emplace_back 代替 push_backemplace_back 会直接在容器中构造元素,避免了不必要的拷贝操作,提高效率:

addrPfnVec.emplace_back(vaddr, pfn);

4. 确保 total_pages 的计算是正确的

在确保 std::vector 不会溢出时,最关键的一步是正确计算并预分配 total_pages。你已经在 calculateTotalPages 函数中正确计算了进程所有虚拟内存范围的页面总数。

5. 运行时监控

在开发和测试过程中,可以使用工具或调试器监控 std::vector 的容量扩展和内存使用情况,确保它按照预期工作。

总结:

  • std::vector 本身会自动管理容量,不会出现插入溢出问题。
  • 预分配足够的空间 (reserve) 是提高性能的好方法,特别是在你知道大概需要多少空间时。
  • 异常处理可以捕获极端情况下的内存分配失败,确保程序的健壮性。
  • 使用 emplace_back 可以减少不必要的拷贝操作,提高插入效率。

通过这些措施,你可以确保 addrPfnVec 在插入大量元素时安全且高效。

8. 怎么验证映射的物理地址pfn的准确性

1. 对照 /proc/[pid]/maps/proc/[pid]/pagemap

  • 你已经通过 /proc/[pid]/maps 获取了进程的虚拟地址范围,然后通过 /proc/[pid]/pagemap 计算得到了虚拟地址对应的 PFN。
  • 验证的一种简单方式是随机选择几个虚拟地址,并手动计算和查找它们的 PFN,然后与程序输出的结果进行比对。

2. 通过 /proc/kpageflags/proc/kpagecount 验证 PFN

  • /proc/kpageflags/proc/kpagecount 文件包含了系统中所有物理页的信息。你可以通过这些文件获取物理页的属性和使用情况来验证 PFN 的准确性。
读取 /proc/kpageflags/proc/kpagecount
  • /proc/kpageflags 文件的每个条目对应于一个物理帧。它保存了物理帧的一些标志,比如是否被分配,是否是匿名页,是否属于缓存等。
  • /proc/kpagecount 文件则保存了每个物理帧的引用计数,表示该物理帧被多少个映射引用。

你可以通过读取这些文件来验证 PFN 的一些特性。例如,如果某个 PFN 被分配且是匿名页,且你的进程确实有匿名内存映射到该 PFN,那么该 PFN 应该在 /proc/kpageflags 中被标记为已分配和匿名。

示例代码(验证 PFN 是否有效并打印其标志和引用计数):
#include <iostream>
#include <fstream>
#include <vector>
#include <cstdint>
#include <unistd.h>

#define PAGE_SIZE 4096

bool checkPfnInKpagefiles(uint64_t pfn) {
    std::ifstream kpageflags("/proc/kpageflags", std::ios::in | std::ios::binary);
    std::ifstream kpagecount("/proc/kpagecount", std::ios::in | std::ios::binary);

    if (!kpageflags.is_open() || !kpagecount.is_open()) {
        std::cerr << "Failed to open /proc/kpageflags or /proc/kpagecount" << std::endl;
        return false;
    }

    kpageflags.seekg(pfn * sizeof(uint64_t));
    kpagecount.seekg(pfn * sizeof(uint64_t));

    uint64_t flags, count;
    kpageflags.read(reinterpret_cast<char*>(&flags), sizeof(flags));
    kpagecount.read(reinterpret_cast<char*>(&count), sizeof(count));

    std::cout << "PFN: " << pfn << std::endl;
    std::cout << "Flags: " << flags << std::endl;
    std::cout << "Page count: " << count << std::endl;

    // 检查是否是分配的页
    if (flags & (1ULL << 0)) {
        std::cout << "Page is allocated." << std::endl;
    } else {
        std::cout << "Page is not allocated." << std::endl;
    }

    return true;
}

int main() {
    uint64_t pfn = 123456;  // 替换为你要验证的 PFN
    checkPfnInKpagefiles(pfn);
    return 0;
}

输出解释:
  • Flags:物理帧的标志,表示该物理帧的状态。
  • Page count:引用计数,表示有多少映射引用了这个物理帧。

通过这种方式,你可以验证 PFN 是否有效,是否被分配,以及其引用计数是否符合预期。

3. 与预期结果对比

  • 在特定的测试场景下,你可以手动计算或预期某些虚拟地址应该映射到哪些物理地址。将这些预期值与程序计算出的 PFN 进行对比,来验证计算的准确性。

4. 跨多个工具验证

  • 使用不同的工具(如 pmapsmemproc 文件系统等)交叉验证你的 PFN 计算是否正确。例如,pmap 可以显示进程的内存映射,你可以与 /proc/[pid]/maps 结果进行对比,确保虚拟地址范围一致。

5. 验证 PFN 对应的物理内存内容

  • 你可以通过 dd 命令从 /dev/mem 中读取物理内存内容,然后将其与进程的虚拟内存内容进行对比,确保映射关系正确。
  • 这种方法涉及到直接访问物理内存,通常需要较高的权限,并且要非常小心以免影响系统稳定性。

6. 分析特定的内存区域

  • 对于某些特定的内存区域,如代码段、数据段、堆、栈,你可以结合编译器和链接器输出的信息,精确知道这些段应映射到哪些物理页,并以此验证 PFN 的准确性。

总结:

通过上述方法,你可以较为全面地验证计算出的 PFN 是否准确。具体选择哪种方法,取决于你验证的目的和需要的精确度。结合这些工具和方法,可以有效地确保程序计算的虚拟地址与物理地址映射的正确性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值