关于fork()之后的指针

jav*_*rbg 9 c unix pointers fork process

这是一个技术问题,如果您了解C和UNIX(也许这是一个非常新手的问题,也许你可以帮助我!)

今天在我们的操作系统课程中分析一些代码时出现了一个问题.我们正在学习在UNIX中"分叉"一个进程意味着什么,我们已经知道它创建了与它并行的当前进程的副本,并且它们具有单独的数据部分.

但后来我认为,如果在执行fork()之前创建一个变量和一个指向它的指针,因为指针存储变量的内存地址,可以尝试通过子进程修改该变量的值.使用该指针.

我们在课堂上尝试了类似的代码:

#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>

int main (){
    int value = 0;
    int * pointer = &value;
    int status;

    pid_t pid;

    printf("Parent: Initial value is %d\n",value);

    pid = fork();

    switch(pid){
    case -1: //Error (maybe?)
        printf("Fork error, WTF?\n");
        exit(-1);

    case 0: //Child process
        printf("\tChild: I'll try to change the value\n\tChild: The pointer value is %p\n",pointer);
        (*pointer) = 1;
        printf("\tChild: I've set the value to %d\n",(*pointer));

        exit(EXIT_SUCCESS);
        break;
    }

    while(pid != wait(&status)); //Wait for the child process

    printf("Parent: the pointer value is %p\nParent: The value is %d\n",pointer,value);

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

如果你运行它,你会得到这样的东西:

父级:初始值为0

孩子:我会尝试改变价值

Child:指针值为0x7fff733b0c6c

孩子:我把值设置为1

父:指针值为0x7fff733b0c6c

父级:值为0

很明显,子进程并没有影响所有父进程.坦率地说,我期待一些"分段错误"错误,因为访问了一个不允许的内存地址.但究竟发生了什么?

请记住,我不是在寻找一种沟通流程的方法,这不是重点.我想知道的是代码做了什么.在子进程内部,更改是可见的,因此它可以解决问题.

我的主要假设是指针对内存不是绝对的,它们与进程的堆栈有关.但是我找不到答案(课堂上没有人知道,谷歌搜索我发现了一些关于过程沟通的问题)所以我想知道你,希望有人知道.

感谢您抽出宝贵的时间阅读!

Dan*_*lls 16

这里的关键是虚拟地址空间的概念.

现代处理器(比80386更新)说有一个内存管理单元,它在每个进程虚拟地址空间映射到内核控制下的物理内存页面.

当内核设置进程时,它会为该进程创建一组页表条目,这些条目将物理内存页定义为虚拟地址空间映射,并且它位于程序执行的虚拟地址空间中.

从概念上讲,当您进行分叉时,内核会将现有的进程页面复制到一组新的物理页面,并设置新的进程页面表,以便就新进程而言,它似乎在与该进程相同的虚拟内存布局中运行.原来的,虽然实际上解决了完全不同的物理记忆.

细节更加微妙,因为除非有必要,否则没有人愿意浪费时间复制数百MB的数据. 当进程调用fork()时,内核会设置第二组页表项(对于新进程),但是将它们指向与原始进程相同的物理页,然后在两组页中设置标志以进行mmu认为他们只读.....

一旦任一进程写入页面,内存管理单元就会生成页面错误(由于PTE条目设置了只读标志),然后页面错误处理程序从物理内存分配新页面,复制数据,更新页表条目并将页面设置回读/写.通过这种方式,页面实际上只是在任何一个进程尝试对写入页面上的副本进行更改时实际复制,并且任何一个进程都会完全忽略这一点.

问候,丹.


Joh*_*ger 6

从逻辑上讲,fork()ed 进程或多或少地获得了自己的独立副本,该副本或多或少是父进程的整个状态。如果子级中的指针指向属于父级的内存,那将无法工作。

特定的类 UNIX 内核如何使其工作的细节可能会有所不同。Linux 通过写时复制页面fork()来实现子进程的内存,这使得相对于其他可能的实现来说相对便宜。在这种情况下,子进程的指针确实指向父进程的内存,直到子进程或父进程尝试修改该内存为止,此时会创建一个副本供子进程使用。这一切都依赖于底层的虚拟内存系统。其他 UNIX 和类 UNIX 系统可以并且已经以不同的方式完成它。