目录
最近最久未使用(LRU, Least Recently Used)
最少使用(LFU, Least Frequently Used)
先进先出(FIFO, First In First Out)
请求分页存储管理方式是一种 commonly used 的虚拟内存管理技术,它结合了分页存储管理和按需装入的概念。在请求分页中,进程的地址空间被划分为多个页,但只将部分页装入内存,其余页留在磁盘上。当进程访问未驻留内存的页时,会触发缺页中断,操作系统负责将该页调入内存。
请求分页中的硬件支持
为了实现请求分页,系统必须提供一定的硬件支持。除了要求一定容量的内存和磁盘外,还需要以下硬件组件:
1. 请求页表机制
请求分页系统需要使用请求页表,其基本作用是将虚拟地址映射到物理地址。为了满足页面换进换出,在请求页表中增加了以下四个字段:
- 存在位(Present Bit):指示该页是否驻留在物理内存中。如果存在位为0,表示该页不在内存中,需要触发缺页中断。
- 访问字段(Accessed Bit):记录该页是否被访问过,用于页面置换算法(如LRU算法)来判断哪些页可以被换出。
- 修改位(Dirty Bit):指示该页是否被修改过。如果被修改过,在换出时需要将该页写回到磁盘。
- 页框号(Frame Number):记录该页在物理内存中的位置。
2. 缺页中断机构
缺页中断是当进程访问未驻留内存的页时触发的中断。缺页中断机构负责以下任务:
- 保护 CPU 环境:保存当前的CPU状态和寄存器内容,以便在中断处理完成后能够恢复。
- 分析中断原因:确定中断是由于缺页引起的,并识别缺页的虚拟地址。
- 转入缺页中断处理程序:调用操作系统中的缺页中断处理程序,负责将所需的页面从磁盘加载到内存中。
- 恢复 CPU 环境:在中断处理完成后,恢复之前保存的CPU状态和寄存器内容,使进程继续执行。
硬件支持的详细工作流程
以下是请求分页过程中硬件支持的详细工作流程:
-
地址转换:
- CPU 生成虚拟地址。
- 请求页表机制将虚拟地址拆分为页号和页内偏移量。
- 检查请求页表中的存在位。
- 如果存在位为1,页面在内存中,直接使用页框号和页内偏移量生成物理地址。
- 如果存在位为0,触发缺页中断。
-
缺页中断处理:
- 缺页中断机构保存当前CPU环境。
- 分析中断原因,确定缺页的虚拟地址。
- 调用操作系统的缺页中断处理程序。
- 从磁盘读取缺页对应的页面。
- 更新请求页表中的页框号和存在位。
- 如果需要,更新访问字段和修改位。
- 恢复CPU环境。
- 重新执行引起缺页中断的指令。
示例代码
以下是一个简化的示例代码,展示了如何处理请求分页中的缺页中断:
#include <stdio.h>
#include <stdlib.h>
#define PAGE_SIZE 4096
#define NUM_PAGES 256
#define MEMORY_SIZE (PAGE_SIZE * NUM_PAGES)
typedef struct {
int present; // 存在位
int accessed; // 访问字段
int dirty; // 修改位
int frame_number; // 页框号
} PageTableEntry;
PageTableEntry page_table[NUM_PAGES];
char memory[MEMORY_SIZE];
void handle_page_fault(int page_number) {
printf("Handling page fault for page number: %d\n", page_number);
// 从磁盘加载页面到内存中(模拟)
int frame_number = page_number; // 简化示例,假设页框号等于页号
page_table[page_number].frame_number = frame_number;
page_table[page_number].present = 1;
// 模拟数据加载
snprintf(memory + frame_number * PAGE_SIZE, PAGE_SIZE, "Data for page %d", page_number);
}
void access_memory(int virtual_address) {
int page_number = virtual_address / PAGE_SIZE;
int offset = virtual_address % PAGE_SIZE;
if (page_number >= NUM_PAGES) {
printf("Invalid virtual address\n");
return;
}
if (!page_table[page_number].present) {
handle_page_fault(page_number);
}
int frame_number = page_table[page_number].frame_number;
char *data = memory + frame_number * PAGE_SIZE + offset;
printf("Accessed data: %s\n", data);
}
int main() {
// 初始化页表
for (int i = 0; i < NUM_PAGES; i++) {
page_table[i].present = 0;
page_table[i].accessed = 0;
page_table[i].dirty = 0;
page_table[i].frame_number = -1;
}
// 模拟内存访问
access_memory(0x1234); // 访问虚拟地址 0x1234
access_memory(0x5678); // 访问虚拟地址 0x5678
return 0;
}
请求分页中的内存分配
请求分页是一种内存管理技术,它结合了分页和按需调页的优势,提高了内存利用率和系统的灵活性。在请求分页中,内存分配涉及几个关键问题:
- 最小物理块数的确定:确保每个进程能够正常运行所需的最小物理块数。
- 分配方式:选择固定分配还是可变分配。
- 分配公平性:决定如何公平地分配物理块,采用平均分配或按比例分配。
最小物理块数的确定
为了保证进程能正常运行,需要为每个进程分配至少一定数量的物理块。确定最小物理块数时,需要考虑以下因素:
- 进程的工作集大小:进程运行过程中实际所需的最小页面数。
- 系统的上下文切换开销:频繁的缺页中断将增加系统开销,可能影响进程的响应时间。
最小物理块数的选择通常是操作系统预设的一个阈值,具体值根据系统资源和应用需求确定。
分配方式
进程的物理块可以采用固定分配或可变分配两种方式:
-
固定分配(Fixed Allocation)
- 定义:为每个进程分配固定数量的物理块,数量在进程创建时确定,不随运行过程中变化。
- 优点:实现简单,易于管理。
- 缺点:可能无法应对进程的动态需求,导致部分进程缺页中断频繁而其他进程空闲。
-
可变分配(Variable Allocation)
- 定义:根据进程运行情况动态调整所分配的物理块数量,灵活应对内存需求。
- 优点:灵活性高,能够更好地适应进程的实际需要。
- 缺点:实现复杂,管理难度高。
分配公平性
为了确保资源利用的公平性,在分配物理块时可选择以下策略:
-
平均分配算法(Equal Allocation Algorithm)
- 实现:将物理块平均分配给每个进程。
- 优点:公平、简单,每个进程获得相同数量的物理块。
- 缺点:忽略了进程实际的工作集大小,可能导致不必要的缺页中断。
-
按比例分配算法(Proportional Allocation Algorithm)
- 实现:根据进程的大小或优先级按比例分配物理块。
- 优点:考虑了进程实际的内存需求,提高了内存利用效率。
- 缺点:需要更多信息和计算,管理复杂。
请求分页存储管理方式中的内存分配策略
-
固定分配局部置换(Fixed Allocation, Local Replacement)
- 描述:为每个进程分配一组固定数量的物理块,运行期间不再改变。
- 缺页处理:当进程发生缺页中断时,只能从分配给该进程的物理块中选择一个页换出,再装入所需页。
- 优点:简单易行,进程间互不干扰,保证了隔离性。
- 缺点:可能导致频繁的缺页中断,内存利用率较低。
-
可变分配全局置换(Variable Allocation, Global Replacement)
- 描述:为每个进程初始分配一定数量的物理块,运行期间可根据需要动态调整。
- 缺页处理:当进程发生缺页中断时,可以从系统的空闲物理块中分配,也可以选择全局范围内任一物理块上的页换出,再装入新页。
- 优点:灵活应对内存需求,提高了内存利用率。
- 缺点:管理和实现复杂,可能导致缺页率增加,影响系统性能。
具体示例
固定分配局部置换示例:
function handlePageFault(process) {
if (process.allocatedFrames < process.maxFrames) {
frame = allocateFrame();
loadPage(process.page, frame);
} else {
victimPage = selectVictimPage(process);
evictPage(victimPage);
frame = victimPage.frame;
loadPage(process.page, frame);
}
}
function selectVictimPage(process) {
// Implement LRU, FIFO or any other page replacement algorithm inside the process's limit
}
可变分配全局置换示例:
function handlePageFaultGlobal(process) {
if (freeFrameAvailable()) {
frame = allocateFrame();
loadPage(process.page, frame);
} else {
victimPage = selectGlobalVictimPage();
evictPage(victimPage);
frame = victimPage.frame;
loadPage(process.page, frame);
}
}
function selectGlobalVictimPage() {
// Implement LRU, FIFO or any other page replacement algorithm across all processes
}
function freeFrameAvailable() {
// Check the central free frame list
return freeFrameList.size > 0;
}
页面调入策略
页面调入策略(Page Replacement Policies)是在发生缺页中断时,操作系统决定将哪个页调入内存,确保系统运行的最优性能。通过合理选择需要调入的页,可以减少缺页中断次数,提高内存利用率和系统效率。以下是几种常用的页面调入策略:
最近最久未使用(LRU, Least Recently Used)
LRU算法选择最近最久未使用的页进行调入。该算法假设最近未被使用的页在将来被访问的可能性最小。
- 实现:为每个页设置一个访问字段,记录该页自上次被访问以来的时间。每次访问页面时,更新该访问字段。
- 过程:
- 发生缺页中断时,查找所有在内存中的页面,选择访问字段值最大的页面(即最近最久未使用的页面)。
- 替换选中的页面,将缺页页面调入内存。
示例伪代码:
function lruPageReplacement(pageTable) {
oldestPage = pageTable[0];
for each page in pageTable {
if page.accessTime > oldestPage.accessTime {
oldestPage = page;
}
}
return oldestPage;
}
function handlePageFault_LRU(pageTable, newPage) {
victimPage = lruPageReplacement(pageTable);
evictPage(victimPage);
loadPage(newPage, victimPage.frame);
pageTable.updateAccessTime(newPage);
}
最少使用(LFU, Least Frequently Used)
LFU算法选择在最近时期使用最少的页进行调入。它假设使用频率最低的页在将来被访问的可能性也较低。
- 实现:为每个页设置一个计数器,记录该页被访问的次数。每次访问页面时,增加该计数器的值。
- 过程:
- 发生缺页中断时,查找所有在内存中的页面,选择计数器值最小的页面(即最近使用最少的页面)。
- 替换选中的页面,将缺页页面调入内存。
示例伪代码:
function lfuPageReplacement(pageTable) {
leastUsedPage = pageTable[0];
for each page in pageTable {
if page.accessCount < leastUsedPage.accessCount {
leastUsedPage = page;
}
}
return leastUsedPage;
}
function handlePageFault_LFU(pageTable, newPage) {
victimPage = lfuPageReplacement(pageTable);
evictPage(victimPage);
loadPage(newPage, victimPage.frame);
pageTable.updateAccessCount(newPage);
}
先进先出(FIFO, First In First Out)
FIFO算法选择最早进入内存的页进行调入。该算法利用队列的数据结构来管理页面按进入内存的先后顺序。
- 实现:为页面设置一个队列,根据调入的先后顺序排列。
- 过程:
- 发生缺页中断时,选择队列中的第一个页(即最早进入内存的页面)。
- 替换选中的页面,将缺页页面调入内存,将新的页面添加到队列末尾。
示例伪代码:
function fifoPageReplacement(pageQueue) {
victimPage = pageQueue.head();
pageQueue.dequeue();
return victimPage;
}
function handlePageFault_FIFO(pageQueue, newPage) {
victimPage = fifoPageReplacement(pageQueue);
evictPage(victimPage);
loadPage(newPage, victimPage.frame);
pageQueue.enqueue(newPage);
}
各页面调入策略的优缺点:
-
LRU
- 优点:较符合实际应用的访问模式,能有效减少缺页中断的发生。
- 缺点:实现复杂,需要维护访问时间记录,开销较大。
-
LFU
- 优点:关注页面的访问频率,对于某些特定使用场景效果较好。
- 缺点:实现复杂,需要维护访问计数器;如果某些页面使用突然频繁,可能导致性能不佳。
-
FIFO
- 优点:实现简单,使用队列管理页面的调入顺序。
- 缺点:可能不符合实际应用的访问模式,容易发生Belady异常(FIFO Anomaly),即增加页框反而增加缺页率。
结语
请求分页存储管理方式是 commonly used 的虚拟内存管理技术,它通过在磁盘和内存之间移动页,实现了按需装入和虚拟内存。请求分页中的硬件支持包括请求页表机制和缺页中断机构,内存分配策略包括固定分配和可变分配,页面调入策略 commonly used LRU、LFU 和 FIFO 算法。了解请求分页存储管理方式,有助于我们理解虚拟内存的管理技术,并提高系统的性能和稳定性。