Process management and control

In the realm of computer science, a process is a program in execution. Every program that runs on a computer system is a process. Understanding process management and control in the C programming language is crucial for developing efficient and robust applications.

Basics

1. Basics of Processes: A process is an instance of a running program. It consists of the program code, its current activity, and a set of system resources such as memory, CPU time, files, and I/O devices. In C, we can create and manage processes using system calls provided by the operating system.

2. Creating Processes: In C, the fork() system call is used to create a new process. When fork() is called, it creates a new process (child process) that is an exact copy of the calling process (parent process). Here’s a basic example:

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

int main() {
    pid_t pid;

    pid = fork();

    if (pid < 0) {
        fprintf(stderr, "Fork failed\n");
        return 1;
    } else if (pid == 0) {
        printf("Child process\n");
    } else {
        printf("Parent process\n");
    }

    return 0;
}

				
			
				
					// output //
Parent process
Child process

				
			

Explanation:

  • The fork() call creates a new process.
  • If fork() returns a negative value, it indicates an error.
  • If fork() returns 0, it means the current process is the child process.
  • If fork() returns a positive value (the process ID of the child), it means the current process is the parent process.

Process Control

Once processes are created, we may need to control their execution. The exec family of functions is used to replace the current process image with a new one. Here’s an example using execl():

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

int main() {
    printf("Before exec\n");

    execl("/bin/ls", "ls", "-l", NULL);

    printf("After exec\n"); // This line won't be reached if exec succeeds
    return 0;
}

				
			
				
					// output //
Before exec
<output of 'ls -l'>
				
			

Explanation:

  • execl() replaces the current process with a new process specified by the provided command.
  • The first argument is the path to the executable.
  • The second argument is the name of the executable.
  • Subsequent arguments are command-line arguments to the executable.
  • The last argument must be NULL.

Process Termination

Processes can terminate voluntarily or due to an error. The exit() function is used to terminate a process explicitly. Here’s an example:

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

int main() {
    printf("Before exit\n");

    exit(0);

    printf("After exit\n"); // This line won't be reached
    return 0;
}

				
			

Explanation:

  • exit() terminates the calling process and returns control to the operating system.
  • The argument passed to exit() is the exit status of the process.

Process Communication

Processes often need to communicate with each other, and inter-process communication (IPC) mechanisms facilitate this. One common IPC mechanism is pipes. Pipes allow one-way communication between processes. Here’s an example of using pipes:

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

int main() {
    int fd[2];
    pid_t pid;

    if (pipe(fd) == -1) {
        perror("Pipe failed");
        exit(1);
    }

    pid = fork();

    if (pid < 0) {
        perror("Fork failed");
        exit(1);
    }

    if (pid > 0) {  // Parent process
        close(fd[0]);  // Close reading end

        write(fd[1], "Hello, child process!", 22);
        close(fd[1]);  // Close writing end
    } else {  // Child process
        close(fd[1]);  // Close writing end

        char buffer[30];
        read(fd[0], buffer, 30);
        printf("Child received: %s\n", buffer);

        close(fd[0]);  // Close reading end
    }

    return 0;
}

				
			
				
					// output //
Child received: Hello, child process!

				
			

Explanation:

  • A pipe is created using the pipe() system call, which returns two file descriptors – one for reading (fd[0]) and one for writing (fd[1]).
  • The fork() call creates a child process.
  • In the parent process, data is written to the pipe using write().
  • In the child process, data is read from the pipe using read().

Process Synchronization

In multi-process systems, synchronization is crucial to prevent race conditions and ensure data consistency. One way to achieve synchronization is through the use of semaphores. Semaphores are integer variables used for signaling between processes. Here’s an example:

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

#define N 5

void critical_section(int sem_id) {
    struct sembuf sem_op;

    sem_op.sem_num = 0;
    sem_op.sem_op = -1;  // Decrement semaphore
    sem_op.sem_flg = 0;

    semop(sem_id, &sem_op, 1);  // Wait

    printf("Critical section\n");
    sleep(2);

    sem_op.sem_op = 1;  // Increment semaphore
    semop(sem_id, &sem_op, 1);  // Signal
}

int main() {
    int sem_id = semget(IPC_PRIVATE, 1, IPC_CREAT | 0666);

    if (sem_id == -1) {
        perror("Semaphore creation failed");
        exit(1);
    }

    semctl(sem_id, 0, SETVAL, 1);  // Initialize semaphore value to 1

    pid_t pid;

    for (int i = 0; i < N; ++i) {
        pid = fork();

        if (pid == -1) {
            perror("Fork failed");
            exit(1);
        }

        if (pid == 0) {  // Child process
            critical_section(sem_id);
            exit(0);
        }
    }

    for (int i = 0; i < N; ++i) {
        wait(NULL);  // Wait for all child processes to finish
    }

    semctl(sem_id, 0, IPC_RMID);  // Remove semaphore

    return 0;
}

				
			

Explanation:

  • Semaphores are created using semget() and initialized using semctl().
  • The semop() function is used to perform semaphore operations such as wait and signal.
  • In this example, each child process enters the critical section one at a time due to semaphore synchronization.

Process management and control in C are essential skills for developing robust and efficient applications. By mastering the creation, control, communication, and synchronization of processes, developers can design complex software systems that utilize the full capabilities of modern computer architectures.Happy coding!❤️

Table of Contents

Contact here

Copyright © 2025 Diginode

Made with ❤️ in India