Saturday, July 6, 2024

OPERATING SYSTEMS CONCEPS: FORK AND WAIT

 The concept of fork and wait system calls is primarily rooted in Unix-like operating systems such as Linux and macOS. Windows does not support the fork() system call directly as it has a different process creation model. However, similar functionality can be achieved using different APIs and mechanisms.

Linux (Unix-like Systems)

On Linux, you can use fork() and wait(). These system calls are native to Unix-like operating systems.

Windows

On Windows, you can achieve similar behavior using the CreateProcess function to create new processes and WaitForSingleObject to wait for process completion. Here's how you can translate the matrix multiplication example to Windows using these functions:

Example in Windows

Implementation in C

#include <windows.h> #include <stdio.h> #define MATRIX_SIZE 4 #define NUM_PROCESSES 4 void multiply_matrix(int start_row, int end_row, int matrixA[MATRIX_SIZE][MATRIX_SIZE], int matrixB[MATRIX_SIZE][MATRIX_SIZE], int result[MATRIX_SIZE][MATRIX_SIZE]) { for (int i = start_row; i < end_row; i++) { for (int j = 0; j < MATRIX_SIZE; j++) { result[i][j] = 0; for (int k = 0; k < MATRIX_SIZE; k++) { result[i][j] += matrixA[i][k] * matrixB[k][j]; } } } } DWORD WINAPI ThreadFunction(LPVOID lpParam) { int *params = (int *)lpParam; int start_row = params[0]; int end_row = params[1]; int (*matrixA)[MATRIX_SIZE] = (int (*)[MATRIX_SIZE])params[2]; int (*matrixB)[MATRIX_SIZE] = (int (*)[MATRIX_SIZE])params[3]; int (*result)[MATRIX_SIZE] = (int (*)[MATRIX_SIZE])params[4]; multiply_matrix(start_row, end_row, matrixA, matrixB, result); return 0; } int main() { int matrixA[MATRIX_SIZE][MATRIX_SIZE] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}}; int matrixB[MATRIX_SIZE][MATRIX_SIZE] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}}; int result[MATRIX_SIZE][MATRIX_SIZE] = {0}; HANDLE threads[NUM_PROCESSES]; int params[NUM_PROCESSES][5]; int rows_per_process = MATRIX_SIZE / NUM_PROCESSES; for (int i = 0; i < NUM_PROCESSES; i++) { params[i][0] = i * rows_per_process; params[i][1] = (i == NUM_PROCESSES - 1) ? MATRIX_SIZE : (i + 1) * rows_per_process; params[i][2] = (int)matrixA; params[i][3] = (int)matrixB; params[i][4] = (int)result; threads[i] = CreateThread(NULL, 0, ThreadFunction, params[i], 0, NULL); if (threads[i] == NULL) { fprintf(stderr, "CreateThread failed\n"); return 1; } } WaitForMultipleObjects(NUM_PROCESSES, threads, TRUE, INFINITE); // Print the result matrix for (int i = 0; i < MATRIX_SIZE; i++) { for (int j = 0; j < MATRIX_SIZE; j++) { printf("%d ", result[i][j]); } printf("\n"); } return 0; }

Explanation:

  • CreateThread: This function is used to create a new thread, which is the closest equivalent to fork() in Windows for the purpose of parallel processing.
  • WaitForMultipleObjects: This function waits for multiple threads to complete, similar to how wait() is used to wait for child processes in Unix-like systems.
  • ThreadFunction: The function that each thread runs, similar to how the child process would perform its task in the Unix example.

Differences:

  • Process vs. Thread: In the Windows example, threads are used instead of processes. While they are not exactly the same, threads can be used to achieve parallel computation.
  • API Functions: Windows uses different API functions (CreateThread, WaitForMultipleObjects) compared to Unix (fork(), wait()).

Both methods allow for parallel computation, improving performance and resource utilization, but they do so using different system-specific mechanisms.

ADAPTATION:

For example, the following C language code can be compiled and run on both Linux and Windows operating systems, but it uses certain features and functions that are more traditionally associated with Unix-like systems (such as Linux). Specifically:

  • The fork() and waitpid() system calls are Unix-based functions and are part of the POSIX standard, which is natively supported on Linux and other Unix-like operating systems (e.g., macOS).
  • On Windows, these functions are not available in the standard libraries. Instead, you would need to use the Windows API for process management, which involves different functions like CreateProcess(), WaitForSingleObject(), and related mechanisms.

Therefore, the code as it stands is tailored for Linux (or other Unix-like operating systems). To run this code on Windows, you would need to modify it to use Windows-specific process management functions. Here is a high-level outline of how you might translate the key parts for Windows:

  1. Replace fork() with CreateProcess().
  2. Replace waitpid() with WaitForSingleObject() and GetExitCodeProcess().
  3. Use appropriate mechanisms for inter-process communication (if needed).

However, translating the below code to Windows is non-trivial due to the different paradigms and complexity of the Windows API. For simplicity and learning purposes, it is recommended to run this code on a Unix-like system where the POSIX functions are natively supported.

C

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <sys/wait.h>


#define MATRIX_SIZE 4

#define NUM_PROCESSES 4


void multiply_matrix(int start_row, int end_row, int matrixA[MATRIX_SIZE][MATRIX_SIZE], int matrixB[MATRIX_SIZE][MATRIX_SIZE], int result[MATRIX_SIZE][MATRIX_SIZE]) {

    for (int i = start_row; i < end_row; i++) {

        for (int j = 0; j < MATRIX_SIZE; j++) {

            result[i][j] = 0;

            for (int k = 0; k < MATRIX_SIZE; k++) {

                result[i][j] += matrixA[i][k] * matrixB[k][j];

            }

        }

    }

}


int main() {

    int matrixA[MATRIX_SIZE][MATRIX_SIZE] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}};

    int matrixB[MATRIX_SIZE][MATRIX_SIZE] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}};

    int result[MATRIX_SIZE][MATRIX_SIZE] = {0};


    pid_t pids[NUM_PROCESSES];

    int rows_per_process = MATRIX_SIZE / NUM_PROCESSES;


    for (int i = 0; i < NUM_PROCESSES; i++) {

        pids[i] = fork();

        if (pids[i] < 0) {

            perror("Fork failed");

            return 1;

        } else if (pids[i] == 0) {

            int start_row = i * rows_per_process;

            int end_row = (i == NUM_PROCESSES - 1) ? MATRIX_SIZE : (i + 1) * rows_per_process;

            multiply_matrix(start_row, end_row, matrixA, matrixB, result);

            exit(0);

        }

    }


    for (int i = 0; i < NUM_PROCESSES; i++) {

        waitpid(pids[i], NULL, 0);

    }


    // Print the result matrix

    for (int i = 0; i < MATRIX_SIZE; i++) {

        for (int j = 0; j < MATRIX_SIZE; j++) {

            printf("%d ", result[i][j]);

        }

        printf("\n");

    }


    return 0;

}

Summary of fork() Process: 

Summarized explanation of the fork() process in operating systems like Linux, along with a simple example in C:

  1. Purpose: fork() is a system call used to create a new process (child process) from an existing process (parent process).

  2. Behavior:

    • After calling fork(), two identical processes are created. The child process is a copy of the parent process at the time of the fork() call.
    • Both processes continue execution independently after fork(), with separate memory spaces.
  3. Return Values:

    • In the parent process, fork() returns the PID (Process ID) of the child process.
    • In the child process, fork() returns 0.
  4. Common Usage:

    • Parallel Processing: Used to achieve parallel execution of tasks by splitting workload between parent and child processes.
    • Multi-processing: Allows programs to perform multiple tasks simultaneously.

Example in C

c

#include <stdio.h> #include <unistd.h> int main() { pid_t pid = fork(); // Create a new process if (pid < 0) { // Fork failed perror("Fork failed"); return 1; } else if (pid == 0) { // Child process printf("Hello from Child! (PID: %d)\n", getpid()); } else { // Parent process printf("Hello from Parent! (PID: %d)\n", getpid()); } return 0; }

Explanation of the Example:

  • fork(): Creates a new process. After fork(), both parent and child processes execute the same code.
  • pid < 0: Handles error if fork() fails.
  • pid == 0: Code block executed by the child process.
  • pid > 0: Code block executed by the parent process (returns child's PID).
  • getpid(): Retrieves the PID of the current process.

Output:

csharp

Hello from Parent! (PID: 1234)
Hello from Child! (PID: 1235)

Summary

  • fork() creates a new process, allowing programs to execute concurrently.
  • Parent and child processes have different PIDs and execute independently after fork().
  • Error handling is essential, especially for resource allocation failures.

This summarizes how fork() works and its basic usage in C programming for process creation and parallel execution.

Glossary-style definitions for fork(), PID, and wait():

Fork()

Fork: In Unix-like operating systems, fork() is a system call and a function in the C language that creates a new process (child process) from an existing process (parent process). The child process is an exact copy of the parent process at the time of the fork() call, with its own memory space and execution context.

Process ID

PID: Process ID is a unique identifier assigned to each process running on a computer system. It is a non-negative integer used by the operating system to manage processes, allocate resources, and facilitate inter-process communication.

Wait

Wait: In operating systems, wait() is a system call used to make the parent process wait until one of its child processes terminates. It allows the parent process to synchronize its execution with the completion of a child process's execution.

These definitions succinctly describe fork(), PID, and wait() in the context of operating systems and process management.

Pages