本文描述了hello程序从一个高级语言描述的源程序文件经过预处理,编译,汇编,链接后生成一个可执行目标文件hello,再通过命令行指挥shell创建一个子进程,加载并运行hello,完成P2P的过程中的各个阶段,以及hello程序从最初未执行前的不占用系统资源,即从无开始,到创建进程,获得内存空间,进程执行结束,回收进程后,不留下任何痕迹,即回归于无,所谓020的过程。
关键词:hello Program Process 计算机系统
目 录
第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.6 hello进程fork时的内存映射......... - 11 -
7.7 hello进程execve时的内存映射..... - 11 -
7.8 缺页故障与缺页中断处理................. - 11 -
7.9动态存储分配管理.............................. - 11 -
7.10本章小结............................................ - 12 -
结论............................................................... - 13 -
附件............................................................... - 14 -
参考文献....................................................... - 15 -
第1章 概述
1.1 Hello简介
P2P即Program to Process,指hello程序由高级语言描述的源码文件hello.c,经由预处理,编译,汇编,链接后,生成可执行目标文件hello,再由shell创建子进程并加载到子进程中运行的过程。
020即Zero to Zero,指hello程序从最初的无,不占用系统资源,到拥有进程,内存地址空间,占用CPU资源来运行,再到进程执行完毕,回收后的无的过程。
1.2 环境与工具
1.2.1 硬件环境
AMD Ryzen 9 7945HX with Radeon Graphics 2.50 GHz;16.0G RAM
1.2.2 软件环境
Windows11 64位; Vmware 11;Ubuntu 20.04 LTS 64位
1.2.3 开发工具
Visual Studio 2010 64位以上;CodeBlocks 64位;vim+gcc
1.3 中间结果
hello.i :源码预处理后的文件,用于验证预处理的过程。
hello.s :汇编语言描述代码,用于验证编译的过程。
hello.o :二进制代码,可重定位目标执行文件,用于验证汇编的过程。
hello :可重定位目标文件和动态库链接后所得可执行目标文件,用于验证链接的过程,以及后续加载并运行用。
1.4 本章小结
本章对P2P,020概念做了初步介绍,介绍了研究所用的环境和工具,以及产生的中间结果。
第2章 预处理
2.1 预处理的概念与作用
预处理是将高级语言描述的源码转化为机器语言可执行程序的过程中的第一个步骤。主要工作是头文件替换,去注释,宏替换,条件编译等。
头文件替换是指将在源码中用#include指令包含在程序中的头文件名称对应的头文件内容复制到源码中,方便后续使用。
去注释指将用“/* */”或“//”标注的程序注释部分去掉,在后续处理中不考虑注释。
宏替换是指将#define指令定义的宏指定的文本进行替换,以处理程序中的宏定义。注意预处理器是想进行去注释再进行宏替换的,hello.c不涉及。
条件编译指用部分指令对部分内容指定编译条件,hello.c不涉及。
作用是对高级语言描述的源码进行第一步精简处理,以方便后续编译工作。
2.2在Ubuntu下预处理的命令
在linux终端中输入如下命令进行hello.c的预处理步骤,生成一个预处理后的文件hello.i。
2.3 Hello的预处理结果解析
可以看到在hello.c的预处理过程中,头文件中的内容被复制到hello.i内。
并且在hello.i中找不到注释了,说明注释在预处理过程中被去除了。
2.4 本章小结
本章节解析了hello.c在从程序到进程的过程中的第一步,也就是预处理的主要概念和作用,并根据hello.c的实际预处理结果验证。在本章之后,hello.c成为了经过预处理后的文件hello.i,其内复制了包含的头文件中的函数,并去除了原有的注释。
第3章 编译
3.1 编译的概念与作用
编译指将预处理后的,高级语言描述的程序源码转化为汇编语言描述的程序,主要包括词法分析,语法分析,语义分析,中间代码生成,代码优化,汇编代码生成。
其作用主要是将汇编器无法处理的高级语言转化为汇编语言,方便后续汇编器翻译为机器语言。同时,在编译时会进行一些代码优化,使程序更高效且节省空间。
3.2 在Ubuntu下编译的命令
在终端中输入如下指令:
即可生成名“hello.s”的汇编语言程序代码文件。
3.3 Hello的编译结果解析
3.3.1 数据类型
3.3.1.1局部变量
以循环计数用局部变量i为例,以下是声明i=0的处理方式:
即将初值相应的立即数存于栈帧中存放局部变量的位置,表示一个局部变量。
后续对该局部变量操作时,也是对栈帧中相应位置进行操作:
在函数返回时,栈帧删除,其中的局部变量也自然被清除。
3.3.1.2 字符串常量
存于静态存储区,无法改变,
需要使用时根据相应地址访问即可。
3.3.1.3 算术表达式
以加法为例,“i++”通过add指令进行处理。减法,乘法等分别有相应的sub,imul指令处理。
3.3.1.4 关系表达式
以“i<10”为例,通过cmp指令处理。
若结果与程序控制有关,结合j指令进行控制。
3.3.1.5 赋值表达式
以“i=0”为例,通过mov指令处理。
3.3.1.6 类型
对于不同字节长度的数据类型,指令会根据“b,w,l,q”组成的后缀区分。
3.3.2 操作
3.3.2.1 算术操作
见3.3.1.3
3.3.2.2 关系操作
见3.3.1.4
3.3.2.3 赋值操作
见3.3.1.5
3.3.2.4 控制转移操作
“if”通过cmp指令和j指令实现,以“if(avgc!=5)”为例:
“for”循环通过结构:
init-expr;
goto test;
loop:
body-statement
update-expr;
test:
t=test-expr
if(t)
goto loop;
实现。以“for(i=0;i<10;i++)”为例:
3.3.2.5 函数操作
3.3.2.5.1 参数传递
不大于6个的参数会通过寄存器%rdi,%rsi,%rdx,%rcx,%r8,%r9传递。
例如“exit(1)”:
倘若参数大于6个,剩余参数会通过栈传递。
3.3.2.5.2 函数调用
通过“call指令+函数名”调用函数。
例如调用printf函数:
3.3.2.5.3 函数返回
通过ret指令返回,返回时会将返回值存于%rax,清除栈帧,%rip修改为存于栈中的返回地址,将返回地址弹出。
3.3.2.6 数组操作
通过一个数组基址和一个偏移量来访问数组元素。
例如访问argv[3]:
其中数组基址存于-32(%rbp),先将数组基址取出,然后加上偏移量24,得到argv[3]的地址。
3.4 本章小结
本章介绍了编译的概念,步骤和作用,根据hello.c中出现的数据类型和操作,根据实际编译结果,分情况分析了编译器是如何处理这些不同数据类型和操作的。通过编译,hello程序由预处理后的高级程序源码hello.i转化为了汇编语言描述的hello.s文件。
第4章 汇编
4.1 汇编的概念与作用
汇编是将汇编语言文件翻译为二进制代码描述的可重定位目标文件的过程。包括词法分析,语法分析,代码生成,赋值信息四个步骤,主要工作为汇编指令翻译为二进制指令,和生成各种表信息。
作用为将机器无法识别的汇编语言转化为可识别的二进制语言。
4.2 在Ubuntu下汇编的命令
在终端输入如下命令进行汇编:
4.3 可重定位目标elf格式
使用readelf -a指令列出各节基本信息。
4.3.1 ELF头
ELF头以一个16字节的序列开始,描述了生成该文件的系统的字系统的字的大小和字节顺序。ELF头剩下的部分包含帮助链接器语法分析和解释目标文件的信息。其中包括ELF头的大小、目标文件的类型、机器类型、节头部表的文件偏移,以及节头部表中条目的大小和数量。
可以看出hello.o为64位文件,字节顺序为小端序,数据以补码表示,文件类型为可重定位目标文件(REL),节头部表偏移为1264字节,机器类型为x86-64,节头部表中条目大小为64字节,条目数量为14,ELF头大小为64字节。
4.3.2 节头部表
节头部表描述了不同节的位置和大小,目标文件中每个节都有一个固定大小的条目。
由于是还未重定位的文件,各节起始地址均为0。节头部表中还标注了该节是否可读,可写,可执行等信息。
4.3.3 节
ELF头与节头部表之间的都是节,分别为:.text;.rodata;.data;.bss;.symtab;.rela.text;.rodata等。
4.3.4 符号表
符号表存放了在程序中定义和引用的函数和全局变量的信息,从中可以看出hello.c中定义了main函数,引用了puts,exit,printf,atoi,sleep,getchar函数。
4.3.5 重定位节(.rel.text)
一个.text节中位置的列表。当链接器把这个目标文件和其他文件组合时,需要修改这些位置。一般而言,任何调用外部函数或者引用全局变量的指令都需要修改。另一方面,调用本地函数的指令则不需要修改。Offset是需要被修改的引用的节偏移,symbol标识被修改引用应该指向的符号,type告知链接器如何修改新的引用,addend是一个有符号常数,一些类型的重定位要使用它对被修改引用的值做偏移调整。从中可以看出调用外部函数puts,exit,printf,atoi,sleep,getchar,引用只读数据节中的两处的指令需要修改。
4.4 Hello.o的结果解析
反汇编代码中包含机器语言和汇编语言,其中机器语言包含操作码,寄存器编号等信息,与汇编语言指令一一对应。机器语言采取的是小端序二进制存储,而汇编是顺序十六进制存储。
分支转移:机器语言中的跳转指令目标地址采取的是相对编址方式,%rip中的值加上操作数才是目标地址。而hello.s中汇编语言采取的是助记符,反汇编文件中汇编语言采取的是一个相对地址。
函数调用: 由于调用的是共享库函数,还未链接,机器语言call指令的操作数填充0。而汇编语言call指令操作数在hello.s中为函数名,在反汇编文件中为下一条指令地址。
4.5 本章小结
本章讲述了汇编的概念和作用,分析了汇编器生成的可重定位文件hello.o中的ELF头,节头部表,各节的信息。并将通过反汇编生成的代码与hello.s中的代码做了比较,分析了其中汇编语言的细微差别,以及机器语言和汇编语言的映射关系。经过本章,汇编语言描述的文件hello.s转化为二进制代码描述的可重定位目标文件hello.o。
第5章 链接
5.1 链接的概念与作用
链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可以被加载到内存并执行。
作用是使得分离编译成为可能,即人们不用将一个大型应用程序组织为一个巨大的源文件,而是可以把它分解成更小,更好管理的模块,可以独立地修改和编译这些模块。
5.2 在Ubuntu下链接的命令
在终端输入如下命令进行链接:
5.3 可执行目标文件hello的格式
5.3.1 ELF头
解读方式同4.3.1。可以看出文件类型为可执行目标文件(EXEC),64位,小端序,起始地址为0x4010f0,可说明重定位已完成。程序头部表条目数为12,大小为64字节。节头部表的条目数为27,大小为64字节,起始偏移量为14208字节。
5.3.2 节头表
节头表中相比hello.o的节头表多了一些条目,由于已完成重定位,节地址为实际地址了。
5.3.3程序头部表
程序头部表描述了可执行文件的连续的片被映射到连续的内存段的映射关系。Offset表示偏移量,Virtaddr,Physaddr为内存地址,Align为对其要求,Memsiz为内存中的段大小,Flags为运行时访问权限,可以看到有些段是只读的。
5.4 hello的虚拟地址空间
Edb显示虚拟空间为0x401000-0x402000,对比各节机器码与起始地址处内存,相符。
5.5 链接的重定位过程分析
由于已完成重定位,hello的反汇编代码不再从0开始,而是确切地址,其中汇编语言的call指令,j指令的操作数也相应的变成了确切地址。call指令机器语言的操作数也不再由0填充,而是PC相对编址。
可执行目标文件hello经链接后,其反汇编文件多了一些其他节。
链接的过程为:1.符号解析:将每个符号引用正好与一个符号定义关联起来。2.重定位。
重定位过程为:链接器通过把每个符号定义与一个内存位置关联起来,从而重定位这些节,然后修改所用这些符号的引用,使得它们指向这个内存位置。当汇编器生成一个目标模块时,它并不知道数据和代码最终将放在内存中的什么位置。它也不知道这个模块引用的任何外部定义的函数和全局变量的位置。所以,无论何时汇编器遇到最终位置未知的引用,它就会生成一个重定位条目,告诉链接器在将目标文件合并为可执行文件时如何修改这个引用。链接器使用汇编器产生的重定位条目的详细指令,不加甄别地执行这样的重定位。
5.6 hello的执行流程
从加载hello到_start,到call main,以及程序终止的所有过程中调用的子程序名有:
dl_start
dl_init
start
libc_start_main
libc_start_main
cxa_atexit
libc_csu_init
init
setjmp
sigsetjmp
main
puts@plt
exit@plt
printf@plt
sleep@plt
getchar@plt
dl_runtime_resolve_avx
exit
5.7 Hello的动态链接分析
当程序调用一个由共享库定义的函数时,编译器没有办法预测这个函数的运行时地址,因为定义它的共享模块在运行时可以加载到任意位置。GNU编译系统使用一种称为延迟绑定的技术将过程地址的绑定推迟到第一次调用该过程时。延迟绑定是通过GOT和PLT之间的交互来实现的, GOT是数据段的一部分,而PLT是代码段的一部分。PLT与GOT协作在运行时解析函数的地址,实现函数的动态链接。
以puts函数为例,使用gdb调试验证。
阅读反汇编文件可知puts函数对应的GOT条目在0x404018处,
在程序第一次调用puts前,查看GOT表,可知条目指向0x401030
第一次调用puts函数后,GOT被重写,指向puts的运行时地址。
5.8 本章小结
本章讲述了链接的概念和作用,对可执行目标文件hello和可重定位文件hello.s的ELF进行了对比分析,关注了hello的虚拟地址空间,对比了hello和hello.s各自反汇编文件的差异,详细讲解了重定位过程,最后分析了hello的动态链接过程。在本章后,可重定位目标文件hello.s转化为可执行目标文件hello。
第6章 hello进程管理
6.1 进程的概念与作用
进程的概念:进程是一个执行中的程序的实例。
进程的作用:进程提供给应用程序两个关键抽象。
1.逻辑控制流:程序似乎独占地使用CPU。
2.私有地址空间:程序似乎独占地使用内存系统。
6.2 简述壳Shell-bash的作用与处理流程
作用:shell是一个命令行解释器,代表用户运行其他程序。
处理流程:
- 打印一个命令行提示符,等待用户输入命令行。
- 对命令行求值,构造argv向量。
- 检查是否为内置命令,是则解释该命令。否则创建一个子进程。
- 在子进程中运行所请求的程序。
- 回收子进程。
- 循环。
6.3 Hello的fork进程创建过程
Shell首先通过判断,判断出输入的命令行不为内置命令,于是调用fork函数创建一个子进程,子进程的用户级虚拟地址空间,打开文件描述符与父进程相同,pid不同。
6.4 Hello的execve过程
在子进程中调用execve函数,提供程序文件路径,参数列表和环境变量列表,找到hello可执行目标文件后,在当前进程的上下文加载并运行hello,完成删除已有的用户区域,共享区和私有区的映射等操作,将%rip设置为代码段入口。
6.5 Hello的进程执行
操作系统上,同一时间内多个进程并行执行。内核使用上下文切换来调度进程的执行。上下文是由程序正确运行所需的状态组成的,这个状态包括存放在内存中的程序的代码和数据,栈,通用寄存器的内容,程序计数器,环境变量以及打开文件描述符的集合。内核为每个进程维持一个上下文。在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程。
hello进程执行到调用sleep函数时,会发生一次上下文切换,内核转至其他进程的执行。sleep结束后又会发送一次上下文切换,恢复hello的上下文,继续hello进程。
hello进程执行到getchar函数时,由于读取数据需要较长时间,会发生一次上下文切换,执行其他进程,待数据读取到缓冲区之后,又会发生一次上下文切换,恢复hello进程的执行。
6.6 hello的异常与信号处理
6.6.1乱按
若没按到回车,乱按的内容只会显示在屏幕上,若按到回车,乱按的内容会被当做命令行输入。
6.6.2 Ctrl+C
内核发送一个SIGINT信号,处理结果是终止前台的进程。
6.6.3 Ctrl+Z
内核发送一个SIGSTP信号,处理结果是停止前台进程。
6.6.4 Ctrl+Z后ps,jobs,pstree,fg,kill
6.6.4.1 ps
列出当前正在执行(包括正停止)的进程名称和pid号。
6.6.4.2 jobs
显示系统中的任务列表及其运行状态。
6.6.4.3 pstree
列出进程树。
6.6.4.4 fg
将被挂起的hello进程放至前台,恢复运行。
6.6.4.5 kill
可以通过kill -9 pid发送SIGKILL信号给hello进程,处理结果是hello进程被终止。
6.7本章小结
本章描述了进程的概念和作用,分析了shell的作用和处理流程,并详细分析了fork创建进程,execve加载程序,hello进程执行的过程,执行中可能遇到的异常和信号处理结果。经过本章,可执行目标文件hello被加载到进程上下文中执行完毕。
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:是指由程式产生的和段相关的偏移地址部分,是相对于当前进程数据段的地址,不和绝对物理地址相干。
线性地址:是逻辑地址到物理地址变换之间的中间层。程式代码会产生逻辑地址,或说是段中的偏移地址,加上相应段的基地址就生成了一个线性地址。
虚拟地址: 是描述程序的地址空间,经过地址翻译转化为物理地址。
物理地址:是指出目前 CPU 外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址。如果启用了分页机制,那么线性地址会使用页目录和页表中的项变换成物理地址。如果没有启用分页机制,那么线性地址就是物理地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
高16位地址称为段基地址,低16位称为段内偏移地址,又称逻辑地址。逻辑地址加上段基地址后,即变换为线性地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
虚拟内存被分割为固定大小的块,称为虚拟页,类似的,物理内存被分割为物理页。虚拟页和物理页存在对应关系,存储于页表中。通过操作系统,MMU,页表,将虚拟页映射至物理页,线性地址变换为物理地址。
7.4 TLB与四级页表支持下的VA到PA的变换
36位VPN被划分位四个9位的片,每个片被用作到一个页表的偏移量。CR3寄存器包含L1页表的物理地址。VPN1提供到一个L1 PET的偏移量,这个PTE包含L2页表的基地址,VPN2提供到一个L2 PTE的偏移量,以此类推。
7.5 三级Cache支持下的物理内存访问
现代计算机系统采用了多级缓冲结构。进行物理内存访问时,会首先访问L1 cache,未命中则访问L2 cache,仍未命中则访问L3 cache,还是未命中才会访问物理内存。
7.6 hello进程fork时的内存映射
当hello进程被fork函数创建时,内核为新进程创建了各种数据结构,并分配了一个唯一的PID。为了给hello进程创建虚拟内存,内核创建了父进程的mm_struct,区域结构和页表的原样副本。它将两个进程中的每个页面都标记为已读,并将两个进程中的每个区域结构都标记为私有的写时复制。
当fork在hello进程中返回时,hello进程的虚拟内存刚好和父进程调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间的抽象概念。
7.7 hello进程execve时的内存映射
Execve函数在hello进程中加载并运行hello,替代了当前程序,需要通过以下步骤:
- 删除已存在的用户区域。
- 映射私有区域。为新程序的代码,数据,bss和栈区域创建新的区域结构
- 映射共享区域。hello与共享对象链接,这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。
- 设置程序计数器。指向代码区域入口点。
下一次调度hello进程时,将从hello程序代码区域入口点开始执行。
7.8 缺页故障与缺页中断处理
DRAM缓存不命中称为缺页。现代系统均采用按需页面调度,即有不命中发生时,才换入页面。通过查询页表PTE可以知道虚拟页在磁盘的位置。缺页处理程序从指定的位置加载页面 到物理内存中,并更新PTE。然后控制返回给引起缺页故障的指令。
7.9动态存储分配管理
动态内存分配器维护着一个进程的虚拟内存区域,称为堆。系统之间的细节不同,但是不失通用性,假设堆是一个请求二进制零的区域,它紧接在未初始化的数据区域后开始,并向上生长(向更高的地址)。对于每个进程,内核维护着一个变量brk,它指向堆的顶部。
分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可以用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放。这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。
7.10本章小结
本章讲述了虚拟地址、物理地址、线性地址、逻辑地址的概念,介绍了段式管理,页式管理,VA到PA的转换,进程fork和execve时的内存映射的内容,描述了系统应对缺页故障和缺页中断的方式,动态内存分配管理机制。
结论
Hello程序被编写为c语言程序hello.c后,
- 先是经历了预处理,头文件展开,去除了注释等信息。
- 经编译器编译,生成了汇编语言代码文件。
- 经汇编器翻译为了二进制代码的可重定位目标文件。
- 经链接器,与动态库链接,生成了可执行目标文件hello。
- 在shell中输入命令行,shell创建了一个子进程,在其上下文中加载了hello程序并运行,为其分配了虚拟地址空间。
- 进入程序,载入物理内存,内核与MMU单元结合页表共同实现内存管理。
- 内核处理运行时接收的各种信号。
- 运行结束,hello进程的数据结构被删除,进程被回收,最终不留下一丝痕迹。
通过本次研究,我深切感受到了计算机系统设计的精妙与高明,费劲心思只为更有效率的系统运作,以及计算机界前人的智慧。
附件
hello.i :源码预处理后的文件,用于验证预处理的过程。
hello.s :汇编语言描述代码,用于验证编译的过程。
hello.o :二进制代码,可重定位目标执行文件,用于验证汇编的过程。
hello :可重定位目标文件和动态库链接后所得可执行目标文件,用于验证链接的过程,以及后续加载并运行用。
参考文献
[1]https://blog.csdn.net/m0_69909682/article/details/128616065?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171820283416800222888450%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=171820283416800222888450&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-128616065-null-null.142^v100^pc_search_result_base6&utm_term=%E9%A2%84%E5%A4%84%E7%90%86&spm=1018.2226.3001.4187
[2]https://blog.csdn.net/qq_43279782/article/details/117261096?ops_request_misc=&request_id=&biz_id=102&utm_term=%E7%BC%96%E8%AF%91%E8%BF%87%E7%A8%8B&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-0-117261096.142^v100^pc_search_result_base6&spm=1018.2226.3001.4187
[3]https://blog.csdn.net/qq_43493208/article/details/101872243?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171826857516800211517537%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=171826857516800211517537&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-1-101872243-null-null.142^v100^pc_search_result_base6&utm_term=%E5%8F%AF%E9%87%8D%E5%AE%9A%E4%BD%8D%E7%9B%AE%E6%A0%87%E6%96%87%E4%BB%B6%E6%9F%A5%E7%9C%8B&spm=1018.2226.3001.4187
[4]https://blog.csdn.net/weixin_44711653/article/details/102055574?spm=1001.2101.3001.6650.4&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-4-102055574-blog-126822037.235%5Ev43%5Epc_blog_bottom_relevance_base3&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-4-102055574-blog-126822037.235%5Ev43%5Epc_blog_bottom_relevance_base3&utm_relevant_index=9
[5]https://blog.csdn.net/JustDoIt_201603/article/details/106462843?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171829090216800222835831%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=171829090216800222835831&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-3-106462843-null-null.142^v100^pc_search_result_base6&utm_term=%E9%80%BB%E8%BE%91%E5%9C%B0%E5%9D%80%EF%BC%8C%E7%BA%BF%E6%80%A7%E5%9C%B0%E5%9D%80%EF%BC%8C%E8%99%9A%E6%8B%9F%E5%9C%B0%E5%9D%80%EF%BC%8C%E7%89%A9%E7%90%86%E5%9C%B0%E5%9D%80&spm=1018.2226.3001.4187
[6] Randal E.Bryant,David R.O’Hallaron,深入理解计算机系统第三版,美国,机械工业出版社