什么叫做写时拷贝,如何做到的

fork之后修改父子进程的公共变量,发现同一个地址是有着两个不同的值,OS是如何做到的?

帮你找了一下资料,部分信息来自GPT,你可以参考一下:
在操作系统中,fork() 是创建一个新进程的系统调用。在调用 fork() 之后,操作系统会复制父进程的地址空间,并将它分配给子进程。这种机制被称为写时复制(Copy-on-Write)。

在父进程和子进程之间,初始时它们共享相同的物理内存页面。这意味着它们的虚拟地址是相同的,但实际上指向的是相同的物理内存页。这样,父子进程之间可以共享数据,减少内存的开销。

然而,当其中一个进程尝试修改共享的内存页时,操作系统会采取一定的措施来保证数据的一致性。具体来说,当有进程要修改一个被共享的内存页时,操作系统首先会为该进程分配一个新的物理内存页,然后将原来的内存页的内容拷贝到新的内存页中。这样一来,父进程和子进程分别拥有各自的内存页副本,它们的虚拟地址对应的物理内存页就不再相同。

因此,通过 fork() 创建的子进程在修改共享变量时会拥有自己的副本,这就解释了为什么父子进程的公共变量在相同的地址上具有不同的值。

需要注意的是,这种写时复制机制只适用于父进程和子进程之间的直接修改。如果父进程创建了子进程,并且子进程继续创建了更多的子进程,那么它们之间的共享内存仍然是相同的,因为它们共享同一个物理内存页。只有当有进程尝试修改这些共享内存页时,才会进行拷贝操作。

此外,每个操作系统在实现 fork() 和写时复制时可能有所不同,但基本原理是相似的。以上解释是一般情况下对 fork() 和写时复制机制的描述,具体细节可能因操作系统的不同而有所差异。

不知道你这个问题是否已经解决, 如果还没有解决的话:
  • 你可以看下这个问题的回答https://ask.csdn.net/questions/7635034
  • 我还给你找了一篇非常好的博客,你可以看看是否有帮助,链接:fork()之后,发生了什么?
  • 除此之外, 这篇博客: fork到底做了什么?中的 fork 后父子进程谁先执行? 部分也许能够解决你的问题, 你可以仔细阅读以下内容或者直接跳转源博客中阅读:

           通常情况下,我们总是用 sleep 等操作来保证另一个进程先执行,但父子进程谁先执行并不是不可预测的。从linux内核2.6.32开始,在默认情况下,父进程将成为fork之后优先调度的对象。采取这种策略的原因很简单:fork是父进程发起的调用,因此fork之后,父进程在CPU中处于活跃的状态,并且其内存管理信息也被置于硬件内存单元的转译后备缓冲器(TLB),所以先调度父进程无论从减少上下文切换、CPU让出等方面都可以提高性能。linux内核从2.6.24开始,内核采用完全公平调度(CFS),用户创建的普通进程,都采用CFS调度策略。对于CFS调度策略,内核提供的控制选项默认是0,表示父进程优先获得调度,如果该值被改为1,子进程会优先获得调度。

           但POSIX标准和linux都没有保证会优先调度父进程,因此在应用中,我们不能对父子进程的执行顺序做任何假设。如果确实需要父子进程的某一特定执行顺序,那么还是得需要进程间的同步手段。

  • 以下回答来自chatgpt:

    写时拷贝(Copy-on-Write)是一种内存管理技术,用于在多进程环境下实现进程间共享内存空间的优化方法。当使用fork操作创建子进程时,在默认情况下父子进程会共享相同的内存空间,包括代码段、数据段和堆栈段。但由于子进程可能会对这些共享的数据进行修改,可能导致数据不一致性的问题。

    为了解决这个问题,操作系统使用写时拷贝机制。写时拷贝的主要思想是,在子进程尝试修改共享内存时,操作系统不立即拷贝整个内存块,而是将该内存块标记为“只读”。当发生实际写操作时,操作系统会检测到这个标记,并执行以下步骤:

    1. 操作系统检测到对于共享内存的写操作,并且该内存块被其他进程引用。
    2. 操作系统为子进程分配一个新的内存页,该页与父进程的内存页相同。
    3. 操作系统将被修改的数据复制到子进程的新内存页中。
    4. 操作系统更新页表,将子进程的虚拟地址指向新的内存页。
    5. 子进程继续执行写操作。

    这样,父子进程之间的内存空间实际上是分离的,修改互不干扰。由于写时拷贝只在发生写操作时才进行拷贝,大大减少了内存的复制开销和内存占用。只有当父子进程中的某个进程要修改内存时,才进行实际的拷贝操作。

    下面是一个示例程序,演示了写时拷贝的情况:

    #include <stdio.h>
    #include <unistd.h>
    
    int main() {
        int value = 42;
        pid_t pid = fork();
    
        if (pid == 0) {
            // 子进程
            printf("子进程 - 修改前:%d\n", value);
            value = 99;
            printf("子进程 - 修改后:%d\n", value);
        } else if (pid > 0) {
            // 父进程
            sleep(1);  // 等待子进程修改
            printf("父进程 - 修改后:%d\n", value);
        } else {
            // 创建子进程失败
            fprintf(stderr, "创建子进程失败\n");
            return 1;
        }
    
        return 0;
    }
    

    输出结果:

    子进程 - 修改前:42
    子进程 - 修改后:99
    父进程 - 修改后:42
    

    在这个示例程序中,父子进程共享变量value的内存空间。当子进程尝试修改value时,操作系统会触发写时拷贝,将value的值拷贝并存储在子进程的独立内存中,因此父子进程的value值互不干扰。

    希望以上解答对你有所帮助,如果还有其他疑问,请随时提问。


如果你已经解决了该问题, 非常希望你能够分享一下解决方案, 写成博客, 将相关链接放在评论区, 以帮助更多的人 ^-^