69天探索操作系统-第22天:虚拟内存 - 对需求分页的深度剖析

236 阅读7分钟

pro13.avif

1.介绍

虚拟内存与需求分页是内存管理中的关键技术,即使进程的整个内存占用空间没有完全放入物理内存中,它们也能执行。这通过只有在访问时才将页面加载到物理内存中来实现,从而创造出更大的地址空间。需求分页显著提高了内存利用率,并允许高效的多任务处理。

需求分页依赖于局部性原理,程序在任何特定时间都倾向于访问其内存页的一小部分。通过只加载必要的页,需求分页可以最大限度地减少磁盘I/O,提高整体系统性能。

2.需求分页基础

需求分页操作基于几个核心概念,包括页状态位、页错类型以及操作系统、硬件和次存储之间的相互作用。理解这些概念是掌握需求分页工作原理的基础。

需求分页利用页面状态位(存在/不存在、脏、引用)来跟踪每页的状态。当访问不在物理内存中的页面时,会发生页面故障,其处理是需求分页的一个重要方面。

  • 页面状态位:

    • 出现/不存在位: 表示页面是否在物理内存中。
    • 修改(脏)位: 表示页面自上次加载以来是否已被修改。
    • 引用位: 表示页面是否最近被访问过。
    • 保护位: 定义访问权限(读取、写入、执行)。
  • 页面故障类型:

    • 硬故障: 页面不在内存中,必须从磁盘加载。
    • 软故障: 页面在内存中,但未映射到页表中。
    • 故障: 尝试访问无效的内存地址。

3.页面故障处理

当发生页面故障时,操作系统会介入,从磁盘中加载缺失的页面到物理内存。这个过程中包括找到一个空闲帧,从磁盘中读取页面,更新页面表,以及恢复被中断的进程。高效地处理页面故障对于系统性能至关重要。

页面故障处理是需求分页的一个重要方面,涉及多个步骤:确定故障原因、定位磁盘上的页面、查找内存中的空闲帧(或者使用替换算法清除页面)、将页面装入帧、更新页面表,最后恢复流程。

4.内存管理单元 (MMU)

MMU 是一个硬件组件,用于将虚拟地址转换为物理地址。它在需求分页中发挥着关键的作用,通过检查页表条目中的当前/不存在位,并在页不在内存中时生成页面故障。

5.页表实现

页表是一种数据结构,用于将虚拟页映射到物理帧。页表被MMU用来进行地址转换。不同的页表实现存在,每种实现都有关于空间和时间复杂度的权衡。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define PAGE_TABLE_SIZE 1024
#define FRAME_SIZE 4096

typedef struct {
    int frame_number;
    unsigned int present : 1;
    unsigned int dirty : 1;
    unsigned int referenced : 1;
} PageTableEntry;

typedef struct {
    PageTableEntry entries[PAGE_TABLE_SIZE];
} PageTable;

PageTable* createPageTable() {
    PageTable* pt = (PageTable*)malloc(sizeof(PageTable));
    if (pt == NULL) {
        perror("Failed to allocate memory for page table");
        exit(EXIT_FAILURE);
    }

    for (int i = 0; i < PAGE_TABLE_SIZE; i++) {
        pt->entries[i].frame_number = -1;
        pt->entries[i].present = 0;
        pt->entries[i].dirty = 0;
        pt->entries[i].referenced = 0;
    }
    return pt;
}

int getFrameNumber(PageTable* pt, int page_number) {
    if (page_number < 0 || page_number >= PAGE_TABLE_SIZE) {
        fprintf(stderr, "Invalid page number: %d\n", page_number);
        return -1;
    }

    if (!pt->entries[page_number].present) {
        return -2;
    }
    return pt->entries[page_number].frame_number;
}

void setFrameNumber(PageTable* pt, int page_number, int frame_number) {
    if (page_number < 0 || page_number >= PAGE_TABLE_SIZE) {
        fprintf(stderr, "Invalid page number: %d\n", page_number);
        return;
    }

    pt->entries[page_number].frame_number = frame_number;
    pt->entries[page_number].present = 1;
}

int main() {
    PageTable* myPageTable = createPageTable();

    setFrameNumber(myPageTable, 0, 5);
    setFrameNumber(myPageTable, 50, 10);

    int frame = getFrameNumber(myPageTable, 0);
    if (frame >= 0) {
        printf("Page 0 is in frame: %d\n", frame);
    } else if (frame == -2) {
        printf("Page fault for page 0\n");
    }

    frame = getFrameNumber(myPageTable, 2000);

    free(myPageTable);

    return 0;
}

6.工作集模型

工作集模型描述了一个进程正在积极使用的页面集。这一模型对于理解和管理内存使用以及防止页面置换过多导致的系统性能下降非常重要。

7.防止崩溃

当一个进程花费更多的时间来处理页面故障而不是执行指令时,就会发生混乱。这可以显著降低系统的性能。工作集模型和页面帧分配策略等技术被用于防止混乱。

typedef struct {
    unsigned int* page_timestamps;
    unsigned int window_size;
} WorkingSetTracker;

int isInWorkingSet(WorkingSetTracker* tracker, unsigned int page, unsigned int current_time) {
    return (current_time - tracker->page_timestamps[page]) <= tracker->window_size;
}

页面框架分配

void adjustPageFrameAllocation(Process* process, SystemStats* stats) {
    if(stats->page_fault_rate > THRESHOLD) {
        increaseFrameAllocation(process);
    } else if(stats->page_fault_rate < LOW_THRESHOLD) {
        decreaseFrameAllocation(process);
    }
}

8.需求分页系统的C实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define PAGE_SIZE 4096
#define PAGE_TABLE_SIZE 1024
#define PHYSICAL_MEMORY_SIZE (256 * PAGE_SIZE)  // 1MB physical memory
#define VIRTUAL_MEMORY_SIZE (1024 * PAGE_SIZE)  // 4MB virtual memory
#define DISK_SIZE (4096 * PAGE_SIZE)           // 16MB disk space

typedef struct {
    unsigned int frame_number : 20;
    unsigned int present : 1;
    unsigned int dirty : 1;
    unsigned int referenced : 1;
    unsigned int protection : 3;
    unsigned int reserved : 6;
} PageTableEntry;

typedef struct {
    PageTableEntry entries[PAGE_TABLE_SIZE];
} PageTable;

typedef struct {
    char* data;
    unsigned int size;
    unsigned int free_frames;
    unsigned int* frame_map;  // Bitmap for frame allocation
} PhysicalMemory;

typedef struct {
    char* data;
    unsigned int size;
} DiskStorage;

typedef struct {
    unsigned long page_faults;
    unsigned long disk_reads;
    unsigned long disk_writes;
    unsigned long tlb_hits;
    unsigned long tlb_misses;
} SystemStats;

SystemStats stats = {0};

// Initialize physical memory
PhysicalMemory* initPhysicalMemory() {
    PhysicalMemory* memory = (PhysicalMemory*)malloc(sizeof(PhysicalMemory));
    memory->data = (char*)calloc(PHYSICAL_MEMORY_SIZE, 1);
    memory->size = PHYSICAL_MEMORY_SIZE;
    memory->free_frames = PHYSICAL_MEMORY_SIZE / PAGE_SIZE;
    memory->frame_map = (unsigned int*)calloc(PHYSICAL_MEMORY_SIZE / PAGE_SIZE / 32, sizeof(unsigned int));
    return memory;
}

DiskStorage* initDiskStorage() {
    DiskStorage* disk = (DiskStorage*)malloc(sizeof(DiskStorage));
    disk->data = (char*)calloc(DISK_SIZE, 1);
    disk->size = DISK_SIZE;
    return disk;
}

PageTable* initPageTable() {
    PageTable* pt = (PageTable*)malloc(sizeof(PageTable));
    memset(pt->entries, 0, sizeof(PageTableEntry) * PAGE_TABLE_SIZE);
    return pt;
}

int findFreeFrame(PhysicalMemory* memory) {
    for(unsigned int i = 0; i < memory->size / PAGE_SIZE / 32; i++) {
        if(memory->frame_map[i] != 0xFFFFFFFF) {
            for(int j = 0; j < 32; j++) {
                if(!(memory->frame_map[i] & (1 << j))) {
                    memory->frame_map[i] |= (1 << j);
                    memory->free_frames--;
                    return (i * 32) + j;
                }
            }
        }
    }
    return -1;
}

void handlePageFault(PageTable* pt, PhysicalMemory* memory, DiskStorage* disk, 
                    unsigned int page_number) {
    stats.page_faults++;
    
    int frame_number = findFreeFrame(memory);
    if(frame_number == -1) {
        // For simplicity, we'll just take the first frame
        frame_number = 0;
        
        // Write back dirty page if necessary
        if(pt->entries[0].dirty) {
            memcpy(disk->data + (0 * PAGE_SIZE),
                   memory->data + (frame_number * PAGE_SIZE),
                   PAGE_SIZE);
            stats.disk_writes++;
        }
    }
    
    // Read page from disk
    memcpy(memory->data + (frame_number * PAGE_SIZE),
           disk->data + (page_number * PAGE_SIZE),
           PAGE_SIZE);
    stats.disk_reads++;
    
    pt->entries[page_number].frame_number = frame_number;
    pt->entries[page_number].present = 1;
    pt->entries[page_number].dirty = 0;
    pt->entries[page_number].referenced = 1;
}

void* accessMemory(PageTable* pt, PhysicalMemory* memory, DiskStorage* disk,
                  unsigned int virtual_address, int write) {
    unsigned int page_number = virtual_address / PAGE_SIZE;
    unsigned int offset = virtual_address % PAGE_SIZE;
    
    if(page_number >= PAGE_TABLE_SIZE) {
        printf("Invalid memory access: address out of bounds\n");
        return NULL;
    }
    
    // Check if page is present
    if(!pt->entries[page_number].present) {
        handlePageFault(pt, memory, disk, page_number);
    }
    
    // Update access bits
    pt->entries[page_number].referenced = 1;
    if(write) {
        pt->entries[page_number].dirty = 1;
    }
    
    // Calculate physical address
    unsigned int frame_number = pt->entries[page_number].frame_number;
    unsigned int physical_address = (frame_number * PAGE_SIZE) + offset;
    
    return &memory->data[physical_address];
}

typedef struct {
    unsigned int* pages;
    unsigned int size;
    unsigned int max_size;
} WorkingSet;

WorkingSet* initWorkingSet(unsigned int max_size) {
    WorkingSet* ws = (WorkingSet*)malloc(sizeof(WorkingSet));
    ws->pages = (unsigned int*)calloc(max_size, sizeof(unsigned int));
    ws->size = 0;
    ws->max_size = max_size;
    return ws;
}

void updateWorkingSet(WorkingSet* ws, unsigned int page_number) {
    // Check if page is already in working set
    for(unsigned int i = 0; i < ws->size; i++) {
        if(ws->pages[i] == page_number) {
            return;
        }
    }
    
    // Add page to working set
    if(ws->size < ws->max_size) {
        ws->pages[ws->size++] = page_number;
    } else {
        // Replace oldest page
        memmove(ws->pages, ws->pages + 1, (ws->max_size - 1) * sizeof(unsigned int));
        ws->pages[ws->max_size - 1] = page_number;
    }
}

int main() {
    PhysicalMemory* memory = initPhysicalMemory();
    DiskStorage* disk = initDiskStorage();
    PageTable* page_table = initPageTable();
    WorkingSet* working_set = initWorkingSet(50);  // Track last 50 pages
    
    for(int i = 0; i < 1000; i++) {
        unsigned int virtual_address = rand() % VIRTUAL_MEMORY_SIZE;
        int write_access = rand() % 2;
        
        void* ptr = accessMemory(page_table, memory, disk, virtual_address, write_access);
        updateWorkingSet(working_set, virtual_address / PAGE_SIZE);
        
        if(ptr == NULL) {
            printf("Memory access failed at address: %u\n", virtual_address);
        }
    }
    
    printf("\nSystem Statistics:\n");
    printf("Page Faults: %lu\n", stats.page_faults);
    printf("Disk Reads: %lu\n", stats.disk_reads);
    printf("Disk Writes: %lu\n", stats.disk_writes);
    printf("Working Set Size: %u\n", working_set->size);
    
    free(memory->data);
    free(memory->frame_map);
    free(memory);
    free(disk->data);
    free(disk);
    free(page_table);
    free(working_set->pages);
    free(working_set);
    
    return 0;
}

9.性能优化

像TLB管理页面预取这样的技术可以显著提高需求页面的性能。这些技术旨在减少与地址转换和页面故障相关的开销。

#define TLB_SIZE 64

typedef struct {
    unsigned int virtual_page;
    unsigned int frame_number;
    unsigned int valid;
} TLBEntry;

TLBEntry tlb[TLB_SIZE];

int checkTLB(unsigned int virtual_page) {
    for(int i = 0; i < TLB_SIZE; i++) {
        if(tlb[i].valid && tlb[i].virtual_page == virtual_page) {
            stats.tlb_hits++;
            return tlb[i].frame_number;
        }
    }
    stats.tlb_misses++;
    return -1;
}

10.总结

虚拟内存与需求分页是一种强大的内存管理技术,可以有效地利用内存并促进多任务处理。然而,仔细管理页面故障、工作集和其他因素对于实现最佳性能至关重要。

11.参考资料和进一步阅读

  • "Operating System Concepts" by Silberschatz, Galvin, and Gagne
  • "Understanding the Linux Virtual Memory Manager" by Mel Gorman
  • "Virtual Memory" by Peter J. Denning
  • Intel® 64 and IA-32 Architectures Software Developer's Manual
  • "The Working Set Model for Program Behavior" by Peter J. Denning