Linux中fork()与物理地址异常问题:普通用户与Root模式差异
问题:普通用户与Root权限下父子进程物理地址输出差异解析
现象说明
运行如下C程序时,出现两种截然不同的输出结果:
- 普通用户模式下,父进程与子进程输出的物理地址完全相同
- Root用户模式下,两者的物理地址存在明显差异
普通用户模式输出
pid:5269, ppid:3152 pid:5270, ppid:5269 Child process : � virtual addr of str=0x7ffd7023bfd0 and &count=0x7ffd7023bfcc, physical addr of str=0xfd0,&count=0xfcc Father process : � count: 1 (0x7ffd7023bfcc), pid: 5269 virtual addr of str=0x7ffd7023bfd0 and count=0x7ffd7023bfcc, physical addr of str=0xfd0,&count=0xfcc count: 2 (0x7ffd7023bfcc), pid: 5270
Root用户模式输出
pid:5294, ppid:3414 pid:5295, ppid:5294 Child process : � virtual addr of str=0x7ffe501a1530 and &count=0x7ffe501a152c, physical addr of str=0x1298db530,&count=0x1298db52c Father process : � count: 1 (0x7ffe501a152c), pid: 5294 virtual addr of str=0x7ffe501a1530 and count=0x7ffe501a152c, physical addr of str=0x12282b530,&count=0x12282b52c count: 2 (0x7ffe501a152c), pid: 5295
相关代码
// file name proc-1.c #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> #include <fcntl.h> #include <stdint.h> #include <sys/stat.h> //#include <linux/capability.h> //#include <linux/sched.h> intptr_t mem_addr(unsigned long vaddr, unsigned long *paddr) { int pagesize = getpagesize(); unsigned long v_pageindex = vaddr / pagesize; unsigned long v_offset = v_pageindex * sizeof(uint64_t); unsigned long page_offset = vaddr % pagesize; uint64_t item = 0; int fd = open("/proc/self/pagemap", O_RDONLY); lseek(fd, v_offset, SEEK_SET); read(fd, &item, sizeof(uint64_t)); if((((uint64_t)1 << 63) & item) == 0) { printf("page present is 0\n"); return 0 ; } uint64_t phy_pageindex = (((uint64_t)1 << 55)- 1) & item; *paddr = (phy_pageindex * pagesize) + page_offset; return *paddr; } int main(void) { char str[10]; int count = 1; unsigned long pa[2]={0,0}; int fd = open("test.txt", O_RDWR); if(fork() == 0) { printf("pid:%d, ppid:%d\n",getpid(), getppid()); read(fd, str, 10); count += 5; printf("Child process : %s\n", (char *)str); mem_addr((unsigned long)str, &pa[0]); mem_addr((unsigned long)&count, &pa[1]); printf("virtual addr of str=%p and count=%p, physical addr of str=%p,&count=%p\n",str,&count, pa[0], pa[1]); printf("count: %d (%p), pid: %d\n", count, &count, getpid()); } else { printf("pid:%d, ppid:%d\n",getpid(), getppid()); read(fd, str, 10); //count ++; printf("virtual addr of str=%p and &count=%p, physical addr of str=%p,&count=%p\n",str,&count, mem_addr((intptr_t)str, &pa[0]), mem_addr((intptr_t)&count,&pa[1])); printf("Father process : %s\n", (char *)str); printf("count: %d (%p), pid: %d\n", count, &count, getpid()); } sleep(10); return 0; }
原因解析
1. 普通用户模式:虚假的相同物理地址
Linux系统对/proc/self/pagemap文件做了权限限制:普通用户仅能读取该文件的第63位(页存在标志位),其余字段(包括真实物理页框号)会被内核清零或返回无效值。
代码中普通用户读取到的phy_pageindex实际为0,计算出的物理地址等于虚拟地址的页内偏移量(比如0xfd0就是0x7ffd7023bfd0 % 页面大小的结果)。由于父子进程的虚拟地址处于同一个内存页,页内偏移相同,所以输出的物理地址看起来完全一致,但这并不是真实的硬件物理地址,属于无效计算结果。
2. Root模式:真实的写时复制(COW)效果
Root用户拥有完整读取pagemap文件的权限,可以获取到真实的物理页框号:
fork()创建子进程后,父子进程共享相同的虚拟地址空间和物理内存页;- 当子进程执行
count +=5时,对共享内存页执行写操作,触发Linux的**写时复制(Copy-On-Write)**机制:内核会为子进程复制一份该页的物理副本,父子进程的虚拟地址从此映射到不同的物理页; - 这就是Root模式下,父子进程相同虚拟地址对应不同物理地址的根本原因。
内容的提问来源于stack exchange,提问作者V_town
相关产品推荐
相关产品推荐

