1、将虚拟地址传入到内核态,借助内核态中mm_struct结构体的pgd页表基地址成员,经过查页表的方式最终获取到物理地址。这种方法虽然很直观,但是一会内核态,一会用户态,操作起来相对表麻烦。虚拟地址如何访问到物理地址_sydyh43的博客-CSDN博客_虚拟地址计算物理地址但是有一点可以明确的,转换出来,获取到的是页帧号(即页对齐),最后加上虚拟地址的低N位(32bit的系统是低12位)得到最终的物理地址。
2、内核开发者会把很多基于内核的操作挪到用户态实现。借助用户态众多的系统接口实现与之前在内核态一样的功能。虚拟地址转换成物理地址就是这么一个例子。
通过
man proc
查看到其中有一项/pro/[pid]/pagemap,可以看出54~0就是对应着物理地址的页帧号,再加上虚拟地址的低12位,就是虚拟地址对应的物理地址。
3、 在用户态,读取/proc/[pid]/pagemap这个节点的数据,获取物理内存的页帧号。
3.1、进程下有很多虚拟地址,pagemap文件相当于存放物理地址的一张表格,需要根据虚拟地址获取到在这张表格的下标,这个下标对应的表格,存放的就是这个虚拟地址对应的物理地址等信息。
#ifdef _ENV_IS_64_
#define PAGEMAP_SIZE sizeof(unsigned long)
#else
#define PAGEMAP_SIZE sizeof(unsigned long long)
#endif
/*
* 1、需要把addr转换成页帧,即下标
* 2、每个下标存放数据的空间是PAGESIZE,需要乘以PAGEMAP_SIZE
* 3、unsigned long的sizeof由CPU环境决定。32bit是4, 64bit是8
*/
size_t offset = (addr/4096) * PAGEMAP_SIZE;
3.2、获取pagemap中的数值后,最终转换成实际的物理地址
/*
* 1、获取pte后,&上地55bit都是高的数值
* 2、左右12bit,加上低12bit的偏移量,获得最终的物理地址
*/
read(fd, &pte, PAGEMAP_SIZE);
phy_addr = ((pte & ((((unsigned long)1) << 55) - 1)) << 12) + addr%4096;
3.3、此刻,我们来验证一下bit63的数值变化,感知下用户态malloc一段内存空间后,暂没有分配内存空间,而是当使用到的时候才会分配实际的物理内存。
有些平台使用的时候,proc目录下没有pagemap的节点,需要打开对应(fs/proc/base.c)的宏CONFIG_PROC_PAGE_MONITOR
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define _ENV_IS_32_
#ifdef _ENV_IS_64_
#define PAGEMAP_SIZE sizeof(unsigned long)
#else
#define PAGEMAP_SIZE sizeof(unsigned long long)
#endif
int main(int argc, char **argv)
{
int fd = 0;
pid_t pid = 0;
char bit_val = 0;
unsigned long addr = 0;
unsigned long phy_addr = 0;
#ifdef _ENV_IS_64_
unsigned long pte;
#else
unsigned long long pte;
#endif
size_t offset = 0;
char procbuf[64] = {0};
pid = getpid();
sprintf(procbuf, "/proc/%d/pagemap", pid);
addr = (unsigned long)malloc(100);
if (!addr) {
printf("malloc error.\n");
return -1;
}
fd = open(procbuf, O_RDONLY);
offset = (addr/4096) * PAGEMAP_SIZE;
lseek(fd, offset, SEEK_SET);
read(fd, &pte, PAGEMAP_SIZE);
phy_addr = (pte & 0x7FFFFFFFFFFFFF)*4096 + addr%4096; //(((1) << 55) - 1)
bit_val = !!(pte & 0x8000000000000000); //((1) << 63)
printf("\nbef: pte=0x%llx, phy addr:%x, in ram:%d\n", pte, phy_addr, bit_val);
memset((char *)addr, 0x00, 100);
lseek(fd, offset, SEEK_SET);
read(fd, &pte, PAGEMAP_SIZE);
phy_addr = (pte & 0x7FFFFFFFFFFFFF)*4096 + addr%4096;
bit_val = !!(pte & 0x8000000000000000);
printf("aft: pte=0x%llx, phy addr:%x, in ram:%d\n", pte, phy_addr, bit_val);
close(fd);
return 0;
}