使用pagemap来检验fork的写时复制

首先我们先要了解以下了解以下几种地址
物理地址:是真实存在的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

使用pagemap来检验fork的写时复制_第1张图片

偏移到具体的项之后 我们需要读取现在偏移量上的值,读取8字节,使用uint64_t类型

接着我们需检查对应的项对应的页是不是在物理内存上,item_bit的最高位present是就是指示它是不是在物理内存上,如果是我们就找到item_bit的低55位(Bits 0-54: page frame number(PFN) if present),即物理页框号,这里相当于说物理内存是一个大的数组,每个数组元素为4K(一个页那么大),而现在的item_bit的低55位是数组的索引,而偏移地址是一直没变的,始终是上面的phy_index,现在找到了具体的物理页的框的偏移量,又找到了该物理页内的偏移量,二者相加就得到了具体的物理地址

使用pagemap来检验fork的写时复制_第2张图片

在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的值

使用pagemap来检验fork的写时复制_第3张图片

可以看到,子进程和父进程映射的是同一个逻辑页,物理页,
接下来我们在fork之后加入下面代码
 if(pid==0)
 {
		 strcpy(s,"hello");
 }
 else
 {
		 strcpy(s,"world");
 }
运行程序

可以看到它们还是映射同一个逻辑页,但是映射的是不同的物理页,证明在进行写入数据后,又额外分配了新的页面
原理看一下《深入理解计算机系统》

使用pagemap来检验fork的写时复制_第4张图片

最后补充一下
(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来检验fork的写时复制_第5张图片

每一个进程都有对应的pagemap文件,外部程序可以通过/proc/self目录访问当前进程的pagemap,因为self目录负责维护每一个连接到它的程序的链接工作。所以我们在程序中有

int fd=open("/proc/self/pagemap",O_RDONLY);

使用pagemap来检验fork的写时复制_第6张图片

测试完整程序

#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);
*/

}

你可能感兴趣的:(Linux)