前言
本文是哈尔滨工业大学《计算机系统原理》大作业的详细记录,以hello.c程序为研究对象,从预处理、编译、汇编、链接到进程执行、存储管理、IO 交互,全程拆解 “程序→进程” 的完整生命周期。文中包含具体操作命令、工具使用截图、底层原理分析,适合学习 CSAPP 的同学参考,也满足课程 “自媒体发表” 的要求。
一、大作业核心目标
1.理解程序编译链接四阶段(预处理→编译→汇编→链接)的工作机制;
2.掌握 ELF 文件格式(可重定位目标文件、可执行文件)的结构与分析方法;
3.剖析进程创建(fork)、加载(execve)、调度、信号处理的完整流程;
4.理解存储管理中的地址转换(VA→PA)、TLB、Cache、缺页中断机制;
5.解析 Linux IO 管理的 “一切皆文件” 思想,以及 printf、getchar 的底层实现。
二、环境与工具准备
2.1软硬件环境
操作系统:Ubuntu 22.04 LTS(64位)
硬件配置:CPU Intel Core i7-12700H | 内存 16GB DDR4 | 存储 512GB NVMe SSD
核心工具:gcc 11.4.0、gdb 12.1、edb 1.3.0、binutils(readelf/objdump)、strace、pstree
2.2工具安装与验证
sudo apt update
sudo apt install -y gcc gdb edb binutils readelf objdump strace pstree
# 验证版本
gcc --version
gdb --version


三、核心步骤与实验过程
3.1源程序准备(hello.c)
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc,char *argv[]){
int i;
if(argc!=5){
printf("用法: Hello 学号 姓名 手机号 秒数!\n");
exit(1);
}
for(i=0;i<10;i++){
printf("Hello %s %s %s\n",argv[1],argv[2],argv[3]);
sleep(atoi(argv[4]));
}
getchar();
return 0;
}
说明:程序接收 4 个命令行参数(学号、姓名、手机号、秒数),秒数 = 手机号 %5,循环打印 10 次后等待用户输入。
3.2编译链接四阶段实现
(1)预处理:生成hello.i
gcc -m64 -Og -no-pie -fno-stack-protector -fno-PIC -E hello.c -o output/hello.i


核心作用:展开头文件(stdio.h/unistd.h/stdlib.h)、删除注释,生成纯 C 代码。
(2)编译:生成 hello.s(汇编文件)
gcc -m64 -Og -no-pie -fno-stack-protector -fno-PIC -S output/hello.i -o output/hello.s


核心作用:将 C 代码转换为 x86-64 汇编指令,包含栈帧建立、条件判断、函数调用等逻辑。
(3)汇编:生成 hello.o(可重定位目标文件)
gcc -m64 -Og -no-pie -fno-stack-protector -fno-PIC -c output/hello.s -o output/hello.o


核心作用:将汇编指令转换为机器码,生成 ELF 格式文件,包含重定位项(未解析的 printf、sleep 等符号)。
(4)链接:生成 hello(可执行文件)
ld -m elf_x86_64 -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o output/hello /usr/lib/x86_64-linux-gnu/crt1.o /usr/lib/x86_64-linux-gnu/crti.o output/hello.o -lc /usr/lib/x86_64-linux-gnu/crtn.o

核心作用:解析重定位项、合并段、建立虚拟地址空间,生成可直接运行的文件。
3.3程序的运行与调试
运行命令(替换为自己的学号、姓名、手机号,秒数 = 手机号 %5):
./output/hello 2024113100 周熙栋 13163559552 2

3.4关键模块分析
(1)ELF 文件格式分析
可重定位文件(hello.o):用readelf -S output/hello.o查看节(.text/.rel.text/.symtab);
可执行文件(hello):用readelf -l output/hello查看程序头(LOAD/INTERP 段)。(插入截图:hello.o 的节分析 + hello 的程序头分析,标注关键段信息)
(2)进程管理与信号处理
进程创建:pstree -p | grep hello查看 bash→hello 的进程树;
信号处理:Ctrl+C(SIGINT 终止)、Ctrl+Z(SIGTSTP 暂停)、jobs/fg 命令操作。(插入截图:进程树截图 + Ctrl+Z 暂停 + fg 恢复运行的完整过程)
(3)存储管理:虚拟地址→物理地址转换
用gdb output/hello查看虚拟地址空间:
(gdb) info proc mappings

(4)IO 管理:printf 与 getchar 底层实现
- printf:通过
strace跟踪到最终调用write(1, ...)系统调用(fd=1 对应显示器); - getchar:通过
strace跟踪到调用read(0, ...)系统调用(fd=0 对应键盘)。

四、核心原理总结
1.编译链接:从 hello.c 到 hello,经历 “预处理(头文件展开)→编译(C→汇编)→汇编(汇编→机器码)→链接(解析符号 + 段合并)”,最终生成可执行文件;
2.进程生命周期:bash fork 创建子进程→execve 加载 hello 程序→OS 调度进程执行→接收信号处理→执行完成后回收资源;
3.地址转换:逻辑地址(汇编偏移)→线性地址(段式转换,x86-64 简化映射)→虚拟地址(链接分配)→物理地址(四级页表 + TLB 转换);
4.IO 机制:Linux 将设备抽象为文件,printf/getchar 通过 write/read 系统调用与硬件交互,内核驱动程序负责具体硬件操作
五、学习感悟与创新思考
1.之前只知道 “写代码→运行”,通过本次大作业,终于理解了程序背后的底层逻辑,从编译器到 OS 再到硬件的协同工作,让我对 “计算机系统” 有了立体认知;
2.动态链接的 “延迟绑定” 机制很巧妙,首次调用函数时才解析地址,既减少了程序加载时间,又节省了内存;
3.存储管理中的 TLB 和 Cache 是性能优化的关键,硬件和软件的协同设计让程序执行效率大幅提升;
4.创新思考:可以尝试用静态链接(gcc -static)编译 hello,对比动态链接与静态链接的文件大小、运行速度差异,进一步理解链接机制。
六、参考文献
1.《深入理解计算机系统(第三版)》Randal E. Bryant, David R. O’Hallaron;
2.Linux 官方手册:https://man7.org/linux/man-pages/index.html;
3.大作业参考 Slides:https://ysyx.oscc.cc/slides/hello-x86.html;
4.printf 实现分析:https://www.cnblogs.com/pianist/p/3315801.html。
转载自CSDN-专业IT技术社区



