微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

为什么不解决分叉过程中的更改?

如何解决为什么不解决分叉过程中的更改?

我试图了解fork()并处理地址空间。我编写了一个基本的概念证明程序,该程序分叉了一个新过程并在新过程中更改了变量。我的期望是,当我在孩子中更改变量时,这应该导致该变量获得新地址。如果我理解正确,Linux会使用fork进行写时复制。因此,我希望父级和子级中的变量地址匹配,直到我在其中之一中更改它为止。然后,我希望它们会有所不同。但是,那不是我所看到的。

这是因为通过写时复制从物理内存中分配了一个页面,但是进程地址空间没有改变-仅由TLB重新映射到新页面吗?还是我不理解这一点或在程序中犯了一个转储错误

概念证明代码

#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

void describe(const std::string &descr,const int &data) {
    pid_t ppid = getppid();
    pid_t pid = getpid();

    std::cout << "In " << descr << ":\n"
              << "Parent Process ID:  " << ppid
              << "\nMy Process ID:  " << pid
              << "\nValue of data:  " << data
              << "\nAddress of data:  " << &data << "\n\n";
}

void change(int &data) {
    // Should cause data to get new page frame:
    data *= 2;
}

int main () {
    int data = 42;
    int status;

    pid_t pid = fork();

    switch(pid) {
        case -1:
            std::cerr << "Error:  Failed to successfully fork a process.\n";
            exit(1);
            break;
        case 0:
            // In forked child
            describe("Child",data);
            // Lazy way to wait for parent to run describe:
            usleep(1'000);
            break;
        default:
            // In calling parent
            describe("Parent",data);
            // Lazy way to wait for child to run describe:
            usleep(1'000);
    }

    if (pid == 0) {
        std::cout << "Only change data in child...\n";
        change(data);
        describe("Child",data);
    } else {
        // Lazy way to wait for child to change data:
        usleep(1'000);
        describe("Parent",data);
    }

    // Wait for child:
    if (pid != 0) {
        wait(&status);
    }

    return 0;
}

示例运行:

ubuntuvm:~$ ./example
In Parent:
Parent Process ID:  265569
My Process ID:  316986
Value of data:  42
Address of data:  0x7fffb63878d4

In Child:
Parent Process ID:  316986
My Process ID:  316987
Value of data:  42
Address of data:  0x7fffb63878d4

Only change data in child...
In Child:
Parent Process ID:  316986
My Process ID:  316987
Value of data:  84
Address of data:  0x7fffb63878d4

In Parent:
Parent Process ID:  265569
My Process ID:  316986
Value of data:  42
Address of data:  0x7fffb63878d4

解决方法

我的期望是,当我在孩子中更改变量时,这应该导致该变量获得新地址。

否,因为它们是虚拟地址。

如果我理解正确,Linux会使用fork进行写时复制。因此,我希望父母和孩子中的可变地址能够匹配,直到我在其中之一中更改它为止。

将在某处使用新的物理页面,但虚拟地址可以(并将)保持不变。

这是因为通过写时复制从物理内存分配了一个新页面,但是进程地址空间没有改变-只是由TLB重新映射到了新页面吗?

当然。否则,它将没有太大用处。如果它按您说的那样工作,那么考虑到您在分叉之前的任何指针将突然变得无效。考虑一下这样简单的代码:

int * p = new int;

if (!fork()) {
    // the child

    *p = 42;

    // now `p` is invalid since we wrote to it?!

    // another read or write would segfault!
    *p = 43;
}

从某种意义上讲,这就像在其中一个游戏上有一个直播程序时,一旦您踩到平台(针对我们的页面)时,它们就会掉下来。很好玩! :)

我们可以通过使操作系统或CPU能够用某种方式用新地址重写(以某种方式)将指针保持新状态来检查问题的解决方法。

但是,即使有可能,我们还有更多问题。例如,您需要处理覆盖多个页面的分配。想象一下堆栈(假设Linux在fork()上也对该堆栈执行CoW)。一旦您将任何内容写入堆栈,您就必须更新堆栈指针并复制所有页面,而不仅仅是修改的页面。

然后,我们必须解决间接指向的指针和数据结构中不指向分配等的指针,这似乎是不可能解决的,如果不跟踪针对每个可能的将来写入需要更新哪些寄存器和指针(或具有一些不同的实现)就像@R提到的C指针一样-寄存器等都一样。)

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。