提问人:V_town 提问时间:10/30/2023 最后编辑:Marco BonelliV_town 更新时间:10/31/2023 访问量:41
关于 Linux 中 fork() 和物理地址的奇怪问题
A weird question about fork() and physical address in Linux
问:
一位教授 Linux 的教授向他的学生提出了这个奇怪的问题......
奇怪的是,这个程序会给出一个输出,其中父进程和子进程在公共用户模式下运行时将获得相同的物理地址,但是当我在 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;
}
我确信这个奇怪的问题应该与文件有关,但我就是无法解决它。/proc/pid/pagemap
答:
您只是在读取非 root 用户时的清零页码。您需要获取正确的 PFN。事实上,你得到的“物理地址”是可疑的,而且太低了。它们只是 ./proc/self/pagemap
CAP_SYS_ADMIN
0xfd0
0xfcc
0 + page_offset
内核文档证实了上述内容:
从 Linux 4.0 开始,只有具有 CAP_SYS_ADMIN 功能的用户才能获得 PFN。 在 4.0 和 4.1 中,通过非特权打开失败,并显示 -EPERM。起价 4.2 如果用户没有 CAP_SYS_ADMIN,则 PFN 字段为零。 原因:有关 PFN 的信息有助于利用 Rowhammer 漏洞。
您正在检查的变量位于堆栈上,父级和子级的堆栈不可能共享相同的物理地址。如果你仔细想想,否则事情很容易破裂。一旦堆栈被子级触及(即,一旦对其局部变量或返回执行任何操作),就会发生写入时复制,并且子级将获得一个与父级页面不同的新物理页面。fork()
即使使用非 root 进程,也要正确观察这一点,您必须从另一个 root 进程中读取。启动第一个,让它打印 PID 并暂停等待输入,然后使用 root 身份运行的另一个进程进行检查。您将看到两个物理地址是不同的。/proc/[pid]/pagemap
/proc/[pid]/pagemap
下一个:为什么变量的地址在这里发生变化?
评论