本论文以“Hello's P2P”程序为例,系统探讨了C语言程序从源代码到进程的完整生命周期,深入分析其在计算机系统中的执行流程与管理机制。通过Ubuntu环境下的工具链(如gcc、gdb、objdump等),逐阶段解析了预处理、编译、汇编、链接的关键过程,并结合进程管理、存储管理及I/O管理,揭示了操作系统对程序执行的底层支持。实验结果表明,程序在P2P(从程序到进程)与020(从无到无)过程中,需依赖编译工具链的协同工作及操作系统的资源调度机制。本文不仅详细记录了各阶段的中间结果与转换逻辑,还通过反汇编、ELF格式分析、动态链接调试等手段,验证了理论模型的实践应用。研究成果有助于深入理解计算机系统层次化设计思想,为系统级编程与优化提供参考。
关键词:计算机系统;程序生命周期;编译;进程管理
目 录
6.2 简述壳Shell-bash的作用与处理流程... - 13 -
6.3 Hello的fork进程创建过程... - 13 -
7.2 Intel逻辑地址到线性地址的变换-段式管理... - 14 -
7.3 Hello的线性地址到物理地址的变换-页式管理... - 14 -
7.4 TLB与四级页表支持下的VA到PA的变换... - 14 -
7.5 三级Cache支持下的物理内存访问... - 14 -
7.6 hello进程fork时的内存映射... - 14 -
7.7 hello进程execve时的内存映射... - 14 -
第1章 概述
1.1 Hello简介
要完成 hello.c 中代码的执行,hello 程序需要历经 P2P(from Program to Process,即从程序到进程)以及 020(from Zero to Zero)这两个关键过程。
对于 P2P 过程而言,hello.c 作为一份 C 语言源代码,在计算机中是以 ASCII 编码的文本形式进行存储的。该源代码文件需在编译器的处理下,依次经历预处理、编译、汇编、链接这四个阶段。
在完成上述过程之后,用户通过 shell 与操作系统进行交互。在 shell 程序的控制下,首先完成 shell 进程的 fork 操作,从而派生出一个子进程。随后,在该子进程中调用 execve 系统调用,实现将 shell 子进程替换为 hello 程序。自此,hello 程序便拥有了自己独立的进程标识符(Pid),正式成为一个独立的进程,能够独立执行自身的机器代码,完成其预定的运行任务。
而 020 过程发生在调用 fork 之后。在此时,hello 程序尚未被加载到内存之中。当通过 execve 将 shell 子进程替换为 hello 进程后,操作系统会为 hello 进程开辟一份独立的虚拟内存空间。当时间片轮转到 hello 进程执行时,操作系统会根据缺页情况,将 hello 程序的相关数据加载到虚拟内存所映射到的物理内存中。当 hello 程序执行完毕后,操作系统会回收为 hello 程序所开辟的内存空间,此时 hello 程序便从内存中被清理出去,恢复到初始的“无”状态。
1.2 环境与工具
此部分为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
硬件环境:
CPU:13th Gen Intel(R) Core(TM) i9-13900HX 2.60 GHz
内存:32GB RAM
软件环境:
Windows 11 / Ubuntu 20.04
开发工具:
gcc / gdb / objdump / readelf / Visual Studio 2022
1.3 中间结果
| 文件名 | 作用 |
| hello.c | 源程序 |
| hello.i | 预处理后的修改的C程序 |
| hello.s | 编译后的汇编程序 |
| hello.o | 汇编后的可重定位目标文件(二进制) |
| hello | 链接后的可执行目标文件 |
| dump_hello_o.txt | hello.o的反汇编代码 |
| elf_hello_o.txt | hello.o的ELF格式 |
| dump_hello.txt | hello的反汇编代码 |
| elf_hello.txt | hello的ELF格式 |
表 1 Hello一生的中间结果
1.4 本章小结
本章对hello的一生进行了简要的介绍和描述,介绍了本计算机的硬件环境、软件环境、开发工具,介绍了为编写本论文的中间文件的名称和其作用。我们做好了准备探索hello的一生。
第2章 预处理
2.1 预处理的概念与作用
预处理是程序编译过程的一个初始阶段,发生在编译器对源代码进行实际编译之前,预处理阶段主要由预处理器(cpp)完成,预处理器会按照特定的规则对源代码进行扫描和处理,识别并处理以#开头的预处理指令,这些指令可以控制预处理的行为,对源代码进行修改和扩展,但不涉及程序的语法和语义分析。
预处理的作用主要体现在以下三个方面:
①宏展开:通过替换宏定义,简化代码编写,提升可维护性。
②文件包含:整合头文件内容,实现代码复用与模块化管理。
③条件编译:根据条件选择性编译代码,增强程序适应性与灵活性。
2.2在Ubuntu下预处理的命令
![]()
图 1 预处理命令
2.3 Hello的预处理结果解析
可以发现整个.i文件相比于.c文件,扩展到了三千多行。

图 2 .i文件中的原代码
这种剧增是因为#预处理命令将头文件的程序、宏变量、特殊符号等插入到代码中。原来的C代码在文本的最末端。

图 3 扩展的宏定义结果
在main函数之前,预处理器就分别读取stdio.h、unistd.h、stdlib.h中的内容,并且根据读入的顺序依次进行内容的展开。如果头文件中仍然有以字符“#”开头的内容,则预处理器继续对其进行处理,最终的hello.i文件中没有宏定义。
2.4 本章小结
本章介绍了预处理的概念和作用,学习了在ubuntu中hello.c文件进行预处理的命令。我浏览了hello.i的代码,对hello.i的内容有了进一步认识。
第3章 编译
3.1 编译的概念与作用
编译阶段,编译器会对预处理后的源代码(.i文件)进行词法分析、语法分析、语义分析,并生成汇编语言程序(.s文件)。
在这一阶段,编译器会对源代码进行深入的语法和语义检查,能够发现预处理阶段可能遗漏的错误,如语法错误、类型不匹配等。同时,编译器会对源代码进行初步的优化,如消除冗余代码、简化表达式等,以提高生成汇编代码的质量。汇编代码作为高级语言与机器语言之间的中间表示,为后续的汇编和链接阶段提供了基础。
3.2 在Ubuntu下编译的命令

图 4 本实验使用的编译命令
3.3 Hello的编译结果解析
3.3.1 数据
1. 常量:
字符串常量:

图 5 .rodata只读数据段
.LC0(错误提示)和 .LC1(格式字符串)存储在 .rodata 段(只读数据),通过标签引用地址;
编译器将中文字符串转为 UTF-8 八进制转义序列(如 \347\224\250 对应“用”)。
数值常量:

图 6 数值常量位置示意
立即数 $5(argc 检查)、$9(循环上限)直接嵌入。
2. 局部变量
for循环中的i为局部变量。
![]()
图 7 i的存储位置
存储局部变量i,用于在循环中计数。汇编代码中通过movl $0, -4(%rbp)初始化该变量。
3. 表达式
整型表达式argc != 5 编译后表达如图。
![]()
图 8 表达式在.s文件中的形式
4. 类型
指针类型 argv 数组通过基址寄存器 + 偏移量访问。

图 9 指针类型的偏移表达
如图 8(%rbx) 对应 argv[1]。
3.3.2 操作
1.赋值操作
在hello程序中,有赋值语句i=0。我们观察它的汇编代码:
![]()
图 10 赋值操作的汇编表达
也就是说,通过movl,我们将立即数0,放入i中(i是int型,占4个字节,与movl对应)。
2.算术操作
hello程序中对于i有自加i++运算,观察汇编代码有:
![]()
图 11 运算操作的汇编表达
也就是说,汇编语言使用addl对i加上了立即数1,完成了i的自加操作。
3.关系操作与控制转移
这一部分通常会通过cmp 指令,对两个操作数进行比较。比较的结果会存放在标志寄存器中,判断大小、相等、为0等一系列关系,并在满足关系时跳转。
本程序有两个实例:

图 12 关系与跳转第一个实例
这是有条件跳转,如果 %edi 的值与5不相等,我们便会前往.L2。

图 13 关系与跳转第二个实例
这是循环控制,jle用于判断cmpl产生的条件码,若后一个操作数的值小于等于前一个,则跳转到.L4——继续循环。
4. 函数操作
函数的调用过程大致可以描述为:
调用前,由调用者保存的寄存器压栈,并将传入的参数依次置入对应的寄存器或栈帧中(前四个参数依次用%rdi,%rsi,%rdx,%rcx )保存,后续参数倒序压入堆栈中)。调用函数,使用 call 指令,自动保存PC(%rip)值,并更新该寄存器,之后程序转入被调用函数。调用结束后,接收置放在%rax中的返回值,并恢复之前保存的寄存器值。
如图展示了三次调用函数:
printf 的调用过程,将 argv[1],argv[2],argv[3],依次置入%rdi,%rsi,%rdx 然后使用 call 指令调用 printf;
atoi 的调用将argv[4]置入%rdi,调用后,返回值存放在%eax,将其置入%edi,作为sleep的输入参数并调用sleep函数。

图 14 函数操作实例
3.4 本章小结
本章简述了编译的概念与作用,对hello.s中的汇编代码进行了具体解析,重点对C语言数据(常量、局部变量、表达式指针类型)和各类操作(赋值操作、算术运算、关系运算、控制转移和函数调用)的处理方式。这些内容帮助我们理解编译器在代码转换过程中的关键作用,为后续的汇编和链接阶段奠定了基础。
第4章 汇编
4.1 汇编的概念与作用
汇编是将汇编语言代码转换为机器语言二进制代码的过程。汇编语言是一种低级编程语言,与机器语言有一一对应的关系。汇编器负责读取汇编语言代码,并将其转换为对应的机器码,生成目标文件(.o文件)。
汇编将人类可读的汇编指令转换为机器可执行的二进制代码,使计算机能够理解和执行程序。主要处理了汇编代码中的标签、变量名等符号,将其转换为具体的内存地址,确保程序在运行时能够正确访问数据和指令,为后续的链接阶段提供输入,最终生成可执行文件。
4.2 在Ubuntu下汇编的命令

图 15 本实验使用的汇编命令
4.3 可重定位目标elf格式
1. ELF头
首先查看ELF 头,其包含了描述ELF文件整体结构和属性的信息,包含了系统信息,编码方式,ELF头大小,节的大小和数量等等一系列信息。

图 16 ELF头信息
2. 节头目表
节头目表描述了ELF文件中各个节的信息,包括节的名称、类型、偏移、大小等。ELF文件的数据和代码通常存储在各个节中。

图 17 节头信息
3. 重定位节

图 18 重定位节信息
.rela类型的条目包含显式的加数。
重定位节 .rela.eh_frame记录了需要进行地址重定位的异常处理框架(exception handling frame)信息。
在 hello 的处理过程中puts,exit,printf,atoi,sleep,getchar等符号对应的涉及到其他模块的函数均需要重定位。
4. 符号表

图 19 .symtab 的具体内容和解释
符号表 .symtab是目标文件中非常重要的一部分,它列出了所有定义的符号,包括函数、变量和节。符号表在链接和调试过程中起着至关重要的作用。
4.4 Hello.o的结果解析
在本步骤,编者使用objdump -d -r hello.o > dump_hello_o.txt指令对hello.o进行反汇编,将结果存入文件中,以便更好分析。

图 20 反汇编后的main函数部分

续图 20
相较 hello.s 而言,反汇编得到的代码有以下不同:
1)在数的表示方面,hello.s中的操作数以十进制形式展现,而在 hello.o 反汇编后的代码里,操作数则采用十六进制表示。
2)在控制转移方面,hello.s用像.L2、.LC1这类段名称作为助记符来实现跳转,反汇编代码则是借助目标代码的虚拟地址来进行跳转。不过,目前暂且留下了重定位条目,跳转地址显示为零,它们需要等到链接环节完成之后,才会被赋予正确的位置信息。
3)在函数调用方面,hello.s 直接运用 call 指令结合函数名称进行调用,而反汇编代码中的 call 指令所对应的则是目标的虚拟地址。
机器语言是计算机硬件直接理解和执行的二进制指令集合,其构成核心在于操作码与操作数的组合。操作码以二进制形式存在,明确指定了计算机要执行的操作类型,而操作数则描述了这些操作的具体对象。
汇编语言作为机器语言的符号化表示,通过助记符替代了晦涩的二进制操作码,同时允许程序员使用变量名、标号等符号来替代直接的地址或数值。这种设计使得汇编指令与机器指令形成了一一映射关系。
4.5 本章小结
本章经过汇编器,将汇编语言转化为机器语言,hello.s文件转化为hello.o可重定位目标文件。我们研究了可重定位目标文件ELF格式,接触了了readelf命令、ELF头、节头部表、重定位节、符号表。我们对比hello.s和hello.o,分析了汇编语言到机器语言的变化。现在,hello.c已经成为了能够被CPU理解的指令,距离能够在程序中运行只差一步之遥了!
第5章 链接
5.1 链接的概念与作用
链接是通过链接器ld,将多个目标文件(.o文件)以及所需的库文件合并成一个可执行文件(如hello)的过程。
链接令分离编译成为可能,方便了程序的修改和编译:无需重新编译整个工程,而是仅编译修改的文件。其核心作用是解析模块间符号依赖、确定代码/数据的最终内存地址,并将分散的编译结果合并为操作系统可加载执行的完整程序,同时处理库引用并优化程序结构。
5.2 在Ubuntu下链接的命令

图 21 使用ld的链接命令
5.3 可执行目标文件hello的格式

图 22 hello的ELF头信息
在ELF格式文件中,节头部表对hello中所有的节信息进行了声明,其中包括载入到虚拟地址的起始地址Address、大小Size以及在程序中的偏移量offset,由此即可定位各个节所占的区间。

图 23 hello的节头部表信息

图 24 hello的符号表
5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息。

图 25 本进程的虚拟地址空间各段信息
| 虚拟地址 | 权限 | 解析 |
| 0x00400000-0x00401000 | r--p | 只读,包含 .interp,.note 等 |
| 0x00401000-0x00402000 | r-xp | 可执行,包含 .text,.plt 等 |
| 0x00402000-0x00403000 | r--p | 只读,包含 .rodata,.eh_frame |
| 0x00403000-0x00404000 | rw-p | 可写,包含 .data,.dynamic 等 |
表 2 对应虚拟地址解析
5.5 链接的重定位过程分析
hello反汇编文件中,新增了.init和.plt节,还有在hello.c中用到的库函数,如exit、printf、sleep、getchar等函数。

图 26 hello反汇编中新增的节

图 27 hello反汇编中新增的函数
hello反汇编文件中,每行指令都有唯一的虚拟地址,而hello.o的反汇编没有,只是相对于代码段(通常是 .text 段)的偏移地址。这是因为hello经过链接,已经完成重定位,每条指令分配了唯一的虚拟地址,每条指令的地址关系已经确定。

图 28 .o与proc的地址分配对比
hello.o并没有链接,需要如图告诉链接器(linker)在链接时需要执行的动作。

图 29 hello.o的链接动作
5.6 hello的执行流程
| 步骤 | 地址<函数名> | 说明 |
| 加载程序 | 0x4010f0 <_start> | 入口点,初始化栈和参数 |
| 调用初始化入口 | 0x401000 <_init> | 全局构造函数初始化 |
| 全局构造函数遍历 | 0x4011c0 <__libc_csu_init> | 调用.init_array中的函数 |
| 用户代码入口 | 0x401125 <main> | 执行用户定义的 main 函数 |
| 动态库函数调用 | 0x401090 <puts@plt> | 通过PLT/GOT实现延迟绑定 |
| 全局析构函数遍历 | 0x401230 <__libc_csu_fini> | 调用.fini_array中的函数 |
| 全局清理 | 0x401238 <_fini> | 执行全局析构函数 |
| 程序终止 | 0x4010d0 <exit@plt> | 终止进程并释放资源 |
表 3 hello程序的函数调用流程
5.7 Hello的动态链接分析
.got节用于动态链接。它是一个包含了所有全局变量和函数的地址表。当程序运行时,动态链接器会更新这个表,以便程序可以正确地访问这些全局变量和函数。它的主要目的是支持位置无关代码(PIC),使得代码可以在不同内存地址加载而不需要重新编译。

图 30 节头中用于动态链接的节
.got.plt节与.got类似,但专门用于延迟绑定(Lazy Binding)的函数调用。延迟绑定是一种优化技术,只有在函数第一次调用时才进行符号解析和重定位。在函数第一次调用后,.got.plt中的条目会更新为实际的函数地址,后续调用会直接跳转到该函数,提高了运行时性能。
![]()
图 31 调用前
![]()
图 32 调用后
5.8 本章小结
本章研究了链接的过程。通过edb查看hello的虚拟地址空间,对比hello与hello.o的反汇编代码,深入研究了链接的过程中重定位的过程。
通过分析目标文件的ELF文件和edb调试以及各种比较,逐步探寻了链接的过程,分析了链接前后程序的异同,在动态链接分析中,我也更熟悉了延迟绑定和其具体作用机理。使我更深入地了解了链接的作用以及其重要性。
第6章 hello进程管理
6.1 进程的概念与作用
进程是程序在计算机中的动态执行实例,它封装了程序代码、运行时数据及操作系统分配的资源(如内存、文件句柄)。
其核心作用是为程序提供独立的运行环境,实现资源隔离与调度管理。通过为每个进程分配独立内存空间,操作系统确保程序间互不干扰,即使某一进程崩溃也不会影响其他进程;同时,进程作为资源分配的基本单位,使操作系统能够按需调度CPU时间片、管理硬件资源,从而支持多任务并行执行,提升系统整体利用率与响应能力。
6.2 简述壳Shell-bash的作用与处理流程
Shell-bash是Unix/Linux系统中用户与操作系统交互的核心接口,作为命令行解释器;是一个交互型应用级程序,代表用户运行其他程序;Shell是信号处理的代表,负责各进程创建与程序加载运行及前后台控制,作业调用,信号发送与管理等。
shell的处理流程为:
1)读取用户输入;
2)解析用户输入;
3)若要执行内部命令,直接执行;
4)若要执行非内部命令,shell会fork子进程,在子进程中execve执行相关命令;
5)根据&的有无,确定程序的前后台运行。
6.3 Hello的fork进程创建过程
当我们在命令行输入./hello以执行我们的hello程序时,由于hello程序并不是shell程序的内部程序,所以shell会使用fork来创建子进程并进行后续操作;子进程得到与父进程用户级虚拟地址空间相同的一份副本,包括代码段、数据段、堆、共享库和用户栈。子进程中,fork返回0,父进程中,返回子进程的PID;
新创建的子进程几乎但不完全与父进程相同,子进程得到与父进程虚拟地址空间相同的但是独立的一份副本,子进程获得与父进程任何打开文件描述符相同的副本,最大区别是子进程有不同于父进程的PID。
6.4 Hello的execve过程
在fork子进程中,操作系统会调用execve() 系统调用,将 hello 程序加载到子进程的地址空间中。 函数原型为int execve(const char * filename, char * const argv[ ], char * const envp[ ])。可见,execve() 系统调用需要提供三个参数:可执行文件的路径、命令行参数数组和环境变量数组。该函数成功运行正确运行时不返回。逻辑控制流交给要运行的程序,即操作系统根据路径加载 hello 程序的可执行文件到子进程的地址空间中。
6.5 Hello的进程执行
当在 shell 中运行./hello 程序时,操作系统会经历一系列步骤来创建和调度新进程。
1. 解析路径
操作系统解析./hello的路径,定位到可执行文件。
2. 创建新进程
父进程通过fork()系统调用创建一个新的子进程。此时,父进程fork() 返回子进程的 PID,子进程fork()返回 0。
3. 进程上下文信息
每个进程都有自己的上下文信息,包括寄存器状态、进程控制块(PCB)、虚拟内存映射等。新创建的子进程继承了父进程的上下文信息,但它们是独立的副本。
4. 从用户态到核心态的转换
fork()调用导致从用户态到核心态的转换,因为fork()是一个系统调用,需要操作系统内核的参与来创建新进程。
5. 加载程序
子进程调用execve()系统调用,加载hello程序。此时,子进程的地址空间被新程序的代码、数据等覆盖。操作系统将新程序的入口点设置为新进程的下一条指令。
6. 进程调度
操作系统的调度器决定哪个进程应该运行。调度器根据调度算法和策略(如时间片轮转、优先级调度等)分配 CPU 时间。每个进程在 CPU 上运行一段时间称为一个时间片。时间片结束后,调度器可能会切换到另一个进程。当时间片结束或有更高优先级的进程需要运行时,调度器保存当前进程的上下文,并加载下一个要运行的进程的上下文。
7. 用户态与核心态转换
进程在执行用户代码(如hello程序的代码)时运行在用户态。当进程执行系统调用(如execve()、read()等)时,会从用户态切换到核心态。时间片结束或 I/O 事件发生时,中断会触发从用户态到核心态的切换,操作系统处理中断并可能进行进程调度。
8. 执行hello程序
一旦execve()成功执行,子进程开始运行hello程序的代码:
hello程序开始在用户态执行。如果hello程序进行I/O操作(如打印输出),会通过系统调用进入核心态。
9. 程序执行完成
当hello程序执行完毕(如main函数返回),进程会通过exit()系统调用通知操作系统它已执行完成。exit()系统调用从用户态切换到核心态。操作系统清理子进程的资源,并向父进程发送终止信号。
10. 父进程处理子进程终止
父进程可以通过wait()或waitpid()系统调用等待子进程结束,并获取子进程的终止状态。
6.6 hello的异常与信号处理

图 33 Ctrl+Z 结果
按下Crtl + Z,进程收到SIGSTP信号,hello进程挂起并向父进程发送SIGCHLD。

图 34 ps信息
使用jobs查看作业信息,可以发现hello程序处于停止状态
![]()
图 35 jobs信息
pstree查看进程树:

图 36 pstree信息
使用fg将后台程序置于前台:

图 37 fg操作
使用kill发送信号:

图 38 用kill函数终止进程
使用Ctrl+C发出SIGINT信号,使得程序终止:

图 39 Ctrl+C方式终止程序
6.7本章小结
本章简述了进程、shell的概念与作用,分析了hello程序使用fork创建子进程的过程以及使用execve加载并运行用户程序的过程,运用上下文切换等知识,分析了hello进程的执行过程,最后分析了hello对于异常以及信号的处理并进行了实际操作。
第7章 hello的存储管理
7.1 hello的存储器地址空间
hello 程序的存储器地址空间包含逻辑地址、线性地址、虚拟地址和物理地址。
逻辑地址:程序编译时,编译器为指令和数据分配的相对地址,如hello程序中main函数的起始地址。
线性地址:逻辑地址经段式管理转换后的地址(在扁平内存模型中,逻辑地址直接作为线性地址),它构成了一维地址空间。
虚拟地址:线性地址经页式管理转换后的地址,是hello程序运行时看到的地址,操作系统为其分配独立的虚拟地址空间。
物理地址:内存硬件的实际地址,hello程序访问内存时,操作系统会将虚拟地址映射到物理地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
在Intel架构中,逻辑地址到线性地址的转换是通过段式管理机制实现的。
逻辑地址由段选择子(Selector)和偏移量(Offset)两部分组成。段选择子是一个16位的值,它指定了段描述符在全局描述符表(GDT)或局部描述符表(LDT)中的索引。偏移量则是一个32位的值,表示段内相对于段基址的偏移量。
当CPU需要访问内存时,它首先根据逻辑地址中的段选择子,从GDT或LDT中查找对应的段描述符。段描述符是一个数据结构,它包含了段的基地址(Base Address)、段限长(Limit)以及段的属性信息,如段的读写权限、特权级等。
一旦获取到段描述符,CPU就可以进行地址计算了。它将段描述符中的基地址与逻辑地址中的偏移量相加,得到的结果就是线性地址。线性地址构成了一个从0开始的一维地址空间,程序可以直接使用这个地址空间来访问内存。如果偏移量超过了段描述符中指定的段限长,CPU会触发一个段错误(Segmentation Fault),以防止程序越界访问内存。
在段式管理的过程中,CPU还会进行特权级检查。它会检查当前程序的特权级(CPL,Current Privilege Level)与段描述符中指定的请求特权级(DPL,Descriptor Privilege Level),以确保程序的访问权限是合法的。如果当前程序的特权级不足以访问某个段,CPU会阻止该访问,从而保护系统的安全性
7.3 Hello的线性地址到物理地址的变换-页式管理
在Intel架构中,线性地址到物理地址的转换通过页式管理机制实现,这一过程涉及地址空间的划分、核心组件的协同工作以及关键优化机制。
分页机制将虚拟地址空间(也称为线性地址空间)划分为固定大小的页(通常是4KB)。操作系统维护一个页表,将虚拟地址空间中的页映射到物理内存中的页框。
分页机制将线性地址分解为页表项(Page Table Entry, PTE)和页内偏移量。对于一个32位的地址(假设页大小为4KB),地址分为三部分:目录项、页表项和页内偏移量。目录项为高10位,页表项为中间10位,页内偏移量为低12位。
操作系统维护一个页目录,每个进程有一个独立的页目录。页目录包含指向多个页表的指针。根据目录项查找页目录,得到对应的页表地址。在对应的页表中,根据页表项查找,得到物理页框地址。
物理页框地址结合页内偏移量,得到物理地址。

图 40 使用页表的地址翻译
7.4 TLB与四级页表支持下的VA到PA的变换
在现代操作系统中,地址转换过程中除了页表机制外,还使用了转换后备缓冲(Translation Lookaside Buffer, TLB)来加速虚拟地址(VA)到物理地址(PA)的转换。
TLB是一种高速缓存,用于存储最近使用的虚拟地址到物理地址的映射。通过TLB,可以避免每次地址转换都进行多级页表查找,从而加速地址转换过程。
将虚拟地址的VPN划分为相等大小的不同的部分,每个部分用于寻找由上一级确定的页表基址对应的页表条目。

图 41 使用k级页表的地址翻译
处理器生成一个虚拟地址,并将其传送给MMU。MMU用VPN向TLB请求对应的PTE,如果命中,则跳过之后的几步。MMU生成PTE地址(PTEA).,并从高速缓存/主存请求得到PTE。如果请求不成功,MMU向主存请求PTE,高速缓存/主存向MMU返回PTE。PTE的有效位为零, 因此 MMU触发缺页异常,缺页处理程序确定物理内存中的牺牲页 (若页面被修改,则换出到磁盘——写回策略)。缺页处理程序调入新的页面,并更新内存中的PTE。缺页处理程序返回到原来进程,再次执行导致缺页的指令。
7.5 三级Cache支持下的物理内存访问

图 42 三级Cache下访问示意
CPU发送一条虚拟地址,随后MMU按照7.4所述的操作获得了物理地址PA。根据Cache大小组数的要求,我们将物理地址进行分割。物理地址(52位)被分割为40位的标记位CT,6位的索引位CI,6位的块偏移CO。通过CT查找告诉缓存中的对应块,通过CI在块中寻找行,依次与每一行的数据比较,有效位有效且标记位一致则命中。如果命中,直接返回想要的数据。如果不命中,就依次去L2,L3,主存判断是否命中,命中时将数据传给CPU同时更新各级cache的储存。
7.6 hello进程fork时的内存映射
当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID,同时为这个新进程创建虚拟内存,创建当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记位只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面。
7.7 hello进程execve时的内存映射
execve函数在当前进程中加载并运行包含在可执行文件hello中的程序,用hello替代了当前bash中的程序。具体做了如下操作:
①删除已存在的用户区域。
②映射私有区域
③映射共享区域
④设置程序计数器(PC)
最后,exceve设置当前进程的上下文中的程序计数器到代码区域的入口点。
7.8 缺页故障与缺页中断处理
当MMU翻译某个地址时,触发了缺页异常。此时,控制便会转移到内核的缺页处理程序。处理程序会判断:
1)虚拟地址是否合法。判断虚拟地址是否处在区域结构定义的区域内。若不合法,便会触发段错误。
2)试图进行内存访问是否合法。判断进程是否有足够的权限。
当内核知道缺页是正常情况时,会选择牺牲页,换入新的页面并进行页表更新,完成缺页处理。

图 43 缺页处理示意
7.9动态存储分配管理
(以下格式自行编排,编辑时删除)
Printf会调用malloc,请简述动态内存管理的基本方法与策略。(此节课堂没有讲授,选做,不算分)
7.10本章小结
本章介绍了存储器地址空间、段式管理、页式管理,VA 到 PA 的变换、物理内存访问, hello 进程fork时和execve 时的内存映射、缺页故障与缺页中断处理。
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
(以下格式自行编排,编辑时删除)
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
(以下格式自行编排,编辑时删除)
8.3 printf的实现分析
(以下格式自行编排,编辑时删除)
[转]printf 函数实现的深入剖析 - Pianistx - 博客园
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
(以下格式自行编排,编辑时删除)
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
(以下格式自行编排,编辑时删除)
结论
Hello程序的一生完整展现了计算机系统从代码到执行的全链路设计:
- 初始时,hello.c是存储在磁盘上的一个文本文件。
- hello.c经过cpp进行了预处理,形成了另一个文本文件hello.i。
- hello.i经过编译器cc1翻译成文本文件hello.s,hello.s中包含着汇编语言代码。
- hello.s经过汇编器as翻译成机器语言指令,并打包形成可重定位目标文件。
- 链接器ld将hello.o需要的各种目标文件与hello.o进行链接,形成可执行目标文件hello。
- 使用shell执行hello。
- shell使用fork创建子进程,使用execve加载并运行hello程序。这一过程中,涉及到了虚拟内存,内存映射等知识。
- hello运行过程中,可能要到各种异常,收到各种信号,hello可能需要陷入到内核,调用异常处理程序。
- hello调用printf,使用UNIX IO来进行输出。
- hello运行结束,被父进程回收。
通过本次作业,深刻体会到计算机系统分层抽象与协同工作的精妙性:硬件与软件的边界通过编译工具链和操作系统内核模糊化,而模块化设计(如动态链接、写时复制)大幅提升了资源利用率。计算机系统的设计不仅是技术的堆砌,更是工程与艺术的高度融合。
附件
| 文件名 | 作用 |
| hello.c | 源程序 |
| hello.i | 预处理后的修改的C程序 |
| hello.s | 编译后的汇编程序 |
| hello.o | 汇编后的可重定位目标文件(二进制) |
| hello | 链接后的可执行目标文件 |
| dump_hello_o.txt | hello.o的反汇编代码 |
| elf_hello_o.txt | hello.o的ELF格式 |
| dump_hello.txt | hello的反汇编代码 |
| elf_hello.txt | hello的ELF格式 |
表 4 同表2
参考文献
[1] Bryant R. E., O’Hallaron D. R. 深入理解计算机系统(原书第3版)[M]. 北京: 机械工业出版社, 2016: 97-135.
转载自CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/2302_81618025/article/details/148227201



