Skip to content
Published on

[OS Concepts] 03. Processes: Concepts, Creation, Communication

Authors

Process Concept

A process is a program in execution. While a program is a passive entity stored on disk, a process is an active entity loaded into memory and running.

Process Memory Layout

[Process Memory Layout]

High Address
+------------------+
|     Stack        |  Local variables, function parameters, return addresses
|        |         |  (grows downward)
|        v         |
|                  |
|        ^         |
|        |         |
|     Heap         |  Dynamically allocated memory (malloc, new)
|                  |  (grows upward)
+------------------+
|   Data           |  Global variables, static variables
+------------------+
|   Text           |  Program code (machine instructions)
+------------------+
Low Address

Process States

A process goes through the following states during execution.

[Process State Transition Diagram]

                    Dispatch
         +--------+-------->+---------+
         |  Ready  |         | Running  |
         |         |<--------+         |
         +--------+  Timer   +---------+
           ^    ^   Interrupt   |     |
           |    |              |     |
 admitted  |    | I/O complete | I/O | exit
           |    |   event     |request|
           |    |              |     |
         +---+  +--------+    |   +--------+
         |New |  | Waiting |<---+   |Terminated|
         |    |  |         |        |          |
         +---+  +--------+        +--------+
  • New: The process is being created
  • Ready: Waiting for CPU allocation
  • Running: Instructions are being executed on the CPU
  • Waiting: Waiting for I/O or event completion
  • Terminated: Execution is complete

Process Control Block (PCB)

Each process is represented in the OS by a PCB (Process Control Block).

// Linux kernel's task_struct structure (simplified)
struct task_struct {
    volatile long state;          // Process state
    pid_t pid;                    // Process ID
    pid_t tgid;                   // Thread group ID

    struct mm_struct *mm;         // Memory management info
    struct files_struct *files;   // Open file table
    struct thread_struct thread;  // CPU register state

    unsigned int policy;          // Scheduling policy
    int prio;                     // Priority
    cpumask_t cpus_allowed;       // Allowed CPUs

    struct task_struct *parent;   // Parent process
    struct list_head children;    // Child process list
    // ... hundreds of additional fields
};

The PCB contains process state, program counter, CPU registers, scheduling information, memory management information, I/O status information, etc.


Process Scheduling

Scheduling Queues

The OS uses multiple queues to manage processes.

[Process Scheduling Queues]

  Ready Queue
  +-----+  +-----+  +-----+  +-----+
  | PCB |->| PCB |->| PCB |->| PCB |---> CPU
  +-----+  +-----+  +-----+  +-----+

  Disk Wait Queue
  +-----+  +-----+
  | PCB |->| PCB |---> Disk Controller
  +-----+  +-----+

  Network Wait Queue
  +-----+
  | PCB |---> Network Interface
  +-----+

Context Switch

When the CPU switches to another process, it must save the state of the current process and restore the state of the new process.

[Context Switch]

Process P0        OS              Process P1
  Running                          Idle
    |                                 |
    |--interrupt/syscall-->          |
    |              Save state to PCB0 |
    |              Load state from PCB1|
    |                        -------->|
    Idle                       Running |
    |                                 |
    |              <--interrupt/syscall|
    |              Save state to PCB1 |
    |              Load state from PCB0|
    |<---------                       |
  Running                            Idle

Context switch time is pure overhead. Depending on the hardware, it takes approximately 1 to 1000 microseconds.


Process Operations

Process Creation: fork()

In Unix/Linux, processes are created using the fork() system call. fork() creates a copy of the calling process.

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

int main() {
    pid_t pid;

    printf("Before fork() - PID: %d\n", getpid());

    pid = fork();  // Create child process

    if (pid < 0) {
        // fork failed
        perror("fork failed");
        return 1;
    } else if (pid == 0) {
        // Child process: fork() returns 0
        printf("Child process - PID: %d, Parent PID: %d\n",
               getpid(), getppid());
    } else {
        // Parent process: fork() returns child's PID
        printf("Parent process - PID: %d, Child PID: %d\n",
               getpid(), pid);
        wait(NULL);  // Wait for child to terminate
        printf("Child process terminated\n");
    }

    return 0;
}
[fork() Operation]

Parent (PID 100)        fork()
     |                    |
     |                    |---> Child (PID 101)
     |                    |      Copy of parent's address space
     |                    |      fork() return value = 0
     | fork() return = 101|
     |                    |

Combining fork() and exec()

After fork(), calling exec() makes the child process execute a new program.

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

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

    if (pid == 0) {
        // Child: execute new program with exec()
        // exec() replaces the current process image with a new program
        printf("Child: executing ls command\n");
        execlp("ls", "ls", "-la", NULL);

        // Code below is not reached if exec() succeeds
        perror("exec failed");
    } else if (pid > 0) {
        // Parent: wait for child to terminate
        int status;
        waitpid(pid, &status, 0);

        if (WIFEXITED(status)) {
            printf("Child exit code: %d\n", WEXITSTATUS(status));
        }
    }

    return 0;
}

Process Termination

A process requests termination with the exit() system call. The parent process collects the child's termination status with wait().

  • Zombie Process: A terminated process whose parent has not called wait()
  • Orphan Process: A process whose parent has terminated first. init (PID 1) becomes the new parent

Inter-Process Communication (IPC)

Processes can be independent or cooperating. Cooperating processes need IPC.

Shared Memory

Multiple processes access the same memory region to exchange data.

// POSIX shared memory - Producer
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>

#define SHM_NAME "/my_shm"
#define SHM_SIZE 4096

int main() {
    // Create shared memory object
    int shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
    ftruncate(shm_fd, SHM_SIZE);

    // Memory mapping
    char *ptr = mmap(NULL, SHM_SIZE,
                     PROT_READ | PROT_WRITE,
                     MAP_SHARED, shm_fd, 0);

    // Write data
    const char *message = "Hello from producer!";
    memcpy(ptr, message, strlen(message) + 1);

    printf("Producer: message written\n");
    munmap(ptr, SHM_SIZE);
    return 0;
}
// POSIX shared memory - Consumer
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>

#define SHM_NAME "/my_shm"
#define SHM_SIZE 4096

int main() {
    // Open existing shared memory
    int shm_fd = shm_open(SHM_NAME, O_RDONLY, 0666);

    // Memory mapping
    char *ptr = mmap(NULL, SHM_SIZE,
                     PROT_READ,
                     MAP_SHARED, shm_fd, 0);

    // Read data
    printf("Consumer: %s\n", ptr);

    munmap(ptr, SHM_SIZE);
    shm_unlink(SHM_NAME);  // Delete shared memory
    return 0;
}

Message Passing

Processes exchange messages through the OS kernel. Slower than shared memory but has built-in synchronization.

[IPC Model Comparison]

Shared Memory Model:            Message Passing Model:

ProcessA   ProcessB             ProcessA   ProcessB
  |    \   /    |                 |           |
  |     \ /     |                 | send(M)   |
  |  Shared Mem |                 |---> Kernel |
  |     / \     |                 |     |     |
  |    /   \    |                 |     v     |
  |           |                   | receive(M)|
                                  |     ----->|
Minimal kernel involvement      Kernel involved in every transfer

Pipes

A pipe is a communication channel between two processes.

Ordinary Pipe

Can only be used between parent-child processes and is unidirectional.

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

int main() {
    int fd[2];  // fd[0]: read end, fd[1]: write end
    pid_t pid;
    char buffer[256];

    // Create pipe
    if (pipe(fd) == -1) {
        perror("Pipe creation failed");
        return 1;
    }

    pid = fork();

    if (pid == 0) {
        // Child: read from pipe
        close(fd[1]);  // Close write end

        int bytes = read(fd[0], buffer, sizeof(buffer));
        printf("Child received: %s (%d bytes)\n", buffer, bytes);

        close(fd[0]);
    } else {
        // Parent: write to pipe
        close(fd[0]);  // Close read end

        const char *msg = "Hello, child process!";
        write(fd[1], msg, strlen(msg) + 1);
        printf("Parent sent: %s\n", msg);

        close(fd[1]);
        wait(NULL);
    }

    return 0;
}

Named Pipe (FIFO)

Can be used between arbitrary processes, not just parent-child relationships.

# Using named pipe in shell
mkfifo /tmp/my_pipe

# Terminal 1: Write
echo "Hello via named pipe" > /tmp/my_pipe

# Terminal 2: Read
cat /tmp/my_pipe

Client-Server Communication

Sockets

A socket is an endpoint for communication. It is identified by the combination of an IP address and a port number.

// TCP server example
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

int main() {
    int server_fd, client_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t addr_len = sizeof(client_addr);
    char buffer[1024];

    // Create socket
    server_fd = socket(AF_INET, SOCK_STREAM, 0);

    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(8080);

    // Bind and listen
    bind(server_fd, (struct sockaddr *)&server_addr,
         sizeof(server_addr));
    listen(server_fd, 5);

    printf("Server waiting (port 8080)...\n");

    // Accept client connection
    client_fd = accept(server_fd,
                       (struct sockaddr *)&client_addr,
                       &addr_len);

    // Receive data and respond
    int bytes = recv(client_fd, buffer, sizeof(buffer), 0);
    buffer[bytes] = '\0';
    printf("Received: %s\n", buffer);

    const char *response = "Server response: message received";
    send(client_fd, response, strlen(response), 0);

    close(client_fd);
    close(server_fd);
    return 0;
}

Remote Procedure Call (RPC)

RPC is a mechanism for calling functions on a remote system as if they were local functions over a network.

[RPC Operation Process]

Client                                Server
    |                                    |
    |-- 1. Local function call form --   |
    |      (stub marshals parameters)    |
    |                                    |
    |-- 2. Transmit over network ------->|
    |                                    |-- 3. Server stub
    |                                    |      unmarshals
    |                                    |
    |                                    |-- 4. Execute actual function
    |                                    |
    |<-- 5. Return result ---------------|
    |      (stub unmarshals result)      |
    |                                    |

Key considerations for RPC are as follows.

  • Data Representation: Client and server may have different data formats, so standard formats like XDR (External Data Representation) are used
  • Binding: How the client finds the server. Fixed port or rendezvous daemon (port mapper)
  • Execution Semantics: Guaranteeing "at most once" or "exactly once" execution

Summary

A process is the core abstraction unit of the OS. Processes are created with fork() and new programs are executed with exec(). There are various mechanisms for inter-process communication including shared memory, message passing, pipes, sockets, and RPC, each with trade-offs in terms of performance and convenience.