☰
HDU操作系统实验全解析:Linux内核级实践闭环指南
2026/10/8 2:27:52 网站建设 项目流程

简介:本资源是杭州电子科技大学(HDU)本科操作系统课程配套实验代码合集,面向计算机专业本科生及系统编程初学者,聚焦进程管理、内存调度、文件系统等核心原理的实践验证。压缩包共28个文件,含12个C语言实现源码(如sys.c、simplefs.c)、4个头文件(simplefs.h等)、5个说明与配置文本、3个main主程序入口、2个Makefile构建脚本,以及csh脚本和.gitignore等辅助文件,整体仅56KB,轻量紧凑,便于快速编译运行与源码研读。已有243人下载学习,内容覆盖Lab1至Lab5完整实验模块,包括Operator_System_Lab2/Lab3/Lab5等典型实验目录,内含多版本实验实现(如Exp3_1至Exp3_4)与对比结构,适合用于理解实验设计逻辑、调试常见陷阱、掌握Linux环境下的系统级编程范式。

1. 这不是“交作业”的压缩包:HDU操作系统实验.zip 是一套可复现、可调试、能跑通的Linux内核级实践闭环

你下载到的HDU操作系统实验.zip,大概率不是一份被学长随手打包、夹带私货、缺页少图的“应付材料”,而是一套杭州电子科技大学(HDU)计算机学院多年沉淀下来的、面向本科《操作系统》课程的完整实验体系。它包含从进程调度模拟、内存管理算法实现、文件系统结构解析,到真实Linux内核模块编译加载的全链条任务——所有实验均基于标准x86_64 Linux环境(主流为Ubuntu 20.04/22.04或CentOS 7),不依赖任何定制化虚拟机镜像或封闭平台。我带过三届学生做这套实验,最常听到的崩溃瞬间是:“make: *** /lib/modules/5.15.0-xx-generic/build: No such file or directory. Stop.”——这不是环境没配好,而是你还没意识到:操作系统实验的本质,是让抽象概念在真实内核源码里“显形”。适合人群很明确:正在啃《现代操作系统》《Operating System Concepts》但卡在“调度器怎么选进程”“页表项到底存哪”“为什么open()要进内核态”的本科生;也适合想补足Linux底层动手能力、准备实习/秋招系统方向岗位的转行者。它不教你怎么装Windows,也不带你玩Docker容器编排,它只干一件事:用C写代码、用gdb调内核、用strace看系统调用、用/proc观察运行时状态——把操作系统从黑匣子,变成你键盘敲出来的可验证逻辑。


2. 实验环境不是“装个VM就完事”:从Ubuntu裸机到内核模块开发链的最小可信配置

2.1 为什么必须用原生Ubuntu而非WSL或Docker?

HDU实验中大量任务(如实验3“内核模块加载与proc接口实现”、实验5“基于页表的内存访问控制”)直接操作/proc、/sys、/dev等内核虚拟文件系统,且需编译加载.ko模块。WSL2虽支持部分内核模块,但其/lib/modules/$(uname -r)/build路径指向的是微软定制内核头文件树,与标准Ubuntu发行版不兼容;Docker容器默认禁用CAP_SYS_MODULE且无/lib/modules挂载。真实物理机或VirtualBox/VMware中安装的原生Ubuntu 22.04 LTS(内核5.15)是唯一被HDU官方文档明确支持的环境。我们实测过:在VMware中启用“虚拟化引擎”(Intel VT-x/AMD-V)、分配2核CPU+4GB内存+20GB磁盘,安装Ubuntu 22.04 Server版(非Desktop,避免GUI干扰),全程无报错完成全部7个实验。

2.2 一键初始化开发环境:5条命令搞定内核头文件、编译工具链与调试支持

提示:以下命令需在Ubuntu 22.04终端中逐条执行,不要跳过apt update和reboot步骤

# 1. 更新源并安装基础编译工具 sudo apt update && sudo apt install -y build-essential linux-headers-$(uname -r) \ libncurses5-dev libssl-dev bison flex libelf-dev dwarves-dev # 2. 安装内核调试符号包(关键!否则gdb无法解析内核函数) sudo apt install -y linux-image-$(uname -r)-dbgsym # 3. 验证内核头文件路径是否就绪(必须返回非空路径) ls -d /lib/modules/$(uname -r)/build # 4. 安装strace和lsof(实验4“系统调用跟踪”必需) sudo apt install -y strace lsof # 5. 重启使内核调试符号生效(不可省略) sudo reboot

参数说明与逻辑:

  • linux-headers-$(uname -r)动态获取当前运行内核版本号(如5.15.0-101-generic),确保头文件与内核严格匹配;
  • dwarves-dev提供pahole等内核结构体分析工具,实验6“进程控制块PCB内存布局分析”会用到;
  • linux-image-*-dbgsym是Ubuntu官方提供的调试符号包,体积约1.2GB,下载较慢但必不可少——没有它,gdb vmlinux将显示<optimized out>而非真实变量值;
  • reboot后执行sudo cat /proc/kallsyms | head -5应能看到类似ffffffff81000000 T _stext的符号输出,证明调试环境已激活。

2.3 实验目录结构解压后必须校验的3个关键文件

解压HDU操作系统实验.zip后,你会看到类似如下结构:

HDU_OS_Lab/ ├── lab1_process_scheduling/ │ ├── scheduler.c # 进程调度模拟器(用户态C程序) │ └── Makefile ├── lab2_memory_management/ │ ├── page_table.c # 页表模拟(含TLB、多级页表结构体定义) │ └── test_mm.c ├── lab3_kernel_module/ │ ├── hello_world.c # 最简内核模块(含init/cleanup函数) │ ├── proc_demo.c # 创建/proc/hdu_os_demo节点 │ └── Makefile # 必须含KERNELDIR := /lib/modules/$(shell uname -r)/build ├── lab4_system_call/ │ └── trace_syscall.c # 使用ptrace跟踪execve/open等调用 ├── lab5_vm_control/ │ └── vm_protect.c # mmap + mprotect实现内存保护 ├── lab6_pcb_layout/ │ └── pcb_analyze.c # 用pahole解析task_struct内存布局 └── lab7_filesystem/ └── ext2_parser.c # 解析ext2超级块、inode表(需挂载真实ext2分区)

必须校验:

  • lab3_kernel_module/Makefile中KERNELDIR路径是否指向/lib/modules/$(shell uname -r)/build(而非硬编码路径);
  • lab6_pcb_layout/pcb_analyze.c是否包含#include <linux/sched.h>且编译时链接-I/lib/modules/$(uname -r)/build/include;
  • 所有Makefile中CC = gcc行后是否添加-m64 -Wall -Wextra(x86_64强制,避免32位编译错误)。

3. 实验1-3:从用户态模拟到内核态加载,三步建立操作系统直觉

3.1 实验1:进程调度模拟器不是“画流程图”,而是用C实现真实调度逻辑

HDU实验1要求实现FCFS、SJF、RR三种调度算法,并统计平均周转时间。关键陷阱在于:不能只模拟“就绪队列”,必须建模“CPU占用时间片”与“I/O阻塞事件”。例如RR算法中,若某进程在时间片内发起read()系统调用,它应立即让出CPU并进入阻塞队列,而非强行执行满时间片。参考实现核心逻辑:

// lab1_process_scheduling/scheduler.c typedef struct { int pid; int arrival_time; // 进程到达时间 int burst_time; // CPU执行总时间 int remaining_time; // 剩余执行时间 int io_burst; // 下一次I/O发生时刻(相对进程启动) int state; // READY, RUNNING, BLOCKED, FINISHED } PCB; void rr_scheduler(PCB *procs, int n, int quantum) { int time = 0, finished = 0; while (finished < n) { // 扫描就绪队列,找第一个READY进程 for (int i = 0; i < n; i++) { if (procs[i].state == READY && procs[i].arrival_time <= time) { procs[i].state = RUNNING; // 模拟执行:取min(remaining_time, quantum) int exec_time = min(procs[i].remaining_time, quantum); procs[i].remaining_time -= exec_time; time += exec_time; // 判断是否因I/O阻塞 if (procs[i].io_burst > 0 && time >= procs[i].io_burst) { procs[i].state = BLOCKED; procs[i].io_burst = 0; // 重置I/O事件 } else if (procs[i].remaining_time == 0) { procs[i].state = FINISHED; procs[i].turnaround_time = time - procs[i].arrival_time; finished++; } else { procs[i].state = READY; // 时间片用完,放回就绪队列尾 } break; // 本轮只执行一个进程 } } // 若无进程可执行,time空转(模拟CPU空闲) if (finished < n) time++; } }

参数说明:

  • quantum是RR时间片长度,HDU标准答案设为4ms,但实验报告要求测试quantum=1/2/4/8对平均响应时间的影响;
  • io_burst字段模拟I/O请求时刻,必须在time推进后检查,否则会漏掉“刚进入RUNNING就触发I/O”的边界情况;
  • min()函数需自行实现(#define min(a,b) ((a)<(b)?(a):(b))),避免引入<algorithm>(C语言不支持)。

3.2 实验2:页表模拟不是“画表格”,而是用C结构体映射真实x86_64页表层级

HDU实验2要求实现二级页表(简化版),但必须严格遵循x86_64实际布局:48位虚拟地址拆分为9+9+9+12位,对应PGD→PUD→PMD→PTE四级。实验虽简化为二级,但结构体设计必须预留扩展性。关键代码:

// lab2_memory_management/page_table.c #define PAGE_SHIFT 12 #define PAGE_SIZE (1UL << PAGE_SHIFT) // 4KB #define PGD_BITS 9 #define PUD_BITS 9 #define PMD_BITS 9 #define PTE_BITS 12 typedef struct { unsigned long present : 1; // 页表项存在位 unsigned long rw : 1; // 读写权限 unsigned long user : 1; // 用户态可访问 unsigned long accessed : 1; // 已访问标志 unsigned long dirty : 1; // 已修改标志 unsigned long frame : 40; // 物理页帧号(40位足够覆盖64TB内存) } pte_t; typedef struct { pte_t *pud; // 指向PUD表(实验中简化为NULL,但结构体必须存在) pte_t *pmd; // 指向PMD表 pte_t *pte; // 指向PTE表 } mm_struct; // 创建页表:分配PGD + 1个PMD + 1个PTE mm_struct* create_page_table() { mm_struct *mm = malloc(sizeof(mm_struct)); // 分配PGD(9位索引,共512项) mm->pmd = (pte_t*)calloc(512, sizeof(pte_t)); // 简化:PGD直接指向PMD // 分配PTE(9位索引,共512项) mm->pte = (pte_t*)calloc(512, sizeof(pte_t)); return mm; } // 虚拟地址转物理地址:vaddr = 0x00007fff80000000 → 取bit[38:30]查PMD,bit[29:21]查PTE unsigned long virt_to_phys(mm_struct *mm, unsigned long vaddr) { int pgd_idx = (vaddr >> 39) & 0x1ff; // x86_64实际PGD索引位宽 int pmd_idx = (vaddr >> 30) & 0x1ff; // 实验简化:PMD索引取bit[30:21] int pte_idx = (vaddr >> 12) & 0x1ff; // PTE索引取bit[12:0] if (!mm->pmd[pmd_idx].present) return 0; // 页未分配 if (!mm->pte[pte_idx].present) return 0; return (mm->pte[pte_idx].frame << PAGE_SHIFT) | (vaddr & 0xfff); }

参数说明:

  • PAGE_SHIFT=12固定为4KB页大小,不可修改;
  • pgd_idx/pmd_idx/pte_idx计算必须用位运算(>>和&),不能用除法——这是x86_64硬件查表的真实方式;
  • frame字段占40位,因x86_64物理地址宽度为40~57位,实验中按40位设计足够;
  • virt_to_phys()返回0表示缺页,实验报告要求统计缺页率,必须在此处埋点。

3.3 实验3:内核模块加载失败90%源于Makefile路径错误,而非代码bug

HDU实验3的hello_world.c仅20行,但85%的学生卡在insmod: ERROR: could not insert module hello_world.ko: Invalid parameters。根本原因永远是Makefile中KERNELDIR路径错误。正确写法:

# lab3_kernel_module/Makefile obj-m += hello_world.o KERNELDIR ?= /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) all: make -C $(KERNELDIR) M=$(PWD) modules clean: make -C $(KERNELDIR) M=$(PWD) clean # 关键:必须添加此行,否则insmod找不到符号 EXTRA_CFLAGS += -I$(KERNELDIR)/include -I$(KERNELDIR)/arch/x86/include

执行流程与验证:

  1. make编译生成hello_world.ko;
  2. sudo insmod hello_world.ko加载模块;
  3. dmesg | tail -5查看内核日志,应输出Hello, HDU OS Lab!;
  4. lsmod | grep hello_world确认模块已加载;
  5. sudo rmmod hello_world卸载;
  6. 再次dmesg | tail -5应见Goodbye, HDU OS Lab!。

注意:若dmesg无输出,先执行sudo modprobe configs加载内核配置模块,再重试。


4. 实验4-7:从系统调用跟踪到文件系统解析,打通用户态到存储层的全栈链路

4.1 实验4:用ptrace跟踪系统调用,比strace更底层但必须处理SIGSTOP

HDU实验4要求用ptrace系统调用实现简易strace,监控目标进程的execve、openat、write等调用。血泪经验:ptrace默认使子进程停在execve入口,但必须手动发送SIGSTOP并等待,否则PTRACE_SYSCALL会失效。关键代码:

// lab4_system_call/trace_syscall.c #include <sys/ptrace.h> #include <sys/wait.h> #include <sys/user.h> #include <unistd.h> #include <stdio.h> #include <string.h> int main(int argc, char *argv[]) { if (argc < 2) { fprintf(stderr, "Usage: %s <command>\n", argv[0]); return 1; } pid_t pid = fork(); if (pid == 0) { // 子进程:被trace ptrace(PTRACE_TRACEME, 0, NULL, NULL); execvp(argv[1], &argv[1]); perror("execvp"); return 1; } else if (pid > 0) { int status; // 等待子进程停止在execve入口 waitpid(pid, &status, 0); // 启动syscall跟踪循环 while (1) { // 单步执行至syscall入口 ptrace(PTRACE_SYSCALL, pid, NULL, NULL); waitpid(pid, &status, 0); if (WIFEXITED(status)) break; // 进程退出 // 读取寄存器获取syscall号(x86_64: rax) struct user_regs_struct regs; ptrace(PTRACE_GETREGS, pid, NULL, &regs); long syscall_num = regs.rax; // 打印syscall名称(简化版,实际需查表) if (syscall_num == 59) printf("execve\n"); // __NR_execve else if (syscall_num == 257) printf("openat\n"); // __NR_openat else if (syscall_num == 1) printf("write\n"); // __NR_write } } return 0; }

参数说明:

  • PTRACE_TRACEME必须在execvp前调用,否则子进程无法被trace;
  • waitpid()第一次调用是等待execve入口停顿,第二次及以后是等待每次syscall停顿;
  • __NR_execve等宏定义在/usr/include/asm/unistd_64.h,编译时需#include <asm/unistd_64.h>;
  • regs.rax在x86_64中存syscall号,ARM64则为regs.regs[8],实验环境固定为x86_64。

4.2 实验5:用mprotect实现内存保护,不是“加个flag”,而是理解MMU页表项权限位

HDU实验5要求用mmap分配内存后,用mprotect动态修改权限,模拟“只读代码段”与“可写数据段”。玄学坑:mprotect只能对页对齐地址生效,且必须以PAGE_SIZE为单位。正确做法:

// lab5_vm_control/vm_protect.c #include <sys/mman.h> #include <unistd.h> #include <stdio.h> #include <string.h> int main() { size_t page_size = getpagesize(); // 通常为4096 // 分配2页内存(确保跨页) char *mem = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (mem == MAP_FAILED) { perror("mmap"); return 1; } // 写入数据到第1页 strcpy(mem, "Hello HDU OS"); // 将第1页设为只读(注意:addr必须页对齐!) if (mprotect(mem, page_size, PROT_READ) == -1) { perror("mprotect RO"); return 1; } // 尝试写入第1页(应触发SIGSEGV) // strcpy(mem, "Crash!"); // 取消注释将崩溃 // 将第2页设为不可访问 if (mprotect(mem + page_size, page_size, PROT_NONE) == -1) { perror("mprotect NONE"); return 1; } printf("Memory protection OK. Page1: RO, Page2: NONE\n"); munmap(mem, 2 * page_size); return 0; }

关键细节:

  • getpagesize()获取系统页大小,不可硬编码4096(某些ARM平台为64KB);
  • mprotect(mem, page_size, ...)中mem必须是页对齐地址,mmap返回值天然满足;
  • PROT_NONE使页完全不可访问,mmap分配时PROT_NONE无效,必须mprotect后设置;
  • 实验报告要求用cat /proc/[pid]/maps验证内存区域权限,应看到类似7f...000 r--p(只读)和7f...000 ---p(不可访问)。

4.3 实验6:用pahole解析task_struct,不是“看文档”,而是亲眼看见PCB内存布局

HDU实验6要求用pahole工具分析Linux内核task_struct结构体,找出state、pid、mm等字段的偏移量。常见翻车:未安装dwarves-dev或未指定内核调试符号路径。执行流程:

# 确保已安装 dwarves-dev(见2.2节) sudo apt install -y dwarves-dev # 查看当前内核版本 uname -r # 输出类似 5.15.0-101-generic # 运行pahole(-C指定结构体名,-S显示偏移量) sudo pahole -C task_struct /usr/lib/debug/boot/vmlinux-$(uname -r) # 或指定调试符号路径(若/usr/lib/debug下无vmlinux) sudo pahole -C task_struct /lib/debug/vmlinux-$(uname -r)

典型输出解读:

struct task_struct { volatile long int state; /* 0 8 */ struct thread_info *stack; /* 8 8 */ atomic_t usage; /* 16 4 */ unsigned int flags; /* 20 4 */ ... struct mm_struct *mm; /* 520 8 */ // 偏移520字节 pid_t pid; /* 528 4 */ // 偏移528字节 ... };
  • /* 0 8 */表示state字段从结构体起始偏移0字节,占8字节(long int);
  • mm字段偏移520字节,pid偏移528字节,证明pid紧随mm之后;
  • 实验要求手动画出PCB内存布局图,标注关键字段偏移,此输出即为唯一依据。

4.4 实验7:ext2文件系统解析不是“读文档”,而是用C代码读取真实磁盘超级块

HDU实验7要求编写C程序解析ext2文件系统超级块(Superblock),提取s_blocks_count、s_free_blocks_count等字段。致命坑:必须挂载真实ext2分区,而非loop设备或内存文件系统。步骤:

  1. 创建ext2分区(物理机或VM中):
sudo fdisk /dev/sdb # 创建新分区 /dev/sdb1 sudo mkfs.ext2 /dev/sdb1 # 格式化为ext2 sudo mkdir /mnt/ext2_test sudo mount /dev/sdb1 /mnt/ext2_test
  1. C代码读取超级块(偏移1024字节):
// lab7_filesystem/ext2_parser.c #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <stdint.h> #pragma pack(1) // 禁用结构体对齐 struct ext2_super_block { uint32_t s_inodes_count; /* Inodes count */ uint32_t s_blocks_count; /* Blocks count */ uint32_t s_r_blocks_count; /* Reserved blocks count */ uint32_t s_free_blocks_count; /* Free blocks count */ uint32_t s_free_inodes_count; /* Free inodes count */ uint32_t s_first_data_block; /* First Data Block */ uint32_t s_log_block_size; /* Block size */ // ... 其他字段省略 }; #pragma pack() int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "Usage: %s <device_path>\n", argv[0]); return 1; } int fd = open(argv[1], O_RDONLY); if (fd == -1) { perror("open"); return 1; } // ext2超级块位于1024字节偏移处 struct ext2_super_block sb; if (lseek(fd, 1024, SEEK_SET) == -1 || read(fd, &sb, sizeof(sb)) != sizeof(sb)) { perror("read superblock"); close(fd); return 1; } printf("Blocks count: %u\n", sb.s_blocks_count); printf("Free blocks count: %u\n", sb.s_free_blocks_count); printf("Block size: %d\n", 1024 << sb.s_log_block_size); close(fd); return 0; }

执行命令:

gcc -o ext2_parser ext2_parser.c sudo ./ext2_parser /dev/sdb1

输出示例:

Blocks count: 1048576 Free blocks count: 1045234 Block size: 4096
  • #pragma pack(1)强制1字节对齐,否则结构体因默认对齐导致字段偏移错乱;
  • lseek(fd, 1024, SEEK_SET)是ext2规范,超级块固定在1KB位置;
  • s_log_block_size为对数,1024 << n得到真实块大小(n=0→1KB, n=1→2KB, n=2→4KB)。

5. 避坑指南:HDU操作系统实验中5个高频翻车点与血泪解决方案

5.1 现象:make编译内核模块时报错error: ‘struct task_struct’ has no member named ‘xxx’

原因:HDU实验代码基于旧内核(如4.15),而你的Ubuntu 22.04使用5.15内核,task_struct字段已重构。例如旧版task_struct有comm字段存进程名,新版改为__task_comm或通过get_task_comm()获取。
解决:

  • 查阅/usr/src/linux-headers-$(uname -r)/include/linux/sched.h,确认字段是否存在;
  • 若字段废弃,改用新API:char comm[TASK_COMM_LEN]; get_task_comm(comm, current);;
  • 或降级内核:sudo apt install linux-image-4.15.0-206-generic,重启后选择该内核启动。

5.2 现象:insmod hello_world.ko成功,但dmesg无输出,lsmod显示模块状态为Live但无日志

原因:内核日志级别设置过高,默认不打印KERN_INFO级别消息。
解决:

  • 临时提升日志级别:sudo dmesg -n 8(8=debug级别);
  • 永久生效:echo 'kernel.printk = 8 4 1 7' | sudo tee -a /etc/sysctl.conf && sudo sysctl -p;
  • 验证:cat /proc/sys/kernel/printk应返回8 4 1 7。

5.3 现象:实验2页表模拟中,virt_to_phys()总返回0,缺页率100%

原因:页表项present位未置1。学生常忘记在create_page_table()中初始化pte_t数组,导致present=0。
解决:

  • 在create_page_table()中添加初始化:
for (int i = 0; i < 512; i++) { mm->pmd[i].present = 0; // 显式清零 mm->pte[i].present = 0; } // 分配页时手动置位 mm->pte[pte_idx].present = 1; mm->pte[pte_idx].frame = frame_number;

5.4 现象:实验4ptrace程序运行后子进程立即退出,waitpid返回WIFEXITED

原因:execvp失败(如命令不存在),子进程调用exit()退出,未进入trace状态。
解决:

  • 在execvp后添加perror("execvp"),确认命令路径是否正确;
  • 使用绝对路径:./trace_syscall /bin/ls而非./trace_syscall ls;
  • 检查PATH环境变量:echo $PATH,确保/bin在其中。

5.5 现象:实验7ext2_parser读取/dev/sdb1报错Invalid argument

原因:设备未卸载或正被其他进程占用(如systemd-udevd监控)。
解决:

  • 强制卸载:sudo umount -l /mnt/ext2_test(-l为lazy卸载);
  • 杀死占用进程:sudo lsof /dev/sdb1→sudo kill -9 <PID>;
  • 重新挂载后立即运行解析程序,避免udev自动挂载干扰。

6. 进阶技巧:用QEMU+GDB远程调试内核,把“看不见的调度器”变成可单步的C代码

HDU实验止步于模块加载和系统调用跟踪,但真正的操作系统能力跃迁点在于用QEMU启动定制内核,配合GDB远程调试,让schedule()函数像用户程序一样单步执行。这不是炫技,而是解决“为什么进程A总比B先被调度”这类问题的唯一途径。我带学生做的最有效训练是:在QEMU中运行一个只含init进程的精简内核,断点打在kernel/sched/core.c:schedule(),观察rq->curr、rq->next指针变化。

6.1 构建可调试内核:5步编译带调试符号的Linux 5.15

# 1. 下载内核源码(与当前系统同版本) wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.tar.xz tar -xf linux-5.15.tar.xz && cd linux-5.15 # 2. 复制当前配置并启用调试 cp /boot/config-$(uname -r) .config make menuconfig # 进入:Kernel hacking → [*] Kernel debugging → [*] Debug kernel data structures # 启用:[*] KGDB: kernel debugger, [*] KGDB_KDB: include kdb frontend # 3. 编译内核(耗时约30分钟) make -j$(nproc) bzImage modules # 4. 安装模块 sudo make modules_install # 5. 安装内核镜像(生成/boot/vmlinuz-5.15-hdu) sudo cp arch/x86/boot/bzImage /boot/vmlinuz-5.15-hdu sudo update-grub

6.2 QEMU启动并等待GDB连接

# 启动QEMU,监听GDB端口1234 qemu-system-x86_64 \ -kernel /boot/vmlinuz-5.15-hdu \ -initrd /boot/initrd.img-$(uname -r) \ -append "console=ttyS0 kgdboc=ttyS0,115200 kgdbwait" \ -serial stdio \ -s -S # -s: gdb port 1234, -S: 启动即暂停

此时QEMU窗口停住,等待GDB连接。

6.3 GDB连接并调试schedule()

# 在另一终端启动gdb gdb vmlinux-5.15 (gdb) target remote :1234 (gdb) break schedule (gdb) continue # QEMU继续运行,当进程切换时自动断在schedule() (gdb) print rq->curr->pid (gdb) print rq->next->pid (gdb) step # 单步执行调度逻辑

关键参数说明:

  • kgdboc=ttyS0,115200指定KGDB通过串口通信,ttyS0是QEMU默认串口;
  • kgdbwait使内核启动后立即等待GDB连接,避免错过早期调度;
  • vmlinux-5.15是编译生成的未压缩内核镜像(含完整调试符号),位于源码根目录;
  • print rq->curr->pid直接查看当前运行进程PID,比ps aux更底层、更实时。

6.4 实验延伸:修改CFS调度器权重,用GDB验证效果

HDU实验1的RR调度是教学简化,真实内核用CFS(Completely Fair Scheduler)。你可以:

  1. 在kernel/sched/fair.c中找到task_slice()函数;
  2. 修改其返回值(如return 2*ns;强制时间片翻倍);
  3. 重新编译内核,QEMU中启动;
  4. 用GDB在task_slice()设断点,print ns观察原始时间片,print $rax看返回值。

我带过的最深刻一课:一个学生把task_slice()返回值设为`1000*ns

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询