一个关于Linux中fork()和物理地址的奇怪问题

问题描述 投票:0回答:1

一位教授 Linux 的教授向他的学生发送了这个奇怪的问题......

奇怪的是,这个程序会给出一个输出,其中父进程和子进程在普通用户模式下运行时将获得相同的物理地址,但是当我在根用户模式下运行时,输出显示:父母和孩子有不同的物理地址,如下所示。

当处于普通用户模式时:

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
,但我就是无法解决它。

linux process linux-kernel fork memory-address
1个回答
0
投票

当您不是 root 时,您只是从

/proc/self/pagemap
读取归零的页框编号。您需要
CAP_SYS_ADMIN
才能获得正确的 PFN。事实上,您获得的“物理地址”很可疑
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 进程中读取

/proc/[pid]/pagemap
。启动第一个,让它打印 PID 并暂停等待输入,然后用另一个以 root 身份运行的进程检查
/proc/[pid]/pagemap
。你会看到两个物理地址是不同的。

© www.soinside.com 2019 - 2024. All rights reserved.