模拟内存管理

文章目录

  • [1. 实验六:内存管理](#1. 实验六:内存管理)
  • [2. 记录内存空间使用情况](#2. 记录内存空间使用情况)
    • [2.1 全局参数](#2.1 全局参数)
    • [2.2 内存空间相关参数](#2.2 内存空间相关参数)
    • [2.3 关键结构体定义](#2.3 关键结构体定义)
    • [2.4 内存系统初始化](#2.4 内存系统初始化)
  • [3. 记录空闲分区](#3. 记录空闲分区)
    • [3.1 采用位图的方式记录物理内存中的空闲帧](#3.1 采用位图的方式记录物理内存中的空闲帧)
      • [3.1.1 记录方式](#3.1.1 记录方式)
      • [3.1.2 举例分析](#3.1.2 举例分析)
    • [3.2 主要操作](#3.2 主要操作)
      • [3.2.1 初始化空闲帧:](#3.2.1 初始化空闲帧:)
      • [3.2.2 分配帧时标记:](#3.2.2 分配帧时标记:)
      • [3.2.3 释放帧时标记:](#3.2.3 释放帧时标记:)
  • [4. 内存分配算法(段页式 + 页级 First‑Fit)](#4. 内存分配算法(段页式 + 页级 First‑Fit))
    • [4.1 分配算法原理](#4.1 分配算法原理)
    • [4.2 主要函数](#4.2 主要函数)
      • [4.2.1 `allocate_for_process(process_id, num_pages)`](#4.2.1 allocate_for_process(process_id, num_pages))
      • [4.2.2 `allocate_segment(int seg_id, int num_pages)`](#4.2.2 allocate_segment(int seg_id, int num_pages))
    • [4.2 内存分配源代码](#4.2 内存分配源代码)
  • [5. 内存释放算法](#5. 内存释放算法)
    • [5.1 算法设计](#5.1 算法设计)
      • [5.1.1 算法流程](#5.1.1 算法流程)
      • [5.1.2 函数调用链](#5.1.2 函数调用链)
      • [5.1.3 关键代码部分](#5.1.3 关键代码部分)
    • [5.2 内存释放源代码](#5.2 内存释放源代码)
  • [6. 测试](#6. 测试)
    • [6.1 产生测试数据](#6.1 产生测试数据)
      • [6.1.1 关键代码:随机为5个进程分别分配和释放内存15次,即随机产生15组数据:(进程Pi 分配内存大小) 或者 (进程Pi结束)](#6.1.1 关键代码:随机为5个进程分别分配和释放内存15次,即随机产生15组数据:(进程Pi 分配内存大小) 或者 (进程Pi结束))
      • [6.1.2 测试程序设计](#6.1.2 测试程序设计)
    • [6.2 输出结果分析](#6.2 输出结果分析)
      • [6.2.1 部分输出结果](#6.2.1 部分输出结果)
      • [6.2.2 具体分析](#6.2.2 具体分析)
        • [6.2.2.1 逐步说明](#6.2.2.1 逐步说明)
          • [(1)操作 5:为进程 P1 分配 2 页内存](#(1)操作 5:为进程 P1 分配 2 页内存)
          • [(2)操作 6:为进程 P2 分配 3 页内存](#(2)操作 6:为进程 P2 分配 3 页内存)
          • [(3)操作 7:释放进程 P1 的内存](#(3)操作 7:释放进程 P1 的内存)
          • [(4)操作 8:为进程 P0 分配 2 页内存](#(4)操作 8:为进程 P0 分配 2 页内存)
        • [6.2.2.1 总结表格](#6.2.2.1 总结表格)
    • [6.3 测试程序源代码](#6.3 测试程序源代码)
  • 7.源代码与完整测试结果
    • [7.1 源代码](#7.1 源代码)
    • [7.2 完整测试结果](#7.2 完整测试结果)
  • 8.参考链接

1. 实验六:内存管理

2. 记录内存空间使用情况

2.1 全局参数

宏定义名称 数值 含义/单位 作用说明
PAGE_SIZE 4096 字节(4KB) 每页的大小,模拟操作系统中的分页单位。常用于计算内存大小。
NUM_PAGES 16 模拟的物理内存总共被划分成的页数(即帧数),表示系统最多有16个可分配的物理页框。
NUM_SEGMENTS 5 系统最多同时支持的段数。每个段对应一个页表,用于段页式管理。
PHYS_MEM_SIZE PAGE_SIZE * NUM_PAGES = 65536 字节(64KB) 模拟的物理内存总大小,实际用于申请内存空间 phys_mem
NUM_PROCESSES 5 个进程 同时最多允许存在的进程数量。每个进程在本模型中最多只拥有一个段。
NUM_OPERATIONS 15 在模拟过程中将执行的随机内存操作数量(包括分配和释放)。

2.2 内存空间相关参数

  • 段表 segment_table[] :保存每段的页表指针 (page_table) 与段长 (limit)。
  • 页表 PageTableEntry[] :保存页是否有效 (valid) 及映射的物理帧号 (frame_number)。
  • 进程‑段-映射 process_segments[] :下标 = 进程 ID,值 = 该进程占用的段号 (‑1 表示未分配)。
    这三张表共同描述"哪一帧属于哪一段、哪一段属于哪个进程",即可完整反映"已用/空闲"。

2.3 关键结构体定义

复制代码
// 页表条目
typedef struct
{
    int valid;
    int frame_number;
} PageTableEntry;

// 段表条目
typedef struct {
    int base;                 // 段起始页,本例固定 0
    int limit;                // 段长(页数)
    PageTableEntry *page_table;
} SegmentTableEntry;

SegmentTableEntry segment_table[NUM_SEGMENTS];
int process_segments[NUM_PROCESSES];  // -1 表未分配
  • SegmentTableEntry:每个段记录其起始位置(base)、页数(limit)、指向页表的指针。
  • PageTableEntry:页表记录每页是否有效(valid)及其对应的物理帧号(frame_number)。

2.4 内存系统初始化

复制代码
void init_memory()
{
    phys_mem = (char *)malloc(PHYS_MEM_SIZE);
    memset(phys_mem, 0, PHYS_MEM_SIZE);
    for (int i = 0; i < NUM_PAGES; i++)
        free_frames[i] = 1;

    for (int i = 0; i < NUM_SEGMENTS; i++)
        segment_table[i].page_table = NULL;

    // 初始化进程段记录为-1(表示未分配)
    for (int i = 0; i < NUM_PROCESSES; i++)
        process_segments[i] = -1;
}
代码 功能说明
phys_mem = malloc(...) 分配一块用于模拟的物理内存空间(64KB)。
memset(...) 将分配的物理内存内容清零,确保起始干净状态。
for (i < NUM_PAGES) free_frames[i] = 1 初始化帧表,每一帧设为"空闲"。
for (i < NUM_SEGMENTS) page_table = NULL 初始化段表,表示系统还没有被分配的段。
for (i < NUM_PROCESSES) process_segments = -1 初始化进程段记录,表示每个进程当前没有段。

3. 记录空闲分区

3.1 采用位图的方式记录物理内存中的空闲帧

3.1.1 记录方式

free_frames[i] == 1 表示第 i帧空闲;0 表示已占用。

复制代码
int free_frames[NUM_PAGES]; // NUM_PAGES = 16

这是一个长度为 16 的整数数组,**每一位对应一个物理帧(页框)**的状态。

  • free_frames[i] == 1:第 i 个页框是空闲的。
  • free_frames[i] == 0:第 i 个页框是已分配的。
  • 初始化全置 1;分配时置 0;释放时再置 1。

3.1.2 举例分析

如果当前有如下内存状态:

复制代码
free_frames = { 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1 };

则代表:

  • 空闲页框:1号、4号、5号、6号、8号、9号、10号、11号、13号、14号、15号
  • 已占用帧:0号、2号、3号、7号、12号

当分配新帧时,函数 allocate_frame() 会顺序扫描并返回第一个为 1 的下标(即空闲帧的位置)。

复制代码
int allocate_frame()
{
    for (int i = 0; i < NUM_PAGES; i++)
    {
        if (free_frames[i])
        {
            free_frames[i] = 0;
            return i;
        }
    }
    return -1; // 没有空闲帧
}

3.2 主要操作

3.2.1 初始化空闲帧:

复制代码
for (int i = 0; i < NUM_PAGES; i++)
    free_frames[i] = 1;

3.2.2 分配帧时标记:

复制代码
if (free_frames[i]) {
    free_frames[i] = 0; // 标记为占用
    return i;
}

3.2.3 释放帧时标记:

复制代码
free_frames[segment_table[seg_id].page_table[i].frame_number] = 1;

4. 内存分配算法(段页式 + 页级 First‑Fit)

4.1 分配算法原理

  1. 找空段 :线性扫描段表,寻找 page_table==NULL 的条目。
  2. 逐页分配物理帧 :对所需页数调用 allocate_frame();该函数顺序扫描位图找到第一张空闲帧(First‑Fit),并把对应位图位置置 0。
  3. 失败回滚:若中途无帧可用,则把已分到的帧全部归还并释放刚创建的页表。
  4. 记录映射 :成功后,将进程 ID 对应的 process_segments[pid] 设为刚找到的段号。

4.2 主要函数

4.2.1 allocate_for_process(process_id, num_pages)

函数设计逻辑

给指定进程分配一个段,并在该段内分配一定页数的内存。

  • 自动选择一个空闲段
  • 利用 allocate_segment() 进行段页式分配
  • 更新进程段占用记录

举例说明

假设进程 P1 请求分配 3 页内存:

复制代码
allocate_for_process(1, 3);

如果 P1 没有分配过内存,系统会:

  1. 找一个空闲段(比如段 2)
  2. 给这个段分配 3 个页,每页对应一个物理帧
  3. segment_table[2] 中建立页表
  4. process_segments[1] = 2 记录下来

如果之后再调用同样的函数为 P1 分配,因其已占用段,函数将返回 -1(失败)


4.2.2 allocate_segment(int seg_id, int num_pages)

项目 内容说明
函数名 allocate_segment()
功能 为指定段分配一张页表,并为该段的每一页分配一个物理帧
成功返回 0
失败返回 -1(非法段号、段已使用、内存不足)
失败后的回滚机制 会释放已分配帧和页表,避免内存泄漏或段状态异常
复制代码
PageTableEntry *pt = (PageTableEntry *)malloc(sizeof(PageTableEntry) * num_pages);// 分配页表空间
for (int i = 0; i < num_pages; i++) {//为每一页分配物理帧 + 填写页表
    int frame = allocate_frame();
    ...
    pt[i].valid = 1;
    pt[i].frame_number = frame;
}
  • 为这个段动态分配一个页表数组 pt,大小为 num_pagesPageTableEntry
  • 每个 PageTableEntry 用来记录一页的状态,包括:
    • 该页是否有效 (valid)
    • 该页对应的物理帧号 (frame_number)

失败时释放已分配帧并回滚:

复制代码
for (int j = 0; j < i; j++)
    free_frames[pt[j].frame_number] = 1;
free(pt);

4.2 内存分配源代码

复制代码
int allocate_frame() {                  // First‑Fit
    for (int i = 0; i < NUM_PAGES; ++i)
        if (free_frames[i]) { free_frames[i] = 0; return i; }
    return -1;                          // 无空闲帧
}

int allocate_segment(int seg,int pages){
    PageTableEntry *pt = malloc(sizeof(PageTableEntry)*pages);
    for (int i = 0; i < pages; ++i){
        int frame = allocate_frame();
        if (frame == -1){               // 回滚
            for (int j = 0; j < i; ++j) free_frames[pt[j].frame_number] = 1;
            free(pt); return -1;
        }
        pt[i].valid = 1; pt[i].frame_number = frame;
    }
    segment_table[seg].limit = pages;
    segment_table[seg].page_table = pt;
    return 0;
}

int allocate_for_process(int pid,int pages){
    if (process_segments[pid] != -1) return -1;        
    int seg = find_free_segment();                     
    if (seg == -1) return -1;
    if (allocate_segment(seg,pages) == 0){             
        process_segments[pid] = seg;                   
        return 0;
    }
    return -1;
}
  • 每个进程最多分配一个段。
  • 每个段内部通过页表映射页到物理帧。
  • 页分配按需调用 allocate_frame() 逐个寻找空闲帧。

5. 内存释放算法

5.1 算法设计

5.1.1 算法流程

(1)调用 free_process_memory(process_id)

(2)如果该进程有段(process_segments[pid] != -1):

  • 获取段号 seg_id
  • 调用 free_segment(seg_id) 释放段

(3)在 free_segment(seg_id) 中:

  • 遍历该段页表中的每个有效页
  • 将页表中记录的帧号释放(free_frames[frame] = 1
  • 释放页表内存,清空段表项

​ 综述:当进程释放内存时,需释放段中所有有效页,并更新位图。先根据 process_segments[pid] 找到该进程占用的段号,后遍历该段的页表:把每个 valid 页对应的 free_frames[frame_number] 置 1,再释放页表内存,最后清空段表项 (page_table=NULL, limit=0);将进程‑段映射改回 ‑1


5.1.2 函数调用链

复制代码
free_process_memory(process_id) → free_segment(seg_id)

5.1.3 关键代码部分

free_segment()

复制代码
for (int i = 0; i < segment_table[seg_id].limit; i++) {
    if (segment_table[seg_id].page_table[i].valid)
        free_frames[segment_table[seg_id].page_table[i].frame_number] = 1; // 标记帧为空闲
}
free(segment_table[seg_id].page_table); // 释放页表
segment_table[seg_id].page_table = NULL;
segment_table[seg_id].limit = 0;

free_process_memory()

复制代码
if (process_segments[process_id] != -1) {
    free_segment(process_segments[process_id]);
    process_segments[process_id] = -1; // 清除进程段映射
}

5.2 内存释放源代码

复制代码
void free_segment(int seg){
    if (segment_table[seg].page_table == NULL) return;
    for (int i = 0; i < segment_table[seg].limit; ++i)
        if (segment_table[seg].page_table[i].valid)
            free_frames[segment_table[seg].page_table[i].frame_number] = 1;
    free(segment_table[seg].page_table);          // 释放页表
    segment_table[seg].page_table = NULL;
    segment_table[seg].limit = 0;
}

void free_process_memory(int pid){
    int seg = process_segments[pid];
    if (seg != -1){
        free_segment(seg);                        
        process_segments[pid] = -1;               
    }
}

6. 测试

6.1 产生测试数据

6.1.1 关键代码:随机为5个进程分别分配和释放内存15次,即随机产生15组数据:(进程Pi 分配内存大小) 或者 (进程Pi结束)

复制代码
// 生成随机操作序列
for (int i = 0; i < NUM_OPERATIONS; i++)
{
	operations[i].op_type = rand() % 2;                // 0: 分配, 1: 释放
	operations[i].process_id = rand() % NUM_PROCESSES; // 进程ID: 0-4
	operations[i].page_count = rand() % 4 + 1;         // 分配1-4页
	operations[i].result = 0;                          // 初始化结果
}

6.1.2 测试程序设计

  1. 随机操作生成:生成15次随机操作,包括进程内存分配和释放
  2. 进程管理 :通过process_segments数组记录每个进程使用的段
  3. 内存布局可视化print_memory_layout()函数可以直观显示帧的使用情况
  4. 详细日志:记录每次操作的详细信息和结果
  5. 分析中断:在执行4组操作后,提供选择是否继续执行剩余操作

6.2 输出结果分析

6.2.1 部分输出结果

复制代码
操作  5: 为进程 P1 分配 2 页内存...
分配成功! 进程 P1 使用段 1
空闲帧: 4 5 6 7 8 9 10 11 12 13 14 15
内存布局示意图:
+-----------------+
| 帧  0: 进程 P2  |
| 帧  1: 进程 P2  |
| 帧  2: 进程 P1  |
| 帧  3: 进程 P1  |
| 帧  4: 空闲    |
| 帧  5: 空闲    |
| 帧  6: 空闲    |
| 帧  7: 空闲    |
| 帧  8: 空闲    |
| 帧  9: 空闲    |
| 帧 10: 空闲    |
| 帧 11: 空闲    |
| 帧 12: 空闲    |
| 帧 13: 空闲    |
| 帧 14: 空闲    |
| 帧 15: 空闲    |
+-----------------+

操作  6: 为进程 P2 分配 3 页内存...
分配失败! 原因: 进程已有分配的内存
空闲帧: 4 5 6 7 8 9 10 11 12 13 14 15
内存布局示意图:
+-----------------+
| 帧  0: 进程 P2  |
| 帧  1: 进程 P2  |
| 帧  2: 进程 P1  |
| 帧  3: 进程 P1  |
| 帧  4: 空闲    |
| 帧  5: 空闲    |
| 帧  6: 空闲    |
| 帧  7: 空闲    |
| 帧  8: 空闲    |
| 帧  9: 空闲    |
| 帧 10: 空闲    |
| 帧 11: 空闲    |
| 帧 12: 空闲    |
| 帧 13: 空闲    |
| 帧 14: 空闲    |
| 帧 15: 空闲    |
+-----------------+

操作  7: 释放进程 P1 的内存...
释放成功! 段 1 已释放
空闲帧: 2 3 4 5 6 7 8 9 10 11 12 13 14 15
内存布局示意图:
+-----------------+
| 帧  0: 进程 P2  |
| 帧  1: 进程 P2  |
| 帧  2: 空闲    |
| 帧  3: 空闲    |
| 帧  4: 空闲    |
| 帧  5: 空闲    |
| 帧  6: 空闲    |
| 帧  7: 空闲    |
| 帧  8: 空闲    |
| 帧  9: 空闲    |
| 帧 10: 空闲    |
| 帧 11: 空闲    |
| 帧 12: 空闲    |
| 帧 13: 空闲    |
| 帧 14: 空闲    |
| 帧 15: 空闲    |
+-----------------+

操作  8: 为进程 P0 分配 2 页内存...
分配成功! 进程 P0 使用段 1
空闲帧: 4 5 6 7 8 9 10 11 12 13 14 15
内存布局示意图:
+-----------------+
| 帧  0: 进程 P2  |
| 帧  1: 进程 P2  |
| 帧  2: 进程 P0  |
| 帧  3: 进程 P0  |
| 帧  4: 空闲    |
| 帧  5: 空闲    |
| 帧  6: 空闲    |
| 帧  7: 空闲    |
| 帧  8: 空闲    |
| 帧  9: 空闲    |
| 帧 10: 空闲    |
| 帧 11: 空闲    |
| 帧 12: 空闲    |
| 帧 13: 空闲    |
| 帧 14: 空闲    |
| 帧 15: 空闲    |
+-----------------+

6.2.2 具体分析

6.2.2.1 逐步说明

(1)操作 5:为进程 P1 分配 2 页内存
  • 成功:P1 使用段 1
  • 系统为 P1 分配了 2 个物理帧
  • 已使用帧:01(P2)、23(P1)

内存布局:

复制代码
| 帧  0: 进程 P2  |
| 帧  1: 进程 P2  |
| 帧  2: 进程 P1  |
| 帧  3: 进程 P1  |
| 帧  4~15: 空闲   |

(2)操作 6:为进程 P2 分配 3 页内存
  • 失败:提示"进程已有分配的内存"
  • 因为 P2 之前已经使用段分配了内存(帧 0、1),不能重复分配
  • 所以内存状态 无变化

内存布局:保持和操作5相同


(3)操作 7:释放进程 P1 的内存
  • 成功释放段 1
  • 释放了帧 2 和 3
  • 空闲帧更新为:234~15

内存布局更新:

复制代码
| 帧  0: 进程 P2  |
| 帧  1: 进程 P2  |
| 帧  2: 空闲    |
| 帧  3: 空闲    |
| 帧  4~15: 空闲   |

(4)操作 8:为进程 P0 分配 2 页内存
  • 成功:P0 使用段 1(刚刚 P1 释放后,段 1 被回收)
  • 系统为 P0 分配 2 个帧,从空闲帧中分配:帧 2 和 3

内存布局更新:

复制代码
| 帧  0: 进程 P2  |
| 帧  1: 进程 P2  |
| 帧  2: 进程 P0  |
| 帧  3: 进程 P0  |
| 帧  4~15: 空闲   |

6.2.2.1 总结表格
操作编号 操作类型 成功/失败 内存变化描述
操作 5 P1 分配 2 页 成功 占用帧 2、3,段 1 被 P1 占用
操作 6 P2 再次分配 3 页 失败 P2 已有内存分配,操作无效,内存不变
操作 7 P1 释放内存 成功 释放段 1,帧 2、3 被回收
操作 8 P0 分配 2 页 成功 重用段 1,P0 分配帧 2、3

6.3 测试程序源代码

复制代码
int main()
{
    // 初始化随机数生成器
    srand(time(NULL));

    init_memory();

    printf("初始化完成\n");
    print_memory_status();
    print_memory_layout();

    // 生成随机操作序列
    for (int i = 0; i < NUM_OPERATIONS; i++)
    {
        operations[i].op_type = rand() % 2;                // 0: 分配, 1: 释放
        operations[i].process_id = rand() % NUM_PROCESSES; // 进程ID: 0-4
        operations[i].page_count = rand() % 4 + 1;         // 分配1-4页
        operations[i].result = 0;                          // 初始化结果
    }

    // 执行操作序列
    printf("\n开始测试随机操作序列...\n");
    for (int i = 0; i < NUM_OPERATIONS; i++)
    {
        Operation *op = &operations[i];

        if (op->op_type == 0)
        { // 分配内存
            printf("\n操作 %2d: 为进程 P%d 分配 %d 页内存...\n",
                   i + 1, op->process_id, op->page_count);

            op->result = allocate_for_process(op->process_id, op->page_count);

            if (op->result == 0)
            {
                printf("分配成功! 进程 P%d 使用段 %d\n",
                       op->process_id, process_segments[op->process_id]);
            }
            else
            {
                printf("分配失败! 原因: ");
                if (process_segments[op->process_id] != -1)
                {
                    printf("进程已有分配的内存\n");
                }
                else
                {
                    printf("内存不足或没有空闲段\n");
                }
            }
        }
        else
        { // 释放内存
            printf("\n操作 %2d: 释放进程 P%d 的内存...\n",
                   i + 1, op->process_id);

            if (process_segments[op->process_id] != -1)
            {
                int segment = process_segments[op->process_id];
                free_process_memory(op->process_id);
                printf("释放成功! 段 %d 已释放\n", segment);
                op->result = 0;
            }
            else
            {
                printf("释放失败! 进程 P%d 没有分配的内存\n", op->process_id);
                op->result = -1;
            }
        }

        print_memory_status();
        print_memory_layout();
    }

    // 清理资源
    for (int i = 0; i < NUM_PROCESSES; i++)
    {
        free_process_memory(i);
    }
    free(phys_mem);

    return 0;
}

7.源代码与完整测试结果

7.1 源代码

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define PAGE_SIZE 4096 // 4KB
#define NUM_PAGES 16   // 16页
#define NUM_SEGMENTS 5 // 5
#define PHYS_MEM_SIZE (PAGE_SIZE * NUM_PAGES)
#define NUM_PROCESSES 5 // 5
#define NUM_OPERATIONS 15

// 模拟物理内存
char *phys_mem;

// 页表条目
typedef struct
{
    int valid;
    int frame_number;
} PageTableEntry;

// 段表条目
typedef struct
{
    int base;
    int limit;
    PageTableEntry *page_table;
} SegmentTableEntry;

SegmentTableEntry segment_table[NUM_SEGMENTS];

// 记录空闲帧
int free_frames[NUM_PAGES];

// 进程占用段的记录
int process_segments[NUM_PROCESSES];

// 创建一个操作日志结构
typedef struct
{
    int op_type;    // 0: 分配, 1: 释放
    int process_id; // 进程ID
    int page_count; // 分配的页数(仅当op_type=0时有效)
    int result;     // 操作结果: 0成功,-1失败
} Operation;

// 存储操作日志
Operation operations[NUM_OPERATIONS];

void init_memory()
{
    phys_mem = (char *)malloc(PHYS_MEM_SIZE);
    memset(phys_mem, 0, PHYS_MEM_SIZE);
    for (int i = 0; i < NUM_PAGES; i++)
        free_frames[i] = 1;

    for (int i = 0; i < NUM_SEGMENTS; i++)
        segment_table[i].page_table = NULL;

    // 初始化进程段记录为-1(表示未分配)
    for (int i = 0; i < NUM_PROCESSES; i++)
        process_segments[i] = -1;
}

int allocate_frame()
{
    for (int i = 0; i < NUM_PAGES; i++)
    {
        if (free_frames[i])
        {
            free_frames[i] = 0;
            return i;
        }
    }
    return -1; // 没有空闲帧
}

// 分段 + 分页 分配
int allocate_segment(int seg_id, int num_pages)
{
    if (seg_id >= NUM_SEGMENTS || segment_table[seg_id].page_table != NULL)
        return -1;

    PageTableEntry *pt = (PageTableEntry *)malloc(sizeof(PageTableEntry) * num_pages);
    for (int i = 0; i < num_pages; i++)
    {
        int frame = allocate_frame();
        if (frame == -1)
        {
            printf("内存不足,释放已分配帧\n");
            for (int j = 0; j < i; j++)
                free_frames[pt[j].frame_number] = 1;
            free(pt);
            return -1;
        }
        pt[i].valid = 1;
        pt[i].frame_number = frame;
    }
    segment_table[seg_id].base = 0;
    segment_table[seg_id].limit = num_pages;
    segment_table[seg_id].page_table = pt;
    return 0;
}

// 释放段
void free_segment(int seg_id)
{
    if (seg_id >= NUM_SEGMENTS || segment_table[seg_id].page_table == NULL)
        return;

    for (int i = 0; i < segment_table[seg_id].limit; i++)
    {
        if (segment_table[seg_id].page_table[i].valid)
            free_frames[segment_table[seg_id].page_table[i].frame_number] = 1;
    }
    free(segment_table[seg_id].page_table);
    segment_table[seg_id].page_table = NULL;
    segment_table[seg_id].limit = 0;
}

void print_memory_status()
{
    printf("空闲帧: ");
    for (int i = 0; i < NUM_PAGES; i++)
    {
        if (free_frames[i])
            printf("%d ", i);
    }
    printf("\n");
}

// 打印内存布局示意图
void print_memory_layout()
{
    printf("内存布局示意图:\n");
    printf("+-----------------+\n");

    for (int i = 0; i < NUM_PAGES; i++) // 确保这个循环只执行NUM_PAGES次
    {
        printf("| 帧 %2d: ", i);

        if (free_frames[i])
        {
            printf("空闲    |\n");
        }
        else
        {
            // 查找该帧属于哪个段/进程
            int found = 0;
            for (int s = 0; s < NUM_SEGMENTS && !found; s++) // 添加!found条件优化
            {
                if (segment_table[s].page_table != NULL)
                {
                    for (int p = 0; p < segment_table[s].limit && !found; p++) // 添加!found条件优化
                    {
                        if (segment_table[s].page_table[p].valid &&
                            segment_table[s].page_table[p].frame_number == i)
                        {
                            // 找到对应的进程ID
                            for (int proc = 0; proc < NUM_PROCESSES; proc++)
                            {
                                if (process_segments[proc] == s)
                                {
                                    printf("进程 P%d  |\n", proc);
                                    found = 1;
                                    break;
                                }
                            }
                        }
                        if (found)
                            break;
                    }
                }
            }
            if (!found)
            {
                printf("已占用   |\n");
            }
        }
    }
    printf("+-----------------+\n");
}

// 为进程分配内存
int allocate_for_process(int process_id, int num_pages)
{
    // 如果进程已经有分配的段,则返回失败
    if (process_segments[process_id] != -1)
    {
        return -1;
    }

    // 寻找一个空闲段
    int seg_id = -1;
    for (int i = 0; i < NUM_SEGMENTS; i++)
    {
        if (segment_table[i].page_table == NULL)
        {
            seg_id = i;
            break;
        }
    }

    if (seg_id == -1)
    {
        return -1; // 没有空闲段
    }

    int result = allocate_segment(seg_id, num_pages);
    if (result == 0)
    {
        // 分配成功,记录进程占用的段
        process_segments[process_id] = seg_id;
    }
    return result;
}

// 释放进程内存
void free_process_memory(int process_id)
{
    if (process_segments[process_id] != -1)
    {
        free_segment(process_segments[process_id]);
        process_segments[process_id] = -1;
    }
}

int main()
{
    // 初始化随机数生成器
    srand(time(NULL));

    init_memory();

    printf("初始化完成\n");
    print_memory_status();
    print_memory_layout();

    // 生成随机操作序列
    for (int i = 0; i < NUM_OPERATIONS; i++)
    {
        operations[i].op_type = rand() % 2;                // 0: 分配, 1: 释放
        operations[i].process_id = rand() % NUM_PROCESSES; // 进程ID: 0-4
        operations[i].page_count = rand() % 4 + 1;         // 分配1-4页
        operations[i].result = 0;                          // 初始化结果
    }

    // 执行操作序列
    printf("\n开始测试随机操作序列...\n");
    for (int i = 0; i < NUM_OPERATIONS; i++)
    {
        Operation *op = &operations[i];

        if (op->op_type == 0)
        { // 分配内存
            printf("\n操作 %2d: 为进程 P%d 分配 %d 页内存...\n",
                   i + 1, op->process_id, op->page_count);

            op->result = allocate_for_process(op->process_id, op->page_count);

            if (op->result == 0)
            {
                printf("分配成功! 进程 P%d 使用段 %d\n",
                       op->process_id, process_segments[op->process_id]);
            }
            else
            {
                printf("分配失败! 原因: ");
                if (process_segments[op->process_id] != -1)
                {
                    printf("进程已有分配的内存\n");
                }
                else
                {
                    printf("内存不足或没有空闲段\n");
                }
            }
        }
        else
        { // 释放内存
            printf("\n操作 %2d: 释放进程 P%d 的内存...\n",
                   i + 1, op->process_id);

            if (process_segments[op->process_id] != -1)
            {
                int segment = process_segments[op->process_id];
                free_process_memory(op->process_id);
                printf("释放成功! 段 %d 已释放\n", segment);
                op->result = 0;
            }
            else
            {
                printf("释放失败! 进程 P%d 没有分配的内存\n", op->process_id);
                op->result = -1;
            }
        }

        print_memory_status();
        print_memory_layout();
    }

    // 清理资源
    for (int i = 0; i < NUM_PROCESSES; i++)
    {
        free_process_memory(i);
    }
    free(phys_mem);

    return 0;
}

7.2 完整测试结果

复制代码
PS C:\Users\23370\Desktop\os> ./test_1
初始化完成
空闲帧: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 
内存布局示意图:
+-----------------+
| 帧  0: 空闲    |
| 帧  1: 空闲    |
| 帧  2: 空闲    |
| 帧  3: 空闲    |
| 帧  4: 空闲    |
| 帧  5: 空闲    |
| 帧  6: 空闲    |
| 帧  7: 空闲    |
| 帧  8: 空闲    |
| 帧  9: 空闲    |
| 帧 10: 空闲    |
| 帧 11: 空闲    |
| 帧 12: 空闲    |
| 帧 13: 空闲    |
| 帧 14: 空闲    |
| 帧 15: 空闲    |
+-----------------+

开始测试随机操作序列...

操作  1: 为进程 P2 分配 2 页内存...
分配成功! 进程 P2 使用段 0
空闲帧: 2 3 4 5 6 7 8 9 10 11 12 13 14 15
内存布局示意图:
+-----------------+
| 帧  0: 进程 P2  |
| 帧  1: 进程 P2  |
| 帧  2: 空闲    |
| 帧  3: 空闲    |
| 帧  4: 空闲    |
| 帧  5: 空闲    |
| 帧  6: 空闲    |
| 帧  7: 空闲    |
| 帧  8: 空闲    |
| 帧  9: 空闲    |
| 帧 10: 空闲    |
| 帧 11: 空闲    |
| 帧 12: 空闲    |
| 帧 13: 空闲    |
| 帧 14: 空闲    |
| 帧 15: 空闲    |
+-----------------+

操作  2: 为进程 P3 分配 1 页内存...
分配成功! 进程 P3 使用段 1
空闲帧: 3 4 5 6 7 8 9 10 11 12 13 14 15
内存布局示意图:
+-----------------+
| 帧  0: 进程 P2  |
| 帧  1: 进程 P2  |
| 帧  2: 进程 P3  |
| 帧  3: 空闲    |
| 帧  4: 空闲    |
| 帧  5: 空闲    |
| 帧  6: 空闲    |
| 帧  7: 空闲    |
| 帧  8: 空闲    |
| 帧  9: 空闲    |
| 帧 10: 空闲    |
| 帧 11: 空闲    |
| 帧 12: 空闲    |
| 帧 13: 空闲    |
| 帧 14: 空闲    |
| 帧 15: 空闲    |
+-----------------+

操作  3: 为进程 P3 分配 3 页内存...
分配失败! 原因: 进程已有分配的内存
空闲帧: 3 4 5 6 7 8 9 10 11 12 13 14 15
内存布局示意图:
+-----------------+
| 帧  0: 进程 P2  |
| 帧  1: 进程 P2  |
| 帧  2: 进程 P3  |
| 帧  3: 空闲    |
| 帧  4: 空闲    |
| 帧  5: 空闲    |
| 帧  6: 空闲    |
| 帧  7: 空闲    |
| 帧  8: 空闲    |
| 帧  9: 空闲    |
| 帧 10: 空闲    |
| 帧 11: 空闲    |
| 帧 12: 空闲    |
| 帧 13: 空闲    |
| 帧 14: 空闲    |
| 帧 15: 空闲    |
+-----------------+

操作  4: 释放进程 P3 的内存...
释放成功! 段 1 已释放
空闲帧: 2 3 4 5 6 7 8 9 10 11 12 13 14 15
内存布局示意图:
+-----------------+
| 帧  0: 进程 P2  |
| 帧  1: 进程 P2  |
| 帧  2: 空闲    |
| 帧  3: 空闲    |
| 帧  4: 空闲    |
| 帧  5: 空闲    |
| 帧  6: 空闲    |
| 帧  7: 空闲    |
| 帧  8: 空闲    |
| 帧  9: 空闲    |
| 帧 10: 空闲    |
| 帧 11: 空闲    |
| 帧 12: 空闲    |
| 帧 13: 空闲    |
| 帧 14: 空闲    |
| 帧 15: 空闲    |
+-----------------+

操作  5: 为进程 P1 分配 2 页内存...
分配成功! 进程 P1 使用段 1
空闲帧: 4 5 6 7 8 9 10 11 12 13 14 15
内存布局示意图:
+-----------------+
| 帧  0: 进程 P2  |
| 帧  1: 进程 P2  |
| 帧  2: 进程 P1  |
| 帧  3: 进程 P1  |
| 帧  4: 空闲    |
| 帧  5: 空闲    |
| 帧  6: 空闲    |
| 帧  7: 空闲    |
| 帧  8: 空闲    |
| 帧  9: 空闲    |
| 帧 10: 空闲    |
| 帧 11: 空闲    |
| 帧 12: 空闲    |
| 帧 13: 空闲    |
| 帧 14: 空闲    |
| 帧 15: 空闲    |
+-----------------+

操作  6: 为进程 P2 分配 3 页内存...
分配失败! 原因: 进程已有分配的内存
空闲帧: 4 5 6 7 8 9 10 11 12 13 14 15
内存布局示意图:
+-----------------+
| 帧  0: 进程 P2  |
| 帧  1: 进程 P2  |
| 帧  2: 进程 P1  |
| 帧  3: 进程 P1  |
| 帧  4: 空闲    |
| 帧  5: 空闲    |
| 帧  6: 空闲    |
| 帧  7: 空闲    |
| 帧  8: 空闲    |
| 帧  9: 空闲    |
| 帧 10: 空闲    |
| 帧 11: 空闲    |
| 帧 12: 空闲    |
| 帧 13: 空闲    |
| 帧 14: 空闲    |
| 帧 15: 空闲    |
+-----------------+

操作  7: 释放进程 P1 的内存...
释放成功! 段 1 已释放
空闲帧: 2 3 4 5 6 7 8 9 10 11 12 13 14 15
内存布局示意图:
+-----------------+
| 帧  0: 进程 P2  |
| 帧  1: 进程 P2  |
| 帧  2: 空闲    |
| 帧  3: 空闲    |
| 帧  4: 空闲    |
| 帧  5: 空闲    |
| 帧  6: 空闲    |
| 帧  7: 空闲    |
| 帧  8: 空闲    |
| 帧  9: 空闲    |
| 帧 10: 空闲    |
| 帧 11: 空闲    |
| 帧 12: 空闲    |
| 帧 13: 空闲    |
| 帧 14: 空闲    |
| 帧 15: 空闲    |
+-----------------+

操作  8: 为进程 P0 分配 2 页内存...
分配成功! 进程 P0 使用段 1
空闲帧: 4 5 6 7 8 9 10 11 12 13 14 15
内存布局示意图:
+-----------------+
| 帧  0: 进程 P2  |
| 帧  1: 进程 P2  |
| 帧  2: 进程 P0  |
| 帧  3: 进程 P0  |
| 帧  4: 空闲    |
| 帧  5: 空闲    |
| 帧  6: 空闲    |
| 帧  7: 空闲    |
| 帧  8: 空闲    |
| 帧  9: 空闲    |
| 帧 10: 空闲    |
| 帧 11: 空闲    |
| 帧 12: 空闲    |
| 帧 13: 空闲    |
| 帧 14: 空闲    |
| 帧 15: 空闲    |
+-----------------+

操作  9: 为进程 P1 分配 4 页内存...
分配成功! 进程 P1 使用段 2
空闲帧: 8 9 10 11 12 13 14 15
内存布局示意图:
+-----------------+
| 帧  0: 进程 P2  |
| 帧  1: 进程 P2  |
| 帧  2: 进程 P0  |
| 帧  3: 进程 P0  |
| 帧  4: 进程 P1  |
| 帧  5: 进程 P1  |
| 帧  6: 进程 P1  |
| 帧  7: 进程 P1  |
| 帧  8: 空闲    |
| 帧  9: 空闲    |
| 帧 10: 空闲    |
| 帧 11: 空闲    |
| 帧 12: 空闲    |
| 帧 13: 空闲    |
| 帧 14: 空闲    |
| 帧 15: 空闲    |
+-----------------+

操作 10: 释放进程 P2 的内存...
释放成功! 段 0 已释放
空闲帧: 0 1 8 9 10 11 12 13 14 15
内存布局示意图:
+-----------------+
| 帧  0: 空闲    |
| 帧  1: 空闲    |
| 帧  2: 进程 P0  |
| 帧  3: 进程 P0  |
| 帧  4: 进程 P1  |
| 帧  5: 进程 P1  |
| 帧  6: 进程 P1  |
| 帧  7: 进程 P1  |
| 帧  8: 空闲    |
| 帧  9: 空闲    |
| 帧 10: 空闲    |
| 帧 11: 空闲    |
| 帧 12: 空闲    |
| 帧 13: 空闲    |
| 帧 14: 空闲    |
| 帧 15: 空闲    |
+-----------------+

操作 11: 为进程 P4 分配 4 页内存...
分配成功! 进程 P4 使用段 0
空闲帧: 10 11 12 13 14 15
内存布局示意图:
+-----------------+
| 帧  0: 进程 P4  |
| 帧  1: 进程 P4  |
| 帧  2: 进程 P0  |
| 帧  3: 进程 P0  |
| 帧  4: 进程 P1  |
| 帧  5: 进程 P1  |
| 帧  6: 进程 P1  |
| 帧  7: 进程 P1  |
| 帧  8: 进程 P4  |
| 帧  9: 进程 P4  |
| 帧 10: 空闲    |
| 帧 11: 空闲    |
| 帧 12: 空闲    |
| 帧 13: 空闲    |
| 帧 14: 空闲    |
| 帧 15: 空闲    |
+-----------------+

操作 12: 释放进程 P3 的内存...
释放失败! 进程 P3 没有分配的内存
空闲帧: 10 11 12 13 14 15
内存布局示意图:
+-----------------+
| 帧  0: 进程 P4  |
| 帧  1: 进程 P4  |
| 帧  2: 进程 P0  |
| 帧  3: 进程 P0  |
| 帧  4: 进程 P1  |
| 帧  5: 进程 P1  |
| 帧  6: 进程 P1  |
| 帧  7: 进程 P1  |
| 帧  8: 进程 P4  |
| 帧  9: 进程 P4  |
| 帧 10: 空闲    |
| 帧 11: 空闲    |
| 帧 12: 空闲    |
| 帧 13: 空闲    |
| 帧 14: 空闲    |
| 帧 15: 空闲    |
+-----------------+

操作 13: 为进程 P3 分配 3 页内存...
分配成功! 进程 P3 使用段 3
空闲帧: 13 14 15
内存布局示意图:
+-----------------+
| 帧  0: 进程 P4  |
| 帧  1: 进程 P4  |
| 帧  2: 进程 P0  |
| 帧  3: 进程 P0  |
| 帧  4: 进程 P1  |
| 帧  5: 进程 P1  |
| 帧  6: 进程 P1  |
| 帧  7: 进程 P1  |
| 帧  8: 进程 P4  |
| 帧  9: 进程 P4  |
| 帧 10: 进程 P3  |
| 帧 11: 进程 P3  |
| 帧 12: 进程 P3  |
| 帧 13: 空闲    |
| 帧 14: 空闲    |
| 帧 15: 空闲    |
+-----------------+

操作 14: 释放进程 P1 的内存...
释放成功! 段 2 已释放
空闲帧: 4 5 6 7 13 14 15
内存布局示意图:
+-----------------+
| 帧  0: 进程 P4  |
| 帧  1: 进程 P4  |
| 帧  2: 进程 P0  |
| 帧  3: 进程 P0  |
| 帧  4: 空闲    |
| 帧  5: 空闲    |
| 帧  6: 空闲    |
| 帧  7: 空闲    |
| 帧  8: 进程 P4  |
| 帧  9: 进程 P4  |
| 帧 10: 进程 P3  |
| 帧 11: 进程 P3  |
| 帧 12: 进程 P3  |
| 帧 13: 空闲    |
| 帧 14: 空闲    |
| 帧 15: 空闲    |
+-----------------+

操作 15: 为进程 P4 分配 1 页内存...
分配失败! 原因: 进程已有分配的内存
空闲帧: 4 5 6 7 13 14 15
内存布局示意图:
+-----------------+
| 帧  0: 进程 P4  |
| 帧  1: 进程 P4  |
| 帧  2: 进程 P0  |
| 帧  3: 进程 P0  |
| 帧  4: 空闲    |
| 帧  5: 空闲    |
| 帧  6: 空闲    |
| 帧  7: 空闲    |
| 帧  8: 进程 P4  |
| 帧  9: 进程 P4  |
| 帧 10: 进程 P3  |
| 帧 11: 进程 P3  |
| 帧 12: 进程 P3  |
| 帧 13: 空闲    |
| 帧 14: 空闲    |
| 帧 15: 空闲    |
+-----------------+

8.参考链接

【操作系统-75】段页式管理方式

操作系统------内存段式和段页式管理

相关推荐
binary思维11 分钟前
cat、more和less的区别
linux·less
想成为大佬的每一天33 分钟前
Linux网络编程day7 线程池and UDP
linux·开发语言
云边有个稻草人1 小时前
【Linux系统】第三节—权限
linux·粘滞位·linux权限·shell命令以及运行原理·文件权限的相关设置方法·使用sudo分配权限·file指令
朝阳5811 小时前
在一台服务器上通过 Nginx 配置实现不同子域名访问静态文件和后端服务
服务器·前端·nginx
学渣676561 小时前
mac连接lniux服务器教学笔记
服务器·笔记·macos
vortex51 小时前
浅谈 Shell 脚本编程中引号的妙用
linux·命令行
余辉zmh1 小时前
【Linux系统篇】:Linux线程控制基础---线程的创建,等待与终止
linux·运维·服务器
大白的编程日记.3 小时前
【Linux学习笔记】基础IO之理解文件
linux·笔记·学习
oioihoii3 小时前
C++23 views::as_rvalue (P2446R2) 深入解析
运维·服务器·c++23
周之鸥3 小时前
Ubuntu 服务器管理命令笔记
服务器·笔记·ubuntu