引言

在现代计算机系统中,内存管理是操作系统的核心功能之一。它负责管理计算机的主存储器(RAM),确保多个程序能够高效、安全地共享有限的物理内存资源。本次实验将深入探讨两种关键的内存管理技术:内存分配策略虚拟内存技术。通过理论分析和实践编程,我们将理解操作系统如何处理内存分配请求、如何通过分页和分段实现虚拟内存,以及这些技术如何影响系统性能。

实验目标包括:

  1. 理解并实现经典的内存分配算法(如首次适应、最佳适应、最坏适应)。
  2. 掌握虚拟内存的基本概念,特别是分页机制。
  3. 通过模拟程序实践内存分配和虚拟内存管理。
  4. 分析不同策略的优缺点及适用场景。

第一部分:内存分配策略详解

内存分配策略主要解决如何在物理内存中为进程分配空间的问题。操作系统需要管理一个连续的内存池,并响应进程的动态内存请求(如 mallocnew)。常见的策略包括连续分配和非连续分配,但本实验重点放在连续分配策略的模拟上。

1.1 连续内存分配基础

在连续分配模型中,进程被分配到一个连续的物理内存块中。操作系统维护一个空闲内存块列表(Free List)。当进程请求内存时,操作系统从空闲列表中查找一个足够大的块进行分配。当进程释放内存时,操作系统将释放的块重新加入空闲列表,并可能与相邻的空闲块合并。

1.2 三种经典分配算法

我们通过模拟一个简单的内存管理器来实现和比较三种算法:首次适应(First Fit)、最佳适应(Best Fit)和最坏适应(Worst Fit)。

1.2.1 首次适应(First Fit)

  • 原理:从空闲列表的起始位置开始搜索,找到第一个大小足够容纳请求的块进行分配。
  • 优点:速度快,因为一旦找到合适的块就停止搜索。
  • 缺点:可能导致低地址部分产生许多小碎片,增加后续分配的搜索时间。

1.2.2 最佳适应(Best Fit)

  • 原理:搜索整个空闲列表,找到能够满足请求的最小空闲块进行分配。
  • 优点:尽量保留大块内存,减少大碎片的产生。
  • 缺点:搜索时间长,且可能产生许多难以利用的小碎片(外部碎片)。

1.2.3 最坏适应(Worst Fit)

  • 原理:搜索整个空闲列表,找到最大的空闲块进行分配,将剩余部分作为新的空闲块。
  • 优点:避免产生过小的碎片,因为每次分配都从大块中切分。
  • 缺点:可能迅速耗尽大块内存,导致后续大请求无法满足。

1.3 代码实现与模拟

下面是一个用Python实现的简单内存管理器模拟程序,用于演示上述三种算法。我们将模拟一个固定大小的物理内存(例如1024字节),并处理一系列分配和释放请求。

import random

class MemoryBlock:
    """表示一个内存块"""
    def __init__(self, start, size, is_free=True):
        self.start = start
        self.size = size
        self.is_free = is_free

    def __repr__(self):
        return f"Block(start={self.start}, size={self.size}, free={self.is_free})"

class MemoryAllocator:
    """内存分配器基类"""
    def __init__(self, total_size):
        self.total_size = total_size
        # 初始时,整个内存是一个空闲块
        self.free_list = [MemoryBlock(0, total_size, True)]
        self.allocated_blocks = {}  # 用于跟踪已分配的块,key为进程ID

    def allocate(self, pid, size):
        """分配内存,返回分配的起始地址,失败返回None"""
        raise NotImplementedError

    def deallocate(self, pid):
        """释放指定进程的内存"""
        if pid not in self.allocated_blocks:
            return False
        block = self.allocated_blocks[pid]
        block.is_free = True
        # 将释放的块加入空闲列表并合并相邻块
        self._merge_free_blocks()
        del self.allocated_blocks[pid]
        return True

    def _merge_free_blocks(self):
        """合并相邻的空闲块"""
        self.free_list.sort(key=lambda x: x.start)
        merged = []
        current = None
        for block in self.free_list:
            if not block.is_free:
                continue
            if current is None:
                current = block
            else:
                if current.start + current.size == block.start:
                    # 相邻,合并
                    current.size += block.size
                else:
                    merged.append(current)
                    current = block
        if current:
            merged.append(current)
        self.free_list = merged

    def get_fragmentation(self):
        """计算外部碎片大小(空闲块总大小减去最大空闲块大小)"""
        free_blocks = [b for b in self.free_list if b.is_free]
        if not free_blocks:
            return 0
        total_free = sum(b.size for b in free_blocks)
        max_free = max(b.size for b in free_blocks)
        return total_free - max_free

    def print_status(self):
        """打印当前内存状态"""
        print("Free List:")
        for block in self.free_list:
            if block.is_free:
                print(f"  Free: start={block.start}, size={block.size}")
        print("Allocated:")
        for pid, block in self.allocated_blocks.items():
            print(f"  PID {pid}: start={block.start}, size={block.size}")
        print(f"External Fragmentation: {self.get_fragmentation()} bytes")
        print("-" * 40)

class FirstFitAllocator(MemoryAllocator):
    """首次适应分配器"""
    def allocate(self, pid, size):
        for block in self.free_list:
            if block.is_free and block.size >= size:
                # 分配
                allocated_block = MemoryBlock(block.start, size, False)
                self.allocated_blocks[pid] = allocated_block
                # 更新空闲块
                remaining = block.size - size
                if remaining > 0:
                    block.start += size
                    block.size = remaining
                else:
                    block.is_free = False
                return allocated_block.start
        return None

class BestFitAllocator(MemoryAllocator):
    """最佳适应分配器"""
    def allocate(self, pid, size):
        best_block = None
        best_idx = -1
        for i, block in enumerate(self.free_list):
            if block.is_free and block.size >= size:
                if best_block is None or block.size < best_block.size:
                    best_block = block
                    best_idx = i
        if best_block:
            allocated_block = MemoryBlock(best_block.start, size, False)
            self.allocated_blocks[pid] = allocated_block
            remaining = best_block.size - size
            if remaining > 0:
                best_block.start += size
                best_block.size = remaining
            else:
                best_block.is_free = False
            return allocated_block.start
        return None

class WorstFitAllocator(MemoryAllocator):
    """最坏适应分配器"""
    def allocate(self, pid, size):
        worst_block = None
        worst_idx = -1
        for i, block in enumerate(self.free_list):
            if block.is_free and block.size >= size:
                if worst_block is None or block.size > worst_block.size:
                    worst_block = block
                    worst_idx = i
        if worst_block:
            allocated_block = MemoryBlock(worst_block.start, size, False)
            self.allocated_blocks[pid] = allocated_block
            remaining = worst_block.size - size
            if remaining > 0:
                worst_block.start += size
                worst_block.size = remaining
            else:
                worst_block.is_free = False
            return allocated_block.start
        return None

def simulate_workload(allocator, workload):
    """模拟工作负载"""
    pid_counter = 1
    for op, size in workload:
        if op == 'alloc':
            addr = allocator.allocate(pid_counter, size)
            if addr is not None:
                print(f"Allocated PID {pid_counter} at {addr} for {size} bytes")
                pid_counter += 1
            else:
                print(f"Failed to allocate {size} bytes for PID {pid_counter}")
        elif op == 'free':
            # 随机释放一个已分配的进程
            if allocator.allocated_blocks:
                pid_to_free = random.choice(list(allocator.allocated_blocks.keys()))
                success = allocator.deallocate(pid_to_free)
                if success:
                    print(f"Freed PID {pid_to_free}")
                else:
                    print(f"Failed to free PID {pid_to_free}")
        allocator.print_status()

# 示例工作负载:混合分配和释放
workload = [
    ('alloc', 100),
    ('alloc', 200),
    ('alloc', 150),
    ('free', 0),  # 0表示随机释放
    ('alloc', 180),
    ('alloc', 120),
    ('free', 0),
    ('alloc', 250),
    ('alloc', 50),
    ('free', 0),
]

print("=== 首次适应 (First Fit) 模拟 ===")
first_fit = FirstFitAllocator(1024)
simulate_workload(first_fit, workload)

print("\n=== 最佳适应 (Best Fit) 模拟 ===")
best_fit = BestFitAllocator(1024)
simulate_workload(best_fit, workload)

print("\n=== 最坏适应 (Worst Fit) 模拟 ===")
worst_fit = WorstFitAllocator(1024)
simulate_workload(worst_fit, workload)

代码解析

  1. MemoryBlock 类表示一个内存块,包含起始地址、大小和空闲状态。
  2. MemoryAllocator 是基类,定义了分配和释放的接口,并实现了内存合并逻辑。
  3. FirstFitAllocatorBestFitAllocatorWorstFitAllocator 分别实现了三种算法的核心逻辑。
  4. simulate_workload 函数模拟了一系列分配和释放操作,并打印每次操作后的内存状态和外部碎片大小。

实验观察

  • 首次适应:通常分配速度最快,但可能在低地址区域留下许多小碎片。
  • 最佳适应:倾向于保留大块内存,但可能产生大量难以利用的小碎片(外部碎片)。
  • 最坏适应:避免小碎片,但可能很快耗尽大块内存,导致后续大请求失败。

通过运行上述代码,你可以直观地看到不同算法在相同工作负载下的表现差异。外部碎片的大小是衡量内存利用率的重要指标。

第二部分:虚拟内存技术实践

虚拟内存是一种内存管理技术,它为每个进程提供一个连续的、私有的地址空间,而无需考虑物理内存的实际布局。它通过将内存访问请求从虚拟地址转换为物理地址来实现。分页(Paging)是实现虚拟内存最常用的方法。

2.1 虚拟内存与分页基础

  • 虚拟地址空间:每个进程拥有独立的虚拟地址空间(例如32位系统为4GB)。
  • 物理地址空间:实际的物理内存,可能小于虚拟地址空间。
  • 页(Page):虚拟地址空间被划分为固定大小的块(如4KB)。
  • 页框(Page Frame):物理内存被划分为与页大小相同的块。
  • 页表(Page Table):一个数据结构,用于将虚拟页映射到物理页框。每个页表项(PTE)包含物理页框号和状态位(如有效位、脏位、访问位)。

2.2 地址转换过程

当进程访问一个虚拟地址时,CPU的内存管理单元(MMU)执行以下步骤:

  1. 将虚拟地址拆分为页号(Page Number)页内偏移(Offset)
  2. 使用页号作为索引查找页表,获取对应的物理页框号。
  3. 将物理页框号与页内偏移组合,形成物理地址。
  4. 如果页表项中的有效位为0,表示该页不在物理内存中,触发缺页异常(Page Fault),操作系统需要从磁盘(交换空间)加载该页到物理内存。

2.3 模拟分页地址转换

下面是一个简单的Python程序,模拟分页机制下的地址转换过程。我们假设一个简化的系统:虚拟地址32位,页大小4KB,物理内存大小1MB(256个页框),页表使用数组实现。

class PageTableEntry:
    """页表项"""
    def __init__(self, frame_number=-1, valid=False, dirty=False, accessed=False):
        self.frame_number = frame_number  # 物理页框号
        self.valid = valid                # 有效位:是否在物理内存中
        self.dirty = dirty                # 脏位:是否被修改过
        self.accessed = accessed          # 访问位:最近是否被访问

class SimplePagingSystem:
    """简单的分页系统模拟"""
    def __init__(self, virtual_bits=32, page_size_kb=4, physical_frames=256):
        self.page_size = page_size_kb * 1024  # 页大小(字节)
        self.page_offset_bits = (self.page_size - 1).bit_length()  # 页内偏移位数
        self.page_number_bits = virtual_bits - self.page_offset_bits
        self.total_virtual_pages = 1 << self.page_number_bits  # 虚拟页总数
        self.physical_frames = physical_frames  # 物理页框数

        # 页表:每个进程一个页表,这里简化为一个全局页表
        self.page_table = [PageTableEntry() for _ in range(self.total_virtual_pages)]
        # 物理内存模拟:一个字节数组,大小为物理内存总字节数
        self.physical_memory = bytearray(self.physical_frames * self.page_size)
        # 空闲页框列表
        self.free_frames = list(range(self.physical_frames))
        # 缺页计数器
        self.page_faults = 0

    def translate_address(self, virtual_address):
        """将虚拟地址转换为物理地址"""
        # 分离页号和页内偏移
        page_number = virtual_address >> self.page_offset_bits
        offset = virtual_address & (self.page_size - 1)

        if page_number >= self.total_virtual_pages:
            raise ValueError(f"虚拟地址超出范围: {virtual_address}")

        pte = self.page_table[page_number]
        if not pte.valid:
            # 缺页异常
            self.page_faults += 1
            print(f"缺页异常: 虚拟页 {page_number} 不在物理内存中")
            # 分配物理页框
            if not self.free_frames:
                raise RuntimeError("物理内存已满,需要页面置换")
            frame = self.free_frames.pop(0)
            pte.frame_number = frame
            pte.valid = True
            # 模拟从磁盘加载数据到物理内存(这里只是标记)
            print(f"  分配物理页框 {frame} 给虚拟页 {page_number}")
            # 在实际系统中,这里会从磁盘读取数据到物理内存的对应位置
            # 例如:self.physical_memory[frame * self.page_size : (frame+1)*self.page_size] = load_from_disk(...)

        # 组合物理地址
        physical_address = (pte.frame_number * self.page_size) + offset
        pte.accessed = True  # 设置访问位
        return physical_address

    def write_memory(self, virtual_address, data):
        """模拟写入内存,会设置脏位"""
        physical_addr = self.translate_address(virtual_address)
        # 模拟写入物理内存
        # 注意:这里只是示例,实际写入需要处理数据类型和边界
        print(f"写入: 虚拟地址 {virtual_address} -> 物理地址 {physical_addr}, 数据: {data}")
        # 设置脏位
        page_number = virtual_address >> self.page_offset_bits
        self.page_table[page_number].dirty = True

    def print_page_table(self, start_page=0, end_page=10):
        """打印页表的一部分"""
        print("页表 (前10项):")
        print("虚拟页 | 物理框 | 有效 | 脏 | 访问")
        for i in range(start_page, min(end_page, self.total_virtual_pages)):
            pte = self.page_table[i]
            print(f"{i:6} | {pte.frame_number:6} | {pte.valid:5} | {pte.dirty:3} | {pte.accessed}")

# 模拟分页系统
print("=== 分页系统模拟 ===")
paging_system = SimplePagingSystem(virtual_bits=32, page_size_kb=4, physical_frames=256)

# 模拟进程访问虚拟地址
print("\n1. 访问虚拟地址 0x1000 (4KB边界内)")
addr1 = 0x1000
phys1 = paging_system.translate_address(addr1)
print(f"虚拟地址 0x{addr1:X} -> 物理地址 0x{phys1:X}")

print("\n2. 写入虚拟地址 0x2000")
paging_system.write_memory(0x2000, "Hello World")

print("\n3. 再次访问虚拟地址 0x1000 (应命中)")
phys1_again = paging_system.translate_address(addr1)
print(f"虚拟地址 0x{addr1:X} -> 物理地址 0x{phys1_again:X}")

print("\n4. 访问一个较大的虚拟地址 0x100000 (可能触发缺页)")
addr2 = 0x100000
phys2 = paging_system.translate_address(addr2)
print(f"虚拟地址 0x{addr2:X} -> 物理地址 0x{phys2:X}")

print("\n5. 打印页表状态")
paging_system.print_page_table(0, 10)

print(f"\n总缺页次数: {paging_system.page_faults}")

代码解析

  1. PageTableEntry 类模拟页表项,包含物理页框号和状态位。
  2. SimplePagingSystem 类模拟分页系统:
    • translate_address 方法实现地址转换,处理缺页异常。
    • write_memory 方法模拟写入操作,设置脏位。
    • 缺页处理时,从空闲列表分配物理页框(这里简化为顺序分配)。
  3. 模拟过程展示了地址转换、缺页处理和页表更新。

关键点

  • 缺页处理:当访问的页不在物理内存时,操作系统需要分配一个物理页框,并从磁盘(交换空间)加载数据。在模拟中,我们只是分配了页框,没有实际加载数据。
  • 页面置换:如果物理内存已满,需要选择一个页面换出到磁盘。常见的算法有FIFO、LRU、Clock等。本模拟未实现置换算法,但可以扩展。
  • 性能影响:缺页异常的处理开销很大,因此操作系统会尽量减少缺页次数。工作集模型和局部性原理是优化的关键。

2.4 扩展:简单的页面置换算法模拟

为了更完整地理解虚拟内存,我们扩展模拟,加入一个简单的页面置换算法(如FIFO)。我们将创建一个包含置换逻辑的分页系统。

class PagingSystemWithReplacement(SimplePagingSystem):
    """带页面置换的分页系统"""
    def __init__(self, virtual_bits=32, page_size_kb=4, physical_frames=256, replacement_alg='FIFO'):
        super().__init__(virtual_bits, page_size_kb, physical_frames)
        self.replacement_alg = replacement_alg
        # 用于FIFO的队列
        self.frame_queue = []
        # 用于LRU的访问时间戳(简化)
        self.access_timestamps = {}

    def translate_address(self, virtual_address):
        """扩展地址转换,处理缺页和页面置换"""
        page_number = virtual_address >> self.page_offset_bits
        offset = virtual_address & (self.page_size - 1)

        if page_number >= self.total_virtual_pages:
            raise ValueError(f"虚拟地址超出范围: {virtual_address}")

        pte = self.page_table[page_number]
        if not pte.valid:
            self.page_faults += 1
            print(f"缺页异常: 虚拟页 {page_number} 不在物理内存中")

            # 如果有空闲页框,直接分配
            if self.free_frames:
                frame = self.free_frames.pop(0)
                pte.frame_number = frame
                pte.valid = True
                print(f"  分配空闲物理页框 {frame} 给虚拟页 {page_number}")
                # 更新置换数据结构
                if self.replacement_alg == 'FIFO':
                    self.frame_queue.append((page_number, frame))
                elif self.replacement_alg == 'LRU':
                    self.access_timestamps[page_number] = 0  # 初始时间戳
                return (frame * self.page_size) + offset

            # 物理内存已满,需要置换
            print("  物理内存已满,执行页面置换...")
            if self.replacement_alg == 'FIFO':
                # FIFO: 选择最早进入的页面
                victim_page, victim_frame = self.frame_queue.pop(0)
                print(f"  置换出虚拟页 {victim_page} (物理框 {victim_frame})")
                # 如果脏位为真,需要写回磁盘(模拟)
                if self.page_table[victim_page].dirty:
                    print(f"    虚拟页 {victim_page} 是脏的,写回磁盘")
                    self.page_table[victim_page].dirty = False
                # 更新页表
                self.page_table[victim_page].valid = False
                self.page_table[victim_page].frame_number = -1
                # 分配给新页
                pte.frame_number = victim_frame
                pte.valid = True
                # 加入队列
                self.frame_queue.append((page_number, victim_frame))
                return (victim_frame * self.page_size) + offset

            elif self.replacement_alg == 'LRU':
                # LRU: 选择最近最久未使用的页面(简化:使用时间戳)
                # 找到时间戳最小的页面
                min_time = float('inf')
                victim_page = None
                victim_frame = None
                for page, frame in self.access_timestamps.items():
                    if self.page_table[page].valid and self.access_timestamps[page] < min_time:
                        min_time = self.access_timestamps[page]
                        victim_page = page
                        victim_frame = self.page_table[page].frame_number
                if victim_page is None:
                    raise RuntimeError("无法找到置换页面")
                print(f"  置换出虚拟页 {victim_page} (物理框 {victim_frame})")
                if self.page_table[victim_page].dirty:
                    print(f"    虚拟页 {victim_page} 是脏的,写回磁盘")
                    self.page_table[victim_page].dirty = False
                self.page_table[victim_page].valid = False
                self.page_table[victim_page].frame_number = -1
                del self.access_timestamps[victim_page]
                # 分配给新页
                pte.frame_number = victim_frame
                pte.valid = True
                self.access_timestamps[page_number] = 0  # 初始时间戳
                return (victim_frame * self.page_size) + offset

        # 命中,更新访问信息
        if self.replacement_alg == 'LRU':
            # 更新时间戳(简化:递增全局时间戳)
            self.access_timestamps[page_number] = self.get_current_time()
        pte.accessed = True
        return (pte.frame_number * self.page_size) + offset

    def get_current_time(self):
        """模拟当前时间戳(简化)"""
        # 在实际系统中,这可能是CPU时钟周期
        import time
        return int(time.time() * 1000)  # 毫秒

# 模拟带置换的分页系统
print("\n=== 带FIFO置换的分页系统模拟 ===")
paging_fifo = PagingSystemWithReplacement(virtual_bits=32, page_size_kb=4, physical_frames=4, replacement_alg='FIFO')

# 模拟一个工作负载,导致缺页和置换
workload_addresses = [
    0x0000,  # 页0
    0x1000,  # 页1
    0x2000,  # 页2
    0x3000,  # 页3
    0x0000,  # 再次访问页0(FIFO命中)
    0x4000,  # 页4,触发置换(物理内存只有4个页框)
    0x1000,  # 再次访问页1(FIFO命中)
    0x5000,  # 页5,再次触发置换
]

for addr in workload_addresses:
    print(f"\n访问虚拟地址 0x{addr:X}")
    phys = paging_fifo.translate_address(addr)
    print(f"  -> 物理地址 0x{phys:X}")

print(f"\n总缺页次数: {paging_fifo.page_faults}")

print("\n=== 带LRU置换的分页系统模拟 ===")
paging_lru = PagingSystemWithReplacement(virtual_bits=32, page_size_kb=4, physical_frames=4, replacement_alg='LRU')

# 使用相同的工作负载
for addr in workload_addresses:
    print(f"\n访问虚拟地址 0x{addr:X}")
    phys = paging_lru.translate_address(addr)
    print(f"  -> 物理地址 0x{phys:X}")

print(f"\n总缺页次数: {paging_lru.page_faults}")

代码解析

  1. PagingSystemWithReplacement 继承自 SimplePagingSystem,增加了页面置换逻辑。
  2. FIFO置换:使用队列跟踪页面进入顺序,置换最旧的页面。
  3. LRU置换:使用时间戳模拟访问时间,置换最近最久未使用的页面(简化实现)。
  4. 工作负载设计为:先填满物理内存,然后访问新页面触发置换。

实验观察

  • FIFO:实现简单,但可能置换出仍被频繁使用的页面(Belady异常)。
  • LRU:更符合程序的局部性原理,通常性能更好,但实现开销较大(需要记录访问历史)。
  • 缺页率:通过比较不同算法的缺页次数,可以评估其效率。在实际系统中,操作系统会根据工作负载动态调整策略。

第三部分:综合实验与性能分析

3.1 实验设计

结合内存分配和虚拟内存,设计一个综合实验:

  1. 场景:模拟一个多进程环境,每个进程有动态内存需求。
  2. 步骤: a. 使用连续分配策略(如首次适应)为进程分配物理内存。 b. 在分配的内存中,模拟分页机制,处理进程的虚拟地址访问。 c. 当物理内存不足时,触发页面置换。
  3. 目标:分析不同内存分配策略如何影响虚拟内存的性能(如缺页率)。

3.2 代码示例(简化版)

由于完整实现较为复杂,这里提供一个简化框架,展示如何将两者结合。

class IntegratedMemorySystem:
    """集成内存分配和虚拟内存的系统"""
    def __init__(self, total_physical_memory, page_size_kb=4):
        self.total_physical_memory = total_physical_memory
        self.page_size = page_size_kb * 1024
        self.physical_frames = total_physical_memory // self.page_size
        # 使用首次适应分配器管理物理内存
        self.allocator = FirstFitAllocator(total_physical_memory)
        # 为每个进程维护一个分页系统
        self.process_paging = {}  # pid -> PagingSystemWithReplacement

    def create_process(self, pid, virtual_memory_size):
        """创建进程,分配虚拟地址空间"""
        # 在实际系统中,虚拟地址空间是预先定义的,这里我们模拟
        # 分配物理内存给进程的页表和初始页
        # 简化:假设页表本身也占用物理内存,这里忽略
        paging_system = PagingSystemWithReplacement(
            virtual_bits=32,
            page_size_kb=4,
            physical_frames=self.physical_frames // 4,  # 简化:每个进程分得一部分物理页框
            replacement_alg='LRU'
        )
        self.process_paging[pid] = paging_system
        print(f"创建进程 {pid},虚拟内存大小 {virtual_memory_size} bytes")

    def access_memory(self, pid, virtual_address, operation='read'):
        """进程访问虚拟内存"""
        if pid not in self.process_paging:
            print(f"错误: 进程 {pid} 不存在")
            return
        paging_system = self.process_paging[pid]
        try:
            physical_addr = paging_system.translate_address(virtual_address)
            if operation == 'write':
                paging_system.write_memory(virtual_address, "data")
            print(f"进程 {pid}: 虚拟地址 0x{virtual_address:X} -> 物理地址 0x{physical_addr:X}")
        except Exception as e:
            print(f"进程 {pid} 访问失败: {e}")

    def print_status(self):
        """打印系统状态"""
        print("\n=== 系统状态 ===")
        self.allocator.print_status()
        for pid, paging in self.process_paging.items():
            print(f"进程 {pid}: 缺页次数 = {paging.page_faults}")

# 模拟集成系统
print("=== 集成内存管理模拟 ===")
system = IntegratedMemorySystem(total_physical_memory=1024*1024)  # 1MB物理内存

# 创建两个进程
system.create_process(1, 1024*1024)  # 进程1虚拟内存1MB
system.create_process(2, 1024*1024)  # 进程2虚拟内存1MB

# 模拟进程访问
print("\n进程1访问内存:")
system.access_memory(1, 0x0000, 'read')
system.access_memory(1, 0x1000, 'write')
system.access_memory(1, 0x2000, 'read')

print("\n进程2访问内存:")
system.access_memory(2, 0x0000, 'read')
system.access_memory(2, 0x1000, 'write')
system.access_memory(2, 0x2000, 'read')

# 模拟更多访问以触发缺页
for i in range(10):
    addr = 0x1000 * (i + 3)
    system.access_memory(1, addr, 'read')

system.print_status()

代码解析

  1. IntegratedMemorySystem 结合了连续内存分配器和分页系统。
  2. create_process 为每个进程创建一个分页系统实例。
  3. access_memory 模拟进程的虚拟地址访问,通过分页系统转换。
  4. 这个简化模型展示了内存管理的层次:物理内存分配 -> 虚拟地址转换。

3.3 性能分析

通过运行上述模拟,可以收集以下数据:

  • 缺页率:缺页次数 / 总访问次数。
  • 外部碎片:连续分配策略产生的碎片大小。
  • 分配时间:不同分配算法的平均分配时间。

分析结论

  • 内存分配策略:影响物理内存的利用率和分配速度。在虚拟内存系统中,物理内存被划分为页框,因此连续分配策略的碎片问题可能被缓解,但页框的分配仍然需要高效管理。
  • 虚拟内存技术:通过分页和页面置换,操作系统可以运行比物理内存更大的程序,并提供内存保护。缺页率是关键性能指标,受页面置换算法和工作负载局部性影响。
  • 综合影响:一个高效的内存管理系统需要平衡分配策略和虚拟内存技术。例如,使用首次适应分配页框可以减少分配开销,而LRU置换可以降低缺页率。

总结

本次实验深入探讨了操作系统的内存管理技术。通过理论分析和代码实践,我们理解了:

  1. 内存分配策略(首次适应、最佳适应、最坏适应)的原理、实现和性能特点。
  2. 虚拟内存技术(分页、地址转换、缺页处理、页面置换)的工作机制。
  3. 综合应用:如何将内存分配和虚拟内存结合,构建一个完整的内存管理系统。

实验表明,内存管理是操作系统设计中极具挑战性的部分,需要根据具体场景选择合适的策略。在实际操作系统中(如Linux),内存管理器结合了多种算法(如伙伴系统用于物理页框分配,LRU/Clock用于页面置换),以达到最佳性能。

通过本次实验,我们不仅掌握了理论知识,还通过编程实践加深了理解。这些技能对于深入学习操作系统、系统编程和性能优化至关重要。