Creating Linux Processes With fork-Free Linux Device Drivers Course In Hyderabad

PREV_LECNEXT_LEC

Creating Linux Processes With fork
How fork() really creates a new process, why it returns twice, and what copy-on-write means for your embedded application — from our free Linux device drivers course
Key Concepts In This Lecture
fork() Parent and child PID Copy-on-write init process Return value handling

In the previous lecture of this free linux kernel development course we drew a hard line between processes and threads. Now we get concrete: how does a Linux process actually come into existence in the first place? The answer is a single, unusual POSIX function called fork(2), and understanding exactly how it behaves is essential groundwork for the rest of this free embedded systems course, especially once we start supervising and restarting processes on a real embedded target.

What You Will Learn

Where the very first Linux process comes from How fork() creates a new process Why fork() returns twice Telling parent and child apart correctly What copy-on-write actually does A working fork() demo with real output

Prerequisites

Read the previous lecture on processes vs threads first — this one builds directly on the address-space model introduced there. Basic familiarity with C and the shell is assumed, consistent with the rest of this free Linux device drivers course.

Where Processes Come From: init and Forking

A Linux system does not start with zero processes and grow arbitrarily — it starts with exactly one. During boot, the kernel creates the very first user-space process, traditionally called init, and gives it process ID (PID) 1. Every other process on the system, without exception, is created afterward by an existing process duplicating itself in an operation known as forking. This means the full set of running processes on any Linux box, from a tiny embedded board to a data-center server, always forms a single tree rooted at PID 1.

fork(): The Function That Returns Twice

The POSIX call to create a new process is fork(2). It has a property that trips up almost every developer the first time they meet it: a single successful call to fork() returns twice — once in the original process, called the parent, and once in the brand-new process, called the child. Both returns happen from the exact same line of code, because immediately after forking, the child starts out as a near-exact copy of the parent: same stack contents, same heap contents, same open file descriptors, same program counter.

One fork() Call, Two Returns
Parent process calls fork() → Parent continues, return value = child’s PID Child process created, return value = 0

The only way your program can tell which copy it is now running as is by inspecting the return value of fork() itself:

Return valueMeaning
0You are in the newly created child process
Greater than 0You are in the parent process, and the value is the PID of the new child
Negative (-1)fork() failed; no child was created, and you are still in the original process

Real code always checks all three cases explicitly. Forgetting to check for -1 is one of the most common bugs in beginner process-management code, since it silently assumes the fork always succeeds.

Copy-on-Write: Why fork() Is Fast Despite “Copying” Everything

Reading the description above, it sounds like fork() should be expensive — duplicating an entire process’s memory sounds like a lot of copying. In practice the kernel is much smarter than that. Rather than physically duplicating every page of memory at fork time, the kernel marks the parent’s and child’s memory pages as shared and read-only, tagged with a copy-on-write (CoW) flag. Both processes keep reading the same physical memory pages until one of them tries to write to a page. Only at that point does the kernel actually copy that single page, giving the writer its own private version while the other process keeps the original. This lazy-copying trick is what makes fork() cheap enough to use routinely, even on memory-constrained embedded targets.

Copy-on-Write in Action
Parent and child share one physical page (read-only) → Child writes to the page → Kernel copies just that page → Child now has a private copy, parent keeps the original

A Minimal fork() Demo

The program below forks once, has the parent and child each print their own view of the world, and demonstrates that a variable modified in the child is not visible to the parent — proof that the two processes really do have separate address spaces despite starting out identical.

/* ep_fork_demo.c
 * Minimal demo: fork a child, show separate address spaces.
 */
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void)
{
    int shared_looking_value = 10;
    pid_t pid = fork();

    if (pid < 0) {
        perror("fork failed");
        return 1;
    } else if (pid == 0) {
        /* child */
        shared_looking_value += 5;
        printf("child:  pid=%d ppid=%d value=%d\n",
               getpid(), getppid(), shared_looking_value);
    } else {
        /* parent */
        wait(NULL); /* wait for child to finish first */
        printf("parent: pid=%d child_pid=%d value=%d\n",
               getpid(), pid, shared_looking_value);
    }

    return 0;
}

Build and run it:

$ gcc -Wall -O2 -o ep_fork_demo ep_fork_demo.c
$ ./ep_fork_demo
child:  pid=4213 ppid=4212 value=15
parent: pid=4212 child_pid=4213 value=10

The PIDs will differ on your machine, but the pattern will not: the child’s copy of shared_looking_value becomes 15, while the parent’s copy stays at 10, even though both started from the same variable at the same line of code. That is the address-space separation from the previous lecture, made concrete.

Real-World Use Cases

  • Service supervisors (systemd, BusyBox init) fork a child to become each managed service, so a crash in the service does not take down the supervisor.
  • Shells fork a child for every external command you run, then use a related family of calls to replace that child’s memory image with the new program.
  • Network daemons historically forked a fresh child per incoming connection to isolate each client session from the others.

Common Mistakes

  • Not checking for fork() failure. On a resource-constrained embedded board, fork() can and does fail under memory pressure — always check for -1.
  • Assuming the child inherits changes made after fork() in the parent, or vice versa. Once fork() returns, the two processes are independent; nothing written afterward crosses over.
  • Leaving zombie children. If a parent never calls wait()/waitpid(), a finished child lingers as a zombie process entry until the parent exits or reaps it.
  • Forking heavy processes on a tight memory budget without understanding CoW. Large writes shortly after fork() can trigger a burst of page copying — worth profiling on memory-constrained boards.

Best Practices

  • Always branch on all three fork() return cases explicitly — never assume success.
  • Reap child processes with wait()/waitpid() to avoid zombie accumulation, especially in long-running embedded daemons.
  • Keep the parent’s post-fork work minimal in performance-sensitive paths, since large writes shortly after fork trigger copy-on-write page faults.
  • Prefer higher-level process-supervision tools (systemd, runit) over hand-rolled fork loops for production service management where possible.

Performance Considerations

Copy-on-write is what keeps fork() fast, but it is not free forever — the first write to each shared page after a fork costs a page fault and a physical copy. On memory-constrained embedded targets running many short-lived forked helper processes, this overhead is worth measuring rather than assuming away, particularly if the parent process has a very large heap at fork time.

Summary and Key Takeaways

Every Linux process except PID 1 is created by an existing process calling fork(), which returns once in the parent with the child’s PID and once in the child with 0. The two processes appear identical immediately after the call but occupy genuinely separate address spaces, made affordable by copy-on-write memory sharing under the hood. Getting comfortable with fork()’s two-return behavior and its failure case is a core skill for the rest of this free Linux kernel development course, and it underpins process supervision techniques we build on in later lectures of this free embedded Linux course.

FAQ

Why does fork() return twice?

Because a single call creates two processes — the original parent and a new child — and both resume execution from the same point right after the call, so both see a return value.

How do I tell the parent and child apart?

By checking the return value of fork(): zero means you are in the child, a positive value (the child’s PID) means you are in the parent, and a negative value means fork() failed.

Does the child get a full physical copy of the parent’s memory?

Not immediately — the kernel uses copy-on-write, sharing the same physical pages until either process writes to one, at which point only that page is copied.

What is process ID 1?

It is the init process, the very first process the kernel starts at boot, and the ancestor of every other process created afterward through forking.

What happens if fork() fails?

It returns -1, no child is created, and the calling process continues on as before — your code should always check for this case.

Do parent and child share file descriptors after fork()?

Yes, at the moment of the fork the child inherits copies of the parent’s open file descriptors, pointing at the same underlying open file descriptions.

Is fork() still relevant on modern embedded Linux?

Yes — it remains the fundamental process-creation primitive underneath shells, service supervisors, and many daemons across embedded and server Linux alike.

Continue the Free Linux Kernel Development Course

You now understand how Linux processes are created. Continue with the next lecture in this free embedded Linux course, or revisit the earlier lecture on processes vs threads for a refresher.

PREV_LECNEXT_LEC

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *