- Authors

- Name
- Youngju Kim
- @fjvbn20031
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.