为什么 parent/child 进程中的对象具有相同的地址?

Why do objects in parent/child processes have identical addresses?

我对以下代码有两个疑问:

代码:

#include <unistd.h>
#include <semaphore.h>
#include <iostream>

int main(int argc, char **argv)
{
    sem_t sem;
    int var = 0;

    /* create, initialize semaphore */
    if( sem_init(&sem,1,1) < 0)
    {
        perror("semaphore initilization");
        exit(0);
    }

    int pid = fork();
    static const size_t loopLen = 5;
    if (0 == pid)
    { /* child process */
        for (size_t i = 0; i < loopLen; ++i)
        {
            sem_wait(&sem);
            std::string str("Child");
            std::cout << str << " process: &var(" << (void*)(&var) << ") var(" << var++ << ") &sem(" << (void*)(&sem) << ")" << std::endl;
            sem_post(&sem);
        }
    }
    else
    { /* parent process */
        for (size_t i = 0; i < loopLen; ++i)
        {
            sem_wait(&sem);
            std::string str("Parent");
            std::cout << str << " process: &var(" << (void*)(&var) << ") var(" << var++ << ") &sem(" << (void*)(&sem) << ")" << std::endl;
            sem_post(&sem);
        }
    }
}

输出:

Parent process: &var(0xffffcbdc) var(0) &sem(0xffffcbe0)
Child process: &var(0xffffcbdc) var(0) &sem(0xffffcbe0)
Parent process: &var(0xffffcbdc) var(1) &sem(0xffffcbe0)
Child process: &var(0xffffcbdc) var(1) &sem(0xffffcbe0)
Parent process: &var(0xffffcbdc) var(2) &sem(0xffffcbe0)
Child process: &var(0xffffcbdc) var(2) &sem(0xffffcbe0)
Parent process: &var(0xffffcbdc) var(3) &sem(0xffffcbe0)
Child process: &var(0xffffcbdc) var(3) &sem(0xffffcbe0)
Parent process: &var(0xffffcbdc) var(4) &sem(0xffffcbe0)
Child process: &var(0xffffcbdc) var(4) &sem(0xffffcbe0)

问题:

为什么从父进程和子进程打印出来的varsem的地址是一样的?我知道子进程获得了父进程内存 space 内容的 副本 ,但我认为进程具有独立且不同的地址 space,因此没有变量将位于相同的内存位置 - 但此输出似乎另有说明。

问题:

这段代码实际上是在同步两个进程吗?我很怀疑。尽管我用非零的 pshared 标志调用了 sem_init,但似乎子进程应该得到信号量的 副本 。我没有看到父进程和子进程之间 sem 是 "shared" 的机制:信号量没有命名,我不明白信号量是如何在父进程和子进程之间共享的.我怀疑每个进程只是获取和释放自己的 "copy" 信号量,但我不确定。

谢谢。

Linux使用了“copy-on-write”的成语,意思是在你调用fork()之后,父进程的内存不会立即复制(作为一个单独的副本)给子进程。仅当子进程尝试将任何数据写入内存时才会发生复制。

了解 "real" 内存地址(即物理内存中的地址)和映射地址(即您的 space 内存中的地址)之间的区别也很重要应用。两个应用程序中的两个指针可能具有相同的值(虚拟地址),但这并不意味着它们真的指向相同的物理位置:Memory mapping.

关于地址,这是因为子进程开始时是父进程的副本。该精确复制包括(虚拟)内存映射。 Read the fork manual page 获取更多信息。

关于信号量,如果你read the sem_init manual page你会看到

If pshared is nonzero, then the semaphore is shared between processes, and should be located in a region of shared memory

共享内存中的这个放置由您来处理,但它不是自动为您完成的。

除了 SingerOfTheFall 的回答之外,我想补充一点,fork(2) 制作了 parent 进程的精确副本 - 相同的内存映射、相同的信号掩码、相同的文件描述符 table - 所以你实际上得到了你的过程的真实副本。

进程确实有不同的地址 spaces,但是为了理解为什么修改其中一个进程不会影响另一个进程,您应该记住虚拟地址和物理地址之间的区别,并且所有进程(甚至是 amd64 上的内核)都在虚拟地址 space.

中执行

长话短说,在 CPU 中有通信 tables(称为页面 tables),每当您尝试访问给定地址时,CPU 查找违规地址的真实物理地址。内核为每个进程填充页面 tables,并为每个进程提供相同的地址(如果未启用 ASLR)。

我不能确定为什么信号量在 parent 和 child 之间共享,但如果你的初始化是正确的,它应该不能从外部世界访问。

比照。

https://en.wikipedia.org/wiki/Virtual_address_space

https://en.wikipedia.org/wiki/Page_table