Processes II and Process Management

Process Creation and the Tree Hierarchy

  • A parent process creates child processes, which can in turn create other processes, resulting in a hierarchical tree of processes.

  • Processes throughout the system are identified and managed via a unique Process Identifier known as a PIDPID.

  • When a parent process creates a child, there are two primary options for execution:

    • The parent and children execute concurrently.

    • The parent waits until some or all of its children have terminated before continuing.

The fork() System Call Mechanics

  • The primary mechanism for creating a new process in Unix-like systems is the fork() system call.

  • Execution Process: fork() creates a child process by making an exact duplicate of the parent process. Post-fork, both processes begin executing concurrently from the next instruction.

  • Unique Nature: The fork() call is unique because it is called once but returns twice:

    • Return to Parent: Returns the unique PIDPID of the newly created child process.

    • Return to Child: Returns a value of 00.

    • Return on Failure: Returns 1-1 if the process creation was unsuccessful.

  • Parent-Child Relationship: The process that initiates the call becomes the parent, while the new process becomes the child.

    • The Child process inherits both the User-level context and the Process context block from the parent process.

  • Memory Isolation: The child process inherits a copy of the parent's memory space, but they do not share the same memory. Each executes in its own independent environment.

Strategic Uses of fork()

  • Dual Code Execution: A process may duplicate itself so that the child and parent can execute different sections of code simultaneously.

    • Example: Network servers. The parent process waits for a service request from a client. When a request arrives, the parent calls fork(), allowing the child to handle that specific request while the parent returns to waiting for the next client.

  • Program Execution: A process may want to execute a completely different program.

    • Example: Shells. In this scenario, the child process typically calls exec() (or a variant) immediately after the fork() returns to replace its process image with a new program.

      • exec() creates another piece of code for the child to execute basically used to execute a different command.

System Limits and fork() Failures

  • There are two primary reasons why a fork() call might fail:

    • The system has already reached the maximum allowable number of processes (indicating systemic resource exhaustion).

    • The total number of processes for the specific real User ID (UIDUID) has exceeded the system-defined limit.

  • System Configuration: The command cat /proc/sys/kernel/pid_max can be used to view the maximum number of unique PIDPIDs the system supports.

Practice Task I: Process Creation and Command Execution

  • Objective: Demonstrate fork(), exec(), and wait() for synchronization.

  • Task Requirements:

    • Create a parent that spawns two child processes.

    • Child 1: Use getpid()/getppid() to print IDs, then use execlp() to run the date command.

    • Child 2: Use getpid()/getppid() to print IDs, then use execlp() to run the whoami command.

    • Parent: Use wait() or waitpid() to wait for both children, then print a confirmation message after each terminates.

    • Use _exit() in children after exec() to prevent flushing parent buffers.

  • Implementation Details:

    • In the solution, pid1 = fork() and pid2 = fork() are used to generate the IDs.

    • Error handling is managed via perror() and exit(1).

    • The parent utilizes a loop: for (int i = 0; i < 2; i++) { terminated_pid = wait(&status); } to synchronize.

Process Synchronization and the wait() Family

  • Importance of Synchronization:

    • Prevents data inconsistency between concurrent processes.

    • Prevents system deadlocks.

    • Prevents race conditions (errors occurring when multiple operations execute simultaneously. For example if two processes are accessing the same data while the first process changes the shared data but the second process is accessing the older version of the data.)

  • Program Execution Flow:

    • A program is loaded from the hard disk into an address space in main memory.

    • It is scheduled onto a CPU.

    • The CPU executes instructions sequentially, tracked by a Program Counter (PCPC).

  • wait() and waitpid() Functionality:

    • These calls force the parent process to suspend execution until a child process completes.

    • wait(): Suspends the parent until the first of its children terminates.

    • waitpid(): Suspends the parent until a specific child process (identified by PIDPID) terminates.

    • Result: On success, they return the PIDPID of the terminated child. On error (e.g., if no child exists), they return 1-1.

  • statloc Argument: pid_t wait(int *statloc)

    • If statloc is NULL (i.e., (int *)0), the status is ignored.

    • If a pointer to an integer is provided, it is bound to the child's status information.

Process States and Suspension (The 7-State Model)

  • Basic States: Ready, Running, and Blocked.

  • Process Suspension: If processes in main memory enter a blocked state, the OS may move one process to a "Suspended" state on the disk to free memory for other processes.

    • While the CPU is in this suspended state the OS preforms a context switch

  • Extended States:

    • Blocked_Suspend: A process that is blocked and resides on the disk.

    • Ready_Suspend: A process that is ready to run but is currently on the disk.

  • Transition: When a Suspended process becomes ready to run, it moves into the Ready_Suspend queue.

Process Termination Dynamics

  • Methods of Termination:

    • Executing the last statement and requesting deletion.

    • Calling exit() from the <stdlib.h> library.

    • A parent using abort() to kill a child (common if the child exceeds resources, the task is no longer needed, or the parent is exiting).

  • Cascading Termination: Some OS architectures do not allow a child to continue if the parent has terminated.

  • Signaling: When a child process terminates, a SIGCHLD signal is sent to the parent process.

Zombies and Orphan Processes

  • Orphan Process: A child process whose parent has terminated while the child is still executing. Orphans are adopted by the system's init process (PID1PID   1).

  • Zombie Process:

    • Occurs when a child terminates but its parent has not yet executed a wait() call to query its exit status.

    • A zombie is not an active process but maintains an entry in the system process table.

  • Resolution: When a parent terminates, both orphans and zombies are adopted by the init process.

Practice Task II: Exit Status and sleep()

  • Objective: Handle a child process with a specific exit status and parent waiting.

  • Task Requirements:

    • Child: Print a message, sleep(2) for two seconds, and exit(42).

    • Parent: Use wait(&status) and extract the status using the macro WEXITSTATUS(status) after checking WIFEXITED(status).

  • Result: The output should confirm "Parent: Child finished with status 4242".

Process Identification and Groups

  • Process ID Identification Functions:

    • getpid(): Returns the process's own PIDPID.

    • getppid(): Returns the PIDPID of the parent.

    • getuid(): Returns the real User ID of the process.

  • Process Groups:

    • A logical cluster of processes belonging to the same application or function.

    • Creation: A child inherits its group from its parent.

    • Management: Use setpgid() to change groups, setpgrp() to set a process's group to itself, and getpgrp() or getpgid() to retrieve IDs.

    • Signal Distribution: Signals directed to a process group are delivered to every member. This is the basis for shell job control (e.g., Ctrl-C terminal signals).

  • Sessions: A collection of one or more process groups.

Process Data and File Descriptor Inheritance

  • Variable Copying: Since the child is a copy, it has its own instance of the parent's data. Changes to a variable in the child (e.g., glbvar++) do not affect the parent's variables.

  • File Descriptors:

    • Both inherit the same open file descriptors.

    • Variables are passed by value (a copy is made).

    • The Read-Write (RWR-W) pointer for a file is maintained by the system and passed by reference.

    • shared Pointer Behavior: Because the RWR-W pointer is shared, a read() or write() in the child directly changes the current position in the file for the parent.

  • lseek() Function:

    • Repositions the file offset.

    • lseek(fd, 0L, SEEK_CUR) sets the offset to its current location plus 00 bytes (essentially querying the current position).

    • Returns the resulting offset location in bytes from the beginning of the file.

Summary of Termination and Cleanup

  • When a process terminates:

    • All open file descriptors are closed (though buffered streams like stdout are not automatically flushed).

    • The process exit status is saved (low-order 88 bits as the program status) to be reported via wait.

    • Children are assigned to the init process (PID1PID   1).

    • A SIGCHLD is sent to the parent.

    • If the termination causes a process group to become orphaned and a member is stopped, a SIGHUP and SIGCONT are sent to each process in that group.