Paging is a memory management scheme that eliminates the need for contiguous allocation of physical memory. It avoids external fragmentation and provides an efficient mechanism for memory protection. The basic idea behind paging is to divide physical memory into fixed-sized blocks called frames and logical memory into blocks of the same size called pages.
In paging, memory addresses are split into two parts:
Example calculation:
For a 32-bit address space with 4KB page size:
struct page_table_entry {
unsigned int frame_number : 20; // Physical frame number
unsigned int present : 1; // Present bit
unsigned int write : 1; // Write permission
unsigned int user : 1; // User-mode access
unsigned int accessed : 1; // Page accessed
unsigned int dirty : 1; // Page modified
unsigned int reserved : 7; // Reserved bits
};
Each entry contains:
Here’s a complete implementation of a basic paging system:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define PAGE_SIZE 4096
#define PAGE_TABLE_SIZE 1024
#define PHYSICAL_MEMORY_SIZE (PAGE_SIZE * PAGE_TABLE_SIZE)
typedef struct {
unsigned int frame_number : 20;
unsigned int present : 1;
unsigned int write : 1;
unsigned int user : 1;
unsigned int accessed : 1;
unsigned int dirty : 1;
unsigned int reserved : 7;
} PageTableEntry;
typedef struct {
PageTableEntry entries[PAGE_TABLE_SIZE];
} PageTable;
typedef struct {
char data[PHYSICAL_MEMORY_SIZE];
} PhysicalMemory;
// Initialize page table
void init_page_table(PageTable *pt) {
for (int i = 0; i < PAGE_TABLE_SIZE; i++) {
pt->entries[i].frame_number = 0;
pt->entries[i].present = 0;
pt->entries[i].write = 1;
pt->entries[i].user = 1;
pt->entries[i].accessed = 0;
pt->entries[i].dirty = 0;
pt->entries[i].reserved = 0;
}
}
// Translate virtual address to physical address
unsigned int translate_address(PageTable *pt, unsigned int virtual_address) {
unsigned int page_number = virtual_address / PAGE_SIZE;
unsigned int offset = virtual_address % PAGE_SIZE;
if (page_number >= PAGE_TABLE_SIZE) {
printf("Invalid page number\n");
return -1;
}
if (!pt->entries[page_number].present) {
printf("Page fault occurred\n");
return -1;
}
return (pt->entries[page_number].frame_number * PAGE_SIZE) + offset;
}
int main() {
PageTable *page_table = (PageTable*)malloc(sizeof(PageTable));
PhysicalMemory *physical_memory = (PhysicalMemory*)malloc(sizeof(PhysicalMemory));
// Initialize page table
init_page_table(page_table);
// Map some virtual pages to physical frames
page_table->entries[0].frame_number = 0;
page_table->entries[0].present = 1;
page_table->entries[1].frame_number = 1;
page_table->entries[1].present = 1;
// Test address translation
unsigned int virtual_address = 5000; // Should be in second page
unsigned int physical_address = translate_address(page_table, virtual_address);
if (physical_address != -1) {
printf("Virtual address %u maps to physical address %u\n",
virtual_address, physical_address);
}
free(page_table);
free(physical_memory);
return 0;
}
struct page {
unsigned long flags;
atomic_t _refcount;
struct address_space *mapping;
pgoff_t index;
struct list_head lru;
void *virtual;
};
Paging remains a fundamental mechanism in modern operating systems, providing:
The trade-offs between page size, TLB size, and page table structure continue to evolve with new hardware architectures.