Operating Systems - Process Management and Memory Layout
Table of Contents
- Process Memory Layout
- User Space and Kernel Space
- Why the Separation?
- Implementation of User and Kernel Mode
- System Calls
- UNIX System Calls
Process Memory Layout
When a program runs, it is loaded into memory and divided into different sections. Understanding this memory layout is essential for debugging, performance optimization, and security.
Key Sections in a Process Memory Layout
- Text Segment (Code Section)
- Stores the compiled machine code of the program.
- It is marked as read-only to prevent accidental modifications.
- This is where function definitions and executable logic reside.
- Data Segment
- Initialized Data Segment: Stores global and static variables that are explicitly initialized before execution.
- Uninitialized Data Segment (BSS - Block Started by Symbol): Stores global and static variables that are declared but not initialized. These are automatically initialized to zero at runtime.
- Heap
- A dynamically allocated memory region used during runtime.
- It grows upwards towards higher memory addresses.
- Used by functions like
malloc()
in C ornew
in C++ for dynamic memory allocation. - Explicit deallocation using
free()
ordelete
is required to avoid memory leaks.
- Stack
- Stores function call frames, local variables, return addresses, and control information.
- It grows downwards towards lower memory addresses.
- Managed automatically by the OS. When a function is called, a new stack frame is pushed onto the stack, and when it returns, the frame is popped.
Stack Example
The stack plays a crucial role in function execution and local variable storage. Let’s analyze a simple function call.
Example: Stack Usage During Function Execution
void fn(int a) {
a = a + 1; // Local variable stored in the stack
}
void main() {
int local = 10;
fn(local); // Function call pushes data onto the stack
}
How the Stack Works in This Example
- Before execution, the stack is empty.
- When
main()
begins, a stack frame is created, andlocal
is stored in it. - When
fn(local)
is called:- A new stack frame is created for
fn()
. - The argument
a
(copy oflocal
) is pushed onto the stack. - Any temporary variables inside
fn()
are also stored in this frame.
- A new stack frame is created for
- When
fn()
finishes execution:- Its stack frame is removed (popped).
- Execution resumes in
main()
, andlocal
remains unchanged.
Key Takeaways
- Stack memory is automatically managed.
- Recursion or excessive function calls can lead to stack overflow, which can crash the program.
- Local variables exist only within the function and disappear after the function exits.
User Space and Kernel Space
To ensure system stability and security, memory is divided into user space and kernel space.
User Space
- The memory area where user applications execute.
- Programs run in user mode, meaning they have restricted access to system resources.
- Each process runs in its own memory space to prevent interference with other processes.
Kernel Space
- The privileged area where the operating system executes.
- Responsible for managing CPU scheduling, memory allocation, and direct hardware access.
- Running in kernel mode allows full control over system resources.
Key Differences Between User and Kernel Space
- User space is where normal applications execute, while kernel space is reserved for OS operations.
- User programs cannot modify system resources directly; they request services via system calls.
- Crashes in user space affect only the running application, but kernel crashes can bring down the entire system.
Why the Separation Between User and Kernel Space?
The separation of user and kernel space ensures security, stability, and controlled access to system resources.
Reasons for This Separation
- Security
- Prevents malicious or faulty applications from modifying critical system resources.
- User applications cannot directly alter memory or CPU states.
- Stability
- A bug in a user program will not crash the entire OS.
- The kernel remains protected from user-level faults.
- Controlled Access
- User programs request OS services via system calls, ensuring controlled execution.
How Privileges Are Enforced
- The CPU maintains a mode bit, determining if a process is in user mode (restricted) or kernel mode (full access).
- When a user process requests a system resource (e.g., reading a file), it triggers a system call, switching the mode bit to kernel mode.
- After the operation completes, the CPU returns to user mode.
Implementation of User and Kernel Mode
The OS ensures that user programs and system operations run in separate spaces using mode switching.
Steps in Mode Switching
- A user program executes in user mode with restricted access.
- When it needs an OS service (e.g., file access), it invokes a system call.
- The CPU switches to kernel mode, and the OS processes the request.
- Once completed, the CPU switches back to user mode and resumes execution.
Example of Mode Switching in C
int fd = open("file.txt", O_RDONLY); // User mode
read(fd, buffer, 100); // Kernel mode to read from file
close(fd); // Back to user mode
- The
open()
andread()
functions invoke system calls, which transition the process into kernel mode. - The OS handles the request before switching the process back to user mode.
System Calls
System calls provide an interface between user processes and the operating system.
Types of System Calls
- Process Control
fork()
,exec()
,exit()
,wait()
- File Management
open()
,read()
,write()
,close()
- Device Management
ioctl()
,read()
,write()
- Memory Management
mmap()
,brk()
- Networking
socket()
,connect()
,accept()
Example of System Call Execution
int pid = fork(); // Creates a new process
if (pid == 0) {
execlp("/bin/ls", "ls", NULL); // Child replaces itself with a new program
} else {
wait(NULL); // Parent waits for the child to finish
}
fork()
creates a child process.exec()
replaces the child process with a new program.wait()
ensures the parent waits until the child finishes.
UNIX System Calls
UNIX provides various system calls for process control, file operations, and memory management.
Key UNIX System Calls
- fork() - Creates a new process.
- exec() - Replaces a process with a new program.
- exit() - Terminates a process.
- wait() - Makes a parent process wait for its child to complete.
Example of Process Creation Using UNIX System Calls
pid_t pid = fork();
if (pid == 0) {
printf("Child Process\n");
execl("/bin/ls", "ls", NULL);
} else {
wait(NULL);
}
- The parent process creates a child process using
fork()
. - The child process executes
execl()
to run a new program (ls
). - The parent waits using
wait()
, ensuring the child completes execution before proceeding.
Conclusion
Understanding process memory layout, kernel-user separation, and system calls is crucial for working with operating systems. These concepts define how processes execute, manage resources, and interact with hardware while ensuring security and stability.