Linux 获取虚拟地址对应的物理地址【转】

现代操作系统中,进程是运行在虚拟地址空间上的。比如在32位机器上,进程可以认为自己有4GB的内存空间可以使用。但是真实被使用到的虚拟地址肯定得有真实的存储介质相对应。如果不考虑swap的话,每一个虚拟地址都对应一个物理地址。现代的硬件一般是以分页的方式管理内存的(x86的分段与分页相结合的所谓段页式也只是为了兼容罢了)。一个虚拟页映射到一个物理页。以常见的4KB的页大小为例,如果一个虚拟地址0xfe0000对应物理地址的0x40000,那么接下来的虚拟地址0xfe0000+i就对应物理地址的0x40000+i,i=0,1,2,...,4095。但是虚拟地址的0xfe1000可能对应着一个相去甚远的物理地址0x1c0000呢。也就是说,在虚拟地址中连续的地址,在物理上可能是碎片似的分散在内存条的各个地方,但是在一个页内,地址是连续地一一对应的。

鉴于此,要把一个虚拟地址转换成物理地址,其实就是要知道该虚拟地址所在的虚拟页对应的物理页。知道了物理页,再加上页内偏移量即可。以4KB的页大小为例,一个32位长的虚拟地址,其高20位就称为虚拟页号,低12位就是页内偏移。Linux为每一个进程都维护了一个页表,放在内存中。页表的每一项就是一个虚拟页号对应的物理页号。所以如果能够访问到页表,那么就能够把虚拟地址转换成物理地址。然而,只有在内核态才有权限访问页表,用户态是无权访问的。另外,不同的硬件结构下,页表的定位方式是不同的,而且可能很复杂,涉及多个寄存器。

一开始我没有找到能在用户态查询页表的方式。就当我快绝望的时候,万能的GitHub又帮我我一把。我在某个项目中发现了其访问/proc/<pid>/pagemap这个虚拟文件。后来我去查了这个虚拟文件,得到如下信息。

Documentation/vm/pagemap.txt

每一个页对应一个64位,也就是8字节的字段。比如虚拟地址0xfe0020,其高20位为0xfe0,也就是其虚拟页号为0xfe0。那么该虚拟页的信息处于/proc/self/pagemap这个文件中偏移量为0xfe0*8=32512的地方。从此处读取一个8字节的数据,先检查最高位'page present',如果是1,那么说明该页处于物理内存中,那么该8字节的第0-54位就是物理页号。假设物理页号是0x40,那么实际的物理地址就是(0x40<<12)+0x20=0x40020。

介绍完原理,那么封装成一个函数就简单多了。我这里就封装一个最容易理解但是效率最低的实现方式:

#include <fcntl.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>

size_t virtual_to_physical(size_t addr)
{
    int fd = open("/proc/self/pagemap", O_RDONLY);
    if(fd < 0)
    {
        printf("open '/proc/self/pagemap' failed!\n");
        return 0;
    }
    size_t pagesize = getpagesize();
    size_t offset = (addr / pagesize) * sizeof(uint64_t);
    if(lseek(fd, offset, SEEK_SET) < 0)
    {
        printf("lseek() failed!\n");
        close(fd);
        return 0;
    }
    uint64_t info;
    if(read(fd, &info, sizeof(uint64_t)) != sizeof(uint64_t))
    {
        printf("read() failed!\n");
        close(fd);
        return 0;
    }
    if((info & (((uint64_t)1) << 63)) == 0)
    {
        printf("page is not present!\n");
        close(fd);
        return 0;
    }
    size_t frame = info & ((((uint64_t)1) << 55) - 1);
    size_t phy = frame * pagesize + addr % pagesize;
    close(fd);
    return phy;
}

最后需要说明的是,只有root权限的进程能够访问/proc/<pid>/pagemap这个文件,否则读出来的都是0。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一个示例程序,用于在Linux中将虚拟地址换为物理地址: ```c #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <sys/mman.h> #define PAGE_SIZE 4096 int main() { int fd; void *mapped_memory; off_t offset; unsigned long virtual_addr, physical_addr; // 打开/proc/self/pagemap文件 fd = open("/proc/self/pagemap", O_RDONLY); if (fd < 0) { perror("open"); exit(EXIT_FAILURE); } // 输入要换的虚拟地址 printf("Enter the virtual address: "); scanf("%lx", &virtual_addr); // 计算虚拟地址对应的页表项在pagemap文件中的偏移量 offset = (virtual_addr / PAGE_SIZE) * sizeof(unsigned long long); // 定位到页表项的位置 if (lseek(fd, offset, SEEK_SET) == -1) { perror("lseek"); close(fd); exit(EXIT_FAILURE); } // 读取页表项的值 if (read(fd, &physical_addr, sizeof(unsigned long long)) == -1) { perror("read"); close(fd); exit(EXIT_FAILURE); } // 关闭文件描述符 close(fd); // 检查页面是否存在 if ((physical_addr & 0x8000000000000000) == 0 || (physical_addr & 0x1ff) == 0) { printf("Page not present or not mapped!\n"); exit(EXIT_FAILURE); } // 获取页框号并计算物理地址 physical_addr = (physical_addr & 0x7fffffffffffff) * PAGE_SIZE + (virtual_addr & (PAGE_SIZE - 1)); printf("Physical address: 0x%lx\n", physical_addr); return 0; } ``` 这个程序使用了`/proc/self/pagemap`文件来获取虚拟地址对应物理地址。在Linux中,每个进程的虚拟地址空间都有一个对应的`/proc/pid/pagemap`文件,其中存储着每个虚拟页对应的页表项信息。通过打开该文件并读取相应的页表项,可以获取虚拟地址对应物理地址。 请注意,这段代码需要以root权限运行,并且只能在x86-64架构的系统上使用。在其他架构上可能需要修改程序中的一些细节。 希望这可以帮助到你!如果有任何问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值