Unit 4 - Notes

CSE325

Unit 4: Process Management & Signals

1. Process Creation using fork()

Concept

The fork() system call is the primary mechanism used in Unix-like operating systems to create a new process. When a process calls fork(), it creates a duplicate of itself.

  • Parent Process: The process that invokes fork().
  • Child Process: The new process created.

Key Characteristics

  1. Duplication: The child gets an exact copy of the parent's memory (text, data, heap, and stack segments). However, modern OSs use Copy-on-Write (COW), meaning physical memory is only duplicated when one of the processes modifies the data.
  2. Concurrency: Both processes continue execution from the instruction immediately following the fork() call.
  3. Return Values: fork() returns different values to distinguish between the two processes:
    • Returns 0 to the Child Process.
    • Returns Positive Integer (Child's PID) to the Parent Process.
    • Returns -1 if the creation failed.

A detailed schematic diagram illustrating the `fork()` system call. The diagram should show a centra...
AI-generated image — may contain inaccuracies

Code Example: fork()

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

int main() {
    pid_t pid;
    pid = fork();

    if (pid < 0) {
        perror("Fork failed");
        return 1;
    } else if (pid == 0) {
        // Child process block
        printf("I am the Child Process. PID: %d\n", getpid());
    } else {
        // Parent process block
        printf("I am the Parent Process. PID: %d, Child PID: %d\n", getpid(), pid);
    }
    return 0;
}


2. Program Execution using exec()

Concept

While fork() creates a copy of the calling process, the exec() family of system calls replaces the current process image with a new process image. It loads a new program into the current process's memory space.

Key Characteristics

  • No Return: If exec() is successful, it never returns to the calling function because the original code is overwritten. It only returns -1 if an error occurs.
  • PID Retention: The Process ID (PID) remains the same; only the code, data, heap, and stack are replaced.

The exec Family

The exec function has several variations based on how arguments are passed and whether the PATH environment variable is searched:

  1. execl(path, arg0, ..., NULL): List of arguments.
  2. execv(path, argv[]): Vector (array) of arguments.
  3. execvp(file, argv[]): Vector of arguments + searches PATH.
  4. execle(path, arg0, ..., NULL, envp[]): List of arguments + Environment variables.

A comparison diagram split into two halves: "Fork" vs. "Exec". On the left (Fork): Show a flow where...
AI-generated image — may contain inaccuracies

Code Example: execvp()

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

int main() {
    printf("Before exec system call\n");
    
    // Arguments: Program name, argument, NULL terminator
    char *args[] = {"ls", "-l", NULL};
    
    // execvp searches for "ls" in system PATH
    execvp(args[0], args);
    
    // This line is only reached if execvp fails
    printf("This line will not be printed if execvp is successful\n");
    
    return 0;
}


3. Orphan and Zombie Processes

These are special states of processes resulting from the parent-child relationship.

Zombie Process (Defunct)

  • Definition: A process that has completed execution (via exit()) but still has an entry in the process table.
  • Cause: The child has died, but the parent has not yet called wait() to read the child's exit status.
  • System Impact: Zombies do not consume memory or CPU, but they consume a Process ID (PID). If too many accumulate, the system may run out of PIDs.
  • Prevention: The parent must call wait() or waitpid().

Orphan Process

  • Definition: A child process that is still running after its parent process has terminated.
  • Mechanism: When a parent dies, the kernel detects orphan children.
  • Adoption: Orphans are immediately adopted by the init process (PID 1) or systemd. The init process periodically calls wait() to clean up these children when they eventually exit, preventing them from becoming zombies.

A flowchart illustrating the lifecycle of Zombie and Orphan processes. The chart should have two bra...
AI-generated image — may contain inaccuracies

Code Example: Creating a Zombie

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

int main() {
    pid_t pid = fork();
    if (pid > 0) {
        // Parent sleeps, allowing child to exit first
        printf("Parent sleeping (Check 'ps -l' for 'Z' state)...\n");
        sleep(10); 
    } else {
        // Child exits immediately
        printf("Child exiting...\n");
        exit(0);
    }
    return 0;
}


4. Process Synchronization using wait()

Concept

Process synchronization ensures that the parent process can coordinate its execution with the termination of its child. The wait() system call blocks the calling process until one of its child processes exits.

System Calls

  1. *`pid_t wait(int status)`**:
    • Blocks the parent until any child terminates.
    • Returns the PID of the terminated child.
    • Stores exit status in the integer pointed to by status.
  2. *`pid_t waitpid(pid_t pid, int status, int options)`**:
    • Waits for a specific child (specified by pid).
    • Can be non-blocking using WNOHANG option.

Status Macros

To interpret the integer status returned by wait, use macros:

  • WIFEXITED(status): True if child exited normally.
  • WEXITSTATUS(status): Returns the exit code of the child.
  • WIFSIGNALED(status): True if child was killed by a signal.

Code Example: wait()

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

int main() {
    pid_t pid = fork();

    if (pid == 0) {
        printf("Child running...\n");
        sleep(2); // Simulate work
        printf("Child finished.\n");
        exit(5); // Exit with code 5
    } else {
        int status;
        printf("Parent waiting for child...\n");
        pid_t child_pid = wait(&status); // Blocks here

        if (WIFEXITED(status)) {
            printf("Child %d terminated normally with exit code: %d\n", 
                   child_pid, WEXITSTATUS(status));
        }
    }
    return 0;
}


5. Signals

Concept

Signals are software interrupts sent to a program to indicate that an important event has occurred. They are a form of inter-process communication (IPC) but are asynchronous.

Common Signals

Signal Description Default Action
SIGINT Interrupt from keyboard (Ctrl+C) Terminate
SIGQUIT Quit from keyboard (Ctrl+) Core Dump
SIGKILL Kill signal (cannot be caught/ignored) Terminate
SIGSTOP Stop process (cannot be caught) Pause/Stop
SIGCHLD Child stopped or terminated Ignore
SIGALRM Timer signal from alarm() Terminate

Signal Handling

A process can react to a signal in three ways:

  1. Default: Let the kernel handle it (usually kills the process).
  2. Ignore: Discard the signal (except SIGKILL/SIGSTOP).
  3. Catch (Handle): Execute a user-defined function when the signal arrives.

A block diagram showing the Signal Delivery Mechanism. On the far left, a block labeled "Signal Sour...
AI-generated image — may contain inaccuracies

Code Example: Catching SIGINT

C
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

// Signal Handler Function
void handle_sigint(int sig) {
    printf("\nCaught signal %d (SIGINT). Not exiting!\n", sig);
}

int main() {
    // Register the signal handler
    signal(SIGINT, handle_sigint);

    printf("Press Ctrl+C. I will trap it inside the loop.\n");
    
    while(1) {
        printf("Running... \n");
        sleep(1);
    }
    return 0;
}