哈工大计算机系统2025大作业——Hello的程序人生
计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算学部
学 号 2023113072
班 级 23L0513
学 生 董国帅
指 导 教 师 史先俊
计算机科学与技术学院
2025年5月
摘 要
本文通过实验分析和理论研究,深入剖析了Hello程序从源代码到进程执行的完整生命周期。系统性地揭示了程序在计算机系统中的转换与执行机制:从预处理阶段的宏处理和头文件包含,到编译环节的语法分析和代码优化;从汇编过程生成可重定位目标文件,到链接阶段完成符号解析和地址绑定;再从进程创建的fork系统调用,到程序加载的execve内存重构;最后到指令执行时的地址转换和资源回收。这一研究不仅清晰地展现了高级语言程序与底层硬件系统之间的映射关系,更通过具体案例验证了计算机系统各组件(包括编译器、链接器、操作系统和硬件单元)如何协同工作来执行一个简单程序。研究发现,即使是基本的Hello程序,其执行过程也涉及复杂的系统机制和多层次的抽象转换,这既体现了现代计算机体系结构的精妙设计,也为进一步理解系统优化和安全机制奠定了实践基础。研究结果证实,全面把握程序生命周期中的每个环节,是深入理解计算机系统工作原理的关键所在。
关键词:计算机系统;计算机体系结构,程序生命周期,底层原理;
目 录
第1章 概述
1.1 Hello简介
1.2 环境与工具
1.3 中间结果
1.4 本章小结
第2章 预处理
2.1 预处理的概念与作用
2.2在Ubuntu下预处理的命令
2.3 Hello的预处理结果解析
2.4 本章小结
第3章 编译
3.1 编译的概念与作用
3.2 在Ubuntu下编译的命令
3.3 Hello的编译结果解析
3.4 本章小结
第4章 汇编
4.1 汇编的概念与作用
4.2 在Ubuntu下汇编的命令
4.3 可重定位目标elf格式
4.4 Hello.o的结果解析
4.5 本章小结
第5章 链接
5.1 链接的概念与作用
5.2 在Ubuntu下链接的命令
5.3 可执行目标文件hello的格式
5.4 hello的虚拟地址空间
5.5 链接的重定位过程分析
5.6 hello的执行流程
5.7 Hello的动态链接分析
5.8 本章小结
第6章 hello进程管理
6.1 进程的概念与作用
6.2 简述壳Shell-bash的作用与处理流程
6.3 Hello的fork进程创建过程
6.4 Hello的execve过程
6.5 Hello的进程执行
6.6 hello的异常与信号处理
6.7本章小结
第7章 hello的存储管理
7.1 hello的存储器地址空间
7.2 Intel逻辑地址到线性地址的变换-段式管理
7.3 Hello的线性地址到物理地址的变换-页式管理
7.4 TLB与四级页表支持下的VA到PA的变换
7.5 三级Cache支持下的物理内存访问
7.6 hello进程fork时的内存映射
7.7 hello进程execve时的内存映射
7.8 缺页故障与缺页中断处理
7.9动态存储分配管理
7.10本章小结
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
8.2 简述Unix IO接口及其函数
8.3 printf的实现分析
8.4 getchar的实现分析
8.5本章小结
结论
附件
参考文献
第1章 概述
1.1 Hello简介
1. 1.1 P2P过程
- 编写程序:Hello的旅程始于一个用C语言编写的源文件hello.c。程序通过文本编辑器被创建,其中包含了必要的代码逻辑(如main函数、打印语句等)。
- 预处理:使用gcc -E命令对hello.c进行预处理,展开头文件(如#include )和宏定义,生成.i文件。
- 编译:通过gcc -S将预处理后的文件编译为汇编代码(.s文件),将高级语言转换为机器指令的中间表示。
- 汇编:使用gcc -c将汇编代码转换为机器码(.o文件),生成可重定位目标文件,包含二进制指令但未解决外部引用。
- 链接:链接器(ld)将hello.o与标准库(如libc.so)合并,解析函数(如printf)的地址,生成可执行文件hello。
- 加载运行:在Shell中输入./hello后,操作系统通过execve加载可执行文件,分配内存空间,创建进程,并开始执行指令,最终在屏幕上输出“Hello, World!”。
1.1.2 020过程
- 进程创建:程序启动时,操作系统为其分配虚拟内存、文件描述符等资源,进程从“零”状态(未运行)被赋予“生命”(运行态)。
- 执行阶段:CPU逐条执行指令,动态库被加载,数据从磁盘读入内存,程序与用户或环境交互(如打印输出)。
- 进程终止:当main函数返回或调用exit时,操作系统回收内存、关闭文件描述符等资源,进程状态回归“零”(终止),释放所有资源。
1.2 环境与工具
1.2.1 硬件环境
处理器:13th Gen Intel(R) Core(TM) i5-13500H 2.60 GHz
内存:16.0 GB
1.2.2 软件环境
Windows 11 家庭中文版, Ubuntu 24.04 LTS
1.2.3 开发与调试工具
Visual Studio 2022;vim,gidit ,objdump,edb,gcc,readelf等开发工具
1.3 中间结果
文件名 功能
hello.c 源程序
hello.i 预处理后得到的文本文件
hello.s 编译后得到的汇编语言文件
hello.o 汇编后得到的可重定位目标文件
hello 可执行文件
hello1.elf 使用readelf对hello处理得到的elf文件
hello.arm hello.o的反汇编文件
1.4 本章小结
本章简要概述了实验内容,hello的简介,实验的工具与环境及实验过程中所产生的各项中间结果的文件名称和文件作用。
第2章 预处理
2.1 预处理的概念与作用
2.1.1. 预处理的概念
预处理是C/C++程序编译的第一个阶段,由预处理器执行。预处理器根据源代码中的预处理指令(以#开头的命令)对代码进行文本级别的处理,生成一个经过修改的源代码文件(.i或.ii),供后续编译阶段使用。
2.1.2. 预处理的作用
表1.预处理的主要任务包括
预处理指令
作用
#include
将指定头文件的内容插入到当前文件中
#define
定义宏(常量或函数式宏),在编译前进行文本替换
#ifdef / #ifndef / #endif
条件编译,根据宏定义决定是否编译某段代码
#pragma
提供编译器特定的指令(如优化、警告控制)
#undef
取消已定义的宏
#error
在预处理阶段强制报错
#line
修改编译器报告的行号和文件名(用于调试)
2.2在Ubuntu下预处理的命令
图 1 Ubuntu下的预处理过程展示
其中,-m64表示按64位模式,-no-pie表示非位置无关可执行,-fno-PIC表示非位置无关代码的编译选项,-E表示只进行预处理。
2.3 Hello的预处理结果解析
图 2 hello.i的部分内容展示
hello.c使用上述命令进行预处理后生成的hello.i文件总行数为3180行。行数的大幅增加主要是由于#include指令将、和等标准库头文件的内容完整地插入到了hello.i文件中,可以看到在3163行之前均为头文件内容。
预处理后的 hello.i 文件会包含:
- 头文件展开:#include 、#include 、#include 被替换为这些头文件的实际内容(可能数百行)。
- 删除注释:原始代码中的注释 // 和 /* */ 被移除。
- 原始hello.c代码:在所有头文件内容展开完毕后,是原始hello.c中main函数及其内部的代码。根据截图示例,这部分代码位于hello.i文件的末尾,从第3167行开始。
2.4 本章小结
本章讲述了在linux环境中,如何用命令对C语言程序进行预处理,以及预处理的含义和作用,接着以hello.c为例,演示了在Ubuntu下如何预处理程序,并对结果进行分析。通过分析,我们可以发现预处理后的文件hello.i包含了标准输入输出库stdio.h的内容,以及一些宏和常量的定义,还有一些行号信息和条件编译指令,并且删除了注释,这也是hello.i比hello.c文件多出大量代码的原因,预处理阶段的完成,为编译器提供了规范化、完整化的输入文本,是程序从高级语言向机器码转化的奠基石。。
第3章 编译
3.1 编译的概念与作用
3.1.1. 编译的概念
编译(Compilation) 是 C 程序构建的第二个阶段,由 编译器(Compiler) 执行,负责将预处理后的中间代码(.i 文件)转换为汇编代码(.s 文件)。这一过程的核心是对高级语言(C 代码)进行词法分析、语法分析、语义分析、优化,最终生成与目标机器架构相关的低级指令(汇编语言)。
3.1.2. 编译的作用
- 语法检查:检查代码是否符合 C 语言语法规则(如缺少分号、括号不匹配等)。
- 语义分析:验证变量类型、函数调用是否合法(如 int x = "hello"; 会报错)。
- 代码优化:对代码进行优化(如常量折叠、死代码消除),提高运行效率(需 -O 选项)。
- 生成汇编指令:将 C 代码转换为目标平台的汇编代码(如 x86、ARM)。
- 符号表生成 :记录变量和函数的地址信息,供后续链接阶段使用。
3.2 在Ubuntu下编译的命令
图3. 由hello.i生成成hello.s的终端
其中,-m64表示按64位模式,-no-pie表示非位置无关可执行,-fno-PIC表示非位置无关代码的编译选项,-E表示仅执行编译阶段,生成汇编代码(.s文件),不进行汇编和链接。
3.3 Hello的编译结果解析
hello.s文件包含了hello.c程序对应的x86-64汇编代码。汇编代码由一系列指令和伪指令组成。伪指令以点(.)开头,用于指导汇编器工作。以下将结合hello.c的C语言构造,分析其在hello.s中的具体体现。
3.3.1. hello.s代码
图4. hello.s的汇编代码
3.3.2. 数据类型处理
1.字符串常量
hello.c中的printf格式字符串 "用法: Hello 学号 姓名 手机号 秒数!\n" ,以UTF-8编码存储在.rodata只读数据段,通过标签.LC0引用。
图5. hello.c源代码中的字符串常量
图6. hello.s文件中字符串常量对应的内容
字符串常量被存储在只读数据段(.rodata),这是为了防止程序意外修改字符串内容,编译器会自动将中文字符转换为UTF-8编码的十六进制序列,使用标签.LC0作为字符串的引用地址,在函数调用时通过movl $.LC0, %edi指令将地址加载到寄存器,这种处理方式保证了字符串的不可变性和可重用性。
- 局部变量
图 7 hello.c中循环变量i的定义
图 8 hello.s中函数序言部分的栈空间预留分配
图 9 hello.s中为局部变量i分配栈空间与初始化
局部变量i被分配在栈上,距离帧指针rbp偏移4字节的位置,movl指令中的"l"表示长字(32位),对应C语言的int类型,栈空间分配通过subq $32, %rsp一次性完成,包含所有局部变量,变量访问采用基址寻址方式,如-4(%rbp)表示从rbp向低地址偏移4字节。
3.3.3. 运算符实现
1. 算术运算
图10 hello.c中i++运算
图11 hello.s中addl指令完成32位整数加法
addl指令完成32位整数加法,立即数1直接编码在指令中,操作数是内存地址-4(%rbp),编译器选择内存直接操作而非寄存器加载-运算-回存,这是未优化(-O0)的典型特征。
- 关系运算
图12 hello.c中if函数
图13 hello.s中cmpl指令比较argc(存储在-20(%rbp))和立即数5
cmpl指令比较argc(存储在-20(%rbp))和立即数5,je(jump if equal)实现条件跳转,采用相对跳转方式,标志寄存器(EFLAGS)的ZF位被cmpl指令设置,je指令根据ZF决定跳转,这种比较-跳转模式是if语句的标准实现方式。
3.3.4. 控制结构实现
1. for循环
图14 hello.c中的for循环函数
图15 hello.s中的对应for循环的部分
首先i初始化,然后跳转到条件判断,循环体开始,i++,条件判断,i