How 2 exact (volatile marked) pointers can point to different values? Forked process

Viewed 28

The goal

Understanding what happens in the code and what have I misconcluded / mispredicted.

Context

While experimenting with fork function (and reading articles I probably misunderstood) I concluded that data used by child was a copy of parent's data.

  1. In order to make a child work on the same data as its parent, I created pointer p, assuming that its value will be the same in all (both) processes after forking.
  2. I made sure that both child's and parent's pointer was pointing to the same memory space by printing values stored by them. What stroke me to the ground, is that they also seem to be the same/exact – have the same addresses.
    Shall the 2 exact pointers point to different values? In my code they did.
  3. Next, to be sure that a compiler would not perform any optimization, I marked the p pointer as volatile.
    That did not change anything. Here is the code.

The general code

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>


int main(){
  volatile int *volatile p= malloc(sizeof(int));
  *p= 6;
  

  if (fork()){ //Parent
    *p= 200; //Parent changes the value in "shared" memory

    printf("Parent: %p : %p : %i\n", &p, p, *p);
    wait(NULL);
  }else{ //Child
    sleep(3); //Child sleeps when parent is setting the "shared" value.

    printf("Child:  %p : %p : %i\n", &p, p, *p); //Child should read the same value.
  }
}

Output:

Parent: 0x7ffc8bb6cbe0 : 0xea9260 : 200
Child:  0x7ffc8bb6cbe0 : 0xea9260 : 6

Expected outputs:

Parent: 0x7ffc8bb6cbe0 : 0xea9260 : 200
Child:  0x7ffc8bb6cbe0 : 0xea9260 : 200

Or

Parent: 0x7ffc8bb6cbe0 : 0xea9260 : 200
Child:  differentAddress: 0xea9260 : 200

2nd code variation (snippet)

Still believing in the fault of compiler optimization, I added steps (sleep, fetch, sleep, copy).

  if (fork()){ //Parent
    *p= 200;
    printf("Parent: %p : %p : %i\n", &p, p, *p);
    wait(NULL);
  }else{ //Child
    sleep(2);
    uintptr_t fetch= (uintptr_t)p;
    sleep(2);
    p= (int*)fetch;
    printf("Child:  %p : %p : %i\n", &p, p, *p);
  }

This also changed nothing in the actual output.

Environment

  • Language C (propably C11)
  • Compiler: clang 7.0.0 (I haven't pass any of O1,O2,O3,... flags.)
  • IDE & platform: https://replit.com/
  • Platform: Linux-5.11.0-1029-gcp-x86_64-with-glibc2.27
  • Machine: x86_64

My other attempts at understanding this

I tried also other similar experiments. That provided me with ambiguous conclusions.

1 Answers

The operating system creates a virtual address space for each process. When a process accesses memory, it does not directly access memory by physical address. Whatever address the process uses is translated by the computer processor from a virtual address to a physical address. That translation is controlled by the operating system: Each process has its own map from virtual addresses to various physical addresses. Each process has largely its own different physical memory.

When a process calls fork, the operating system creates a copy of the process. For any memory that is modified by one processor the other, the operating system creates a copy in separate physical memory and adjusts the address translation. Even though two processes use the same virtual address to access memory, it may refer to different physical memory.

(For efficiency, any memory that is only read by processes is generally shared: Their virtual addresses are mapped to the same physical addresses, and there is only one physical copy of the data in memory. When a process first calls fork, both processes may use the same memory even though they have permission to write to memory. When they actually write to memory, then the operating system copies that portion of memory and adjusts the address translation.)

It is possible for processes to share physical memory that they write to, but this must be explicitly requested by calling routines such as shmat to manage shared memory.

Related