Skip to content

Lecture No. 07

Dated: 28-04-2025

The wait() System Call

The wait() system call suspends a process until one of its immediate children terminates or stops.
It returns early if a signal is received or immediately if no children are active.
On success, it returns the PID of a child.
If the parent terminates, its children are reparented to init, which collects their status.

Zombie Process

A zombie process is a process that has terminated but whose exit status has not yet been received by its parent process or by init.

The execlp() System Call

After fork(), execlp() is typically used by one process to replace its memory with a new program from an executable file or interpreter data.
A successful execlp() does not return, as the original process image is replaced.
This allows the two processes to communicate and then operate independently.

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

int main () {

    int pid, status;

    pid = fork();

    if (pid == -1) {
        perror("fork failed"); // Use perror for better error message
        exit(1);
    }

    if (pid == 0) { /* Child */
        if (execlp("/bin/ls", "ls", NULL) < 0) {
            printf("exec failed\n");
            exit(1);
        }
    } 
    
    else { /* Parent */
        wait(&status);
        printf("Well done, kid!\n");
        exit(0);
    }

    return 0;
}

Cooperating Processes

Processes in an OS can be independent or cooperating.
Independent processes do not affect others, while cooperating processes share data.
Cooperating processes offer benefits like:

  • Information Sharing: Multiple users access shared resources.
  • Computation Speedup: Tasks are divided into parallel subtasks on multiprocessor systems.
  • Modularity: Systems are organized into separate processes or threads.
  • Convenience: Users can perform multiple tasks simultaneously.

Example:
The producer-consumer problem illustrates communication between processes.
A producer generates data for a consumer, requiring a shared buffer.
Synchronization ensures the consumer waits if the buffer is empty, and the producer waits if full.
Buffers can be bounded (fixed size) or unbounded (no size limit), implemented via inter-process communication or shared memory.

#define BUFFER_SIZE 10
typedef struct {
    // ...
} item;

item buffer[BUFFER_SIZE];

int in = 0;
int out = 0;

The shared buffer is a circular array with two pointers:

  • in points to the next free slot.
  • out points to the next full slot.
    The buffer is empty when in == out, and full when (in + 1) % BUFFER_SIZE == out.
// Producer Process
while (1) {
    /* Produce an item in nextProduced */
    while (((in + 1) % BUFFER_SIZE) == out) {
        /* do nothing */
    }
    buffer[in] = nextProduced;
    in = (in + 1) % BUFFER_SIZE;
}

// Consumer Process
while (1) {
    while (in == out) {
        /* do nothing */
    }
    nextConsumed = buffer[out];
    out = (out + 1) % BUFFER_SIZE;
    /* Consume the item in nextConsumed */
}