本报告通过分析hello程序从hello.c源代码到进程终止的完整生命周期,系统阐述了计算机系统的多层次协作机制。首先,通过预处理、编译、汇编与链接阶段,将C语言源代码转换为可执行文件;其次,结合进程管理、存储管理及输入输出管理,深入探讨了进程创建、地址空间转换、动态链接、信号处理等核心机制。实验部分利用GCC、GDB、ReadELF等工具,验证了逻辑地址到物理地址的页式转换、动态链接的PLT/GOT机制、异常处理与终端I/O交互等关键技术。本报告通过理论与实践结合,揭示了操作系统、编译器与硬件的协同设计原理,为深入理解计算机系统提供了完整案例。
关键词:预处理;编译;动态链接;进程管理;存储管理;输入输出管理
目 录
4.4.2 机器语言与汇编语言的映射特点... - 20 -
6.2 简述壳Shell-bash的作用与处理流程... - 36 -
6.3 Hello的fork进程创建过程... - 36 -
7.2 Intel逻辑地址到线性地址的变换-段式管理... - 44 -
7.3 Hello的线性地址到物理地址的变换-页式管理... - 45 -
7.4 TLB与四级页表支持下的VA到PA的变换... - 45 -
7.5 三级Cache支持下的物理内存访问... - 46 -
7.6 hello进程fork时的内存映射... - 46 -
7.7 hello进程execve时的内存映射... - 47 -
第1章 概述
1.1 Hello简介
Hello程序的完整生命周期可概括为P2P(Program to Process)和020(Zero to Zero)两个核心过程:
1. P2P(从程序到进程)
预处理(Preprocessing):hello.c通过宏替换、头文件展开和条件编译生成hello.i,消除注释与冗余信息。
编译(Compilation):将hello.i转换为汇编代码hello.s,完成语法语义分析并生成机器无关的低级表示。
汇编(Assembly):将hello.s翻译为机器指令,生成可重定位目标文件hello.o,包含二进制代码和符号表。
链接(Linking):将hello.o与C标准库(如libc.so)动态链接,解析外部符号(如printf),生成可执行文件hello。
进程加载(Process Loading):Shell通过fork创建子进程,execve将hello加载至内存,形成独立进程空间,CPU执行指令直至终止。
2. 020(从零到零)
进程执行结束后,操作系统回收其占用的内存、文件描述符等资源,进程控制块(PCB)被销毁,状态回归初始“零”状态。
1.2 环境与工具
硬件环境:
处理器 13th Gen Intel(R) Core(TM) i7-13700H 2.40 GHz
机带 RAM 16.0 GB (15.7 GB 可用)
系统类型 64 位操作系统, 基于 x64 的处理器
软件环境:Windows11 64位,VMware,Ubuntu
开发与调试工具:Visual Studio;vim objump edb gcc readelf等工具
1.3 中间结果
表1-1 中间结果
文件名称 |
功能 |
hello.c |
源程序 |
hello.i |
预处理后得到的文本文件 |
hello.s |
编译后得到的汇编语言文件 |
hello.o |
汇编后得到的可重定位目标文件 |
hello.elf |
用readelf读取hello.o得到的ELF格式信息 |
hello.asm |
反汇编hello.o得到的反汇编文件 |
hello |
链接后得到的可执行文件 |
hello1.elf |
用readelf读取hello得到的ELF格式信息 |
hello1.asm |
反汇编hello得到的反汇编文件 |
1.4 本章小结
本章概述了Hello程序从源代码到进程的完整生命周期(P2P)及其终止后的资源回收(020),明确了预处理、编译、汇编、链接与进程加载的关键步骤。同时,列出了实验所需的软硬件环境与工具,并展示了生成的中间结果文件。后续章节将基于此框架,逐步深入分析每个环节的实现细节。
第2章 预处理
2.1 预处理的概念与作用
预处理是C程序编译过程中的第一阶段,其核心任务是对源代码进行文本级别的处理,为后续的编译阶段提供“纯净”的输入。具体作用包括:
1. 宏替换:处理#define定义的宏,将其替换为实际值或代码片段。
2. 头文件展开:递归展开#include指令,将头文件内容插入源文件中。
3. 条件编译:根据#ifdef、#ifndef等指令选择性保留或删除代码块。
4. 删除注释:移除所有单行(//)和多行注释(/* ... */)。
5. 添加行号标记:插入#line指令,便于编译器报错时定位原始代码位置。
2.2在Ubuntu下预处理的命令
使用的命令是:gcc -E hello.c -o hello.i
同时,通过ls命令验证文件的存在。
图2-1 预处理
如图2-1所示, hello.i出现在了目录下。
2.3 Hello的预处理结果解析
打开hello.i文件,分析预处理后的代码变化:
图2-2 hello.i
1. 头文件展开
原始代码:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
预处理结果:
#include指令被替换为对应头文件的全部内容。例如,stdio.h展开后包含printf、getchar等函数的声明,以及FILE、size_t等类型的定义,代码量从几行扩展至近千行。
2. 宏与条件编译处理
原始代码中未定义自定义宏,但标准库中的宏(如NULL)被展开为((void*)0)。
条件编译指令(如#ifdef)未被使用,因此预处理后无变化。
3. 注释删除与代码格式
所有注释(如// 秒数=手机号%5)被完全删除。
代码保留原始缩进结构,但行号因头文件插入而大幅增加。
2.4 本章小结
预处理阶段通过宏替换、头文件展开和注释删除,将原始的hello.c转换为仅包含有效代码的hello.i。此阶段未修改程序的逻辑结构,但为后续编译提供了标准化的输入。生成的hello.i文件是后续编译阶段的起点,其内容直接决定了汇编代码的生成方式。
第3章 编译
3.1 编译的概念与作用
编译是将预处理后的高级语言代码转换为目标机器架构相关的汇编语言代码的过程。其主要作用包括:
1. 语法与语义检查:验证代码是否符合C语言规范,检测类型错误、未声明变量等问题。
2. 中间代码生成与优化:生成抽象语法树(AST)或中间表示(IR),并进行初步优化(如常量折叠、死代码删除)。
3. 汇编代码生成:将优化后的中间代码转换为特定架构(如x86-64)的汇编指令,为后续汇编阶段提供输入。
hello程序中,编译阶段将hello.i文件转换为hello.s,为机器码生成奠定基础。
3.2 在Ubuntu下编译的命令
使用的命令是:gcc -S hello.i -o hello.s
图3-1 编译
如图3-1所示,hello.s出现在了目录下。
3.3 Hello的编译结果解析
3.3.1 数据类型与变量
1. 局部变量
C代码:int i;(未赋初值)
汇编实现:
图3-2 局部变量
变量i存储在栈帧偏移-4(%rbp)处,初始值通过movl $0显式赋0。
subq $32, %rsp为main函数分配32字节栈空间,包含局部变量和参数对齐。
2. 字符串常量
C代码:
"用法: Hello 学号 姓名 手机号 秒数!"
"Hello %s %s %s\n"
汇编实现:
图3-3 字符串常量
字符串常量存储在只读数据段(.rodata),通过标签(如.LC0)引用。编译器将字符串中的汉字编码成UTF-8格式,一个汉字占3个字节。字节之间用\分割开。
3.3.2 赋值与表达式
1. 赋值操作</