Memory Semantics
of 3
Code Examples
The Text Segment is Always Shared
The text segment contains the compiled machine code of your program. Unlike the data, heap, and stack, the text segment is read-only — your code doesn’t modify itself at runtime. Because it never changes, after fork() the parent and child always share the same physical code pages. No copy-on-write needed, no copy ever made.
The kernel maps the text segment with r-x (read + execute, no write) permissions. Any attempt to write to code pages causes a SIGSEGV. Since it can never be written, CoW is never triggered.
After fork(), the child is a copy of the parent — both run the same program. Their code is identical, so there is zero reason to copy it. Sharing saves RAM proportional to the program’s code size.
Not just your code — the text pages of libc.so, libpthread.so and every shared library are also shared across all processes that use them. This is a major memory saving system-wide.
read-only
r-x-p
Both page tables point to the same physical frames for text. No copy ever.
This program reads its own memory map before and after fork to show that the text segment virtual address is identical in parent and child:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
void print_text_mapping(const char *label)
{
char path[64], line[256];
FILE *f;
snprintf(path, sizeof(path), "/proc/%d/maps", getpid());
f = fopen(path, "r");
if (!f) return;
printf("\n--- %s (PID=%d) text mappings ---\n", label, getpid());
while (fgets(line, sizeof(line), f)) {
/* Show only executable (code) pages marked r-xp */
if (line[3] == 'x') /* r-xp = read+exec, private */
printf(" %s", line);
}
fclose(f);
}
int main(void)
{
print_text_mapping("Parent before fork");
pid_t pid = fork();
if (pid == -1) { perror("fork"); exit(1); }
if (pid == 0) {
print_text_mapping("Child after fork");
/* Notice: same virtual address range as parent */
_exit(0);
}
wait(NULL);
return 0;
}
/* Compile: gcc -o maps_demo maps_demo.c
Expected: parent and child show IDENTICAL r-xp addresses */
00400000-00401000 r-xp). The r-xp flags mean read + execute, private mapping (but shared physically).Because the text segment is shared (same virtual addresses), function pointers set in the parent work correctly in the child without any changes:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
typedef int (*op_fn)(int, int);
int add(int a, int b) { return a + b; }
int mul(int a, int b) { return a * b; }
int sub(int a, int b) { return a - b; }
int main(void)
{
/* Parent sets up function pointer table */
op_fn ops[3] = { add, mul, sub };
const char *names[3] = { "add", "mul", "sub" };
printf("[Parent] Function pointers set up:\n");
for (int i = 0; i < 3; i++)
printf(" ops[%d] = %s, address = %p\n",
i, names[i], (void*)ops[i]);
pid_t pid = fork();
if (pid == -1) { perror("fork"); exit(1); }
if (pid == 0) {
/* Child: function pointers still valid — same text segment */
printf("\n[Child] Using inherited function pointers:\n");
for (int i = 0; i < 3; i++)
printf(" ops[%d](10, 3) = %d (address %p)\n",
i, ops[i](10, 3), (void*)ops[i]);
_exit(0);
}
wait(NULL);
printf("\n[Parent] Function pointers still valid after child exits:\n");
for (int i = 0; i < 3; i++)
printf(" ops[%d](10, 3) = %d\n", i, ops[i](10, 3));
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <math.h>
void show_lib_mappings(const char *label)
{
char path[64], line[256];
FILE *f;
snprintf(path, sizeof(path), "/proc/%d/maps", getpid());
f = fopen(path, "r");
if (!f) return;
printf("\n--- %s (PID=%d) library text pages ---\n",
label, getpid());
while (fgets(line, sizeof(line), f)) {
/* Show libc and libm executable mappings */
if (line[3] == 'x' &&
(strstr(line, "libc") || strstr(line, "libm")))
printf(" %s", line);
}
fclose(f);
}
int main(void)
{
/* Use libm to ensure it's loaded */
double x = sqrt(2.0);
printf("sqrt(2) = %f (forces libm load)\n\n", x);
show_lib_mappings("Parent");
pid_t pid = fork();
if (pid == -1) { perror("fork"); exit(1); }
if (pid == 0) {
show_lib_mappings("Child");
/* libc/libm text pages: same virtual addresses as parent
AND same physical pages — shared across the whole system */
_exit(0);
}
wait(NULL);
printf("\nlibc text pages are shared across all processes.\n");
printf("A 2 MB libc is mapped once in RAM, shared by 100 processes = only 2 MB used.\n");
return 0;
}
/* Compile: gcc -o lib_sharing lib_sharing.c -lm */
No. The text segment is mapped read-only (r-x permissions). Because it can never be written, copy-on-write is never triggered for it. Parent and child permanently share the exact same physical pages of code. This applies to the program’s own code and all shared library text pages.
The text segment has r-x (read + execute, no write) permissions. Read allows loading instructions. Execute allows the CPU to run code on those pages. No write permission means any attempt to modify code at runtime causes SIGSEGV. This also protects against certain code injection attacks.
The text (code) pages of shared libraries like libc are mapped read-only into every process that uses them. Because they are read-only and identical, the kernel maps them to the same physical frames for all processes. 200 processes using a 2 MB libc still only need 2 MB of physical RAM for that code — not 400 MB.
The text segment appears as a line with r-xp permissions (read, execute, no-write, private mapping). It shows the virtual address range and the path to the ELF binary. For shared libraries, each library has its own r-xp entry. Parent and child show identical virtual addresses.
