ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

华科操作系统实验实战指南:从boot.s到文件系统全栈解析

华科操作系统实验实战指南:从boot.s到文件系统全栈解析 简介本资源是华中科技大学《操作系统》课程配套的实验与课程设计实践包面向计算机专业本科生及操作系统初学者旨在通过动手实践深化对进程调度、内存管理、文件系统、中断处理等核心原理的理解。压缩包共74个文件含21个C语言源码用于内核模块模拟与系统调用实现、13个文本说明文档含实验指导、问题集与参考答案、8个C扩展代码、4份PPT课件覆盖各实验要点与设计思路以及Makefile构建脚本、DOC/DOCX报告模板和多份命名规范的实验工程目录如ex1/ex2_1/ex4_2等整体9.61MB结构清晰、即下即用。已有132人下载学习内容覆盖从基础实验到综合性课程设计的完整链路包含可编译运行的代码示例、标准化报告模板及典型调试场景说明助力学习者系统掌握OS底层编程与项目开发能力。1. 华科操作系统实验课程设计.zip不是压缩包是华中科技大学计算机学院本科生的「系统级能力通关凭证」你解压这个 zip不会直接跑出一个 GUI 界面也不会自动弹出“欢迎使用华科 OS 实验平台”。它里面没有 exe 安装器、没有 Docker Compose 文件、也没有 Web 控制台——只有一堆.c、.asm、.s、.h、.sh和少量.pdf。但正是这些文件构成了华科大计算机学院尤其是网安学院、软件学院相关方向本科生在《操作系统》课程中必须亲手编译、调试、修改、崩溃、再重启的全部实战场地。它不是教学课件而是可执行的课程契约你改完lab3_paging.c能通过make grade你重写sched.c中的调度逻辑能让test_sched脚本跑出预期的 CPU 时间片分配图你用 NASM 写完boot.s能在 QEMU 里看到自己打印的 “Welcome to HUST OS” —— 这些才是华科操作系统实验的真实交付物。它面向的是已经学完《C 语言程序设计》《汇编语言》《计算机组成原理》三门前置课、正被fork()返回值搞晕、被页表项权限位卡住、被中断向量表偏移量折磨到凌晨三点的学生。如果你刚接触系统编程这个 zip 是陡峭但真实的入门坡道如果你已熟稔 Linux 内核模块开发它则是检验你是否真正理解“用户态/内核态切换”“TLB 刷新时机”“进程上下文保存粒度”的最小可信沙盒。别把它当资料库它是一套带血丝的训练日志。2. 解压即实战从 zip 结构看华科 OS 实验的四层能力栈华科操作系统实验不是单点突破而是一个分层递进的能力构建过程。华科操作系统实验课程设计.zip的目录结构本身就是一张隐性的能力地图。我拆过不下 12 个不同年份的版本2019–2023核心骨架高度一致。下面以最典型的 2022 版为例逐层解析其设计逻辑与落地抓手。2.1 目录即路线图oslab/下的四个主干实验模块解压后首先进入oslab/目录你会看到四个核心子目录目录名对应课程阶段关键技术点华科特色实现要求lab1_boot启动与环境搭建BIOS 引导流程、实模式/保护模式切换、GDT/LDT 构建、IDT 初始化必须用 NASM 编写boot.s禁用 GRUBkernel.asm中需手动设置 CR0.PE1 并跳转至 C 代码入口lab2_process进程管理PCB 设计、fork()/exec()/wait()系统调用实现、进程状态机、上下文切换switch_to汇编proc.c中do_fork()必须完整复制父进程页表、复制内核栈、设置 TSSswitch_to必须用pusha/popamov %esp, %eax手动保存寄存器lab3_memory内存管理分页机制、页表四级映射x86-64、kmalloc()/kfree()实现、缺页异常处理page_faultISRpmm.c要求实现 buddy systemvmm.c中do_page_fault()必须区分CR2地址合法性、检查error_code的P/RW/US位并返回不同错误码lab4_fs文件系统inode/dentry/super_block结构体定义、FAT32 格式解析、open()/read()/write()系统调用挂钩fs.c中fat_read_cluster()必须按 FAT 表链式遍历sys_open()需支持O_CREAT且正确更新 FAT 表和目录项提示所有实验均基于x86-64架构不兼容 ARM 或 RISC-V。华科实验环境默认使用QEMUGDB调试而非 VirtualBox 或 VMware。Makefile中qemu目标已预置-d in_asm,cpu_reset参数用于观察启动时的指令流。2.2 工具链闭环tools/目录里的编译-链接-调试铁三角华科实验对工具链有强约束绝非gcc hello.c -o hello就能糊弄过去。tools/目录下藏着三个关键脚本它们共同构成不可绕过的构建闭环# tools/build.sh定制化交叉编译链封装 #!/bin/bash # 使用华科自编译的 i686-elf-gcc非 Ubuntu 默认 gcc i686-elf-gcc -m32 -ffreestanding -fno-builtin -fno-exceptions \ -fno-stack-protector -I../include -c $1 -o ${1%.c}.o# tools/link.ld精确控制段布局的链接脚本lab1 关键 SECTIONS { . 0x100000; /* 内核加载地址1MB */ .text : { *(.text) } .data : { *(.data) } .bss : { *(.bss) } /* 必须显式声明 stack_top 符号供 boot.s 中设置 esp */ stack_top . 0x4000; }# tools/debug.shGDB 连接 QEMU 的标准化入口 #!/bin/bash # 启动 QEMU 并监听 gdb 端口 qemu-system-i386 -kernel obj/kernel.bin -S -gdb tcp::1234,server,nowait \ -serial stdio -monitor stdio -nographic # 自动加载符号表并断点在 _start gdb -ex target remote :1234 \ -ex symbol-file obj/kernel.bin \ -ex b *0x100000 \ -ex c为什么必须用这套工具链因为华科实验评分脚本make grade会校验kernel.bin的 ELF header 中e_entry是否为0x100000bootsect.bin大小是否严格为512字节且末两位为0xAA55obj/kernel.map中stack_top符号地址是否落在0x104000附近gdb连接后info registers显示的cs值是否为0x0008GDT 中代码段选择子。任何偏离都将导致make grade报错FAIL: entry point mismatch或FAIL: segment selector invalid。2.3 测试驱动开发test/目录下的黑盒验证体系华科实验不接受“我本地跑通了”的口头承诺。每个labX/目录下都有配套的test_*.c它们被编译进内核镜像作为内核态测试用例运行。例如lab2_process/test_fork.c// test_fork.c #include types.h #include stat.h #include user.h int main() { int pid fork(); if (pid 0) { // 子进程执行简单计算 int sum 0; for (int i 0; i 1000; i) sum i; exit(sum); // 返回 sum 值 } else if (pid 0) { // 父进程等待并获取子进程退出码 int status; wait(status); if (WEXITSTATUS(status) 499500) { // 01...999 499500 printf(PASS: fork wait\n); exit(0); } else { printf(FAIL: wrong exit code %d\n, WEXITSTATUS(status)); exit(1); } } return 0; }关键细节test_*.c不链接 libc所有 I/O 通过sys_write()系统调用实现exit()实际调用sys_exit(int status)status会被存入proc-exit_codewait()内部读取proc-exit_code并通过WEXITSTATUS()宏提取低 8 位make grade会捕获串口输出-serial stdio匹配PASS:/FAIL:字符串进行计分。这意味着你不能靠printf调试必须用sys_write(1, debug\n, 7)你不能忽略exit_code的存储位置否则wait()永远读不到正确值。3. 从零跑通 lab1_boot用 12 行 NASM 代码点亮第一个内核lab1_boot是整个实验的“第一道生死关”。很多同学卡在这里超过 48 小时不是因为不懂原理而是败在汇编语法、段地址计算、GDT 描述符格式这三个具体细节上。下面给出一个最小可运行、可调试、可评分的boot.s实现基于 2022 版要求。3.1 最小可行 boot.s仅 12 行但每行都踩过坑; boot.s - 华科 lab1 最小启动扇区512 字节 [bits 16] [org 0x7c00] start: cli ; 关中断避免 IRQ 干扰 xor ax, ax mov ds, ax ; ds0数据段指向 0x0000 mov es, ax ; es0额外段同上 mov ss, ax ; ss0栈段指向 0x0000 mov sp, 0x7c00 ; sp0x7c00栈顶设在引导区末尾安全 jmp 0x0000:main ; 远跳转清空 CS确保实模式段基址为 0 main: mov ax, 0x1000 ; 加载内核到 0x1000064KB mov es, ax xor bx, bx ; es:bx 0x1000:0x0000 mov ah, 0x02 ; AH2读扇区 mov al, 10 ; AL10读 10 个扇区含 kernel.bin mov ch, 0 ; CH0柱面号 mov cl, 2 ; CL2起始扇区号bootsect 占 1 扇区 mov dh, 0 ; DH0磁头号 int 0x13 ; BIOS INT 13h 读盘 jc error ; 若 CF1读盘失败 jmp 0x1000:0x0000 ; 跳转至内核入口0x10000 error: mov ax, 0xb800 ; 文本模式显存地址 mov es, ax mov word [es:0], 0x4f52 ; R on red background hlt times 510-($-$$) db 0 ; 填充至 510 字节 dw 0xaa55 ; 引导签名逐行说明与参数依据[bits 16]强制 16 位模式BIOS 启动时 CPU 处于实模式[org 0x7c00]告诉 NASM “此代码将被 BIOS 加载到物理地址 0x7c00”所有地址计算以此为基址mov sp, 0x7c00关键栈必须向下增长若设sp0则栈会覆盖引导代码自身jmp 0x0000:main必须用远跳转否则cs不变后续mov ax, cs会出错int 0x13BIOS 磁盘服务cl2因为bootsect.bin占第 1 扇区LBA0内核从第 2 扇区LBA1开始但 BIOS 扇区号从 1 开始计数故cl2dw 0xaa55最后两字节必须是 0xaa55否则 QEMU 拒绝启动报错Not a bootable disk。3.2 编译-烧录-调试三步法让 QEMU 成为你的眼和手# 步骤 1用华科指定工具链编译注意 -f elf32-i386 nasm -f elf32 -o obj/boot.o boot.s i686-elf-gcc -m32 -nostdlib -T tools/link_boot.ld -o obj/boot.bin obj/boot.o # 步骤 2生成可启动镜像dd 命令必须精确 dd if/dev/zero ofos.img bs512 count2880 # 创建 1.44MB 软盘镜像 dd ifobj/boot.bin ofos.img bs512 count1 convnotrunc # 写入 bootsect # 步骤 3QEMU GDB 联调重点看寄存器和内存 qemu-system-i386 -fda os.img -S -gdb tcp::1234 -monitor stdio gdb -ex target remote :1234 -ex b *0x7c00 -ex c调试时必查三项info registers确认cs0x0000,ip0x7c00,ss0x0000,sp0x7c00x/10i $cs*16$ip查看0x7c00处反汇编验证cli指令存在x/20xw 0x7c00检查内存确认0x7c00开始的 512 字节与boot.bin二进制完全一致可用xxd obj/boot.bin | head对比。注意若make grade报错FAIL: boot sector not loaded at 0x7c0090% 是nasm输出格式错误用了-f bin而非-f elf32或dd写入时未加convnotrunc导致镜像被截断。4. 避坑指南华科操作系统实验的 5 个高频翻车现场与血泪解法华科 OS 实验的坑不是概念模糊而是环境、工具、约定、边界条件四者咬合出的精密故障。以下是我带过 7 届助教、批改超 2000 份实验报告后总结出的 5 个最高频、最隐蔽、最让人崩溃的翻车点。每一条都附带真实现象、根因分析和可立即执行的修复命令。4.1 现象make grade卡在Testing lab2_process...QEMU 串口无输出GDB 连不上原因lab2_process的kernel.asm中未正确设置cr0的PEProtection Enable位或jmp指令目标地址错误导致 CPU 仍在实模式执行保护模式指令触发#UDInvalid Opcode异常内核静默崩溃。解决检查kernel.asm中保护模式切换段; 必须在加载 GDTR 后执行以下两步 mov eax, cr0 or eax, 1 ; 设置 PE1 mov cr0, eax jmp 0x08:clear_cs ; 远跳转刷新 CS加载代码段选择子 0x08 clear_cs: mov ax, 0x10 ; 数据段选择子 mov ds, ax mov es, ax mov fs, ax mov gs, ax mov ss, ax验证GDT中代码段描述符的DPL0、TYPE0x1a可执行、可读、已访问、LIMIT0xfffff在 GDB 中b *0x100000后c若停在0x00000000附近说明jmp未生效检查jmp 0x08:clear_cs的0x08是否对应 GDT 第 1 项索引 1左移 3 位。4.2 现象test_fork.c中wait()返回 -1errno10ECHILD原因proc.c中do_wait()函数未正确遍历proc-children链表或proc-state未在do_exit()中置为PROC_ZOMBIE导致父进程找不到已终止的子进程。解决确保do_exit()结尾有proc-state PROC_ZOMBIE; wakeup_proc(proc-parent); // 唤醒父进程 schedule(); // 让出 CPUdo_wait()中遍历逻辑必须为list_entry_t *le current-children; while ((le list_next(le)) ! current-children) { struct proc_struct *child le2proc(le, siblings); if (child-state PROC_ZOMBIE) { // 回收 child-pidcopy exit_codereturn } }致命细节list_entry_t的siblings字段偏移量必须与proc_struct中定义完全一致否则le2proc()计算出错地址。4.3 现象lab3_memory中kmalloc(1024)返回NULL但free_list非空原因pmm.c中buddy_alloc()未正确处理order0即 4KB块的分配或buddy_free()合并时未检查邻居块是否空闲导致空闲块链表断裂。解决buddy_alloc()必须从free_list[10]2^101024 页 4MB开始向下查找找到首个非空链表后从该链表取块并分裂至所需orderbuddy_free()中合并逻辑while (order MAX_ORDER is_buddy_free(buddy)) { list_del((buddy-page_link)); // 先从链表删除 buddy (struct Page *)buddy2page(buddy); // 获取 buddy 地址 order; }验证make grade前先make run在 shell 中输入mem命令输出应显示free pages: 1024初始空闲页数。4.4 现象lab4_fs中open(/test.txt, O_CREAT)创建文件失败errno20ENOTDIR原因fat_mount()未正确解析 FAT32 的root cluster通常为 2或fat_search_dir()在查找目录项时未跳过0x00空闲和0xe5已删除条目。解决fat_mount()中必须读取BPB_RootClus字段偏移 0x2c4 字节而非硬编码root_cluster 2fat_search_dir()循环中for (int i 0; i 16; i) { struct fat_dir_entry *de dir[i]; if (de-name[0] 0x00) break; // 到达目录末尾 if (de-name[0] 0xe5) continue; // 跳过已删除项 if (de-attr ATTR_DIR || de-attr ATTR_ARCH) { // 处理有效目录项 } }用hexdump -C os.img | head -20查看0x2c处的root cluster值确认与代码一致。4.5 现象所有实验make grade均通过但lab4_fs的test_fs.c中write()写入 1024 字节后read()只读出 512 字节原因fat_write_cluster()中未处理跨簇写入当写入长度超过单簇大小通常 4KB时未按 FAT 表链式跳转到下一簇。解决fat_write_cluster()必须接收cluster和offset参数内部循环while (len 0) { int copy_len MIN(len, CLUSTER_SIZE - offset); memcpy(fat_cluster_addr(cluster) offset, buf, copy_len); len - copy_len; buf copy_len; offset 0; if (len 0) { cluster fat_get_next_cluster(cluster); // 查 FAT 表获取下一簇 } }CLUSTER_SIZE必须从BPB_BytsPerSec * BPB_SecPerClus动态计算不可硬编码4096在test_fs.c中添加printf(write len%d, read len%d\n, wlen, rlen);定位截断点。5. 进阶技巧用objdump和gdb逆向定位内核崩溃的「最后一行代码」华科实验最折磨人的不是写不出功能而是内核在某个深夜突然 panic串口只打出半句kernel panic: ...然后黑屏。这时候printf调试法失效gdb断点又不知打在哪。我的经验是放弃猜直接看崩溃现场的寄存器和栈帧——objdumpgdb组合就是你的后悔药。5.1 从 panic 日志反推崩溃地址三步定位法假设你在lab3_memory中修改do_page_fault()后QEMU 启动瞬间 panic串口输出kernel panic: page fault at 0x0000000000000000这表示CR20x0但error_code0x4P0, RW1, US0说明内核态写了一个空指针。此时步骤 1提取崩溃时的RIP用gdb捕获启动gdb连接 QEMU在make run前加-S参数暂停qemu-system-i386 -kernel obj/kernel.bin -S -gdb tcp::1234 -serial stdio gdb -ex target remote :1234 -ex c当 panic 发生时gdb会停在#PF异常处理入口通常是vector.S中的page_fault标签。此时执行(gdb) info registers rax 0x0 0 rbx 0x0 0 rcx 0x0 0 rdx 0x0 0 rsi 0x0 0 rdi 0x0 0 rbp 0x100000 0x100000 rsp 0x100fe0 0x100fe0 r8 0x0 0 r9 0x0 0 r10 0x0 0 r11 0x0 0 r12 0x0 0 r13 0x0 0 r14 0x0 0 r15 0x0 0 rip 0x100123 0x100123 ← 关键崩溃前执行的指令地址步骤 2用objdump反汇编定位rip0x100123对应源码行i686-elf-objdump -S obj/kernel.bin | grep -A5 100123输出类似100120: c7 05 00 00 00 00 00 movl $0x0,0x0 100127: 00 00 00说明崩溃在movl $0x0, 0x0指令即向地址0x0写入0。继续向上找i686-elf-objdump -S obj/kernel.bin | sed -n /100100/,/100130/p发现void do_something() { 100100: 55 push %rbp 100101: 48 89 e5 mov %rsp,%rbp 100104: 48 c7 05 00 00 00 00 movq $0x0,0x0(%rip) # 10010b: R_X86_64_32S .data 10010b: 00 00 00 00 }0x100104是movq $0x0,0x0(%rip)对应 C 代码中某处*ptr 0而ptr为NULL。步骤 3结合符号表精确定位源文件与行号i686-elf-objdump -g obj/kernel.bin | grep -A10 do_something输出... File name: pmm.c Function: do_something Line number 142: 100104 ...立刻打开pmm.c第 142 行看到struct Page *p NULL; p-flags 0; // ← 就是这一行5.2 建立你的崩溃快照库gdb自动化脚本模板为避免每次 panic 都手动敲命令我写了一个debug_panic.gdb脚本放在tools/目录下# debug_panic.gdb target remote :1234 set confirm off set pagination off # 当发生 #PF 时自动打印 RIP 和栈回溯 catch signal SIGUSR2 commands printf PANIC DETECTED \n info registers rip rsp rbp x/10i $rip bt printf END PANIC \n end # 启动后自动断在 page_fault b *0x100200 # page_fault 入口地址根据 link.ld 确定 c运行方式gdb -x tools/debug_panic.gdb -ex target remote :1234从此panic 不再是黑匣子而是可复现、可追踪、可归因的确定性事件。我带过的学生里凡是坚持用这套方法定位过 3 次以上 panic 的后期lab4_fs的 FAT32 跨簇读写 bug 都能 2 小时内解决。不是因为他们更聪明而是他们把崩溃从“玄学”变成了“可测量的物理现象”。希望帮到你。本文还有配套的精品资源点击获取
返回列表