题 目程序人生-Hello’s P2P
专 业 计算机
学 号 120L020109
班 级2003003
学 生 张异凡
指 导 教 师史先俊
结合计算机系统的相关知识,分析了一篇文章hello从编写成功到运行成功的各个阶段,包括预处理、编译、汇编、链接、加载运行、管理存储、IO管理等内容可以对计算机系统的相关知识有更深入的了解。
计算机系统;预处理;编译;汇编;链接;流程管理;存储管理;IO管理
1.1 Hello简介
1.2 环境与工具
1.3 中间结果
1.4 本章小结
2.1 预处理的概念和作用
2.2在Ubuntu下预处理的命令
2.3 Hello的预处理结果解析
2.4 本章小结
3.1 编译的概念与作用
3.2 在Ubuntu下编译的命令
3.3 Hello的编译结果解析
3.4 本章小结
4.1 汇编的概念与作用
4.2 在Ubuntu下汇编的命令
4.3 可重定位目标elf格式
4.4 Hello.o的结果解析
4.5 本章小结
5.1 链接的概念与作用
5.2 在Ubuntu下链接的命令
5.3 可执行目标文件hello的格式
5.4 hello的虚拟地址空间
5.5 链接的重定位过程分析
5.6 hello的执行流程
5.7 Hello的动态链接分析
5.8 本章小结
6.1 进程的概念与作用
6.2 简述壳Shell-bash的作用与处理流程
6.3 Hello的fork进程创建过程
6.4 Hello的execve过程
6.5 Hello的进程执行
6.6 hello的异常与信号处理
6.7本章小结
7.1 hello的存储器地址空间
7.2 Intel逻辑地址到线性地址的变换-段式管理
7.3 Hello的线性地址到物理地址的变换-页式管理
7.4 TLB与四级页表支持下的VA到PA的变换
7.5 三级Cache支持下的物理内存访问
7.6 hello进程fork时的内存映射
7.7 hello进程execve时的内存映射
7.8 缺页故障与缺页中断处理
7.9动态存储分配管理
7.10本章小结
8.1 Linux的IO设备管理方法
8.2 简述Unix IO接口及其函数
8.3 printf的实现分析
8.4 getchar的实现分析
8.5本章小结
第1章 概述
1.1 Hello简介
P2P:From Program to Process
利用C语言在任意的编辑软件中编写Hello.c文件形成了program,经过code:blocks等IDE进行预处理生成hello.i文件,再通过编译生成hello.s,通过汇编生成可重定位目标文件hello.o,通过链接后生成可执行文件Hello,在shell中运行Hello,shell用fork为Hello创建新进程,用exeve为进程加载函数,再为其分配虚拟内存,最终能成功运行,成为Process,即P2P。
020:From Zero-0 to Zero-0
起初并没有hello的进程,再shell中运行Hello,通过fork为Hello创建新进程,用exeve为进程加载函数,再为其分配虚拟内存。实现从无到有。待Hello运行完毕后,再通过父进程利用waitpid函数对其进行回收,并接受其结束的信号,若父进程已结束,则由init进程对其进行回收,hello的内存将会被释放回收,不留下痕迹,实现了From Zero-0 to Zero-0。
1.2 环境与工具
AMD Ryzen 5 4600H with Radeon Graphics 3.00 GHz;16G RAM;512GB SSD
2. 软件环境
Windows11 64 位;Vmware 15 Pro;Ubuntu 20.04.4 64 位
Visual Studio 2010 64 位以上;CodeBlocks 64 位;vi/vim/gedit+gcc
3. 开发工具
Visual Studio 2022 64 位;CodeBlocks 64 位;vim;gdb;edb
1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
文件名称 |
作用 |
hello.c |
c语言程序源文件 |
hello.i |
预处理后生成的文本文件 |
hello.s |
编译后产生的汇编语言文件 |
hello.o |
汇编后产生的二进制可重定位目标文件 |
hello |
链接后产生的可执行文件 |
hello_o_elf.txt |
Hello.o的ELF格式文件的文本 |
hello_o_obj.txt |
Hello.o的反汇编文件的文本 |
hello_elf.txt |
hello的ELF格式文件的文本 |
hello_obj.txt |
Hello的反汇编文件的文本 |
1.4 本章小结
简要介绍了hello程序P2P和O2O的特点,介绍了对hello进行分析的硬件环境,软件环境及开发工具,说明了编写该文章所产生的中间文件的名称及作用。
第2章 预处理
2.1 预处理的概念与作用
预处理的概念:预处理一般是指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程。在汇编之前,将程序中的预编译指令在源文件的基础上进行处理,生成.i文件。
预处理的作用:预处理器(cpp)根据以字符#开头的预编译处理指令,修改原始的C程序。如将#include指令指示的文件加入到对应的位置,将#define删除,并将对应的所有宏定义替换等等,最终生成.i文件。
2.2在Ubuntu下预处理的命令
Ubuntu下的预处理命令:gcc -E hello.c -o hello.i
图2.2.1
成功生成hello.i文件
2.3 Hello的预处理结果解析
Hello.i文件中的main函数与之前相同,而main函数前的三千多行即为之前的三个头文件的内容。
图2.3.1
图2.3.2
从hello.i文件的开头可以看到,hello.c中的注释也被删除了。
图2.3.3
2.4 本章小结
对源文件进行预处理后,源文件中的注释被删除,对应的头文件内容也被加入程序中。使文件在后续编译中更好的执行。
第3章 编译
3.1 编译的概念与作用
编译的概念:通过编译器将预处理后产生的高级语言程序(.i文件)转变为汇编语言文件(.s文件)
编译的作用:通过语法分析和词法分析将人类更易理解的高级语言转化为机器更容易识别的汇编语言,并且可以对程序进行一定程度的优化。
3.2 在Ubuntu下编译的命令
编译指令:gcc -S hello.i -o hello.s,生成了hello.s文件。
图3.2.1
3.3 Hello的编译结果解析
3.3.1 文件声明
图3.3.1.1
源文件:.file "hello.c"
代码段:.text
只读数据段:.section .rodata
对齐格式:.align 8
3.3.2 数据
(1)数字常量:
程序用到的常量由0,1,2,3,4,8储存在.text代码段中。
(2)字符串常量:
hello用到的两个字符串常量为两个printf函数中的内容,第一个字符串为LC0,其中的中文字符用UTF-8用三个字节表示。第二个字符串LC1.
图3.3.2.1
(3)Main函数参数:
Main函数的两个参数为int argc和字符型指针数组char *argv[],分别表示用户输入的参数个数,以及参数的内容,分别储存在%edi和%rsi中。
- 局部变量:
For循环用到了局部变量i。
3.3.3 赋值
局部变量赋值:
将i初始化为0.可以看到在栈中为i分配了4个字节的空间。
图3.3.3.1
3.3.4 算术运算
在for循环中每次为i加一。
图3.3.4.1
3.3.5 关系操作
在if语句中判断agrc与4的大小。其中argc的值被存入-20(%rbp)的位置,与4比较。
图3.3.5.1
在for语句中判断i与8的大小。(此处编译器将其与7比较大小,小于等于时再次循环,与源函数中小于8时循环继续意义相同。)
图3.3.5.2
3.3.6数组/指针操作
Hello程序中由char *arg[]指针数组,由之前的分析得到char *arg[]被存储在%rsi被存入-32(%rdx)的位置。通过先将-32(%rdx)存入%rax中,在将其分别加8,16,24得到argv[1],argv[2],argv[3]的地址。在取其地址值,赋值到%rdi,%rsi,%rdx中,作为参数传入对应的函数。
图3.3.6.1
3.3.7 控制转移
Hello程序中一共有两次控制转移,一次是if语句,一次是for语句。
如图所示,if语句将argc与4进行比较,若等于,则跳转到for语句段,否则,调用printf函数输出程序用法。
For语句将i初始化后,判断其与7的大小,若小于等于,则跳转到.L4进入循环,否则,调用getchar函数,并在之后return。
图3.3.7.1
3.3.8 函数操作
Hello程序中涉及的函数有main函数,printf函数,exit函数,atoi函数,sleep函数,getchar函数.
- main函数
图3.3.8.1
图3.3.8.2
- printf函数
传入的参数是字符串LC0,函数作用是打印传入的字符串参数。
图3.3.8.3
- exit函数
传入的参数是进程返回的值,函数作用是结束进程。
图3.3.8.4
- atoi函数和sleep函数
Atoi函数将字符型变量转为整型变量,sleep函数传入整型,则让程序停止对应的秒数。
图3.3.8.5
- getchar函数
函数没有参数,其返回值,程序也未使用,函数用处是读取键盘的一次输入。
图3.3.8.6
3.4 本章小结
利用linux下的指令对预处理后的hello.i文件进行了编译,得到了hello.s的汇编语言文件,并分析了源文件中的各个部分在汇编语言中的实现。
第4章 汇编
4.1 汇编的概念与作用
注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。
汇编的概念:通过汇编器将汇编代码转化为机器语言。
汇编的作用:汇编器(as)将hello.s翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序(relocatable object program)的格式,并将结果保存在目标文件hello.o。
4.2 在Ubuntu下汇编的命令
Linux下汇编的指令:gcc -c hello.s -o hello.o
图4.2.1
4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
将hello.o的elf格式文件存入hello_o_elf.txt中。使用指令readelf -a hello.o > hello_o_elf.txt
图4.3.1
- elf头
Elf头描述了生成该文件的系统的字的大小和字节顺序,以及系统架构等一系列信息。
图4.3.2
- 节头部表
节头部表描述了不同节的名称,类型,大小,地址,对齐方式和偏移量。
.text节:以编译的机器代码。
.rela.text节:一个.text节中位置的列表。
.data节:已初始化的静态和全局C变量。
.bss节:未初始化的全局和静态C变量,以及所有被初始化为0的全局或静态变量。在目标文件中这个节不占据实际的空间,它仅仅是一个占位符。
.rodata节:存放只读数据,例如printf中的格式串和开关语句中的跳转表。
.comment节:包含版本控制信息。
.note节:注释节详细描述。
.eh_frame节:处理异常。
.rela.eh_frame节:一个.eh_frame节中位置的列表。
.shstrtab节:该区域包含节区名称。
.symtab节:一个符号表,它存放在程序中定义和引用的函数和全局变量的信息。
.strtab节:一个字符串表,其内容包括.symtab和.debug节中的符号表,以及节头部的节名字。
.symtab节:本节用于存放符号表。
图4.3.3
- 重定位节
表述了各个段引用的外部符号等,在链接时,需要通过重定位节对这些位置的地址进行修改。链接器会通过重定位条目的类型判断该使用什么养的方法计算正确的地址值,通过偏移量等信息计算出正确的地址。
本程序需要重定位的信息有:.rodata中的模式串,puts,exit,printf,atoi,sleep,getchar这些符号。
图4.3.4
- 符号表
符号表存放在程序中定义和引用的函数和全局变量的信息。可以看到,hello程序中引用的函数名,如exit,printf,atoi等。
图4.3.5
4.4 Hello.o的结果解析
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
将objdump产生的数据存入hello_o_obj.txt中,便于分析。
可以看到机器语言中指令和寄存器及数字均是由二进制数表示的,而objdump产生的文件中左侧为机器语言,而右侧为其对应的汇编语言。
图4.4.1
图4.4.2
通过分析由hello.o反汇编出的汇编语言与hello.s中的汇编语言进行对比,可以得到以下三种区别。
- 操作数的区别
经汇编后,汇编语言中的操作数如立即数,偏移量,地址等均采用16进制,而非hello.s文件中的十进制。
- 分支转移的区别
经过汇编后,jump类的语句跳转的地址是基址+偏移量的模式,而不是hello.s中通过label进行跳转
- 函数调用的区别
经过汇编后,call进行函数调用其后调用的是函数相对main函数的偏移量,而不是hello.s中通过函数名称进行调用。
4.5 本章小结
在linux中利用指令将hello.s文件汇编为hello.o文件,并且通过readelf指令查看了文件的elf格式,观察了文件的有关信息。又利用objdump指令对hello.o进行反汇编,通过与hello.s中汇编语言的比较,得出了汇编后机器语言的一些特点,同时也感受到了机器语言与汇编语言的映射关系。
第 5 章 链接
5.1 链接的概念与作用
注意:这儿的链接是指从 hello.o 到hello生成过程。
链接的概念:链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。
链接的作用:提供了一种模块化的方式,可以将程序编写为一个较小的源文件的集合,且实现了分开编译更改源文件,从而减少整体文件的复杂度与大小,增加容错性,也方便对某一模块进行针对性修改。
5.2 在Ubuntu下链接的命令
使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件
链接的命令: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 /usr/lib/gcc/x86_64-linux-gnu/9/crtbegin.o hello.o -lc /usr/lib/gcc/x86_64-linux-gnu/9/crtend.o /usr/lib/x86_64-linux-gnu/crtn.o -z relro -o hello
图5.2.1
5.3 可执行目标文件hello的格式
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
将hello的elf格式文件存入hello_elf.txt中。
图5.3.1
- ELF头:hello文件的elf头与hello.o文件的elf头与提供的内容基本相同,而hello文件的elf头将文件类型改为了可执行文件,并且为程序分配了入口点地址和程序头起点。
图5.3.2
- 节头:
节头部表展示了文件中各个节的名称,大小,类型,地址和偏移量等信息,而链接后的各个节的地址信息更加具体完善。
图5.3.3
- 程序头:程序头部分是一个结构数组,描述了系统准备程序执行所需的段或其他信息。
图5.3.4
- 重定位节:
链接后,重定位部分的各个符号的偏移量更加明确,类型也更加具体,符号名称也发生了改变。
图5.3.6
- 符号表:
链接后的符号表,与hello.o相比,增加了更多的符号。符号的定义和引用都在此声明。
图5.3.7
5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
从edb中data dump一栏可以看到加载到hello的虚拟地址空间,而从hello的elf文件中的程序头一栏可以看到链接器加载是运行的内容及与动态链接相关的信息,每个项目均又对应的虚拟内存地址,可以在edb中找到。
图5.4
5.5 链接的重定位过程分析
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
利用指令objdump -d -r hello > hello_obj.txt指令将hello的反汇编文件存入hello_obj.txt中。
- 增加了hello程序中需要调用的外部函数,放在.plt.sec节中。如printf,exit,atoi,sleep,getchar等。
图5.5.1
- 增加了.init节,.plt节,节中分别存放这init函数和plt函数。
图5.5.2
- 地址访问:hello.o中的相对偏移量地址变成了hello中的虚拟内存地址。
图5.5.3
- 函数调用:函数调用的地址从hello.o中的相对偏移量地址变成了函数调用地址。
图5.5.4
5.6 hello的执行流程
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
ld-2.27.so!_dl_start 0x7fce8cc38ea0
ld-2.27.so!_dl_init 0x7fce8cc47630
hello!_start 0x400500
libc-2.27.so!__libc_start_main 0x7fce8c867ab0 -libc-2.27.so!__cxa_atexit 0x7fce8c889430
-libc-2.27.so!__libc_csu_init 0x4005c0
hello!_init 0x400488
libc-2.27.so!_setjmp 0x7fce8c884c10
-libc-2.27.so!_sigsetjmp 0x7fce8c884b70 --libc-2.27.so!__sigjmp_save 0x7fce8c884bd0
hello!main 0x400532
hello!puts@plt 0x4004b0
hello!exit@plt 0x4004e0
*hello!printf@plt --
*hello!sleep@plt --
*hello!getchar@plt --
ld-2.27.so!_dl_runtime_resolve_xsave 0x7fce8cc4e680 -ld-2.27.so!_dl_fixup 0x7fce8cc46df0
--ld-2.27.so!_dl_lookup_symbol_x 0x7fce8cc420b0
libc-2.27.so!exit 0x7fce8c889128
5.7 Hello的动态链接分析
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
动态链接器使用过程链接表 PLT+全局偏移量表GOT实现函数的动态链接,在GOT中存放函数目标地址,PLT使用 GOT中地址跳转到目标函数,在加载时,动态链接器会重定位GOT中的每个条目,使得它包含目标的正确的绝对地址。
在hello的elf格式文件中可以看到有关节的地址。
图5.7.1
5.8 本章小结
本章讲解了链接的概念和作用,分析了hello的elf格式文件与hello.o文件的不同,利用edb查看了hello程序的虚拟内存地址,分析了hello文件的执行过程,重定位过程,以及动态链接过程。
第 6 章 hello进程管理
6.1 进程的概念与作用
进程的概念:进程是操作系统一个正在运行的程序的一种抽象。程序在系统上运行时,操作系统会提供一种假象,就好像系统上只有这个程序在运行。程序看上去是独占地使用处理器,主存和I/O设备。处理器看上去就像在不间断地一条接一条的执行程序中的指令。
进程的作用:进程提供给应用程序两个关键抽象
1.逻辑控制流 (Logical control flow)
每个程序似乎独占地使用CPU,通过OS内核的上下文切换机制提供。
2.私有地址空间 (Private address space)
每个程序似乎独占地使用内存系统,OS内核的虚拟内存机制提供。
6.2 简述壳Shell-bash的作用与处理流程
Shell-bash的作用:shell是一个交互型应用级程序,代表用户运行其他程序。如Windows下的命令行解释器,cmd、powershell,图形界面的资源管理器。Linux下的Terminal/tcsh、bash等等,当然也包括图形化的GNOME桌面环境。
Shell处理流程:
- shell会不断读取命令行参数
- 接着判断输入的参数是内部命令还是外部命令,如果是内部命令直接执行。
- 如果是外部命令,则为其创建子进程并加载函数。
- 在进程结束后,通过父进程或init进程对子进程进行回收。
6.3 Hello的fork进程创建过程
在shell中输入./Hello 120L020109 张异凡 1,shell会读取命令行参数,带参数执行Hello程序。Shell会通过fork函数创建子进程,子进程拥有与父进程相同的上下文,并且拥有自己的pid。当子进程结束时,会由父进程进行回收,若父进程已结束,则由init进程进行回收。
6.4 Hello的execve过程
在创建完全新的子进程后,通过execve函数加载运行hello程序,从而该子进程便拥有了自己独特的上下文。execve函数将删除该进程的代码和地址空间内的内容并将其初始化,然后通过跳转到程序的第一条指令或入口点来运行该程序,将私有的区域映射进来,重新设置程序计数器pc,并设置映射共享区。
6.5 Hello的进程执行
结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。
(1)上下文信息
内核为每个进程维持一个上下文,上下文就是内核重新启动一个被抢占的进程所需的状态。并通过上下文切换的机制来将控制转移到新的进程。
- 进程时间片
一个进程执行它的控制流的一部分的每一时间段叫做时间片。
- 进程调度的过程
程序运行后,shell为hello分配了子进程。若hello进程不被抢占,则可一直在用户态运行,若遇到抢占,则由用户态转入内核态,进行上下文切换,切换到其他进程,切换后再从内核态转回用户态。例如程序中会运行的sleep函数,程序正常运行到sleep函数时,sleep会申请将进程挂起,转入内核态,并开启倒计时,待倒计时结束后,重新转入用户态,hello程序继续进行。
6.6 hello的异常与信号处理
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
(1)正常运行:
图6.6.1
- ctrl-c
在hello运行中途输入ctrl-c后,回向内核发送SIGINT信号,将前台运行的所有进程终止,输入ps指令可以看到hello进程已终止
图6.6.2
- ctrl-z
在hello运行中途输入ctrl-z后,回向内核发送SIGTSTP信号,将前台运行的所有进程挂起,输入ps指令可以看到hello进程已挂起。
图6.6.3
输入jobs,可以看到hello的作业已停止。
图6.6.4
输入pstree,可以看到hello的进程位置。
图6.6.5
输入fg 1,可以将hello转回前台作业,继续执行。
图6.6.6
输入kill -9 2988(hello的pid),强制将hello进程杀死
图6.6.7
- 乱按
在hello程序中途乱按,shell会记录输入的字符,当hello程序结束后,shell会将字符作为指令尝试运行。
图6.6.8
6.7本章小结
本章介绍了进程和shell的概念和作用,根据hello程序运行的情况分析了通过fork创建进程,通过execve加载运行程序,以及进程的异常信号处理等内容。
第 7 章 hello的存储管理
7.1 hello的存储器地址空间
结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。
- 逻辑地址
包含在机器语言中用来指定一个操作数或一条指令的地址。每一个逻辑地址都由一个段和偏移量组成,偏移量指明了从段开始的地方到实际地址之间的距离。就是hello.o里相对偏移地址。
- 线性地址
逻辑地址经过段机制转化后为线性地址,其为处理器可寻址空间的地址,
用于描述程序分页信息的地址。具体以 hello 而言,线性地址标志着hello
应在内存上哪些具体数据块上运行。
- 虚拟地址
与线性地址为同一概念。
- 物理地址
用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应。地址翻译会将hello的一个虚拟地址转化为物理地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
Intel 处理器从逻辑地址到线性地址的变换通过段式管理的方式实现。每个程
序在系统中都保存着一个段表,段表保存着该程序各段装入主存的状况信息,包括段号或段名、段起点、装入位、段的长度、主存占用区域表、主存可用区域表等,从而方便进行段式管理。
在段寄存器中,存放着段选择符,可以通过段选择符来得到对应段首地址。其包含三部分:索引,TI,RPL 。
通过索引可以确定当前使用的段描述符在描述符表中的位置,进而通过段描述符得到段基址。段基址与偏移量结合就得到了线性地址,即虚拟地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
VM系统通过将虚拟内存分割为称为虚拟页的大小固定的块,类似的,物理内存被分割为物理页。系统通过操作系统软件、mmu中的地址翻译硬件和一个存放在物理内存中叫做页表的数据结构来完成缓存各种操作。页表将虚拟页映射到物理页,每次地址翻译硬件将一个虚拟地址转换为物理地址时,都会读取页表。
页表就是一个页表条目的数组。虚拟地址空间中的每个页在页表中一个固定的偏移量处都有一个PTE。PTE包含了一个有效位和一个n位字段,有效位表明了该虚拟页当前是否被缓存在DRAM中。因为DRAM是全相联的,所以任意物理页都可包含任意虚拟页。
虚拟地址可被分为两个部分:VPN(虚拟页号)和 VPO(虚拟页偏移量),根据计算机系统的特性可以确定 VPN 与 VPO 的具体位数,由于虚拟内存与物理内存的页大小相同,因此 VPO 与 PPO(物理页偏移量)一致。而PPN(物理页号)则需通过访问页表中的页表条目(PTE)获取。若PTE有效位为1,则发生页命中,若为1,则发生缺页故障。
7.4 TLB与四级页表支持下的VA到PA的变换
每次CPU产生一个虚拟地址,MMU(内存管理单元)就必须查阅一个PTE(页表条目),以便将虚拟地址翻译为物理地址。在最糟糕的情况下,这会从内存多取一次数据,代价是几十到几百个周期。如果PTE碰巧缓存在L1中,那么开销就会下降1或2个周期。然而,许多系统都试图消除即使是这样的开销,它们在MMU中包括了一个关于PTE的小的缓存,称为翻译后备缓存器(TLB)。
通过虚拟地址得到虚拟页号(VPN),然后将虚拟页号(VPN)传入TLB中得到对应的物理页号,然后结合虚拟偏移(VPO),确定唯一的物理地址;若是在TLB中找不到,则需要从高速缓存或者磁盘中取出对应的虚拟页表,放入TLB再进行操做。若TLB命中,则从TLB中可以直接找到各级页表,然后得到PPN,与PPO结合即可得到物理地址。若TLB不命中,则需要从高速缓存中到PPN。
7.5 三级Cache支持下的物理内存访问
物理地址由CT,CI,CO三部分组成。CT为缓存标记位;CI为缓存组索引;CO为缓存偏移。
使用CI进行组索引,每组8路,对8路的块分别匹配CT如果匹配成功且块的valid标志位为1,则命中,根据数据偏移量CO取出数据返回。如果没有匹配成功或者匹配成功但是标志位是1,则不命中,向下一级缓存中查询数据。查询到数据之后,可以采取多种放置策略进行解决。例如LFU策略,若有空闲页则放置,若无,则寻找使用次数最少的页覆盖放置。
7.6 hello进程fork时的内存映射
当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给他一个唯一得PID。同时为这个新进程创建虚拟内存。
新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同,当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面。因此,也就为每个进程保持了私有空间地址的抽象概念。