fork() Basics
of 4
Code Examples
What Really Happens Inside fork()?
When your program calls fork(), the Linux kernel does a lot of work in the background. It doesn’t just blindly copy everything — it’s smart about memory. Understanding what happens internally helps you write better, bug-free programs that use processes correctly.
Kernel creates a new
task_struct for the child and assigns it a new PID.Most fields copied: signal handlers, open file descriptors, credentials (UID/GID), working directory, etc.
The child’s page table entries for the text segment point to the same physical pages as the parent. Marked read-only — no copy needed.
Child’s page table entries point to parent’s pages, but both are marked read-only. Actual copying deferred until a write happens.
Parent’s fork() returns child PID. Child’s fork() returns 0. Both are placed in the run queue. Kernel decides which runs first.
Open file descriptors
File offset positions
Signal handlers
Signal mask
Process group ID
Controlling terminal
Real UID, GID
Working directory (cwd)
Root directory
Resource limits (ulimit)
Environment variables
Program text (shared, read-only)
Parent Process ID (PPID = parent’s PID)
Its own copy of pending signals
File locks are NOT inherited
CPU time counters reset to 0
Memory locks (mlock) not inherited
Semaphore adjustments (semadj) reset
Timers (alarm, setitimer) reset
Asynchronous I/O not inherited
This shows the child inherits the working directory and environment:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
char cwd[256];
pid_t pid;
/* Parent changes directory */
if (chdir("/tmp") == -1) { perror("chdir"); exit(1); }
getcwd(cwd, sizeof(cwd));
printf("[Parent] CWD = %s\n", cwd);
pid = fork();
if (pid == -1) { perror("fork"); exit(1); }
if (pid == 0) {
/* Child checks its own cwd */
getcwd(cwd, sizeof(cwd));
printf("[Child PID=%d] CWD inherited = %s\n",
getpid(), cwd);
/* Child changes to a DIFFERENT directory */
chdir("/var");
getcwd(cwd, sizeof(cwd));
printf("[Child] After chdir: CWD = %s\n", cwd);
exit(0);
}
wait(NULL);
/* Parent's cwd is NOT changed by child's chdir */
getcwd(cwd, sizeof(cwd));
printf("[Parent] After child exits: CWD = %s\n", cwd);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
void sigint_handler(int sig)
{
printf("[PID=%d] Caught SIGINT (Ctrl+C)\n", getpid());
}
int main(void)
{
/* Install SIGINT handler in parent */
signal(SIGINT, sigint_handler);
pid_t pid = fork();
if (pid == -1) { perror("fork"); exit(1); }
if (pid == 0) {
/* Child inherits the SIGINT handler */
printf("[Child PID=%d] Handler inherited. "
"Sending SIGINT to myself...\n", getpid());
raise(SIGINT); /* send signal to self */
printf("[Child] After signal handler returned.\n");
exit(0);
}
wait(NULL);
/* Parent can also reset child's handlers after fork if needed */
printf("[Parent] Done.\n");
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
int main(void)
{
int fd;
struct flock lock;
/* Open a file and take an exclusive write lock */
fd = open("/tmp/test_lock.txt",
O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1) { perror("open"); exit(1); }
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0; /* lock whole file */
if (fcntl(fd, F_SETLK, &lock) == -1) {
perror("fcntl lock"); exit(1);
}
printf("[Parent] File locked.\n");
pid_t pid = fork();
if (pid == -1) { perror("fork"); exit(1); }
if (pid == 0) {
/* Child does NOT inherit the file lock.
File locks belong to the process, not the fd. */
printf("[Child PID=%d] File lock is NOT inherited.\n",
getpid());
/* Child can acquire its own lock if needed */
exit(0);
}
wait(NULL);
/* Unlock */
lock.l_type = F_UNLCK;
fcntl(fd, F_SETLK, &lock);
close(fd);
printf("[Parent] Lock released.\n");
return 0;
}
Not immediately. Linux uses copy-on-write (CoW). The text segment is shared (read-only). Data, heap, and stack page table entries point to the same physical pages, but are marked read-only. A physical copy is made only when either process tries to write to that page.
Yes, after fork() the child inherits all signal dispositions. However, after exec(), all signal handlers reset to their default actions (SIG_DFL), because the handler code belongs to the old program that no longer exists.
No. POSIX file locks (fcntl F_SETLK) are associated with a process, not a file descriptor. The child does NOT inherit the parent’s file locks. The child starts with no file locks, even if it inherits the same open file descriptors.
The child does NOT inherit pending alarms. The alarm clock is reset to zero for the child. The parent’s alarm continues unaffected. Interval timers (setitimer) are also not inherited by the child.
It is indeterminate and depends on the kernel scheduler and system load. Linux 2.6.32+ runs the parent first by default (configurable via /proc/sys/kernel/sched_child_runs_first). Programs must never rely on a specific execution order — use synchronization if order matters.
