首先我们先要了解以下了解以下几种地址
物理地址:是真实存在的RAM的具体存储单元地址
逻辑地址:由于x86自80838以来出现的分段内存管理方式,操作系统将内存分为若干个段,各个段的起始地址(段基址)由CS,DS,SS,ES,我们平时的程序对变量取的地址叫作段内偏移地址,也就是逻辑地址。
线性地址:线性地址的组成为段基址<<4+逻辑地址(一般情况),线性地址就是存在于虚拟地址空间上的地址,又称为虚拟地址。
接下来我们需要了解一下pagemap文件,pagemap文件是每个进程私有的虚拟页到物理页的映射情况,每个pagemap项由8个字节64bit组成。格式如下
/proc/pid/pagemap:这个文件允许一个用户态的进程查看到每个虚拟页映射到的物理页,每一个虚拟页都包含了一个64位的值,信息如下:
Bits 0-54: page frame number(PFN) if present
Bits 0-4: swap type if swapped
Bits 5-54: swap offset if swapped
Bits 55-60:page shift
Bit 61: reserved ofr future use
Bit 62: page swapped
Bit 63: page present
如果这个page是在swap状态,然后PFN包含一个编码的交换文件号码,再将页的offset值写入swap中。没有映射的页就返回一个null PFN。这样就可以精确判断一个page是映射的或是swap的,并且可以比较不同进程间的映射页。
我们可以利用/proc/pid/maps去判断内存中哪块区域是被映射的,然后通过llseek就可以跳过没有映射的区域。
有此,我们假设出入&a,则&a/4k=vir_index &a%4k=m_offset(物理页面内的偏移量),我们便写出了如下代码
接着我们需要跳过文件中的若干个item项,具体偏移多少呢?当然是vri_index*8
偏移到具体的项之后 我们需要读取现在偏移量上的值,读取8字节,使用uint64_t类型
接着我们需检查对应的项对应的页是不是在物理内存上,item_bit的最高位present是就是指示它是不是在物理内存上,如果是我们就找到item_bit的低55位(Bits 0-54: page frame number(PFN) if present),即物理页框号,这里相当于说物理内存是一个大的数组,每个数组元素为4K(一个页那么大),而现在的item_bit的低55位是数组的索引,而偏移地址是一直没变的,始终是上面的phy_index,现在找到了具体的物理页的框的偏移量,又找到了该物理页内的偏移量,二者相加就得到了具体的物理地址
在main函数内下面程序
76 int a=10;
77 printf("%x\n",&a);
78 uint64_t phy=vir_to_phy(&a);
79 printf("addr=%x\n",phy);
编译运行
基本的地址映射过程我们解决了,我们现在来看fork的写时复制
屏蔽上面的代码,我们写出如下代码
char* s=(char*)malloc(128);
assert(s!=NULL);
pid_t pid=fork();
assert(pid!=-1);
uint64_t phy=vir_to_phy(s);
if(pid==0)
{
printf("child s=%x\n",s);
printf("child phy=%x\n",phy);
printf("\n");
}
else
{
printf("parent s=%x\n",s);
printf("parent phy=%x\n",phy);
}
在fork之前申请了一块内存,并在子进程和进程中分别打印s的值
可以看到,子进程和父进程映射的是同一个逻辑页,物理页,
接下来我们在fork之后加入下面代码
if(pid==0)
{
strcpy(s,"hello");
}
else
{
strcpy(s,"world");
}
运行程序
可以看到它们还是映射同一个逻辑页,但是映射的是不同的物理页,证明在进行写入数据后,又额外分配了新的页面
原理看一下《深入理解计算机系统》
最后补充一下
(1)pagemap文件的格式
1)/proc/pid/pagemap:这个文件允许一个用户态的进程查看到每个虚拟页映射到的物理页,每一个虚拟页都包含了一个64位的值,信息如下:
Bits 0-54: page frame number(PFN) if present
Bits 0-4: swap type if swapped
Bits 5-54: swap offset if swapped
Bits 55-60:page shift
Bit 61: reserved ofr future use
Bit 62: page swapped
Bit 63: page present
如果这个page是在swap状态,然后PFN包含一个编码的交换文件号码,再将页的offset值写入swap中。没有映射的页就返回一个null PFN。这样就可以精确判断一个page是映射的或是swap的,并且可以比较不同进程间的映射页。
我们可以利用/proc/pid/maps去判断内存中哪块区域是被映射的,然后通过llseek就可以跳过没有映射的区域。
64bb8
0110 0100 1011 1011 1000 0100 1110 0100
/proc/[pid]/pagemap (since Linux 2.6.25)
This file shows the mapping of each of the process's virtual
pages into physical page frames or swap area. It contains one
64-bit value for each virtual page, with the bits set as
follows:
63 If set, the page is present in RAM.
62 If set, the page is in swap space
61 (since Linux 3.5)
The page is a file-mapped page or a shared
anonymous page.
60-56 (since Linux 3.11)
Zero
55 (since Linux 3.11)
PTE is soft-dirty (see the kernel source file
Documentation/vm/soft-dirty.txt).
54-0 If the page is present in RAM (bit 63), then these
bits provide the page frame number, which can be
used to index /proc/kpageflags and
/proc/kpagecount. If the page is present in swap
(bit 62), then bits 4-0 give the swap type, and
bits 54-5 encode the swap offset.
Before Linux 3.11, bits 60-55 were used to encode the base-2
log of the page size.
(2)由于我们需要接受8字节无符号类型,所以需要加入stdint.h这个头文件,引入uint64_t类型
(3)我们对于每个程序,在/proc目录中都有唯一的pid目录对应,如下
每一个进程都有对应的pagemap文件,外部程序可以通过/proc/self目录访问当前进程的pagemap,因为self目录负责维护每一个连接到它的程序的链接工作。所以我们在程序中有
int fd=open("/proc/self/pagemap",O_RDONLY);
测试完整程序
#include
#include
#include
#include
#include
#include
#include
#define PAGE_SIZE 4096
#define ITEM_SIZE 8
uint64_t vir_to_phy(unsigned long vaddr)
{
unsigned long vir_index=vaddr/PAGE_SIZE;
unsigned long m_offset=vaddr%PAGE_SIZE;
unsigned long file_offset=vir_index*ITEM_SIZE; //file inner offer
int fd=open("/proc/self/pagemap",O_RDONLY);
if(fd<0)
{
perror("open error");
}
lseek(fd,file_offset,SEEK_SET);
uint64_t item_bit=0;
read(fd,&item_bit,sizeof(uint64_t));
if(item_bit & (uint64_t)1<<63)
{
uint64_t phy_index=item_bit & (((uint64_t)1<<55) -1);
uint64_t phy_add=phy_index*PAGE_SIZE+m_offset;
return phy_add;
}
else
{
printf("phy addr not exist\n");
}
close(fd);
}
int main()
{
// printf("size=%d",sizeof(uint64_t));
//
//
char* s=(char*)malloc(128);
assert(s!=NULL);
pid_t pid=fork();
assert(pid!=-1);
/*
if(pid==0)
{
strcpy(s,"hello");
}
else
{
strcpy(s,"world");
}
*/
uint64_t phy=vir_to_phy(s);
if(pid==0)
{
printf("child s=%x\n",s);
printf("child phy=%x\n",phy);
printf("\n");
}
else
{
printf("parent s=%x\n",s);
printf("parent phy=%x\n",phy);
}
/*
int a=10;
printf("%x\n",&a);
uint64_t phy=vir_to_phy(&a);
printf("addr=%x\n",phy);
*/
}