Class 25 of 37 • System Programming (C) \t\t\t\t

Linux Process Management & System Calls (C Programming)

In advanced Linux programming, a Process is defined as an instance of a computer program in execution. Unlike shell scripts which run sequential command-line tools, system programming involves writing compiled C programs that directly invoke POSIX operating system primitives called System Calls.

The Process Lifecycle & State Transitions

In Linux, new processes are created through duplication using the fork() system call. The child process may then replace its memory space with an entirely new executable using execve(), while the parent monitors its lifecycle using wait():

Linux Process Lifecycle Diagram: fork, execve, wait, exit, zombie and orphan processes

Figure 4: Complete Linux process lifecycle and system call interaction.


1. The fork() System Call

The fork() system call creates a new process by duplicating the calling process. It returns twice:

     In the Child Process: fork() returns 0.

     In the Parent Process: fork() returns the PID of the child (> 0).

     On Failure: fork() returns -1.

Complete C Code Example: Creating a Child Process


/*
 * fork_demo.c - Demonstrates process creation using fork()
 * Compile: gcc fork_demo.c -o fork_demo
 * Run    : ./fork_demo
 */

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main() {
    pid_t pid;

    printf("Parent process started (PID: %d)\n", getpid());

    // Fork the process
    pid = fork();

    if (pid < 0) {
        perror("Fork failed");
        return 1;
    } else if (pid == 0) {
        // Child process code block
        printf("[CHILD]  Hello from Child! PID: %d, Parent PID: %d\n", getpid(), getppid());
        sleep(2);
        printf("[CHILD]  Child process finishing.\n");
        return 42; // Exit status
    } else {
        // Parent process code block
        int status;
        printf("[PARENT] Spawned child with PID: %d. Waiting for child to terminate...\n", pid);
        
        // Wait for child to exit to prevent zombie state
        waitpid(pid, &status, 0);

        if (WIFEXITED(status)) {
            printf("[PARENT] Child terminated normally with exit code: %d\n", WEXITSTATUS(status));
        }
    }

    return 0;
}
							

2. The exec() Family of System Calls

While fork() creates a clone, the exec() family replaces the entire memory space of the process (code text, data segment, stack, and heap) with a fresh executable binary file from disk:


/*
 * exec_demo.c - Executes the 'ls -l' command using execlp()
 */
#include <stdio.h>
#include <unistd.h>

int main() {
    printf("About to execute 'ls -l' using execlp...\n");

    // execlp searches PATH for "ls", passing arguments terminated by NULL
    execlp("ls", "ls", "-l", "/home", NULL);

    // This line ONLY executes if execlp encounters an error
    perror("execlp execution failed");
    return 1;
}
							

3. Zombie vs Orphan Processes Explained

Property Zombie Process (Defunct) Orphan Process
Definition Child has terminated via exit(), but parent hasn't collected status via wait(). Parent process terminated or crashed while the child is still actively running.
Process Table Status Shows status Z in ps aux or top. Active, running normally.
Parent Process ID Original Parent PID. Adopted by PID 1 (systemd / init).
How to Clean Up Parent must call wait() or waitpid(). Killing parent forces init to reap it. Cleaned up automatically by PID 1 upon child termination.

Practical Lab Assignment (BCA / B.Tech Semester Exam)

Problem Statement: Write a C program on Linux that creates 2 child processes simultaneously. Child 1 prints its PID and exits with code 10. Child 2 sleeps for 1 second, prints its PID, and exits with code 20. The parent must wait for both children and display each child's exit status.


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main() {
    pid_t c1, c2;
    int status;

    printf("Parent PID: %d\n", getpid());

    if ((c1 = fork()) == 0) {
        printf("Child 1 (PID: %d) executing.\n", getpid());
        exit(10);
    }

    if ((c2 = fork()) == 0) {
        sleep(1);
        printf("Child 2 (PID: %d) executing.\n", getpid());
        exit(20);
    }

    // Parent waits for both children
    pid_t terminated_pid;
    while ((terminated_pid = wait(&status)) > 0) {
        printf("Parent reaped Child PID %d with exit code: %d\n", 
               terminated_pid, WEXITSTATUS(status));
    }

    printf("All children reaped successfully.\n");
    return 0;
}
							

Frequently Asked Questions (FAQ)

In modern Linux kernels, fork() does not immediately copy all physical RAM pages of the parent to the child. Instead, both processes share the same read-only pages. A page is only duplicated in physical memory when either process writes to it. This optimization makes fork() extraordinarily fast.

Run the command: ps aux | grep 'Z' or top (check the 'zombie' count in the top summary line). Defunct zombie processes show as <defunct> in process listings.

Next Step in Your Linux Learning Path:

Explore open, read, write, close, and lseek system calls in Class 26: Low-Level File I/O in C.

Continue to Class 26: Low-Level File I/O in C → Previous: ← String Operations | Full Syllabus

Share Share on Facebook Share on Twitter Share on LinkedIn Pin on Pinterest Share on Stumbleupon Share on Tumblr Share on Reddit Share on Diggit

You may also like this!