计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 人工智能2+x
学 号
班 级
学 生
指 导 教 师 郑贵滨
计算机科学与技术学院
2023年5月
本文对hello程序的生命周期进行了分析。首先在Linux下完成hello.c源程序的编写,之后使用预处理器cpp对其进行预处理,生成hello.i,再运行编译器(ccl)将其进行翻译生成汇编语言文件hello.s,然后运行汇编器(as)将其翻译成一个可重定位目标文件hello.o,最后运行链接器程序ld将hello.o和系统目标文件组合起来,创建了一个可执行目标文件hello。当shell接收到./hello的指令后开始调用fork函数创建hello进程,execve加载hello进入内存,由CPU控制程序逻辑流的运行,中断,上下文切换和异常控制流的处理。最后结束进程并由父进程进行回收,hello走向“生命”的尽头。
关键词:计算机系统 预处理 编译 汇编 链接 信号 虚拟内存 I/O管理
目 录
2.1 预处理的概念与作用........................................................................... - 5 -
2.2在Ubuntu下预处理的命令.................................................................. - 5 -
2.3 Hello的预处理结果解析...................................................................... - 5 -
3.1 编译的概念与作用............................................................................... - 6 -
3.2 在Ubuntu下编译的命令..................................................................... - 6 -
3.3 Hello的编译结果解析.......................................................................... - 6 -
4.1 汇编的概念与作用............................................................................... - 7 -
4.2 在Ubuntu下汇编的命令..................................................................... - 7 -
4.3 可重定位目标elf格式......................................................................... - 7 -
5.1 链接的概念与作用............................................................................... - 8 -
5.2 在Ubuntu下链接的命令..................................................................... - 8 -
5.3 可执行目标文件hello的格式............................................................. - 8 -
5.4 hello的虚拟地址空间........................................................................... - 8 -
5.5 链接的重定位过程分析....................................................................... - 8 -
5.7 Hello的动态链接分析.......................................................................... - 8 -
第6章 hello进程管理............................................................................. - 10 -
6.1 进程的概念与作用............................................................................. - 10 -
6.2 简述壳Shell-bash的作用与处理流程.............................................. - 10 -
6.3 Hello的fork进程创建过程................................................................ - 10 -
6.4 Hello的execve过程........................................................................... - 10 -
6.6 hello的异常与信号处理..................................................................... - 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 -
第1章 概述
(0.5分)
hello程序的生命周期是从一个高级C语言程序开始的。然而,为了在系统上运行, hello.c程序每条C语句都必须被转化为一系列的低级机器语言指令。P2P(From Program to Process),中文意思是从程序到过程。hello.c经过预处理,编译,汇编,链接生成hello。生成hello后,在shell下输入./hello运行,shell为其运行fork,产生子进程。shell通过evecve加载并执行hello,为其映射虚拟内存,然后进入main执行代码,然后cpu为其分配时间片,执行逻辑控制流。结束后,shell作为父进程回收hello,内核删除相关数据。从0产生,在0结束,这就是From Zero-0 to Zero-0,也就是O2O。
1.2 环境与工具
1.2.1 硬件环境
设备名称 联想Carbon-1
处理器 Intel(R) Core(TM) i7-10710U CPU @ 1.10GHz 1.61 GHz
机带 RAM 16.0 GB (15.8 GB 可用)
设备 ID 5E48AB39-C07D-4031-9DF8-8C7FD8CE38F1
产品 ID 00331-20020-00000-AA463
系统类型 64 位操作系统, 基于 x64 的处理器
笔和触控 没有可用于此显示器的笔或触控输入
1.2.2 软件环境
Windows10 64位,Vmware17,Ubuntu18.04
1.2.3 开发工具
Visual Studio 2019, Codeblocks16.01
1.3 中间结果
文件作用 | |
hello.c | 程序的源代码 |
hello.i | hello.c文件预处理后的文本文件 |
hello.s | hello.i文件编译后的汇编文件 |
hello.o | hello.s汇编后的可重定位目标文件 |
hello | hello.s链接后的可执行文件 |
1.4 本章小结
本章介绍了hello程序P2P,O2O的概念,给出了完成大作业的软硬件环境与开发工具。并给出了hello程序执行的完整过程。列举了中间产物与环节的名称,为下面的环节做好准备。
第2章 预处理
(0.5分)
2.1 预处理的概念与作用
预处理的概念:
预处理器cpp根据以字符#开头的命令,修改原始的C程序。比如hello.c 中第1行的#include <stdio.h>命令告诉预处理器读取系统头文件stdio.h的内容,并把它直接插入程序文本中。同时预处理还可以去除c程序中的注释。预处理的结果就得到了另一个C程序,通常是以.i作为文件扩展名。
图2.1-1hello包含的头文件
预处理的作用:
- 实现宏定义,在预处理阶段用定义的实际数值将宏替换。
- 实现注释,将c文件中的注释从代码中删除。
- 实现头文件引用,将头文件的内容复制到源程序中以实现引用。如#include "FileName"等。该指令将头文件中的定义统统都加入到它所产生的输出文件中,以供编译程序对之进行处理。
- 实现条件编译,通过预处理可以实现部分代码的在某些条件下的选择性编译。如#ifdef,#ifndef,#else,#elif,#endif等。 这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。
- 实现特殊符号的使用。如处理#line、#error、#pragma等。例如在源程序中出现的LINE标识将被解释为当前行号(十进制数),FILE则被解释为当前被编译的C源程序的名称。预编译程序对于在源程序中出现的这些串将用合适的值进行替换。
2.2在Ubuntu下预处理的命令
预处理命令:gcc hello.c -E -o hello.i
图2.2-1 Ubuntu下的预处理命令
图2.2-2 同目录下新生成的hello.i文件
2.3 Hello的预处理结果解析
在运行预处理命令后,我们可以看到当前加载的目录下产生了新文件hello.i。打开hello.i文件,发现它是一个纯文本文件。它的内容比hello.c多得多:hello.c包含的头文件中的内容被复制在了.i文件中。同时,.c文件中的注释行被删除了。
图2.3-1 hello.i文件的部分内容
文中出现了大量extend语句。其作用是在别处定义的函数,在该目录下使用。
图2.3-2 hello.i文件的部分内容
2.4 本章小结
本章介绍了预处理的概念,Linux下的预处理操作指令,对hello.c经过预处理生成的hello.i进行了分析。
第3章 编译
(2分)
3.1 编译的概念与作用
编译的概念:在编译阶段,编译器(ccl)检查是否有语法错误。检查无误后,编译器将.i文本文件翻译成.s汇编语言文件。
编译的作用:编译过程把高级程序代码转变成了易于机器阅读的汇编语言。汇编语言程序为不同高级语言的不同编译器提供了通用的输出语言,是一种低级机器语言。编译将高级语言转化为汇编语言能够检测代码的正确性,并为接下来将汇编语言生成机器可识别的机器码做准备。
3.2 在Ubuntu下编译的命令
Ubuntu下编译的命令:gcc -S hello.s -o hello.i
图3.2-1 编译指令
3.3 Hello的编译结果解析
图3.3-1 汇编代码
图3.3-2 汇编代码
3.3.1伪指令
所有以’.’开头的行都是指导汇编器和链接器工作的伪指令。这些指令最后不会执行。伪指令的种类有很多:
.file:声明源文件
.text:代码节
.section: .rodata:只读代码段
.align 8:数据或者指令的地址对齐方式
.string:声明一个字符串(.LC0,.LC1)
.global:声明全局变量(main)
.type:声明一个符号是数据类型还是函数类型
3.3.2数据
(1)字符串
LC0指向的是字符串:“用法: Hello 学号 姓名 秒数!”。之所以没有出现中文,主要是因为字符串用UTF-8格式编码。
图3.3-3 字符串“用法: Hello 学号 姓名 秒数!”
(2)局部变量i
Main函数声明了局部变量i,由汇编代码可知,i被赋初值0。由于i是int型,占四个字节,编译器进行编译时将变量放置在堆栈中,通过rbp的相对偏移4位来访问。For循环每次i加1,使用操作addl。
图3.3-4局部变量保存在堆栈中
图3.3-4 i自增
(3)数组元素
c代码中数组访问有argv[1]、argv[2]、argv[3],由于数组是地址中连续的存储空间,故在汇编代码中访问这三个元素,使用数组首地址加偏移量的方式。
图3.3-5argv,argc
(4)参数传递
符号数argc和字符型数组指针argv与局部变量i一样,被储存在栈上(开始由%edi,%rsi传递),并通过栈偏移访问。
图3.3-6argv,argc
3.3.3操作
(1)类型转换
hello.c中的sleep(atoi(argv[3]))语句调用atoi()的类型转换,atoi()函数将字符串转化成int,hello.s中call语句调用atoi函数强制处理该类型转换。
图3.3-7使用atoi函数进行类型准换
(2)比较指令
在.L3中,比较立即数7与局部变量i的大小,如果i小于等于7,跳转到.L4。
图3.3-8使用cmp指令
(3)跳转指令(条件转移)
Hello.s中存在大量的跳转指令,以下是一些常见的条件转移指令。
图3.3-9常用跳转指令
比较argc与4的大小,小于跳转到L2。
图3.3-10跳转指令
先给i赋初值0,再跳转到L3执行。
图3.3-11跳转指令
比较i与7的大小,当i小于等于7时,跳转到L4。
图3.3-11跳转指令
(4)函数调用
在hello.c中多处涉及函数操作,调用另一个函数来执行当前任务,如printf、atoi、getchar、sleep、exit等。在hello.s中对此的处理均是先完成参数传递(有参数的情况下),然后在用call语句转到相应函数的入口处执行。
getchar:(无传递参数)
图3.3-11getchar调用
Printf: %rdi传递:
图3.3-12 printf调用
Atoi:用%rdi:
图3.3-13 Atoi调用
Exit:%edi传递:
图3.3-14 exit调用
Sleep:用%edi传递:
图3.3-15 sleep调用
leave指令是函数返回时一个非常重要的指令,其作用就是将被调用函数的栈帧抹除,实现过程如下图所示:
图3.3-16 leave指令
本章小结
本章介绍了hello.i文件被编译为汇编代码的过程,同时对编译结果进行解析。编译器先对原始代码进行错误检查,在确认没有错误之后,编译器会按照一定的规范生成与原始代码等价的汇编代码。在这个过程中,编译器可能会按照自己的理解,对原始代码结构和数据做出调整。
第4章 汇编
(2分)
4.1 汇编的概念与作用
概念:汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。汇编器(as)将汇编程序翻译成机器语言指令,把这些指令打包成可重定位目标程序的格式,并将结果保存在.o 目标文件中。.o 文件是一个二进制文件,它包含程序的指令编码。
作用:将.s文件生成.o格式机器码,使其能被链接器ld链接生成可执行文件。
4.2 在Ubuntu下汇编的命令
汇编命令:gcc -c hello.s -o hello.o
图4.2-1 汇编指令
图4.2-2 hello.s汇编生成hello.o
4.3 可重定位目标elf格式
4.3.1 ELF格式的具体内容
图4.3.1‑1 ELF格式的具体内容
4.3.2 ELF头
打开ELF 的指令:readelf -a hello.o
可以看到ELF文件从ELF头开始, ELF头以一个16个字节的序列开始,这个序列描述了生成该文件的系统的字大小和字节顺序。该ELF头显示,以小端码机器存储,文件类型为可重定位目标文件。
图4.3.2‑1 ELF头的具体内容
4.3.3 节头表
节头部表存有十三个节的相关信息,例如节名称,类型,读写权限,长度等等。
图4.3.3‑1 节头表的具体内容
4.3.4 符号表
每个可重定位目标模块m都有一个符号表。它包含m定义和引用的符号信息。包括这三种符号:由模块m定义并能被其他模块引用的全局符号。由其他模块定义并被模块m引用的全局符号。只被模块m定义和引用的局部符号。符号表是由汇编器构造的,使用编译器输出到汇编语言.s文件中的符号。
图4.3.4‑1 符号表的具体内容
4.3.5 重定位节
重定位节包含了.text文件中需要重定位的信息,在链接器将目标文件与其它文件进行链接时需要修改这些信息,可执行文件中不包含重定位节。
图4.3.5‑1 重定位节具体内容
4.4 Hello.o的结果解析
命令:objdump -d -r hello.o
图4.4‑1 hello.o反汇编内容
图4.4‑2 hello.s内容
反汇编代码和汇编代码在指令格式上非常相似,但在以下几个方面存在不同:
- 跳转指令和函数调用等指令,在反汇编代码中表示为对应地址的偏移,而在hello.s中直接表示为函数名或定义的符号。
- 在反汇编代码中,立即数是16进制显示的,而在hello.s中立即数是以十进制显示的。
- 反汇编代码除了汇编代码之外,还显示了机器代码,在左侧用16进制表示。
- 反汇编的语言mov、add、push后无其他字符如b、w等。
- 反汇编语言通过pc相对寻址,通过rip+x的值进行访问,未重定位,故用0占位。在汇编语言中,通过.LC0+rip进行访问。
4.5 本章小结
本章首先介绍了汇编的概念和作用,接着通过Linux汇编指令,对hello.s文件进行汇编,生成ELF可重定位目标文件hello.o。接着使用readelf工具,通过设置不同参数,查看了hello.o的ELF头、节头表、可重定位信息和符号表等,通过分析理解可重定位目标文件的内容。最后与hello.s比较,分析不同,并说明机器语言与汇编语言的一一对应关系。值得注意的是,hello.o还是不能直接运行,它还不是可执行程序,需要进一步的链接来生成可执行程序。
第5章 链接
(1分)
链接就是通过链接器将可重定位目标程序(hello.o)与printf.o等库函数(包括共享库)链接起来,生成可重定位目标程序(hello)。链接包括动态链接,静态链接。链接时间分为在第一次加载时链接,已经开始运行后链接。
链接的作用:链接可以在编译、汇编、加载和运行时执行。链接方便了模块化编程。链接器在软件开发过程中扮演着一个关键的角色,因为它们使得分离编译成为可能。我们不用将一个大型的应用程序组织为一个巨大的源文件,而是可以把它分解为更小、更好管理的模块,可以独立地修改和编译这些模块。当我们改变这些模块中的一个时,只需简单地重新编译它,并重新链接应用,而不必重新编译其它文件。
链接命令: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 hello.o进行链接
5.3 可执行目标文件hello的格式
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
5.3.1 hello ELF头
图5.3.1‑1 hello的ELF头内容
可执行文件的ELF头与可重定位目标文件的ELF头有以下几个不同:
- 节头的数量产生了变化。
- 文件的类型不同,可执行文件的类型不再是REL而是EXEC。
- 程序的入口点不一样,因为连接上了库文件,使得main函数不再是从0x0开始。同理节头的开始位置也发生了变化。
5.3.2 hello节头表
图5.3.2‑1 hello的节头表内容(部分)
hello中经过重定位每个节的地址不再是0,而是根据自身大小加上对齐规则计算的偏移量。这与hello.o不同,可执行文件中经过重定位每个节的地址总是0。
5.3.3 hello符号表
图5.3.3‑1 hello的符号内容(部分)
通过观察可以发现,可执行文件(hello)比hello.o中多出了.dynym节。这里面存放的是通过动态链接解析出的符号。
5.3.4 hello重定位节
图5.3.4‑1 hello的重定位节
由于链接了别的头文件,重定位节的偏移量与hello.o完全不同。
5.4 hello的虚拟地址空间
根据节头表的信息,我们可以知道ELF是从0x400000开始的,如图5.4-1所示。查看edb,可以看出hello的虚拟地址空间开始于0x400000,结束于0x40ff0
图5.4.‑1 elf的虚拟地址空间
图5.4.‑2 .text的虚拟地址空间
5.5 链接的重定位过程分析
与hello.o相比,hello中多了很多函数,这些都是在hello.c中没有定义却直接使用的函数,这些函数定义在共享库中,在链接时完成了符号解析和重定位,如printf、sleep等。
其次,在hello.o中call、jmp指令后紧跟着的是相对地址,而hello中紧跟的是虚拟内存的确定地址。
图5.5-1 链接的重定位过程分析
5.6 hello的执行流程
程序名称 | 地址 |
ld-2.32.so!_dl_start | 0x7f96ed2e1ea0 |
ld-2.32.so!_dl_init | 0x7f96ed2f0630 |
hello! _start | 0x400500 |
libc-2.32.so! __libc_start_main | 0x7fbdf0cccab0 |
hello!puts@plt | 0x401030 |
hello!exit@plt | 0x401060 |
hello!printf@plt | 0x401050 |
hello!sleep@plt | 0x401070 |
hello!getc@plt | 0x401080 |
libc-2.32.so!exit | 0x7f66b7b9e120 |
表5.6-1 hello的执行流程
5.7 Hello的动态链接分析
以GOT.PLT为例:
dl_start运行前:
图5.7-1 运行dl_start之前.GOT.PLT表
调用dl_start之后:
图5.7-2 运行dl_start之后.GOT.PLT表
人类利用代码段和数据段的相对位置不变的原则计算正确地址。对于库函数而言,需要plt、got合作才能计算。plt初始存的是一批代码,跳转到got所指示的位置,然后调用链接器。初始时got里面存的都是plt的第二条指令,随后链接器修改got,下一次再调用plt时,指向的就是正确的内存地址。
本章学习了链接的概念,通过对比hello.o和hello,理解了链接的作用和链接是怎样符号解析、重定位的。这是程序生成可执行文件的最后一步,也是将大型程序项目分解成小模块的关键所在。
第6章 hello进程管理
(1分)
6.1 进程的概念与作用
进程就是一个执行中的程序的实例。系统中每个程序都运行在某个进程的上下文中。进程提供给应用程序两个假象:
独立的逻辑控制流:每个程序似乎独占CPU。(内核通过上下文切换机制来实现)私有的空间地址:每个程序似乎独占内存。(由内核的虚拟内存机制来实现)
6.2 简述壳Shell-bash的作用与处理流程
shell的作用:shell作为UNIX的一个重要组成部分,是它的外壳。也是用户与UNIX系统的交互作用界面。Shell是一个命令解释程序。除此,它还是一个高级程序设计语言。用shell编写的程序称为shell过程。shell的一项主要功能是在交互方式下解释从命令行输入的命令。shell的另一项重要功能是制定用户环境,这通常在shell的初始化文件中完成。shell还能用作解释性的编程语言。
处理流程:
1、从终端读入输入的命令。
2、将输入字符串切分获得所有的参数
3、如果是内置命令则立即执行
4、否则调用相应的程序执行
5、shell 应该接受键盘输入信号,并对这些信号进行相应处理
6.3 Hello的fork进程创建过程
输入./hello后,父进程会用fork函数创建子进程hello。新创建的子进程几乎和父进程相同。子进程得到和父进程相同但独立的一份副本,包括代码和数据段,堆,共享库和用户栈。由于子进程有和父进程任何打开文件描述符相同的副本,所以父进程打开的文件,子进程可以读写。父进程和子进程最大不同是它们有不同pid。
fork是并发运行的。fork执行一次,返回两次,第一次是在父进程中,返回子进程pid,第二次是在子进程中,返回0。
图6.3-1 一个fork函数的例子
6.4 Hello的execve过程
Execve函数简单来说,就是加载并运行程序。如果成功,调用1次,返回0次;如果失败返回-1。详细步骤如下:
- 删除已存在的用户区域。
- 映射私有区域,所有新区域都是私有,写时复制.bss区域,请求二进制0,并映射到匿名文件中。代码和数据区域被映射到a.out文件中。
- 映射共享区域。同时要删除原进程页表和Um-area-struct链表,创建新的列表Vm-area-struct 链表。
- 将栈设定为请求二进制文件0的区域。映射到匿名文件且初长为0。
6.5 Hello的进程执行
调度:在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程,这种决策就叫做调度,是由内核中称为调度器的代码处理的。当内核选择一个新的进程运行,我们说内核调度了这个进程。在内核调度了一个新的进程运行了之后,它就抢占了当前进程,并使用上下文切换机制来将控制转移到新的进程。
以printf为例:
初始时,程序在hello进行,处于用户模式,当调用printf时,其使用上下文切换机制,把控制转给printf程序,且进入到内核模式。过一段时间后,发送中断信号,内核切换到hello中,重新变成用户模式。
图6.5-1 进程时间片示意图
图6.5-2 进程上下文切换
6.6 hello的异常与信号处理
6.6.1 异常处理简述
异常可以分为四类:中断、陷阱、故障和终止。
类别 | 原因 | 异步/同步 | 返回行为 |
中断 | 来自I/O设备的信号 | 异步 | 总是返回到下一条指令 |
陷阱 | 有意的异常 | 同步 | 总是返回到下一条指令 |
故障 | 潜在可恢复的错误 | 同步 | 可能返回到当前指令 |
终止 | 不可恢复的错误 | 同步 | 不会返回 |
表6.6.1-1 异常分类
处理方式:
中断
图6.6.1-1 中断
陷阱:
图6.6.1-2 陷阱
故障:
图6.6.1-3 故障
终止:
6.6.2 键盘向hello发送信号
- 乱按
可以看到,虽然乱按不会影响程序的运行,但是会在程序运行结束后对shell发送许多无效指令。
图6.6.2-1 乱按
- Crtl+C
Hello运行时,输入Crtl+C,程序立即停止运行。
图6.6.2-2 Crtl+C
- Crtl+Z
输入Crtl+Z,程序被放入后台并暂停运行。
图6.6.2-3 Crtl+Z
- Ps命令
输入ps命令后,我们可以看到当前在运行的进程及其PID
图6.6.2-4 ps命令
- Jobs命令
图6.6.2-5 jobs命令
- pstree命令
通过pstree命令,我们可以得出所有进程的父子关系。
图6.6.2-6 pstree命令(部分)
- fg命令
fg命令可以让暂停的进程继续工作。
图6.6.2-7 fg命令
- kill命令
kill可以杀死进程。
图6.6.2-8 kill命令
6.7本章小结
本章我们了解了进程的相关概念和作用,了解了shell的作用,和hello如何调用fork,evecve函数。并通过对hello的实际操作,了解了hello对异常和信号的处理的操作。
第7章 hello的存储管理
( 2分)
7.1 hello的存储器地址空间
物理地址(physical address)用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相相应。是指出目前CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址。如果启用了分页机制,那么线性地址会使用页目录和页表中的项变换成物理地址。如果没有启用分页机制,那么线性地址就直接成为物理地址了。
线性地址(Linear Address)是逻辑地址到物理地址变换之间的中间层。程式代码会产生逻辑地址,或说是段中的偏移地址,加上相应段的基地址就生成了一个线性地址。如果启用了分页机制,那么线性地址能再经变换以产生一个物理地址。若没有启用分页机制,那么线性地址直接就是物理地址。
虚拟地址(Virtual Address)虚拟内存为每个程序提供了一个大的、一致的和私有的地址空间。其每个字节对应的地址成为虚拟地址。虚拟地址包括 VPO(虚拟页面偏移量)、VPN(虚拟页号)、TLBI(TLB 索引)、TLBT(TLB 标记)。
7.2 Intel逻辑地址到线性地址的变换-段式管理
一个逻辑地址由两部分组成,段标识符,段内偏移量。段标识符是一个16位长的字段组成,称为段选择符,其中前13位是一个索引号。可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段。
转换的具体过程:首先,给定一个完整的逻辑地址,它的格式如下:[段选择符:段内偏移地址]。然后,看段选择符的T1=0还是1,知道当前要转换是全局段描述符表(存储全局的段描述符),还是局部段描述符表(存储进程自己的段描述符),再根据相应寄存器,得到其地址和大小,得到一个数组。最后,拿出段选择符中前13位,查找到对应的段描述符,进而找到基地址,base+offset得到线性地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
线性地址(也就是虚拟地址 VA)到物理地址(PA)之间的转换通过分页机制完成。而分页机制是对虚拟地址内存空间进行分页。 (每个页面的大小为P = 2p字节)。
而分页机制的作用就是通过将虚拟和物理内存分页,并且通过MMU建立起相应的映射关系,可以充分利用内存资源,便于管理。一般来说一个页面的标准大小是4KB,有时可以达到4MB。而且虚拟页面作为磁盘内容的缓存,有以下的特点:DRAM缓存为全相联,任何虚拟页都可以放置在任何物理页中需要一个更大的映射函数,不同于硬件对SRAM缓存更复杂精密的替换算法太复杂且无限制以致无法在硬件上实现DRAM缓存总是使用写回,而不是直写。
虚拟页面地集合被分为三个不相交的子集:已缓存、未缓存和未分配。
图7.3-1 虚拟页映射到物理页
7.4 TLB与四级页表支持下的VA到PA的变换
使用四级页表的地址翻译步骤:
如果TLB未命中,MMU会向页表中查询,CR3确定第一级页表的起始地址,VPN1确定第一级页表的偏移量,查询出PTE,如果在物理内存中,且权限符合,则确定第二级页表的起始地址,以此类推,最终在第四级页表中查询到PPN与VPO组合成PA。
图7.4-1 四级页表
7.5 三级Cache支持下的物理内存访问
首先,根据物理地址的s位组索引索引到L1 cache中的某个组,然后在该组中查找是否有某一行的标记等于物理地址的标记并且该行的有效位为 1,若有,则说明命中,从这一行对应物理地址 b 位块偏移的位置取出一个字节,若不满足上面的条件,则说明不命中,需要继续访问下一级 cache,访问的原理与L1相同,若是三级 cache 都没有要访问的数据,则需要访问内存,从内存中取出数据并放入cache。
图7.5-1 三级cache
7.6 hello进程fork时的内存映射
新进程现在的虚拟内存刚好和调用fork时的虚拟内存相同。当这两个进程中的任一个后来进行写操作的时候,写时的复制机制就会创建新页面。这样就为每个进程保持了私有地址空间新的抽象概念。
当故障处理程序注意到保护异常进程试图在写时复制区域中的一个页面读写,它就会在物理内存中创建这个页面的一个新副本,更新页表条目指向这个新的副本,然后恢复这个页面的可写权限。
图7.6-1 fork内存映射
7.7 hello进程execve时的内存映射
Execve函数在shell中加载并运行包含在可执行目标文件hello中的程序,用hello程序有效的替代了当前程序。
加载并运行hello分为以下四个步骤:
1. 删除已存在的用户区域。删除shell虚拟地址的用户部分中的已存在的区域结构。
2. 映射私有区域。为hello的代码、数据、bss 和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello 文件中的.text和.data 区。bss 区域是请求二进制零的,映射到匿名文件,其大小包含在hello 中。栈和堆区域也是请求二进制零的,初始长度为零。
3. 映射共享区域。如果hello程序与共享对象(或目标)链接,比如标准C 库libc. so, 那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。
4. 设置程序计数器(PC) 。execve 做的最后一件事情就是设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。
图7.7-1 加载器是如何映射用户地址空间的区域的
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本章小结
本章介绍了hello的存储管理机制。介绍了物理地址与虚拟地址的概念,以及它们如何进行转换。并且指出了转换中重要的CPU单元——内存管理单元(MMU),以及常用的转换工具——页表(VP),快表(TLB)。掌握这些概念对内理解存转换有很大的帮助。
结论
(0分,必要项,如缺失扣1分,根据内容酌情加分)
Hello经历的历程:
编辑:在IDE中写好hello.c的c语言代码。
预处理:预处理器(cpp)把hello.c处理成hello.i文件。
编译:通过编译器(ccl)把hello.i文件编译为hello.s汇编语言文件。
汇编:汇编器(as)将hello.s文件翻译成可重定位目标程序hello.o。
链接:链接器(ld)将hello.o与其他目标文件链接(如printf.o等),生成可执行文件hello。
运行:在终端输入./hello 2021113092 lry ,开始运行hello程序
创建新进程:fork函数创建一个新进程。
加载:使用execve函数,将hello映射到虚拟内存中。
执行:虚拟页翻译成物理页。
信号处理:hello运行时,输入ctrl+c,ctrl+z,发送信号给hello,进行调度。
终止:输出八遍字符串,等待用户输入,按下回车后,hello终止。
回收:终止后发送SIGCHLD信号给shell,shell推出,进行回收。
感悟心得:时光荏苒,计算机系统的学习暂时告一段落了。回想一路走来,学了大计基,c语言,数据结构,感觉虽然学了不少的课,但是还是有很多底层的东西模棱两可,一个C语言程序是如何执行的?汇编代码是如何生成的?这些问题,都在计算机系统这门课给出了答案。我不由赞叹计算机系统的设计者的高超智慧。感谢郑老师让我们体验了CMU原汁原味的《深入理解计算机系统》,这是其他很多学校无法做到的。千言万语汇成一句话:好书,好课,好老师!相信这段经历会让我终生难忘!
附件
hello源代码 | |
hello.i | 预处理之后的文本文件 |
hello.s | hello的汇编代码 |
hello1.s | hello.o的反汇编代码 |
hello2.s | hello的反汇编代码 |
hello.o | hello的可重定位文件 |
hello | hello的可执行文件 |
hello.elf | hello的elf文件 |
hello1.elf | hello.o的elf文件 |
参考文献
[1] 大卫R.奥哈拉伦,兰德尔E.布莱恩特. 深入理解计算机系统[M]. 机械工业出版社,2016.
[2] csapp大作业_weixin_45714360的博客-CSDN博客
[3] 程序人生-Hello’s P2P_王狗王的博客-CSDN博客
[4] https://blog.csdn.net/x1299135087/article/details/124881230
[5] https://blog.csdn.net/Michael_Byrant/article/details/124868586