简介:本资源是华北电力大学计算机系《计算机操作系统》课程配套的完整实验报告,面向高校操作系统初学者及实践教学场景,聚焦EOS操作系统内核与应用开发的实操入门。报告系统覆盖OS Lab集成实验环境的全流程使用:从用户注册、界面布局认知,到Windows控制台项目的新建、编译、执行与调试(含断点设置、单步跟踪、变量监视、调用堆栈分析),并延伸至EOS内核项目的构建与调试机制,帮助学习者建立操作系统底层开发的直观认知与动手能力。资源为1个788KB的Word文档(.doc),内容结构完整,含封面、实验目的、预备知识、分步操作指南、截图示意及规范排版,便于直接用于课程提交或自学复盘。目前已有1360人学习下载,适合需要掌握OS Lab工具链、理解EOS编译-烧录-执行闭环的本科生与实验指导教师。
1. 这不是普通实验报告:它是一套可运行的 x86 实模式操作系统教学套件
华北电力大学这版《操作系统实验报告》常被误认为是 PDF 文档或理论讲义,但它的本质是一份完整、可编译、可调试、可单步跟踪的实模式操作系统教学工程包。它不依赖 Linux 或 Windows 内核,而是基于 EOS(Embedded Operating System)——一个为教学定制的、运行在 Bochs 虚拟机上的 32 位 x86 实验性内核。整套实验从软盘引导扇区(boot.asm)开始,经 loader 加载器、内核初始化(KiSystemStartup)、进程创建(CreateProcess)、线程调度(PspSelectNextThread),一直覆盖到物理内存管理(pm 命令)、虚拟地址空间(vm 命令)和二级页表映射(getcr3.asm),形成一条贯穿“硬件启动 → 内存管理 → 进程/线程 → 同步调度”的完整技术链。它面向的是刚学完汇编与 C 语言、尚未接触真实 OS 开发的学生,但对已有 Linux 驱动或嵌入式经验的工程师而言,其清晰的模块划分(ke/ps/boot 目录结构)、带注释的 NASM 引导代码、以及可逐行进入内核函数(如 PsCreateProcess)的调试路径,反而成为理解现代操作系统底层机制的极佳反向教材。你不需要重写内核,只需按报告步骤修改几行 C 或 asm,就能亲眼看到一个进程如何被分配 PCB、一个信号量如何阻塞线程、一个页目录项(PDE)如何被动态修改——这种“所见即所得”的系统级调试体验,在当前主流操作系统课程中极为稀缺。
2. OS Lab 环境搭建与 EOS 内核项目生成:从双击图标到生成 boot.bin/kernel.dll
OS Lab 并非 IDE 插件或 Web 工具,而是一个集成 Bochs 调试器、NASM 编译器、MinGW GCC 工具链与图形化 FloppyImageEditor 的 Windows 桌面应用。它的核心价值在于将复杂的交叉编译流程封装成点击操作,但要真正掌控它,必须理解其背后生成逻辑与文件流向。本节将拆解从新建项目到生成三个关键二进制文件(boot.bin、loader.bin、kernel.dll)的全过程,并给出可验证的命令行等价操作。
2.1 安装与首次配置:注册信息与项目路径的隐含约束
OS Lab 启动后强制弹出的注册对话框(学号+姓名)并非仅用于记录,它直接影响后续生成路径中的默认命名空间。例如,当学号填为20231101、姓名为ZhangSan时,OS Lab 会在内部缓存中将用户标识为20231101_ZhangSan,该标识会参与部分日志文件名生成(如20231101_ZhangSan.oud)。更关键的是路径约束:报告明确要求 EOS 内核项目保存在C:\根目录(如C:\eos),而非用户文档目录。这是因为 OS Lab 的构建脚本(build.bat)硬编码了C:\eos\debug\作为输出目标,若项目建在D:\oslab\下,F7 生成时会因路径不存在而静默失败,仅在“输出”窗口显示error: cannot create directory,无明确报错行。因此,必须严格遵守路径约定:
# 手动验证路径存在性(在 CMD 中执行) mkdir C:\eos mkdir C:\eos\debug # 若提示 "拒绝访问",需以管理员身份运行 CMD提示:OS Lab 的“项目配置”下拉菜单(Debug/Release)本质是切换两套预定义的 Makefile 变量。Debug 配置启用
-g -O0(带调试符号、关闭优化),Release 配置启用-O2 -DNDEBUG(优化、移除断言)。切换后需重新生成,否则调试时无法定位源码行。
2.2 新建 EOS Kernel 项目:模板背后的文件结构解析
在“新建项目”中选择“EOS Kernel”模板,OS Lab 实际执行的是以下操作:
- 在指定路径(如
C:\eos)创建标准目录树:C:\eos\ ├── boot\ # NASM 汇编源码:boot.asm (512B 引导扇区), loader.asm ├── ke\ # 内核核心:start.c, sysproc.c, sched.c 等 C 源码 ├── ps\ # 进程/线程子系统:process.c, semaphore.c, sched.c ├── sdk\ # 生成的 SDK:包含头文件与导入库(供应用程序调用) └── debug\ # F7 生成后输出目标:boot.bin, loader.bin, kernel.dll - 自动在
boot\boot.asm中插入 BIOS 中断调用(int 0x13读软盘)、在ke\start.c中设置 IDT/GDT、在ps\process.c中定义PsCreateProcess函数原型。
2.3 F7 生成的实质:四阶段编译链接流程
按下 F7 后,OS Lab 调用内部构建系统,执行以下不可见但可复现的步骤:
2.3.1 引导扇区编译(NASM → boot.bin)
# 等价命令行(需在 OS Lab 安装目录下找到 nasm.exe) nasm -f bin -o C:\eos\debug\boot.bin C:\eos\boot\boot.asm # 关键参数说明: # -f bin : 输出纯二进制格式(非 ELF/COFF),确保恰好 512 字节 # -o ... : 指定输出路径,OS Lab 严格依赖此路径注意:
boot.asm必须以times 510-($-$$) db 0填充至 510 字节,并以dw 0xaa55结尾,否则 Bochs 启动时会报Boot sector signature not found。
2.3.2 Loader 编译(NASM → loader.bin)
nasm -f bin -o C:\eos\debug\loader.bin C:\eos\boot\loader.asm # loader.asm 负责加载 kernel.dll 到内存 0x100000 处,并跳转执行2.3.3 内核编译(GCC → kernel.o + kernel.dll)
# 1. 编译所有 .c 文件为 .o(使用 MinGW GCC) gcc -m32 -I"C:\eos\sdk\include" -c -o C:\eos\debug\ke_start.o C:\eos\ke\start.c gcc -m32 -I"C:\eos\sdk\include" -c -o C:\eos\debug\ps_process.o C:\eos\ps\process.c # 2. 链接生成 kernel.dll(注意:非 Windows DLL,而是 EOS 可加载模块) ld -m i386pe -Ttext 0x100000 -o C:\eos\debug\kernel.dll \ C:\eos\debug\ke_start.o C:\eos\debug\ps_process.o \ --entry=KiSystemStartup # 关键参数说明: # -m i386pe : 指定 PE 格式(兼容 Bochs 加载器) # -Ttext 0x100000 : 设置代码段起始地址为 1MB(传统实模式保护模式切换点) # --entry=KiSystemStartup : 指定入口函数,Bochs 加载 kernel.dll 后跳转至此2.3.4 软盘镜像合成(Floppy.img 构建)
OS Lab 在调试前自动执行:
# 将三个二进制文件写入 Floppy.img(1.44MB 软盘镜像) # - boot.bin 写入扇区 0(引导扇区) # - loader.bin 写入扇区 1-17(约 8KB) # - kernel.dll 写入后续连续扇区(由 loader 运行时读取) # 此过程由 OS Lab 内置工具完成,不可手动替换,否则启动失败2.4 验证生成结果:三文件校验与 Bochs 兼容性检查
生成成功后,必须人工验证三个文件的完整性与 Bochs 兼容性:
| 文件名 | 预期大小 | 校验方法 | 不符合的后果 |
|---|---|---|---|
boot.bin | 512 字节 | certutil -hashfile C:\eos\debug\boot.bin SHA256,末尾 2 字节应为0x55 0xaa | Bochs 报No bootable device |
loader.bin | 2048 字节 | dir C:\eos\debug\loader.bin,大小应为 2048 | 内核无法加载,停在黑屏 |
kernel.dll | >100KB | file C:\eos\debug\kernel.dll(Linux 下)或用PE Explorer查看架构为i386 | Bochs 加载时报Invalid PE header |
提示:若
kernel.dll生成失败,90% 原因为ke\start.c中KiSystemStartup函数未正确定义为__declspec(dllexport),或ps\process.c中PsCreateProcess的函数签名与sdk\include\process.h不一致。此时需打开C:\eos\debug\build.log查看 GCC 错误行。
3. 调试 EOS 启动全过程:从 BIOS 中断到 KiSystemStartup 的单步追踪
EOS 启动调试是理解操作系统“第一行代码”如何执行的关键环节。OS Lab 将 Bochs 调试器深度集成,但其界面隐藏了底层调试命令。本节将还原 Bochs 控制台的真实交互,教你如何在sreg、r、u等原生命令下,精确跟踪从 CPU 复位向量(0xFFFF0)到内核主函数KiSystemStartup的每一步,并定位常见启动卡死点。
3.1 配置 Bochs 为远程目标机:调试通道的建立
在 OS Lab 中右键项目节点 → “属性” → 将“远程目标机”设为Bochs Debug,这一操作实际向 Bochs 配置文件(bochsrc.bxrc)注入了:
debugger: enabled=1, display=0, port=1234并启动 Bochs 进程监听 TCP 端口 1234。OS Lab 的“调试”菜单(F5/F10/F11)本质是向该端口发送 GDB-style 命令(如continue、stepi)。若需绕过 OS Lab 直接操作,可手动启动 Bochs:
# 在 OS Lab 安装目录下执行(假设 bochs.exe 在 bin/ 子目录) bin\bochs.exe -q -f bochsrc.bxrc # -q : 静默模式,-f : 指定配置文件3.2 BIOS 调试:确认 CPU 状态与中断向量表
启动调试后,OS Lab 会暂停在 BIOS 初始化阶段。此时必须在 Bochs Console 窗口(非 OS Lab 主窗口)输入命令验证硬件状态:
3.2.1 查看段寄存器与内存布局
# 输入 sreg 查看当前段寄存器值 (sreg) cs = 0xf000, ds = 0x0000, es = 0x0000, ss = 0x0000, fs = 0x0000, gs = 0x0000 # 关键解读:CS=0xF000 表明 CPU 正在执行 BIOS ROM 区域(0xF0000-0xFFFFF) # 此时 IP 应为 0xFFF0,即复位向量地址3.2.2 查看通用寄存器与指令指针
# 输入 r 查看所有寄存器 (r) eax: 00000000 ebx: 00000000 ecx: 00000000 edx: 00000000 esi: 00000000 edi: 00000000 ebp: 00000000 esp: 00000000 eip: 0000fff0 eflags: 00000002 # eip=0xfff0 是关键!证明 CPU 已正确跳转到复位向量3.2.3 反汇编当前指令(确认 BIOS 起始代码)
# 输入 u /10 0xfff0 查看从 0xFFF0 开始的 10 条指令 (u /10 0xfff0) 0000fff0: ( ) jmp far 0xf000:0xe05b 0000fff5: ( ) add byte [bx+si], al ... # 第一条指令是 far jmp,跳转到 BIOS 主程序入口(0xF000:0xE05B) # 若此处显示非法指令(如 ???),说明 Bochs 配置错误或镜像损坏3.3 软盘引导扇区调试:从 int 0x13 到 loader.bin 加载
当 BIOS 执行int 0x13读取软盘时,OS Lab 会自动将控制权交予boot.bin。此时需在boot.asm的关键位置设断点:
3.3.1 在 boot.asm 中设置断点(OS Lab 界面操作)
- 打开
C:\eos\boot\boot.asm - 在
mov ax, 0x07c0(设置数据段)行左侧空白处单击,添加红色断点 - 按 F5 启动调试,OS Lab 会在该行中断,黄色箭头指向此行
3.3.2 Bochs Console 验证引导扇区加载
中断后,在 Bochs Console 输入:
# 查看内存 0x7c00 处的指令(boot.bin 被 BIOS 加载至此) (x /10i 0x7c00) 0x00007c00: mov ax, 0x07c0 0x00007c03: mov ds, ax ... # 若显示乱码(如 ???),说明 boot.bin 未正确写入 Floppy.img 或 BIOS 未读取3.3.3 跟踪 loader.bin 加载过程
boot.asm最终会执行jmp 0x1000:0x0000跳转到loader.bin。此时需在loader.asm的入口处设断点:
loader.asm第一行通常为org 0x1000,入口标签为start- 在
start:行设断点,F5 后黄色箭头应出现在此处 - 输入
r查看cs=0x1000, ip=0x0000,确认跳转成功
3.4 内核调试:定位 KiSystemStartup 的首次执行
loader.bin的职责是将kernel.dll从软盘读入内存0x100000并跳转。这是最易出错的环节:
3.4.1 验证 kernel.dll 加载地址
在loader.asm的跳转指令(如jmp 0x100000)处中断后,输入:
# 查看内存 0x100000 处的前 10 字节(应为 kernel.dll 的 PE 头) (x /10b 0x100000) 0x00100000: 0x4d 0x5a 0x90 0x00 0x03 0x00 0x00 0x00 0x04 0x00 # 0x4d5a 是 "MZ" 标志,确认 PE 文件头存在3.4.2 在 KiSystemStartup 设置断点并验证
- 在
C:\eos\ke\start.c中找到VOID KiSystemStartup(VOID)函数 - 在其第一行
KiInitializePic();设断点(报告中第 61 行) - F5 启动后,若黄色箭头停在此行,则
kernel.dll加载与跳转成功 - 常见失败点:
kernel.dll的链接地址(-Ttext 0x100000)与loader.bin的跳转地址不一致,导致跳转到垃圾内存。此时 Bochs 会报Exception 13 (General Protection)。
提示:若
KiSystemStartup无法中断,可在 Bochs Console 输入set pm=1强制进入保护模式,再用u /5 0x100000反汇编,确认首条指令是否为push ebp(C 函数标准入口)。
4. 进程与线程调试实战:CreateProcess 源码级跟踪与状态转换可视化
EOS 的进程/线程模型是教学重点,其实现比 Linux 简洁但原理相通。本节以CreateProcessAPI 为切入点,通过 OS Lab 的调试器深入ps/process.c源码,展示一个进程如何从EOSApp.exe被创建、分配 PCB、加入就绪队列,再到线程状态(就绪/运行/阻塞/挂起)的实时转换。所有操作均可在报告提供的NewProc.c和loop.c示例中复现。
4.1 CreateProcess 调用链:从应用程序到内核的四层穿透
在NewProc.c的main()中调用CreateProcess("Hello.exe", ...)后,调试器可逐层进入:
4.1.1 应用层:EOSApp.exe 的 CreateProcess 调用
- 在
NewProc.c第 57 行(CreateProcess(...))设断点 - F5 中断后,按 F11 进入
CreateProcess函数体(位于sdk\lib\process.c) - 此函数本质是向内核发送
INT 0x2E软中断,并传递参数地址
4.1.2 系统调用层:KiSystemService 处理
- F11 继续进入,到达
ke\sysproc.c中的KiSystemService函数 - 查看
eax寄存器值,应为0x100(SYS_CreateProcess系统调用号) KiSystemService根据eax调用对应内核函数PsCreateProcess
4.1.3 内核层:PsCreateProcess 的核心逻辑
- F11 进入
ps\process.c的PsCreateProcess函数 - 关键步骤(报告中第 163 行):
// 1. 分配进程控制块(PCB) pProcess = ExAllocatePool(sizeof(EPROCESS)); // 2. 初始化 PCB 字段(PID、状态、父进程等) pProcess->UniqueProcessId = PsGetNextProcessId(); pProcess->State = ProcessStateReady; // 3. 创建首个线程(主线程) pThread = PsCreateThread(pProcess, StartAddress, ...); // 4. 将新进程加入就绪队列 InsertTailList(&PsReadyListHead, &pProcess->ReadyListEntry);
4.1.4 线程创建层:PspCreateProcessEnvironment
- 在
PsCreateProcess中调用PspCreateProcessEnvironment行设断点(报告中 create.c 第 163 行) - F11 进入后,可观察到:
ExAllocatePool分配虚拟内存给进程地址空间MmCreateProcessAddressSpace建立页目录/页表ObCreateObject创建进程对象句柄
注意:
PsCreateProcess返回后,新进程Hello.exe并未立即运行,而是处于ProcessStateReady状态,等待调度器选中。
4.2 线程状态转换:用 loop 命令与 suspend/resume 实时观测
报告中loop命令是观测线程状态的黄金工具。它创建一个无限循环线程,通过suspend/resume命令可直观看到状态变化:
4.2.1 启动 loop 线程并获取其 PID
- 启动 EOS 后,在控制台输入
loop - 观察输出:
Loop thread started with ID: 31(ID 因环境而异) - 此时
loop线程状态为ThreadStateRunning(正在占用 CPU)
4.2.2 挂起(Suspend)操作与状态变更
- 按
Ctrl+F2切换到 Console-2 - 输入
suspend 31(假设 PID 为 31) - 切回 Console-1,观察
loop计数停止增长 - 在 OS Lab 中打开
ps\thread.c,定位PsSuspendThread函数:pThread->State = ThreadStateSuspended; // 状态从 Running → Suspended if (pThread == KeGetCurrentThread()) { KeSwitchThread(); // 若挂起自身,立即触发调度 }
4.2.3 恢复(Resume)操作与调度唤醒
- 在 Console-2 输入
resume 31 - Console-1 中计数恢复增长
PsResumeThread函数将状态设为ThreadStateReady,并调用KeInsertQueueApc唤醒线程
4.2.4 状态转换可视化表格
| 操作 | 当前线程状态 | 调度器行为 | OS Lab 可观察现象 |
|---|---|---|---|
loop命令执行 | Running | 无(线程已运行) | 控制台计数持续增加 |
suspend 31 | Suspended | 将线程从运行队列移除 | 计数停止,CPU 被空闲线程占用 |
resume 31 | Ready | 将线程插入就绪队列,下次调度选中 | 计数恢复,可能有短暂延迟 |
pt命令查看 | — | 无 | 输出中State字段显示SUSPENDED或READY |
提示:
pt(Process Table)命令输出的State字段直接映射EPROCESS结构体的State成员。在ps\process.h中可查到枚举定义:ProcessStateIdle,ProcessStateReady,ProcessStateRunning,ProcessStateWaiting。
5. 信号量与时间片轮转:生产者-消费者同步与调度算法改造
EOS 的同步机制(信号量)与调度算法(优先级抢占 + 时间片轮转)是操作系统核心概念的教学载体。本节聚焦两个关键实验:用信号量解决生产者-消费者问题,以及为PspRoundRobin函数添加时间片轮转逻辑。所有修改均在报告指定的源文件(ps/semaphore.c,ps/sched.c)中进行,修改后可立即在rr命令下验证效果。
5.1 生产者-消费者问题:信号量工作过程的四阶段调试
报告中producer_consumer.c示例使用PsCreateSemaphore创建Empty和Full信号量。调试其工作过程需分四步:
5.1.1 创建信号量:初始化计数器与等待队列
- 在
PsCreateSemaphore调用处设断点 - F11 进入
ps\semaphore.c,观察:pSemaphore->Count = InitialCount; // Empty=10, Full=0 InitializeListHead(&pSemaphore->WaitListHead); // 初始化空等待队列 Count字段是信号量核心,WaitListHead存储阻塞线程
5.1.2 等待信号量(不阻塞):Count > 0 时的原子减
- 在生产者线程的
PsWaitForSemaphore(hEmpty, INFINITE)处设断点 - F11 进入
PsWaitForSemaphore,关键逻辑:if (pSemaphore->Count > 0) { pSemaphore->Count--; // 原子操作,无需锁 return STATUS_SUCCESS; } // Count=10 时,此分支执行,不阻塞
5.1.3 等待信号量(阻塞):Count = 0 时的线程挂起
- 当
Empty被消耗完(Count=0),再次调用PsWaitForSemaphore:// Count=0,进入阻塞分支 InsertTailList(&pSemaphore->WaitListHead, &pThread->WaitListEntry); pThread->State = ThreadStateWaiting; // 线程状态变为 Waiting KeSwitchThread(); // 主动让出 CPU
5.1.4 释放信号量(唤醒):唤醒等待队列首个线程
- 消费者调用
PsReleaseSemaphore(hFull, 1)后:pSemaphore->Count++; // Count 从 0→1 if (!IsListEmpty(&pSemaphore->WaitListHead)) { pThread = CONTAINING_RECORD(pSemaphore->WaitListHead.Flink, ETHREAD, WaitListEntry); RemoveEntryList(&pThread->WaitListEntry); pThread->State = ThreadStateReady; // 唤醒线程 InsertTailList(&PsReadyListHead, &pThread->ReadyListEntry); }
5.2 时间片轮转调度:PspRoundRobin 函数改造详解
报告要求修改ps/sched.c中的PspRoundRobin函数(第 337 行),使其支持时间片轮转。原始函数为空,需添加以下逻辑:
5.2.1 理解时间片轮转的核心思想
- 同优先级线程按固定时间片(如 100ms)轮流执行
- 当前线程用完时间片后,调度器将其移到就绪队列末尾,选择队首线程运行
- EOS 中,每个线程有
Quantum字段(时间片长度)和QuantumUsed字段(已用时间)
5.2.2 修改 PspRoundRobin 函数(关键代码)
// 文件:ps/sched.c,函数 PspRoundRobin(替换原有空函数) VOID PspRoundRobin(VOID) { PKTHREAD pCurrentThread = KeGetCurrentThread(); PLIST_ENTRY pListEntry; PKTHREAD pNextThread; // 1. 检查当前线程是否用完时间片(QuantumUsed >= Quantum) if (pCurrentThread->QuantumUsed < pCurrentThread->Quantum) { return; // 未用完,不切换 } // 2. 重置当前线程的时间片计数 pCurrentThread->QuantumUsed = 0; // 3. 将当前线程移到就绪队列末尾(同优先级队列) RemoveEntryList(&pCurrentThread->ReadyListEntry); InsertTailList(&PsReadyListHead, &pCurrentThread->ReadyListEntry); // 4. 选择下一个同优先级线程(队首) if (!IsListEmpty(&PsReadyListHead)) { pListEntry = PsReadyListHead.Flink; pNextThread = CONTAINING_RECORD(pListEntry, KTHREAD, ReadyListEntry); // 调度pNextThread运行(实际由KeSwitchThread完成) } }5.2.3 集成到调度循环
PspRoundRobin需在每次时钟中断(KiTimerDpcRoutine)中被调用- 在
ke\timer.c的KiTimerDpcRoutine函数末尾添加:// 在时钟中断 DPC 中调用轮转函数 if (PsIsRoundRobinEnabled()) { // 需实现此函数,读取全局开关 PspRoundRobin(); }
5.2.4 验证 rr 命令效果
- 修改后按 F7 生成内核,F5 启动
- 输入
rr命令,观察输出:- 修改前:仅第一个线程(ID=0)持续输出,其他线程无响应
- 修改后:20 个线程 ID 交替出现,如
Thread 0: count=1,Thread 1: count=1,Thread 0: count=2...
- 此现象证明时间片轮转已生效,线程按 FIFO 顺序共享 CPU。
注意:
rr命令本身在ke/sysproc.c中通过ConsoleCmdRoundRobin创建 20 个同优先级线程,并设置Quantum=10(单位:时钟滴答)。若未看到交替输出,请检查PspRoundRobin是否被正确调用,或QuantumUsed是否在每次时钟中断中递增(需在KiTimerDpcRoutine中添加pCurrentThread->QuantumUsed++)。
6. 物理内存与页表管理:pm/vm 命令源码分析与 getcr3.asm 解析
EOS 的内存管理实验(实验 7、8)通过pm(Physical Memory)、vm(Virtual Memory)、mm(Memory Map)等控制台命令,将抽象的内存管理概念转化为可视化的数字。本节深入这些命令的源码,解析其如何读取物理内存位图、遍历进程虚拟地址描述符(VAD),并用getcr3.asm汇编代码提取 CR3 寄存器值,最终在 Bochs 中查看真实的二级页表结构。
6.1 pm 命令:物理内存位图的读取与修改
pm命令显示物理内存使用情况,其核心是读取MmPhysicalMemoryBlock全局变量。该变量由内核初始化时构建,是一个位图(Bitmap),每位代表一个物理页(4KB)是否被占用。
6.1.1 pm 命令源码定位与关键逻辑
- 源文件:
ke/sysproc.c中的ConsoleCmdPhysicalMemory函数 - 关键数据结构:
typedef struct _PHYSICAL_MEMORY_BLOCK { ULONG BasePage; // 起始物理页帧号(PFN) ULONG PageCount; // 页数量 PUCHAR Bitmap; // 位图指针,每位表示一页 } PHYSICAL_MEMORY_BLOCK, *PPHYSICAL_MEMORY_BLOCK; extern PHYSICAL_MEMORY_BLOCK MmPhysicalMemoryBlock; pm命令逻辑:// 1. 遍历位图,统计已用页数 for (ULONG i = 0; i < MmPhysicalMemoryBlock.PageCount; i++) { if (MmPhysicalMemoryBlock.Bitmap[i / 8] & (1 << (i % 8))) { UsedPages++; } } // 2. 输出:Total Pages: X, Used Pages: Y, Free Pages: Z
6.1.2 手动分配/释放物理页的调试
报告要求修改pm.c添加分配代码。核心函数是MmAllocatePages和MmFreePages:
// 分配一页物理内存 PVOID pPage = MmAllocatePages(1); // 返回物理地址(如 0x12345000) // 释放该页 MmFreePages(pPage, 1);- 在
pm命令中调用后,再次执行pm,Used Pages数值应相应增减 - 若数值不变,检查
pPage是否为NULL(内存不足),或MmPhysicalMemoryBlock.Bitmap地址是否有效(用x /1w &MmPhysicalMemoryBlock.Bitmap查看)
6.2 vm 命令:进程虚拟地址空间的遍历
vm命令显示当前进程的虚拟地址描述符(VAD)树,反映进程如何划分虚拟内存(代码段、堆、栈等)。
6.2.1 VAD 数据结构与遍历逻辑
- 源文件:
ke/vm.c中的ConsoleCmdVirtualMemory函数 - 关键结构:
typedef struct _MM_VAD { ULONG StartingVpn; // 起始虚拟页号(VPN) ULONG EndingVpn; // 结束虚拟页号 ULONG ControlArea; // 所属区域(如代码、堆) struct _MM_VAD *Parent; struct _MM_VAD *LeftChild; struct _MM_VAD *RightChild; } MM_VAD, *PMM_VAD; extern PMM_VAD MmSystemVadRoot; // 系统进程 VAD 根节点 vm命令执行中序遍历(Left-Root-Right),输出每个 VAD 的StartingVpn
本文还有配套的精品资源,点击获取