本报告以“Hello’s P2P”程序为研究对象,系统地探讨了从C语言源代码到进程执行的全生命周期管理机制,深入剖析了计算机系统中程序编译、内存管理、进程调度等核心环节。通过预处理、编译、汇编、链接等步操作,将hello.c逐步转化为可执行文件,并结合进程的创建(fork)、加载(execve)与执行,揭示了操作系统对程序逻辑控制流、虚拟地址空间及物理资源的动态管理机制。同时,本篇报告还分析了存储管理中的段页式地址转换(逻辑地址→线性地址→物理地址)、TLB与四级页表加速机制、三级Cache优化策略,以及缺页中断处理等问题,阐明其如何支撑程序高效访问内存并实现多任务并发。通过实验验证了进程上下文切换、信号处理(如Ctrl-C、Ctrl-Z)及动态内存映射(写时复制、共享库加载)的实际效果,最终呈现了程序020的完整生命周期。
关键词:计算机系统,编译,链接,虚拟内存,动态链接,汇编,进程,预处理;
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
第1章 概述................................................................................... - 4 -
1.1 Hello简介............................................................................ - 4 -
1.2 环境与工具........................................................................... - 4 -
1.3 中间结果............................................................................... - 4 -
1.4 本章小结............................................................................... - 4 -
第2章 预处理............................................................................... - 5 -
2.1 预处理的概念与作用........................................................... - 5 -
2.2在Ubuntu下预处理的命令................................................ - 5 -
2.3 Hello的预处理结果解析.................................................... - 5 -
2.4 本章小结............................................................................... - 5 -
第3章 编译................................................................................... - 6 -
3.1 编译的概念与作用............................................................... - 6 -
3.2 在Ubuntu下编译的命令.................................................... - 6 -
3.3 Hello的编译结果解析........................................................ - 6 -
3.4 本章小结............................................................................... - 6 -
第4章 汇编................................................................................... - 7 -
4.1 汇编的概念与作用............................................................... - 7 -
4.2 在Ubuntu下汇编的命令.................................................... - 7 -
4.3 可重定位目标elf格式........................................................ - 7 -
4.4 Hello.o的结果解析............................................................. - 7 -
4.5 本章小结............................................................................... - 7 -
第5章 链接................................................................................... - 8 -
5.1 链接的概念与作用............................................................... - 8 -
5.2 在Ubuntu下链接的命令.................................................... - 8 -
5.3 可执行目标文件hello的格式........................................... - 8 -
5.4 hello的虚拟地址空间......................................................... - 8 -
5.5 链接的重定位过程分析....................................................... - 8 -
5.6 hello的执行流程................................................................. - 8 -
5.7 Hello的动态链接分析........................................................ - 8 -
5.8 本章小结............................................................................... - 9 -
第6章 hello进程管理.......................................................... - 10 -
6.1 进程的概念与作用............................................................. - 10 -
6.2 简述壳Shell-bash的作用与处理流程........................... - 10 -
6.3 Hello的fork进程创建过程............................................ - 10 -
6.4 Hello的execve过程........................................................ - 10 -
6.5 Hello的进程执行.............................................................. - 10 -
6.6 hello的异常与信号处理................................................... - 10 -
6.7本章小结.............................................................................. - 10 -
第7章 hello的存储管理...................................................... - 11 -
7.1 hello的存储器地址空间................................................... - 11 -
7.2 Intel逻辑地址到线性地址的变换-段式管理................... - 11 -
7.3 Hello的线性地址到物理地址的变换-页式管理............. - 11 -
7.4 TLB与四级页表支持下的VA到PA的变换.................... - 11 -
7.5 三级Cache支持下的物理内存访问................................ - 11 -
7.6 hello进程fork时的内存映射......................................... - 11 -
7.7 hello进程execve时的内存映射..................................... - 11 -
7.8 缺页故障与缺页中断处理................................................. - 11 -
7.9动态存储分配管理.............................................................. - 11 -
7.10本章小结............................................................................ - 12 -
第8章 hello的IO管理....................................................... - 13 -
8.1 Linux的IO设备管理方法................................................. - 13 -
8.2 简述Unix IO接口及其函数.............................................. - 13 -
8.3 printf的实现分析.............................................................. - 13 -
8.4 getchar的实现分析.......................................................... - 13 -
8.5本章小结.............................................................................. - 13 -
参考文献....................................................................................... - 16 -
第1章 概述
1.1 Hello简介
P2P:指From Program to Process,根据自白其指的是从hellow.c(Program)到运行时的进程(Process)。从程序到进程需要经历一系列的步骤:预处理(预处理器展开宏,生成hello.i),编译(编译器将预处理后的代码翻译成汇编代码hello.s,并完成语法和语义分析),汇编(汇编器将汇编代码转换为机器指令hello.o),链接(链接器将hello.o文件与标准库动态链接,生成可执行文件hello),进程创建(在shell中执行hello)等。
020:即From Zero-0 to Zero-0。指最初内存中并没有hello文件的相关内容,shell使用execve函数启动hello程序,把虚拟内存对应到物理内存,并从程序入口开始加载和运行,进入main函数执行代码,程序结束后,shell父进程回收hello进程,内核删除与hello文件相关的文件。
1.2 环境与工具
硬件环境:
处理器:13th Gen Intel(R) Core(TM) i7-13700H 2.40 GHz
机带RAM:16.0GB。
系统类型:64 位操作系统, 基于 x64 的处理器
软件环境:
Windows 11,VMware,ubuntu-24.04.2,
开发与调试工具:
vscode,gvim,gcc,objdump,edb
1.3 中间结果
hello.i 预处理后得到的文件
hello.s 编译后得到的文件
hello.o 汇编后得到的文件
hello.asm 反汇编hello.o得到的反汇编文件
hello2.asm 反汇编hello得到的反汇编文件
hello.elf hello.o的ELF文件
hello2.elf hello的ELF文件
1.4 本章小结
该部分首先详细的介绍了P2P和020的流程,然后详细的寿命了本次实验所用到的软硬件设备和开发调试所用的工具以及本实验所生成的中间结果的名称和工能。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
2.1.1预处理的概念
预处理是指在程序运行前,对源文件进行简单地加工的过程,由预处理器执行,负责解析源代码中以 `#` 开头的预处理指令(如 #include、#define、#ifdef 等),并根据指令对源代码进行文本替换、文件合并等操作。同时预处理还会删除程序中的注释和多余的空白字符。
2.1.2预处理的作用
预处理并不会直接解析程序中的源代码内容,而是对源代码进行相应的分割、处理、替换。
- 头文件包含:将外部头文件(如 stdio.h)的内容直接插入到当前文件中。
- 宏替换:预处理器将定义的符号常量或者宏函数替换为指定的文本以达到简化代码提高可复用性等功能。
- 条件编译:根据条件判断决定某段代码段是否需要被编译。
- 删除注释和空白字符:移除所有的注释和不必要的空白字符。
- 其他预处理指令:如#error、#line、#pragma等。
2.2在Ubuntu下预处理的命令
gcc -E hello.c -o hello.i
2.3 Hello的预处理结果解析
在虚拟机中打开hello.i文件,发现其main中代码没有改变,而前边的代码#include <stdio.h>#include <unistd.h> #include <stdlib.h>被扩展了很多行,除此之外其将所有的注释全部都删除掉了:
在预处理阶段,预处理器会将#include指令的指定头文件包含到源文件中,如果指定头文件中仍然含有#include等指令则会递归地进行预处理,直至源文件中不再出现预处理指令。与此同时,预处理器不会对头文件中的内容进行任何的运算或化简,只是进行单纯的文本搬移。
2.4 本章小结
本章主要讲述了在linux环境中,使用gcc指令对hello.c代码进行预处理的过程,详细的解释了,为什么短短的几行代码在经过预处理之后会变成成百上千行,并且通过实践验证了预处理阶段并不会改变文本的具体内容,只会将头文件中的内容进行递归地搬移同时对机器执行代码无关紧要的注释和空字符进行删除。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
3.1.1编译的概念
汇编是将预处理后的源代码翻译成为等价的汇编代码的过程,该过程由编译器完成。包含词法分析,语法分析,语义分析,中间代码生成,代码优化等阶段。
3.1.2编译的作用
(1)编译可以将高级语言转换为与机器相关的汇编代码,为后续生成的二进制机器码奠定基础。
(2)代码优化,编译可以通过某些优化策略来提高程序的运行速度。
(3)提高兼容性和可移植性,编译可以针对不同的CPU架构和操作系统生成对应的汇编代码
3.2 在Ubuntu下编译的命令
gcc -S hello.i -o hello.s
3.3 Hello的编译结果解析
3.3.1声明部分:
.file | 用来标注该汇编代码对应的原始的源文件的名称 |
.text | 声明代码段,用于存放后续代码编译过后的机器指令 |
.section .rodata | 表示只读数据段,存放常量数据 |
.align | 声明数据对齐的格式,这里是8字节对齐 |
.string | 声明一个以\0为结尾的字符串常量 |
.globl | 声明main为全局可见,允许链接器访问 |
.type | 用来声明一个符号的类型,这里声明main为函数 |
3.3.2数据部分
字符串常量:储存在只读字符串中,通过标签.LC0和.LC1引用。
使用时通过如下方式加载:
整形常量:使用$符号直接向前到汇编指令中,如:
参数argc: 参数argc是main函数的第一个参数,被存放在寄存器%edi中
可见寄存器%edi地址被压入栈中。
局部变量:循环变量的i,存储在栈中(偏移量为-4(%rbp))初始未赋值,通过movl语句显式初始化为0:
3.3.3赋值与表达式
赋值操作:
1)局部变量赋值:
3.3.2中提到的i的赋值,直接使用movl语句进行赋值
2)参数传递赋值:
命令行参数argv通过寄存器进行传递,存储到栈中:
循环变量自增的赋值:
3.3.4类型转换
字符串转整数:
通过调用atoi函数将argv[4]的字符串转换为整数:
3.3.5算数操作
加法,使用add指令实现如:
减法,使用sub指令来实现如:
3.3.6关系操作
比较操作:
判断相等操作,程序中判断argc是否等于5如果相等则跳转:
判断小于等于操作,程序中判断i是小于等于9,如果成立则继续循环,不成立则跳出循环:
3.3.7数组和指针操作
数组的访问,通过基地址+偏移量计算得到,同时指针的解引用通过movq (%rax),rax实现:
3.3.8控制转移
条件分支:
If(argv!=5)的实现中使用cmp进行比较,相同跳过错误处理:
循环:
首先从L2中将i赋值为0然后跳转到L3中和9比较如果小于等于9那么跳转到L4中经过循环内的一系列操作后让i+1后会再次进入到.L中,如此循环。
3.3.9函数操作
参数传递:
printf 中的参数通过寄存器来进行传递:
函数调用:
printf、atoi和sleep函数的调用:
atoi函数将参数argv[3]放入寄存器%rdi中用作参数传递,简单使用call指令调用。
然后,将转换完成的秒数从%eax传递到%edi中,edi存放sleep的参数,再使用call调用。
局部变量的管理:
通过栈针为局部变量分配32字节的栈空间:
函数返回:
3.4 本章小结
本章中着重介绍了C编译器将hello.i文件转换为hello.s文件的过程,首先解释了编译的含义及其作用,然后展示了本次实验中编译所用的指令,最后分析了生成的hello.s文件中如何进行调用函数、进行数组和指针操作、运算和赋值等操作的。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
4.1.1汇编的概念
汇编是指将由编译器生成的.s文件翻译成机器语言指令,并把这些指令打包成一个可重定位目标文件的格式,最终生成目标文件.o文件的过程,其中.o文件是一个包含源代码中所有指令二进制文件。该过程由汇编器完成。
4.1.2汇编的作用
汇编是将人类可读的汇编代码转化为CPU可直接执行的二进制指令。汇编器将.s文件翻译成机器语言指令,将这些指令打包成可重定位目标程序的格式。
4.2 在Ubuntu下汇编的命令
指令:gcc -m64 -no-pie -fno-PIC -c hello.s -o hello.o
4.3 可重定位目标elf格式
使用命令readelf -a hello.o > hello.elf指令得到ELF文件:
ELF头:
ELF头中首先展示了一个16字节的序列。接下来,ELF头给出了包括大小,文件类型,机器类型,系统架构,节头部表的文件偏移,以及节头部表中条目的大小和数量等一系列信息,帮助接下来的链接器完成语法分析等功能
节头:
节头中包含了各节的名称,类型,地址,偏移量,大小,全体大小,旗标,链接,信息,对齐等信息。
重定位节:
重定位节记录代码段(.text)和异常处理帧(.eh_frame)中需要重定位的地址引用。当链接器把这个目标文件和其他文件组合时,需要修改这些位置。一般而言,任何调用外部函数或者引用全局变量的指令都需要修改,而调用本地函数的指令不需修改。可执行目标文件中不包含重定位信息。
符号表:
符号表中记录了全局符号的信息,但不包含局部变量的信息。
4.4 Hello.o的结果解析
首先使用命令objdump -d -r hello.o > hello.asm 输出其反汇编的文件:
然后将hello.s文件和hello.asm文件进行对比:
(1)反汇编文件中增加了机器语言
相同的语句在hello.s中:
在hello.asm中:
可见在.asm文件中汇编指令前边多出了一段16进制代码,这即起这段指令的机器语言。
(2)反汇编文件中使用的是16进制而hello.s中使用的是10进制
如(1)中:
.s中使用的立即数是32而.asm中使用的是0x20。可见虽然进制不同但表示的内容是相同的。
(3)分支跳转的表达不同
.s中:
jle后边直接跟的是跳转到的段落。
.asm中:
jle后面使用的是main函数加偏移量的形式。
(4)函数调用表达不同
例:.s中:
.asm中:
在.s中直接call后边跟的函数名称,而在.asm中使用的是汇编器给出的临时地址,链接时经过重定位后才会修正为目标地址。
4.5 本章小结
本章首先解释了汇编的概念和作用,然后通过readelf指令得到了hello.o的ELF文件,之后逐节的观察并分析了每一节的作用,并对文件中的每一节做出了简单的解析。之后对hello.o文件进行了反汇编处理,通过分析hello.o的反汇编代码和hello.s代码的区别和相同点,进一步地阐述了汇编语言和机器语言的区别以及机器语言与汇编语言的映射关系。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
5.1.1链接的概念
链接(linking)是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。链接可以执行于编译时(compile time),也就是在源代码被翻译成机器代码时;也可以执行于加载时(load time),也就是在程序被加载器(loader)加载到内存并执行时;甚至执行于运行时(run time),也就是由应用程序来执行。
5.1.2链接的作用
链接是由叫做链接器的程序自动执行的。链接器在软件开发中扮演着一个关键的角色,因为它们使得分离编译(separate compilation)成为可能。我们不用将一个大型的应用程序组织为一个巨大的源文件,而是可以把它分解为更小、更好管理的模块,可以独立地修改和编译这些模块。当我们改变这些模块中的一个时,只需简单地重新编译它,并重新链接应用,而不必重新编译其他文件。
5.2 在Ubuntu下链接的命令
ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/x86_64-linux-gnu/crt1.o /usr/lib/x86_64-linux-gnu/crti.o hello.o /usr/lib/x86_64-linux-gnu/libc.so /usr/lib/x86_64-linux-gnu/crtn.o -o hello
5.3 可执行目标文件hello的格式
使用readelf指令hello的ELF格式输出出来:
ELF头:
hello2.elf中的ELF头和hello.elf中的ELF头包含的信息种类基本相同,以描述了生成该文件的系统的字的大小和字节顺序的16字节序列开始,接下来,ELF头给出了包括大小,文件类型,机器类型,系统架构,节头部表的文件偏移,以及节头部表中条目的大小和数量等一系列信息。与hello.elf相比,类型程序头大小和节头数量增加了,并且具有入口地址0x4010f0。
节头:
描述了各个节的大小,偏移量等信息。
程序头:
程序头部分描述了系统准备程序执行所需的信息。
Dynamic section:
符号表:
符号表中保存着定位、重定位程序中符号定义和引用的信息
5.4 hello的虚拟地址空间
使用edb打开hello,可以看到从地址0x401000开始。
由程序头文件中的LOAD地址为0x400000。
程序头中的各部分均能在edb中找到。程序头标在程序执行时使用,它能够告诉链接器运行时需要加载的内容,并提供动态链接信息。
5.5 链接的重定位过程分析
使用objdump -d -r hello > hello2.asm 生成hello的反汇编文件:
与hello.asm进行对比:
首先多出了printf puts getchar等函数部分代码:
动态链接器在链接时将库中的hello.c用到的函数加入到了可执行文件中。
其次call指令后面的16进制代码发生改变,例:
Call后边的401090即为puts函数的地址,通过这个16进制码在反汇编代码中即可找到该函数:
同时跳转的指令也发生了同样的变化:
链接过程:链接负责经多个目标文件和库合并为一个可执行文件,其主要包括符号解析,地址空间分配和重定位等过程。
重定位:首先,链接器将各模块中相同类型的节合并为聚合节,并为这些聚合节、输入模块中的每个独立节以及所有符号(如函数、全局变量)分配唯一的运行时内存地址,确保程序中的指令和全局变量在内存中拥有确定的位置;接着,链接器通过目标模块中的重定位条目,修改代码节和数据节中对符号的所有引用,将原先的占位地址替换为符号的真实运行时地址,从而保证程序执行时能正确访问到已分配地址的代码和数据。
5.6 hello的执行流程
使用edb进行单步调试:
_start ->_libe_start_main->_main->printf->_exit->_sleep->getchar->exit
5.7 Hello的动态链接分析
动态链接的核心思想在于将程序模块拆分为独立部分(如共享库),在运行时完成地址绑定而非编译时静态链接,从而支持库的灵活加载与多进程共享。编译器在生成可执行文件时,无法预知共享库函数的具体加载地址,因此对这类引用生成重定位记录,由动态链接器在程序加载时解析。延迟绑定(Lazy Binding)机制通过全局偏移表(GOT)与过程链接表(PLT)协作实现:PLT中的条目初始指向动态链接器的解析代码(如_dl_runtime_resolve),而GOT初始存储PLT的第二条指令地址;首次调用共享库函数(如printf)时,PLT跳转至GOT条目触发解析流程,动态链接器计算函数真实地址并更新GOT,后续调用则直接通过GOT跳转到目标函数。以hello程序为例,由5.3阅读hello的ELF文件可知.got.plt段起始于虚拟地址0x404000,在调用dl_init初始化前和初始化后的相应字节如下图,可以发现其初始化前后字节发生变化,初始化后动态链接器填充GOT条目为实际函数地址。对于变量访问,编译器利用代码段与数据段的相对位置固定性直接计算地址;而对于函数调用,PLT与GOT的协同机制既减少了启动时的绑定开销,又通过运行时地址修正确保了共享库的灵活加载,最终在提升内存利用率的同时保障了程序执行效率。
调用dl_init前:
调用dl_init后:
5.8 本章小结
在本章,首先阐述了链接的概念和作用,展示了如何使用命令链接并生成hello可执行文件,同时观察了hello.ELF文件,利用edb了解了虚拟地址空间的使用,最后通过edb阐述了重定位和动态链接的过程。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
6.1.1进程的概念
进程的经典定义就是一个执行中程序的实例。进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
6.1.2进程的作用
进程为程序提供了一种假象,程序好像是独占的使用处理器和内存,处理器好像是无间断地一条接一条地执行我们程序中的指令。进程作为一个执行中程序的实例,系统中每个程序都运行在某个进程的上下文中。
6.2 简述壳Shell-bash的作用与处理流程
作用:Shell是用户与操作系统内核(Kernel)之间的命令行接口,核心功能包括:命令解析与执行、环境管理、脚本编程、输入输出重定向。
处理流程:首先从终端读入输入的命令,对输入的命令进行解析,如果命令是内置命令,则直接执行。如果不是,则调用fork创造一个新的进程执行该程序。
6.3 Hello的fork进程创建过程
首先,向shell输入:./hello 2023111524 于博文 15636995956。这时,shell读入这个命令,然后判断这个不是内置命令,于是就调用fork函数创建一个子进程,该自己成会继承父进程,包括数据段,栈等信息,然后执行该程序。执行后,在父进程中fork返回子进程的PID,在子进程中返回0。
6.4 Hello的execve过程
首先,向shell输入:./hello 2023111524 于博文 15636995956。在经过上述过程的进程创建后,子进程会去调用execve()来加载可执行文件到当前进程,通过虚拟内存机制,将hello的各个段加载到对应子进程的空间,同时加载hello所用到的共享库。最后,子进程的程序将跳转到hello的入口点并执行
6.5 Hello的进程执行
Hello程序运行时,操作系统通过进程抽象为应用程序提供两大核心机制:独立的逻辑控制流与私有地址空间。逻辑控制流创造进程独占处理器的假象,表现为连续的程序计数器(PC)序列,即便实际执行中通过时间片轮转与其他进程并发交替运行;私有地址空间则通过虚拟内存管理隔离物理内存,保障程序独占访问其内存区域的权限。操作系统进一步通过上下文切换实现多任务调度:内核为每个进程维护包含寄存器、堆栈、状态等信息的上下文,在时间片耗尽或系统调用触发时,保存当前进程上下文并加载下一进程上下文,完成控制权转移。时间片机制将CPU资源划分为固定时长片段分配给各进程,结合用户态与内核态模式切换(如调用printf、sleep时陷入内核态执行特权指令),实现安全高效的资源调度。例如,Hello程序启动时通过execve分配新虚拟地址空间,用户态下执行输出操作后,调用sleep进入内核态挂起进程,此时CPU切换上下文运行其他进程,待睡眠结束再恢复Hello进程继续执行,通过动态的上下文切换与时间片分配,最终在宏观上呈现多任务并发的效果。
6.6 hello的异常与信号处理
异常的种类:
异常的处理方式:
正常运行:
乱按:
Ctrl-C:
按下后,shell进程受到信号SIGINT会回收hello进程。
Ctrl-Z:
按下后,shell进程受到信号SIGSTP,shell会显示提示信息,并挂起hello进程。
·ps:
·jobs:
·pstree:所有进程以树状图形式显示
·kill -9 %1:杀死hello进程
·fg:继续运行最后一个放入后台的工作
6.7本章小结
在该部分首先介绍了进程的概念与作用,然后描述了shell-bash的作用与处理流程。然后通过以hello程序为例解释了进程的创建启动运行等过程,最后总结了hello执行过程中可能会遇到的异常,以及系统的处理方法,并且通过在程序运行过程中进行各种操作来进行解释和说明。
(第6章2分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:是由程序产生的地址,用于指定一个操作数或一条指令。每一个逻辑地址都由一个段和偏移量组成,偏移量指明了从段开始的地方到实际地址之间的距离。对于hello中的函数,其对应的逻辑地址就由段地址和偏移地址两部分组成。
线性地址:是逻辑地址到物理地址变换之间的一步,程序hello的代码会产生逻辑地址,在分段部件中逻辑地址是段中的偏移地址,加上基地址就是线性地址。如果启用了分页机制,线性地址可以再经过变换产生物理地址;若没有采用分页机制,线性地址就是物理地址
虚拟地址:程序访问存储器所使用的逻辑地址称为虚拟地址。虚拟地址经过地址翻译得到物理地址。与实际物理内存容量无关,是hello中的虚拟地址
物理地址:是内存芯片级的单元寻址方式,与CPU的地址总线直接关联。物理地址通常由32位或36位无符号整数表示。在CPU实模式下,“段基址+段内偏移地址”就是物理地址,CPU可以使用此地址直接访问内存。即hello的实际地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
Intel处理器的段式管理将程序划分为多个逻辑段(如代码段、数据段),通过段表实现逻辑地址到线性地址的转换。逻辑地址由段选择符(16位,高13位为索引,低3位为标志位)和段内偏移量构成。CPU根据段选择符的索引值,在全局描述符表(GDT)或局部描述符表(LDT)中查找对应的段描述符,获取段基址和长度限制。转换时,线性地址=段基址+偏移量。GDT存储系统级段(如内核代码段)及所有LDT的入口;每个进程的LDT则定义其私有段(如用户代码段)及门描述符。
7.3 Hello的线性地址到物理地址的变换-页式管理
在Hello程序的执行过程中,操作系统通过页式管理机制实现从线性地址到物理地址的转换。虚拟内存空间被划分为连续的虚拟页,物理内存则对应划分为相同尺寸的物理页,同时磁盘上的交换空间作为虚拟页的备份存储。页表作为核心数据结构,由多个页表条目构成,每个PTE包含有效位和地址信息:有效位标识该虚拟页是否已缓存在物理内存中,若有效位为1,地址字段指向物理页帧的起始位置;若为0,则地址指向磁盘中该页的备份位置。当CPU访问某个虚拟地址时,内存管理单元(MMU)自动将虚拟地址拆分为虚拟页号(VPN)和页内偏移量,通过页表查询VPN对应的PTE——若该页已加载至物理内存,MMU将物理页帧号与偏移量组合成物理地址;若触发缺页(有效位为0),则引发缺页中断,操作系统从磁盘加载缺失页至物理内存,更新页表并重新执行访存指令。
7.4 TLB与四级页表支持下的VA到PA的变换
在Hello程序的地址转换过程中,处理器通过TLB与四级页表协同实现高效的虚拟地址(VA)到物理地址(PA)映射。当CPU生成虚拟地址时,MMU首先提取虚拟页号(VPN)的高位作为TLB标记(TLBT)和索引(TLBI),在TLB缓存中快速匹配是否存在有效页表条目(PTE)。若TLB命中,则直接获取物理页帧号(PPN),与页内偏移(VPO)组合生成物理地址PA,大幅减少访问延迟。若TLB未命中,MMU将启动四级页表遍历:CR3寄存器指向顶级页全局目录(PGD)的基址,VPN被拆分为四个9位字段(VPN1~VPN4),依次索引PGD→页上层目录(PUD)→页中间目录(PMD)→末级页表(PTE),最终从第四级页表中获取PPN。PPN与VPO拼接形成完整的物理地址PA,同时将该PTE存入TLB以加速后续访问。四级页表通过分层结构压缩页表内存占用,TLB则利用局部性原理缓存热点地址。
7.5 三级Cache支持下的物理内存访问
物理地址(PA)被划分为标记(Tag)、组索引(Set Index)和块偏移(Block Offset)三部分。访问L1 Cache时,首先根据组索引定位到特定组,随后将标记位与该组内8路缓存的标记逐一比对——若匹配且有效位为1,则缓存命中,通过块偏移从64KB的缓存块中提取目标数据返回CPU;若未命中,则逐级向L2、L3 Cache及主存发起请求,获取数据后按LRU替换策略更新L1缓存行。L1专精低延迟,L2/L3提供更大容量,通过空间局部性缓存连续内存块(如循环变量i频繁访问时可驻留L1),而时间局部性则使得高频数据(如argv参数)长期保留在高速缓存中,显著降低平均内存访问延迟,支撑Hello程序高效执行。
7.6 hello进程fork时的内存映射
当父进程调用fork创建hello子进程时,内核首先为新进程分配独立PID,并复制父进程的虚拟内存结构,将共享的物理页标记为只读,区域属性设为私有写时复制。初始时刻父子进程共享全部物理页,形成完全一致的虚拟地址空间视图。当任一进程尝试修改内存(如更新全局变量)时,CPU检测到只读页写操作触发保护异常,陷入内核态后分配新物理页,复制原页内容并更新当前进程页表指向新页,同时解除只读标记允许写入。
7.7 hello进程execve时的内存映射
当通过execve加载并运行hello程序时,内核将彻底重构当前进程的虚拟内存空间以替代原有程序:首先清除原用户态内存映射,释放所有用户区域,如代码、数据段及堆栈等;随后建立私有内存区域,将hello可执行文件的.text代码段和.data数据段映射为写时复制的私有只读页,.bss段及初始堆栈则映射为匿名零页(请求二进制零填充),堆空间初始长度为零;动态链接共享库则通过共享区域映射至进程地址空间,实现库代码与数据的多进程复用。最后,内核将程序计数器(PC)设置为代码段入口点,完成进程上下文切换。
7.8 缺页故障与缺页中断处理
当触发缺页故障时,首先CPU生成的虚拟地址经MMU查询页表条目(PTE),若其有效位为0,则引发缺页中断,陷入内核态执行缺页处理程序。处理程序首先验证地址合法性:检查虚拟地址是否属于进程的VMA(虚拟内存区域),若非法访问将触发段错误终止进程;其次检查权限(如写只读页),权限不符则抛出保护异常终止。合法且权限通过后,内核选择物理内存中的牺牲页——若该页已被修改,则将其换出至磁盘交换区;随后从磁盘加载目标页至空闲物理页帧,更新PTE的有效位及物理页号,并可能调整页表层级结构。完成映射后,CPU重新执行触发缺页的指令:虚拟地址再次经MMU转换,此时PTE已有效,命中物理页完成数据存取。
7.9本章小结
本部分主要介绍了hello的存储地址空间、Intel逻辑地址到线性地址的变换-段式管理、Hello的线性地址到物理地址的变换-页式管理、TLB与四级页表支持下的VA到PA的变换、三级Cache支持下的物理内存访问、hello进程fork时的内存映射、hello进程execve时的内存映射、缺页故障与缺页中断处理这几部分内容。通过hello,更深刻的熟悉了存储方面的知识
(第7章 2分)
结论
hello 所经历的过程:
- 首先程序员编写c程序源代码hello.c
- 预处理,将hello.c进行预处理,将文件调用的所有外部库进行合并,生成hello.i文件
- 编译,将hello.i文件翻译成一个汇编语言的文件hello.s
- 汇编,对hello.s进行汇编生成一个可重定位的目标文件hello.o
- 链接,将hello.o文件和和各种链接库链接起来,生成可执行程序hello
- 运行,在shell中输入./hello 2023111524 于博文 15636955632 1,来运行hello 文件
- 创建进程,终端判断输入的不是内置指令,通过调用fork函数来创造一个新的子进程
- 将hello加载到子进程,通过调用execve函数,映射虚拟内存等,将hello加载到子进程
- 执行命令,访问内存
- 执行过程中,接受来自shell的命令,比如ctrl + z等。
- 终止,当子进程执行完程序后,父进程会回收子进程。
感悟:
通过本次实验,我完整地梳理了课上讲过的知识,让我体会到计算机系统的精致,每完成一个任务都需要计算机调动很多的资源并进行整合才能够完成,而这在当今往往只需要几秒钟就能够完成,我深刻的体会到了计算机的强大。
(结论0分,缺失-1分)
附件
文件名 | 作用 |
hello | 可执行文件 |
hello.o | 汇编后得到的文件 |
hello.i | 预处理后得到的文件 |
hello.s | 编译后得到的汇编文件 |
hello.elf | readelf hello.o 得到的ELF文件 |
hello.asm | hello.o 得到的发汇编文件 |
hello2.asm | hello 得到的反汇编文件 |
hello2.elf | readelf hello 得到的ELF文件 |
(附件0分,缺失 -1分)
参考文献
为完成本次大作业你翻阅的书籍与网站等
[1] Randal E.Bryant David R.O'Hallaron.深入理解计算机系统(第三版).机械工业出版社,2016.
[3] https://www.cnblogs.com/diaohaiwei/p/5094959.html
[4] 【ARM-MMU】ARMv8-A 的4K页表四级转换(VA -> PA)的过程-CSDN博客
(参考文献0分,缺失 -1分)