程序人生-Hello’s P2P

打印 上一主题 下一主题

主题 2121|帖子 2121|积分 6363

摘  要

本文以“程序人生-Hello's P2P”为例,系统探讨了C语言程序从源代码到历程执行的全生命周期及其底层计算机系统支持。通过编写Hello程序,结合Ubuntu环境下的GCC工具链,详细分析了预处置处罚、编译、汇编、链接等编译过程的核心机制,并利用ELF文件解析、反汇编工具及EDB调试器深入探究了可执行文件的格式与虚拟地址空间映射。进一步结合历程管理、存储管理及异常处置处罚等操纵系统原理,叙述了Shell环境下程序的加载、执行、调治与终止过程,揭示了逻辑控制流、上下文切换、动态链接及内存分页机制的现实应用。本文通过理论与实践结合,完整呈现了程序从静态代码到动态历程的转换过程,为理解计算机系统条理化设计与软硬件协同工作提供了直观案例。
关键词:程序生命周期;预处置处罚;编译与链接;历程管理;虚拟内存;异常处置处罚                           
第1章 概述

1.1 Hello简介

P2P即From Program to Process,Program 是写下的 hello.c 文件,它只是在磁盘上的代码文本。Process 是当在命令行中执行它之后,操纵系统为它“赋予生命”的一个运行实体。首先我在编辑器中写下hello.c,保存成一个“Program”。然后经过cpp预处置处罚,ccl编译,as汇编和ld链接等过程终极形成一个可执行文件hello。
而O2O指的是一个程序从0开始终极又归于0的一整个过程,在Shell运行该hello程序时,内核为其分配虚拟地址空间,随着虚拟内存发生缺页故障和物理缓存中的冷不命中,hello载入内存。而后内核利用异常控制流对历程举行调治,Shell调用fork函数创建子历程,并通过execve函数将hello程序载入并创建运行环境,好比分配虚拟内存,运行完成后,Shell接纳该历程,释放内存空间,hello的一生就竣事了。
1.2 环境与工具

1.2.1 硬件环境
处置处罚器:AMD Ryzen 9 7945HX with Radeon Graphics
RAM:16G
1.2.2 软件环境
Windows 11 ,VMware® Workstation 17 Pro Ubuntu 64-2024ICS
1.2.3 开发与调试工具
Visual Studio Code,Codeblocks,GDB,gcc,Vim
1.3 中间结果


  • hello.i:预处置处罚后的文本文件
  • hello.s:对hello.i编译后生成的文件
  • Hello.o:汇编之后的可重定位目的文件
  • Hello:经过链接的可执行目的文件
  • elf.txt:hello.o的elf格式
  • elf1.txt:hello的elf格式
1.4 本章小结

本章对于一个程序hello的产生、执行与竣事举行了完整的叙述,介绍了hello的P2P执行与调治的完整过程,同时也对完成作业的环境举行了简要介绍。

(第1章0.5分)




第2章 预处置处罚


2.1 预处置处罚的概念与作用

概念:预处置处罚是程序编译过程中的第一步,它在正式编译源代码之前对源程序举行文本替换和处置处罚,主要任务是处置处罚以 # 开头的预处置处罚指令,在编译器对源代码举行词法和语法分析之前,预处置处罚器会按照预处置处罚指令对代码举行处置处罚,预处置处罚器并不理解C语言的语法规则,它只负责文本级的替换和条件编译等操纵。预处置处罚处置处罚完的代码会生成一个“预处置处罚过的源文件”,然后交给编译器举行进一步的处置处罚。
作用:

  • 将指定的头文件内容插入到当前文件中,便于代码的模块化和重用。
  • 用宏定义名来替换指定的文本,加强代码可读性和可维护性。
  • 举行条件编译,根据条件选择性地编译某段代码,实现平台兼容性、调试开关、版本控制等功能。
  • 取消已经定义的宏。
  • 还有其他辅助功能,好比#error和#pragma等就不举行一一介绍了。
2.2在Ubuntu下预处置处罚的命令

预处置处罚命令为:gcc -E hello.c -o hello.i


图2.2.1 利用gcc对hello.c举行预处置处罚

2.3 Hello的预处置处罚结果解析

利用文本编辑器打开hello.i查看文件内容
首先看到其引入了外部库.h文件

图2.3.1 预处置处罚生成的hello.i第一部分

然后对typedef举行数据类型名称的替换


图2.3.2 预处置处罚生成的hello.i第二部分

然后引入外部函数

图2.3.3预处置处罚生成的hello.i第三部分

最后还有我们所编写的main函数

图2.3.4预处置处罚生成的hello.i第四部分

整个过程将c文件举行预处置处罚后仍为文本文件,但由于添加了大量头文件的内容,好比有很多typedef开头的数据类型定义,以及extern开头的头文件中包罗的函数的声明。

2.4 本章小结


本章利用Ubuntu的Linux下gcc编译器对源程序hello.c举行了预处置处罚,并将hello.i文件举行了部分解读,这使我们对于预处置处罚中所做的头文件拓展插入和宏定义替换等操纵有了进一步的了解熟悉。
(第2章0.5分)


第3章 编译


3.1 编译的概念与作用


概念:编译是将高级语言写成的源程序举行预处置处罚后翻译成汇编语言的过程。这个过程由编译器如cc1完成。汇编语言指令和二进制呆板代码一一对应,在下一步的汇编中可以被翻译为二进制呆板指令
作用:

  • 将高级语言翻译成汇编语言指令的形式。
  • 对于语法举行查抄和分析,分析语句是都符合语法规则,帮助开发者调试。
  • 优化程序性能,编译器会对代码举行优化,进步程序运行效率。
3.2 在Ubuntu下编译的命令

编译命令:gcc -S hello.i -o hello.s

图3.2.1 编译命令执行图

3.3 Hello的编译结果解析


首先是常量说明:

图3.3.1 main之前的常量信息说明

字符串常量:

"用法: Hello 学号 姓名 秒数!\n"

"Hello %s %s\n"。
字符串常量放入只读数据段 .rodata,用 .string 存储,所以在LC0和LC1中,编译器将字符串常量抽出成标签供引用,指针指向这些标签。而中笔墨符以 UTF-8 编码形式呈现,例如 \347\224\250 是 UTF-8 编码。
还有一些立刻数常量,好比在for循环中的立刻数9:

图3.3.2 立刻数常量

然后是变量:
C 语言中局部变量和函数参数通常保存在栈中。编译器将它们分配在 %rbp 为基准的偏移地址处。所有基本类型(int, char* 等)都被映射为对应巨细的内存段。

图3.3.3 局部变量存储在栈中

接下来还有算术操纵:
好比整型赋值和加法表达式:
把i赋值0,对i举行加法操纵++

图3.3.4 把i赋值0


图3.3.5 把i执行++加法操纵

然后是类型转换操纵:
atoi() 将字符串转换为整数,%eax 是返回的 int 值。
没有显式类型转换的指令,由于 atoi 返回的就是 int,直接赋给 int 类型参数。


图3.3.6 类型转换

然后是关系操纵:
好比if条件语句的实现:

图3.3.7关系操纵if举例1

cmpl 比较两个整数:-20(%rbp)(即 argc)和常数 5。je 是条件跳转(==),表示相等就跳。编译器用比较 + 条件跳转指令实现 if, ==, !=, >, <, 等关系逻辑。
再好比:

图3.3.8关系操纵if举例2

这里接纳了init – 跳转到中间 – 循环判断的模式。jle用于判断cmpl产生的条件码,若后一个操纵数的值小于等于前一个,则跳转到.L4——重新执行循环。
接下来是数组/指针/结构访问:

图3.3.9数组访问举例

这是对 argv 的访问,反映了指针加偏移 + 解引用的过程:
argv 是一个 char*[](指针数组),将argv[1] 翻译为 *(argv + 1),汇编实现:先偏移再解引用
最后还有函数调用和返回:
编译器利用 call 指令调用函数,函数名后面是 @PLT,参数通常通过 %rdi, %rsi, %rdx, %rcx, %r8, %r9 通报,返回值存在 %eax(int)、%rax(64 位类型)。

图3.3.10函数调用举例1


图3.3.11函数调用举例2


图3.3.12函数调用举例3

3.4 本章小结

本章介绍了编译的概念和整个完整的流程,通太过析hello.s文件团体说明了c语言有高级语言转换成为汇编代码的过程,以及汇编代码如何实现如常量、变量、条件控制、逻辑控制、数组指针访问等内容的说明和利用。
(第3章2分)


第4章 汇编


4.1 汇编的概念与作用


概念:汇编是汇编器as将汇编语言文件好比我们的hello.s文件翻译成呆板语言的可重定位目的文件好比hello.o文件的过程。这里.o文件将变为二进制文件。
作用:我们通过汇编能将代码转化为计算机可以或许理解的呆板语言指令。再把这些指令打包成可重定位目的程序的格式,并将结果保存在.o目的文件中。
4.2 在Ubuntu下汇编的命令

利用gcc执行汇编命令:gcc -c hello.s -o hello.o

图4.2.1利用gcc举行汇编

4.3 可重定位目的elf格式

我利用readelf -a hello.o > ./elf.txt指令完成了对 hello.o 执行 readelf 的全部分析,并将终端输出重定向到 elf.txt 文件中保存查看
首先是ELF头,它是是 ELF 文件最前面的“总说明书”,包罗文件是 32 位还是 64 位、利用哪种字节序、目的平台、文件类型等底子信息。

图4.3.1 ELF头


然后是头节目表:
其内容包罗.o文件中出现的各个节区的基本信息,包罗节名、位置和巨细等信息。

图4.3.2 节区信息

然后是重定位节表:
目的文件 .o 中不会举行链接,因此调用外部符号如 puts@PLT、exit@PLT 等必须通过重定位表记载。

图4.3.3 重定位节表

然后是符号表:
.symtab存放在程序中定义和引用的函数和全局变量的信息,这些符号将留待链接阶段由链接器(ld)解析。

图4.3.4 符号表

4.4 Hello.o的结果解析

反汇编结果如下:


图4.4.1 hello.o反汇编结果

hello.s文件如下:


图4.4.2 hello.s文件

对比可得以下结论:
首先二者指令一一对应,说明了汇编语言与呆板语言指令是一一对应的。
其不同点有:

  • 控制跳转命令有所不同:hello.s的跳转目的是.L1,.L2如许的符号来实现的,而反汇编代码却是利用目的代码的虚拟地址举行跳转。
  • 数的表示不同:在hello.s中操纵数利用十进制举行表示,而反汇编代码中操纵数是利用十六进制举行表示的。
  • 函数调用有所不同:和控制跳转命令相似,hello.s直接利用call函数的名称即可实现函数的调用,而反汇编代码对目的的虚拟地址举行call操纵。
4.5 本章小结

本章通过对hello.s举行汇编,将代码转换为呆板代码,如许使得代码可以或许真正被呆板所理解,我们通过elf文件了解了文件中的节和符号等信息,后续利用反汇编工具比较了hello.s和hello.o的区别,进一步得知了汇编语言和呆板语言之间有所变革之处。
(第4章1分)


5链接


5.1 链接的概念与作用

概念:链接是将各种不同文件的代码和数据片段网络并组合从而综合成为了一个单一文件的过程,这个文件可被加载(复制)到内存并执行。这此中包罗符号解析和重定位等过程,链接可以执行于编译时,也可以执行于加载时,分别对应静态链接和动态链接。
作用:链接令分离编译成为可能,我们可以将软件举行模块化设计,然后举行模块化编程,如许方便了程序的修改和编译:无需重新编译整个工程,而是仅编译修改的文件,使得分组工作高效举行。
链接还有利于构建共享库。源程序节流空间而未编入的常用函数文件(如printf.o)举行归并,生成可以正常工作的可执行文件。
5.2 在Ubuntu下链接的命令

命令如下:ld -o hello -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


图5.2.1 链接命令

5.3 可执行目的文件hello的格式









通过ELF查看hello的信息,首先,从ELF头信息来看,与hello.o文件不同的是,其文件类型变为了可执行文件,节头数目也有所增加。

图5.3.1 ELF头


接下来查看各节信息,通过节头部表,我们可以查看各个节的巨细、偏移量和其他属性。链接器链接时,会将各个文件的相同段归并成一个大段,并且根据这个大段的巨细以及偏移量重新设置各个符号的地址。我们也发现了一些动态链接所增加的段


图5.3.2 节头部表



5.4 hello的虚拟地址空间

利用edb加载hello后,通过Data Dump窗口可以查看到历程的虚拟地址空间,发现hello的虚拟地址从0x401000开始。对照5.3中各节位置查看相应位置的内容,对比反汇编结果得知现实运行时的虚拟地址和ELF文件中节头部信息中的地址是一致的,这说明了操纵系统为每一个历程维护了一个相对独立的虚拟内存空间。

图5.4.1  edb查看虚拟地址空间

5.5 链接的重定位过程分析

利用指令举行重定位后:

图5.5.1 hello重定位结果

经过与hello.o的重定位项目举行对比,可发现一下不同:
首先是有新增的函数,链接加入了在hello.c中用到的库函数,如exit、printf、sleep、getchar等函数。

图5.5.2 新增函数

其次就是有新增的节:

图5.2.3 新增节

还有函数调用的不同:
调用函数不再有重定位类型和偏移量,而是直接为函数的绝对地址和它的函数名。hello实现了调用函数时的重定位,所以在调用函数时调用的地址已经是函数确切的虚拟地址。

图5.2.4 函数调用

链接的过程:链接是链接器(ld)将各个目的文件(各种.o文件)组装在一起,文件中的各个函数段按照一定规则累积在一起。从.o提供的重定位条目将函数调用和控制流跳转的地址填写为终极的地址。
5.6 hello的执行流程

通过symbol搜索找到_start即程序入口点

图5.6.1 程序入口点

  然后调用__libc_start_main,渐渐调用函数可得以下调用子程序和程序地址:

图5.6.2 程序地址和执行顺序



5.7 Hello的动态链接分析

通过查看elf文件可以看到:

图5.7.1 查看elf

然后进入edb举行查看:
好比查看.plt段:
未执行之前:

图5.7.2 查看.plt段1

执行之后:

图5.7.3 查看.plt段2
利用代码段和数据段的相对位置不变的原则计算变量的正确地址。而对于库函数,必要plt、got的协作。
plt初始存的是一批代码,它们跳转到got所指示的位置,然后调用链接器。初始时got内里存的都是plt的第二条指令,随后链接器修改got,下一次再调用plt时,指向的就是正确的内存地址。plt就能跳转到正确的区域。


5.8 本章小结

本章研究了链接的过程。主要介绍了链接器如何将hello.o可重定向文件与动态库函数链接起来,通过edb查看hello的虚拟地址空间,对比hello与hello.o的反汇编代码,分析了可重定位文件与可执行文件ELF的差异,深入研究了链接的过程中重定位的过程。

(第5章1分)



6hello历程管理


6.1 历程的概念与作用

概念:历程(Process)是计算机中的程序关于某数据集合上的一次运行运动,是系统举行资源分配和调治的基本单位,是操纵系统结构的底子。历程的经典定义是一个执行中程序的实例,系统的每个程序都运行在某个历程的上下文。上下文是由程序正确运行所需的状态组成的,这个状态包罗存放在内存里的程序的代码和数据,它的栈,通用目的寄存器的内容,程序计数器,环境变量以及打开文件描述符的集合。在早期面向历程设计的计算机结构中,历程是程序的基本执行实体;在当代面向线程设计的计算机结构中,历程是线程的容器。程序是指令、数据及其组织形式的描述,历程是程序的实体。
作用:历程提供给应用程序的关键抽象:一个独立的逻辑控制流,犹如程序独占处置处罚器;一个私有的地址空间,犹如程序独占内存系统。可以说,如果没有历程,体系云云庞大的计算机不可能设计出来。通过历程,我们会得到一种假象,似乎我们的程序是当前唯一运行的程序,我们的程序独占处置处罚器和内存,我们程序的代码和数据似乎是系统内存中唯一的对象。而这些假象就是通过历程来实现的。
6.2 简述壳Shell-bash的作用与处置处罚流程

Shell 是一种用户级的交互型的应用级程序,用户可以或许通过 Shell 与操纵系统内核举行交互,控制操纵系统中的任务。而 bash是 Linux 中最常见的 Shell 类型之一。
具体处置处罚流程如下:
1.Shell读取用户输入的命令:$./hello。
2.shell命令行解释器构造argv和envp;
3.调用fork()函数创建子历程,其地址空间与shell父历程完全相同,包罗只读代码段、读写数据段、堆及用户栈等
4.调用execve()函数在当前历程(新创建的子历程)的上下文中加载并运行hello程序。将hello中的.text节、.data节、.bss节等内容加载到当前历程的虚拟地址空间。
5.调用hello程序的main()函数,hello程序开始在一个历程的上下文中运行。
6.3 Hello的fork历程创建过程

当Shell吸收到./hello命令时,它会对该命令举行解析,发现这是一个加载并运行可执行文件的命令。接着,Shell会创建一个对应./hello的作业,并调用fork()系统调用来创建一个子历程。fork()函数用于创建一个新的历程,这个新历程是调用历程(即父历程)的副本。新创建的子历程与父历程几乎完全相同,它们共享相同的代码段、数据段、堆、共享库和栈段。然而,子历程与父历程有不同的历程ID(PID),且fork()在父历程中返回子历程的PID,在子历程中返回0,这使得父子历程可以相互区分。父历程和子历程在各自的虚拟地址空间内独立并发的向前推进。当子历程运行竣事时,如果父历程仍然存在,执行对子历程的接纳,否则有init历程接纳子历程。
6.4 Hello的execve过程

execve函数加载并运行可执行目的文件,且带参数列表argv和环境变量列表envp。在exevce加载了后,它调用启动代码,启动代码会设置栈,并将控制通报给新程序的主函数。
1.删除已存在的用户区域(自父历程独立)
2.映射私有区:为 hello 的代码、数据、.bss 和栈区域创建新的区域结构,所有这些区域都是私有的、写时才复制。
3.映射共享区:好比 hello 程序与共享库 libc.so 链接。
4.设置 PC:exceve() 做的最后一件事就是设置当前历程的上下文中的程序计数器,使之指向代码区域的入口点
5. execve在调用乐成的情况下不会返回,只有当出现错误时,例如找不到必要执行的程序时,execve才会返回到调用程序。
6.5 Hello的历程执行

6.5.1 逻辑控制流和时间片:
历程的运行本质上是CPU不断从程序计数器 PC 指示的地址处取出指令并执行,值的序列叫做逻辑控制流。操纵系统会对历程的运行举行调治,执行历程A->上下文切换->执行历程B->上下文切换->执行历程A->… 云云循环往复。 在历程执行的某些时候,内核可以决定抢占当前历程,并重新开始一个先前被抢占了的历程,这种决议就叫做调治,是由内核中称为调治器的代码处置处罚的。当内核选择一个新的历程运行,我们说内核调治了这个历程。在内核调治了一个新的历程运行了之后,它就抢占了当前历程,并利用上下文切换机制来将控制转移到新的历程。在一个程序被调运行开始到被另一个历程打断,中间的时间就是运行的时间片。
6.5.2 用户模式和内核模式:
用户模式的历程不允许执行特殊指令,不允许直接引用地址空间中内核区的代码和数据。
内核模式历程可以执行指令会合的任何命令,并且可以访问系统中的任何内存位置。
6.5.3 上下文:
上下文就是内核重新启动一个被抢占的历程所必要恢复的原来的状态,由寄存器、程序计数器、用户栈、内核栈和内核数据结构等对象的值构成。
6.5.4 调治的过程:
在对历程举行调治的过程,操纵系统主要做了两件事:加载保存的寄存器,切换虚拟地址空间。
6.5.5 用户态与核心态转换:
为了能让处置处罚器安全运行,必要限定应用程序可执行指令所能访问的地址范围。因此分别了用户态与核心态。
核心态可以说是拥有最高的访问权限,处置处罚器以一个寄存器当做模式位来描述当前历程的特权。历程只有故障、中断或陷入系统调用时才会得到内核访问权限,其他情况下始终处于用户权限之中,保证了系统的安全性。
6.6 hello的异常与信号处置处罚

以下格式自行编排,编辑时删除
 hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处置处罚的。
 程序运行过程中可以按键盘,如不绝乱按,包罗回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps  jobs  pstree  fg  kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处置处罚。
正常运行状态:

图6.6.1 正常运行

异常情况:

  • 不绝乱按,包罗回车:

图6.6.2 乱按情况异常

在程序执行时乱按,程序无响应,但Shell会将回车前输出的字符串看成命令。

  • Ctrl + C:

图6.6.3 输入Ctrl+C的异常情况

会立刻终止历程,通过ps命令发现hello历程被接纳。

  • Ctrl + Z

图6.6.4 输入Ctrl+Z的异常情况

会在后台制止,fg放到前台运行时,会输出剩下的7个字符串。

  • kill命令:

图6.6.5 kill命令的异常情况

挂起的历程被终止,在ps中无法查到到其PID。


6.7本章小结

本章通过在Linux终端下运行hello,在运行时发送信号,查看历程等展示了通过shell运行与管理程序的过程,了解了hello历程的执行过程。在hello运行过程中,内核对其调治,异常处置处罚程序为其将处置处罚各种异常。每种信号都有不同的处置处罚机制,对不同的shell命令,hello也有不同的响应结果。
(第6章2分)


7hello的存储管理


7.1 hello的存储器地址空间

7.1.1 逻辑地址
逻辑地址(Logical Address)是指由程序产生的与段相干的偏移地址部分。在这里指的是hello.o中的内容。
7.1.2 线性地址
线性地址(Linear Address)是逻辑地址到物理地址变更之间的中间层。程序hello的代码会产生段中的偏移地址,加上相应段的基地址就生成了一个线性地址。
7.1.3 虚拟地址
CPU启动保护模式后,程序hello运行在虚拟地址空间中。注意,并不是所有的“程序”都是运行在虚拟地址中。CPU在启动的时候是运行在实模式的,Bootloader以及内核在初始化页表之前并不利用虚拟地址,而是直接利用物理地址的。
7.1.4 物理地址
放在寻址总线上的地址。放在寻址总线上,如果是读,电路根据这个地址每位的值就将相应地址的物理内存中的数据放到数据总线中传输。如果是写,电路根据这个地址每位的值就在相应地址的物理内存中放入数据总线上的内容。物理内存是以字节(8位)为单位编址的。
7.2 Intel逻辑地址到线性地址的变更-段式管理

在 Intel 平台下,逻辑地址(logical address)是 selectorffset 这种形式,selector 是 CS 寄存器的值,offset 是 EIP 寄存器的值。如果用 selector 去 GDT( 全局描述符表 ) 里拿到 segment base address(段基址) 然后加上 offset(段内偏移),这就得到了 linear address。我们把这个过程称作段式内存管理。
一个逻辑地址由段标识符和段内偏移量组成。段标识符是一个16位长的字段(段选择符)。可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段。
全局的段描述符,放在“全局段描述符表(GDT)”中,一些局部的段描述符,放在“局部段描述符表(LDT)”中。
给定一个完整的逻辑地址段选择符+段内偏移地址,看段选择符的T1=0还是1,知道当前要转换是GDT中的段,还是LDT中的段,再根据相应寄存器,得到其地址和巨细。拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,就得到了其基地址。Base + offset = 线性地址。
7.3 Hello的线性地址到物理地址的变更-页式管理

Linux将虚拟地址空间分为虚拟页,对应磁盘中相同巨细的物理页,将磁盘中的数据按页缓存到内存中。所有虚拟页被组织为内存中常驻的一个数据结构,称为页表,页表中每一条代表一个虚拟页,含有一位有用位+地址,有用位为1说明该页已经被缓存在内存中,这时地址就为该页在内存中地址的前半段,为0说明该页还没被加载到内存中,这时若地址为空,代表该虚拟页没有对应磁盘中物理页,大概为磁盘中物理页的地址。操纵系统为每一个历程维护一个独立的页表,页表在内存中的起始位置被保存在CPU中的寄存器中。
虚拟地址(VA)被提供给内存管理单元(MMU)中的地址翻译硬件,虚拟地址的前半段(VPN)被看作页表的索引,MMU根据它生成对应的页表项在内存中的物理地址,向内存请求该项,如果有用位为1,则将页表项中的地址(PPN)和虚拟地址的后半段(VPO)合成为目的单元在内存中的物理地址(PA),向内存请求;如有用位为0,则触发一个缺页异常,在缺页处置处罚程序中将磁盘页加载到内存,覆盖一个捐躯页,处置处罚完后再次执行访存指令时这一页就可以直接在内存中找到了。
7.4 TLB与四级页表支持下的VA到PA的变更

由于请求页表项必要访问内存,翻译后备缓冲器(TLB)被用来缓存页表的内容,MMU拿到虚拟地址后会先在TLB中寻找,如果所需的页表项已经被缓存在TLB中,将减少获取页表项耗费的时间。
由于虚拟页的数目浩繁,单级页表中每一项都对应一个虚拟页,在内存中将占用较大内存。采用多级页表的方式将更多的页举行归并,可以减小页表在内存中所占的空间。虚拟地址的VPN被分为VPN1+VPN2+VPN3+VPN4,MMU先在TLB中寻找页表项,若找不到,将以VPN1为索引在一级页表中查找页表项,该项指向对应二级页表在内存中的位置,进而以VPN2为索引在二级页表中查找页表项,逐级直到找到最后一级页表,得到PPN。在这之间任何一步有用位为0都将触发缺页异常。
7.5 三级Cache支持下的物理内存访问

得到物理地址后,CPU并不直接访问内存,而是访问L1 Cache,而L1是L2的缓存,L2是L3的缓存,L3是内存的缓存。

数据被组织为块在缓存之间通报。缓存被分为多少路,每一起有一个标志和可以存下一个块的存储空间,多少路被组织为一组。一个物理地址被分为标志+组索引+块偏移,当访问缓存时,组索引被用来找到组,标志被用来对比找到路,块偏移被用来找到对象在块中的位置。当某一级缓存中找不到目的对象时,它会向下一级缓存索要这个对象所在的块,并替换掉本级缓存中的一起,进而可以向上一级缓存提供这个对象。
7.6 hello历程fork时的内存映射

当fork函数被当前历程调用时,内核为新历程创建各种数据结构,并分配给它一个唯一的PID,同时为这个新历程创建虚拟内存,创建当前历程的mm_struct、区域结构和页表的原样副本。它将两个历程中的每个页面都标志位只读,并将两个历程中的每个区域结构都标志为私有的写时复制。
当fork在新历程中返回时,新历程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个历程中的任一个后来举行写操纵时,写时复制机制就会创建新页面。
7.7 hello历程execve时的内存映射

1)在bash中的历程中执行了如下的execve调用:execve("hello",NULL,NULL);
2)execve函数在当前历程中加载并运行包罗在可执行文件hello中的程序,用hello替换了当前bash中的程序。
3)删除已存在的用户区域。
4)映射私有区域
5)映射共享区域
6)设置程序计数器(PC)
最后,exceve设置当前历程的上下文中的程序计数器到代码区域的入口点。
7.8 缺页故障与缺页中断处置处罚

页面命中完全是由硬件完成的,而处置处罚缺页是由硬件和操纵系统内核协作完成的:
1.处置处罚器生成一个虚拟地址,并将它传送给MMU
2.MMU生成PTE地址,并从高速缓存/主存请求得到它
3.高速缓存/主存向MMU返回PTE
4.PTE中的有用位是0,所以MMU出发了一次异常,通报CPU中的控制到操纵系统内核中的缺页异常处置处罚程序。
5.缺页处置处罚程序确认出物理内存中的捐躯页,如果这个页已经被修改了,则把它换到磁盘。
6.缺页处置处罚程序页面调入新的页面,并更新内存中的PTE
7.缺页处置处罚程序返回到原来的历程,再次执行导致缺页的命令。CPU将引起缺页的虚拟地址重新发送给MMU。由于虚拟页面已经换存在物理内存中,所以就会命中。

图7.8.1缺页说明图

7.9动态存储分配管理

动态储存分配管理利用动态内存分配器(如malloc)来举行。动态内存分配器维护着一个历程的虚拟内存区域,称为堆。分配器将堆视为一组不同巨细的块的集合。每个块就是一个一连的虚拟内存页,要么是已分配的,要么是空闲的。
已分配的块显式地保留为供应用程序利用。
空闲块保持空闲,直到它显式地被应用所分配。
一个已分配的块保持已分配的状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。动态内存分配主要有两种基本方法与计谋:
7.9.1 带边界标签的隐式空闲链表分配器管理
带边界标志的隐式空闲链表的每个块是由一个字的头部、有用载荷、(可能的)额外填充以及一个字的尾部组成。
隐式空闲链表:空闲块通过头部的巨细字段隐含地连接着。分配器遍历堆中所有的块,间接地遍历整个空闲块的集合。
当一个应用请求一个k字节的块时,分配器搜索空闲链表,查找一个足够大的可以放置所请求块的空闲块。分配器有三种放置计谋:初次适配、下一次适配和最佳适配。分配器在面临释放一个已分配块时,可以归并相邻的空闲块,此中一种简单的方式,是利用隐式空闲链表的边界标志来举行归并。
7.9.2 显式空间链表管理
显式空闲链表是将堆的空闲块组织成一个双向链表,在每个空闲块中,都包罗一个前驱与一个后继指针。举行内存管理。在显式空闲链表中。可以采用后进先出的顺序维护链表,将最新释放的块放置在链表的开始处,也可以采用按照地址顺序来维护链表,此中链表中每个块的地址都小于它的后继地址,在这种情况下,释放一个块必要线性时间的搜索来定位符合的前驱。
7.10本章小结

本章介绍了存储器地址空间、段式管理、页式管理,VA 到 PA 的变更、物理内存访问, hello 历程fork时和execve 时的内存映射、缺页故障与缺页中断处置处罚、包罗隐式空闲链表和显式空闲链表的动态存储分配管理。
(第7章 2分)


结论

从 hello.c 到运行完毕,它经历了如下过程:
1. 源代码编写阶段:
程序员利用高级语言 C 编写 hello.c。
内容包罗 main 函数、调用如 printf、sleep、exit 等标准库函数。

  • 预处置处罚阶段(预处置处罚器 cpp):
睁开所有 #include 的头文件。替换所有宏定义、处置处罚条件编译。生成纯粹的标准 C 源代码。

  • 编译阶段:
举行词法分析、语法分析、语义分析。生成中间代码,再优化成汇编代码。输出为 .s 汇编语言文件。

  • 汇编阶段:
将汇编代码翻译为呆板码。生成目的文件 .o,此中包罗代码段(.text)、数据段(.data/.rodata)、符号表等。目的文件是可重定位目的文件(Relocatable Object File),非可执行。

  • 链接阶段:
链接外部库如 libc 中的 printf、sleep、exit 等函数。填充重定位表(relocation entries),解决符号地址。归并多个 .o 文件,生成 可执行 ELF 文件。

  • 程序加载阶段:
Shell fork 出一个子历程。
内核执行 execve() 系统调用,加载 ELF 文件:
映射代码段、数据段、符号表到虚拟地址空间。
设置堆栈、环境变量、命令行参数。
跳转到 _start 入口。

  • 程序运行阶段:
_start 是链接器默认入口,它负责调用初始化函数,然后调用 main。main 执行逻辑分支判断、调用库函数如 printf 输出、sleep 等。执行完毕后,调用 exit 整理资源,最后由 _exit 系统调用闭幕历程。

  • 程序终止阶段:
exit() 会通过 syscall 通知内核,接纳资源(堆栈/打开的文件等)。子历程的退出状态返回给父历程。Shell 吸收返回值并提示用户。

这此中有了我对计算机系统设计与实现的深切感悟:通过这个完整的过程,我看到了一个简单的 C 程序背后,是当代计算机系统工程化的奇迹。,程序执行的每一层都是对底层封装的抽象,程序员只必要编写简单的代码,系统却完成了多个模块的协同运作。
(结论0分,缺失-1分)


附件


1.hello.i:预处置处罚后的文本文件

2.hello.s:对hello.i编译后生成的文件

3.Hello.o:汇编之后的可重定位目的文件

4.Hello:经过链接的可执行目的文件

5.elf.txt:hello.o的elf格式

6.elf1.txt:hello的elf格式

(附件0分,缺失 -1分)


参考文献


为完本钱次大作业你翻阅的册本与网站等


  • Randal E.Bryant . 深入理解计算机系统[M]. 北京:机械出版社,2016.7
  • 潘帅漫笔. 历程的创建与终止.2024.7https://zhuanlan.zhihu.com/p/674143520
  • 水墨不写bug . [Linux]虚拟地址到物理地址的转化,2025.6.https://blog.csdn.net/2301_79465388/article/details/147687696
(参考文献0分,缺失 -1分)


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

王柳

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表