
简介本资源是重庆大学软件学院《操作系统》课程的实验一配套文档面向计算机专业本科生及操作系统初学者聚焦系统调用原理与内核级编程实践。内容完整覆盖EPOS嵌入式POSIX操作系统的环境搭建、SVN源码获取、QEMU虚拟机运行与Bochs调试全流程并详解如何在Kernel Spacemachdep.c/kernel.h/syscall-nr.h和User Spacesyscall-wrapper.S/syscall.h/main.c协同实现自定义time系统调用包括汇编接口封装、C语言调用约定及时间戳返回逻辑。资源为1个740KB的docx文档结构清晰含实验步骤、代码片段、关键截图说明与常见问题提示便于对照源码理解系统调用从用户态到内核态的完整链路。已有185人学习下载适合需要动手深化OS底层机制认知、掌握跨空间函数调用与调试技巧的学习者。1. 为什么在 Linux 内核里加一个sys_hello就能让学生当场怀疑人生软院操作系统实验一的真实水深“重大软院操作系统实验一系统调用”——这行字出现在实验手册第一页时多数人只当是照着文档敲几行汇编、改个 Makefile 就能交差的入门任务。直到第一次make -j4报出undefined reference to sys_hellodmesg | tail看不到任何输出strace ./test显示unimplemented syscall 333才意识到这不是在用户态写个printf而是在内核的呼吸间隙里亲手缝合一条通往用户空间的神经通路。这个实验真正考的不是“会不会改代码”而是“能不能看懂内核怎么把一次int 0x80或syscall指令拆解成寄存器搬运、表索引、函数跳转、权限校验、返回值封装这一整套黑匣子流程”。它面向的是刚啃完《现代操作系统》前四章、但还没摸过真实内核源码的本科生目标不是做出工业级系统调用而是让getpid()调用路径在你脑中能具象成userspace → entry_SYSCALL_64 → sys_call_table[20] → sys_getpid → return这样可追踪的链条。如果你正卡在“编译不通过”“调用无响应”“返回值总是 -1”说明你已经踩进这个实验最硬的三块石头ABI 对齐、符号导出、调用号注册——它们不写在实验指导书里但决定你能否在 deadline 前看到那一行Hello from kernel!。2. 从零手撕sys_hello基于 Linux 5.10 内核的最小可行路径这个实验不是让你魔改整个内核而是以“最小侵入、最易验证”为原则在稳定内核版本上打一个可复现、可调试、可回滚的补丁。我们选 Linux 5.10LTS 版本社区支持久、文档全、学生机常见所有操作均在干净虚拟机Ubuntu 20.04 GCC 9.4中验证。核心动作只有三步定义系统调用函数、注册到系统调用表、暴露给用户态调用。每一步都必须和内核 ABIApplication Binary Interface严丝合缝否则就是静默失败。2.1 在kernel/目录下新增hello.c并实现核心逻辑不要试图把函数塞进sys.c或fork.c这类已有文件——那会增加 diff 复杂度也违背“最小修改”原则。新建独立文件更利于定位问题、后续清理。// kernel/hello.c #include linux/kernel.h #include linux/syscalls.h #include linux/uaccess.h SYSCALL_DEFINE0(hello) { printk(KERN_INFO Hello from kernel!\n); return 0; }逻辑说明SYSCALL_DEFINE0是内核提供的宏专用于定义无参数系统调用0表示参数个数。它自动处理寄存器保存、返回值封装、错误码转换等底层细节比手写asmlinkage long sys_hello(void)更安全、更符合当前内核惯例。printk(KERN_INFO ...)是内核态唯一可靠的日志输出方式KERN_INFO级别确保默认可见dmesg可查且不依赖用户态syslogd。关键点函数名必须是hello非sys_hello因为SYSCALL_DEFINE0(hello)宏会展开为sys_hello符号但注册时只认hello这个逻辑名。2.2 修改kernel/Makefile将hello.o编译进内核镜像内核构建系统不会自动扫描新文件必须显式声明编译目标。打开kernel/Makefile找到类似obj-y sched/core.o的行在其后添加obj-y hello.o参数说明obj-y表示该模块强制编译进内核镜像vmlinux而非作为可加载模块.ko。系统调用必须静态链接否则sys_call_table初始化时无法解析符号。若误写为obj-m hello.o编译虽通过但sys_hello符号根本不会进入vmlinux后续注册必失败。这是新手第一大翻车点。2.3 在arch/x86/entry/syscalls/syscall_64.tbl中分配并注册调用号x86_64 架构使用独立的系统调用表文件必须为你的调用分配一个未被占用的编号。打开arch/x86/entry/syscalls/syscall_64.tbl找到最后一行如332 common pkey_mprotect sys_pkey_mprotect在其后新增333 64 hello sys_hello字段说明制表符分隔第一列333系统调用号。Linux 5.10 当前最大号为 332因此 333 是安全选择实验环境无需考虑冲突生产环境需查tools/syscalltbl/工具生成。第二列64架构标识64表示仅 x86_64 支持common表示通用32表示 i386。实验限定 x86_64填64即可。第三列hello系统调用名称必须与SYSCALL_DEFINE0(hello)中的名字完全一致小写、无下划线。第四列sys_hello内核函数符号名由SYSCALL_DEFINE0宏自动生成不可手写为hello或my_hello。致命陷阱若此处写成sys_hello但hello.c里用SYSCALL_DEFINE0(my_hello)链接时sys_call_table将指向一个不存在的符号内核启动即 panic。2.4 重新编译并安装新内核关键验证步骤完成上述修改后执行标准内核构建流程。注意不要跳过make modules_install部分符号依赖模块符号表# 清理旧对象避免缓存干扰 make clean # 配置直接复用当前运行内核配置最稳 cp /boot/config-$(uname -r) .config make olddefconfig # 编译-j$(nproc) 加速但首次建议 -j1 便于定位错误 make -j$(nproc) # 安装模块必需否则某些符号解析失败 sudo make modules_install # 安装内核镜像和 System.map sudo make install验证是否成功编译编译完成后检查vmlinux是否包含sys_hello符号nm vmlinux | grep sys_hello # 应输出类似ffffffff81a01234 T sys_hello若无输出说明hello.o未正确链接进vmlinux立即回查kernel/Makefile和SYSCALL_DEFINE0宏名一致性。3. 用户态调用用syscall(2)直接触发绕过 glibc 封装内核侧就绪后用户态不能直接#include unistd.h然后hello()—— glibc 根本不认识这个调用。必须用syscall(2)系统调用接口传入调用号和参数本例无参。这是实验要求的核心能力理解用户态如何与内核约定通信协议。3.1 编写测试程序test_hello.c#define _GNU_SOURCE #include unistd.h #include sys/syscall.h #include stdio.h // 声明系统调用号必须与 syscall_64.tbl 一致 #ifndef __NR_hello #define __NR_hello 333 #endif int main() { long ret syscall(__NR_hello); if (ret 0) { printf(System call hello executed successfully.\n); } else { perror(syscall hello failed); } return 0; }逻辑说明#define __NR_hello 333是关键syscall(2)函数需要知道调用号才能构造正确的rax寄存器值。glibc 不提供此宏必须手动定义。perror()会打印errno对应的字符串但sys_hello返回 0所以这里主要用于捕获-1错误如调用号无效。为什么不用int 0x80实验环境为 x86_64int 0x80是 32 位兼容模式会走不同入口且调用号映射不同__NR_hello在 32 位表中是另一编号极易出错。坚持用syscall(2)是最可靠路径。3.2 编译并运行测试程序gcc -o test_hello test_hello.c sudo ./test_hello预期输出System call hello executed successfully.同时在另一终端执行dmesg | tail -n 5应看到[12345.678901] Hello from kernel!为什么必须sudo系统调用本身无权限限制sys_hello未做鉴权但dmesg默认只允许 root 查看内核日志。若想普通用户也能验证可临时执行sudo dmesg -n 8 # 提高日志级别使 KERN_INFO 可见 echo 1 | sudo tee /proc/sys/kernel/printk # 允许用户态读取部分日志仅调试用勿留生产环境4. 避坑指南那些让软院学生熬夜到三点的五个真实血泪现场这个实验的失败几乎从不报错在表面而是静默失效syscall返回 -1、dmesg无输出、内核启动失败……以下是我在某高校助教期间从上百份实验报告中提炼出的五大高频坑每一条都对应真实git blame记录和dmesg截图。4.1 现象syscall(__NR_hello)返回 -1dmesg无任何Hello输出原因系统调用号333在syscall_64.tbl中填写错误或SYSCALL_DEFINE0名称与表中第三列不一致。内核找不到对应函数直接返回-ENOSYS系统调用未实现。解决用nm vmlinux | grep sys_hello确认符号存在用grep -n hello arch/x86/entry/syscalls/syscall_64.tbl确认行格式为333\t64\thello\tsys_hello制表符非空格检查hello.c中宏是否为SYSCALL_DEFINE0(hello)而非SYSCALL_DEFINE0(sys_hello)。4.2 现象make报错undefined reference to sys_hello原因kernel/Makefile中未添加obj-y hello.o或添加位置错误如放在注释行后导致被忽略导致hello.o未参与链接。解决执行make V1 | grep hello.o确认编译命令中是否出现gcc ... -c ... hello.c检查kernel/Makefile是否有语法错误如漏掉换行符、多出空格可用make -f kernel/Makefile --dry-run预览构建步骤。4.3 现象新内核启动后ls、ps等基础命令全部Segmentation fault原因修改syscall_64.tbl时误删或破坏了原有行末的换行符导致表格解析错位sys_call_table数组长度异常后续所有系统调用地址偏移错误。解决用hexdump -C arch/x86/entry/syscalls/syscall_64.tbl | tail检查最后几行是否以0aLF结尾用wc -l arch/x86/entry/syscalls/syscall_64.tbl对比修改前后行数确保只增加 1 行玄学技巧用vim打开该文件输入:set list显示不可见字符确认每行结尾是$LF而非^MCRLF。4.4 现象dmesg显示Hello from kernel!但用户态程序仍返回 -1原因用户态程序中__NR_hello宏定义错误或未重新编译gcc缓存了旧的.o文件。解决在test_hello.c中添加printf(__NR_hello %d\n, __NR_hello);确认输出为333强制重新编译gcc -o test_hello test_hello.c -Werrorimplicit-function-declaration开启隐式声明警告避免syscall未声明错误删除旧可执行文件再编译rm test_hello gcc -o test_hello test_hello.c。4.5 现象内核编译通过但sudo ./test_hello提示Operation not permitted原因SELinux 或 AppArmor 强制访问控制策略拦截了未知系统调用。Ubuntu 20.04 默认启用 AppArmor。解决临时禁用验证仅实验用sudo systemctl stop apparmor sudo aa-teardown永久方案不推荐实验环境为test_hello创建 AppArmor profile但远超实验范围更优实践在虚拟机中进行实验快照备份后直接关闭安全模块避免污染主机环境。5. 进阶验证用ftrace动态追踪sys_hello的完整执行路径当dmesg看到Hello只是“能跑”而ftrace才能证明“真正在内核里跑了”。ftrace是 Linux 内置的轻量级函数跟踪器无需重启、无需 recompile5 分钟就能可视化sys_hello从用户态陷入到printk的每一帧调用。5.1 启用ftrace并过滤sys_hello相关函数# 挂载 tracefs若未挂载 sudo mount -t tracefs nodev /sys/kernel/tracing # 清空旧 trace echo 0 /sys/kernel/tracing/tracing_on echo /sys/kernel/tracing/trace # 设置跟踪器为 function_graph显示调用栈 echo function_graph /sys/kernel/tracing/current_tracer # 过滤只显示含 hello 或 sys_ 的函数 echo *hello* /sys/kernel/tracing/set_ftrace_filter echo sys_* /sys/kernel/tracing/set_ftrace_filter # 开启 trace echo 1 /sys/kernel/tracing/tracing_on5.2 触发调用并捕获 trace在另一个终端运行测试程序sudo ./test_hello然后立即查看 trace 结果cat /sys/kernel/tracing/trace预期输出节选# tracer: function_graph # # CPU DURATION FUNCTION CALLS # | | | | | | | 1) 0.232 us | sys_hello(); 1) 0.112 us | printk(); 1) 0.089 us | /* sys_hello returns */这段输出明确告诉你sys_hello()函数被调用、耗时 0.232 微秒、内部调用了printk()、最终正常返回。它比dmesg多提供了两个关键信息函数确实被执行了而非printk被优化掉以及执行上下文是内核态无用户态函数混入。5.3 关键参数表ftrace常用控制文件与作用文件路径作用典型值注意事项/sys/kernel/tracing/current_tracer选择跟踪器类型function_graph推荐、function、irqsofffunction_graph显示调用栈function只列函数名/sys/kernel/tracing/set_ftrace_filter白名单过滤函数sys_hello、*hello*、sys_*通配符*有效多行用追加/sys/kernel/tracing/tracing_on开关 trace 记录1开、0关关闭后 trace 文件停止更新但内容保留/sys/kernel/tracing/trace查看实时 trace 结果cat读取大量输出时用tail -n 50避免阻塞为什么不用perfperf功能强大但依赖vmlinux符号表、需debuginfo包、学习成本高。ftrace是内核自带、零依赖、开箱即用完美匹配教学场景——它不追求性能分析只回答一个朴素问题“我的sys_hello到底有没有进内核”我自己的习惯每次新增系统调用必跑一遍ftrace。它像一个内核的“心电图”printk只是心跳声而ftrace能看到心室收缩、血液流动的完整生理过程。有一次发现sys_hello调用后紧跟着do_page_fault顺藤摸瓜揪出copy_from_user未加access_ok检查的越界访问——这种深度验证是实验手册绝不会写的却是工程师真正的护城河。希望帮到你。本文还有配套的精品资源点击获取