一位教授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有关,但我就是解决不了它.

推荐答案

当您不是超级用户时,您只是在读取一个从/proc/self/pagemap开始归零的页框编号.您需要CAP_SYS_ADMIN个才能获得正确的PFN.事实上,你得到的"物理地址"可能是0xfd00xfcc,太低了.它们只是0 + page_offset的结果.

Kernel documentation证实了以上几点:

从Linux 4.0开始,只有具有CAP_SYS_ADMIN能力的用户才能获得PFN. 在4.0和4.1中,以非特权FAIL打开-EPERM.Starting from 4.2 the PFN field is zeroed if the user does not have CAP_SYS_ADMIN. 原因:有关PFN的信息有助于利用Rowhammer漏洞.

您正在判断的变量在堆栈上,父级和子级的堆栈不可能共享相同的物理地址.如果你仔细想想,事情很容易就会破裂.一旦子级接触到堆栈(即,只要fork()对其局部变量或返回做了任何操作),copy-on-write就会发生,并且子级将获得不同于父级的新的物理页面.

为了正确地观察到这一点,即使进程不是根进程,您也必须从另一个根进程读取/proc/[pid]/pagemap.启动第一个进程,使其打印ID并暂停等待输入,然后以超级用户身份运行另一个进程判断/proc/[pid]/pagemap.您将看到这两个物理地址是不同的.

Linux相关问答推荐

如何根据具体情况打印两行输出?

使用信号处理程序实现Hibernate 功能

无法在嵌入式 Linux 中使用 libjpeg 以 RGB888 在 LCD(黑色)上显示 JPEG

如何使用netcat为DPDK实例提供输入?

如何使用 Golang 清除终端中的最后一行

使用 grep 时如何跳过第一行和最后一行?

如何在Linux中将文件的特定行号中的数字乘以2(双)?

提交SLURM作业(job)时出现nohup问题

使用 awk 将索引列添加到 csv

如何在 Linux 上使用 Python 判断进程是否仍在运行?

如何使用多个版本的 GCC

为什么在 Linux 中使用 select

diff 命令仅获取不同行的数量

将 jiffies 转换为毫秒

根据文件名模式和文件内容列出文件名?

如何使用终端打开-虚线文件名?

如何为 Git 命令设置自动完成功能?

conda 命令会提示错误:Bad Interpreter: No such file or directory

Linux:删除多个文件的文件扩展名

在tmux中绑定Ctrl+Tab和Ctrl+Shift+Tab