计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算机科学与技术
学 号 2021110794
班 级 21w0312
学 生 彭梓
指 导 教 师 史先俊
计算机科学与技术学院
2022年5月
摘 要
本文结合hello实例,分析其预处理,编译,汇编,链接的过程,探讨了hello的进程管理、内存映射和I/O管理。
关键词:hello,p2p,020,进程管理,内存映射
目 录
第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 –
结论………………………………………………………………………………….. – 14 –
附件………………………………………………………………………………….. – 15 –
参考文献…………………………………………………………………………… – 16 –
第1章 概述
1.1 Hello简介
Hello的p2p过程就是Hello从一个高级C语言源程序经过多步变成可执行程序的过程。首先,hello.c经过预处理命令生成hello.i,再经过编辑器生成hello.s。hello.s经过汇编器生成可重定位二进制程序hello.o,最后链接成可执行目标程序hello。
020过程就是在程序开始运行时,进程管理为程序创造进程,然后进程管理将虚拟内存映射为物理内存,结合异常控制和系统级I/O,完成程序功能,最后,发送信号结束进程并由父进程回收。
1.2 环境与工具
硬件:AMDR5,联想小新PRO16,GTX1650
软件:Ubuntu20.04 VMware Workstations 16 player
开放工具:gdb,vccode,gcc,gdb,edb.
1.3 中间结果
hello.i:将高级语言程序预处理得到;
hello.s:汇编程序
hello.o:可重定位目标程序
hello:可执行目标程序
1.4 本章小结
本章介绍了hello的P2P和020的概念,给出了中间文件名。
第2章 预处理
2.1 预处理的概念与作用
(以下格式自行编排,编辑时删除)
预处理是指在编译之前,预处理器(cpp)对源文件进行简单加工的过程。C语言提供了三种预处理命令:宏定义,文件包含,条件编译。宏定义是指用一个符号替代字符串。文件包含是将另外一个源程序的全部内容包含进来,如#include,预处理时会将stdio.h的内容全部复制到程序中。条件编译是指,只有在满足一些条件的情况下才进行编译。
2.2在Ubuntu下预处理的命令
如图:
2.3 Hello的预处理结果解析
hello.i的文件的开头被填充入路径
中间部分为大量的替换
文件的最后面是我们原始的C语言程序,由此可知,预处理将我们使用的那些#include命令指定的文件用对应的文件和宏定义替换。代码的长度变长了许多。
2.4 本章小结
预处理步骤是程序编译的第一步,这一步中,预处理器将很多代码复制到了我们得到的hello.i文件中,进行了大量的重命名,扩充了我们的源程序,原始的C语言程序在最后被保存了下来。
本小章通过实例展示了预处理的过程。也让我们更加深入理解了引用头文件的实现方法和宏定义的实现。
第3章 编译
3.1 编译的概念与作用
编译是指编译器基于编程语言的规则、目标机器的指令集和操作系统遵循的惯例,经过一系列的过程将中间代码转化为机器代码的过程。
编译的主要作用是翻译,将人能看懂的源代码翻译成机器能读懂的机器语言指令集的程序。
3.2 在Ubuntu下编译的命令
(以下格式自行编排,编辑时删除)
3.3 Hello的编译结果解析
3.3.1 数据部分
常数:
LCO对应于输入参数个数不为4时的情况输出的常量字符串,LC1对应于“Hello %s %s\n”的情况。
变量:
%rbp-4中存放着i的值。
argv[2]的地址存在%rbp-16中,argv[1]的则存在%rbp-24中,最后x的字符串的值送给rdx和rsi,这里的判断基于的是传参的寄存器的使用。
3.3.2 赋值:
将i的值初始化为0。
3.3.3 算数操作:
由上面的分析可知,此处代表i=i+1;
3.3.4 关系操作:
判断i是否小于等于8.
判断argc与4的关系。
3.3.5 数组:
基于前面对变量的分析已经知道数组的使用。
3.3.6 控制转移:
两个都是if判断语句,第一个是if(argv!=4),第二个是if(i<9)的判断语句
3.3.7 函数操作:
第一个是调用getchar,第二个是调用printf和atoi,第三个是调用exit()
由前面的分析可知,printf的三个参数通过寄存器传递,exit的返回状态存在ebx中(为1)。
3.4 本章小结
本章主要分析了hello.s这个汇编语言文本,划分了各种C语言的数据和操作。通过阅读汇编程序,了解了hello运行时对于寄存器和堆栈的调用。
第4章 汇编
4.1 汇编的概念与作用
汇编时将汇编语言源程序翻译成可重定位目标程序的过程。
4.2 在Ubuntu下汇编的命令
4.3 可重定位目标elf格式
如图使用,readelf -a命令列出所有内容,ELF头以一个16字节的序列开始,这个序列描述了生成该文件的字的大小,字节顺序。在下面,列出了节头,程序头,段节,动态段,重定位节,符号表,以及一些详细信息。
如图,在可重定位节中包含了五个信息:偏移量,信息,类型,符号值,符号名称+加数。第一部分的符号主要是外部库,第二部分的符号主要是一些调用的库中的函数。
这些符号的偏移量按照递增排列,被调用的标准库函数的符号值均为0,类型上全为外部符号,主函数等被定义为全局符号,部分只在hello中定义和引用的为局部符号。
4.4 Hello.o的结果解析
机器语言是计算机硬件能够直接识别的指令的集合。汇编语言是指令,寄存器,立即数的组合,不涉及到地址和机器级指令的使用。汇编语言的一大特点是它的操作对象不是具体的数据而是寄存器。对于机器语言而言,每一条指令就是机器语言的一个语句。汇编指令语言是机器指令的一种符号表示。
对于跳转指令,汇编语言通常用跳转表实现,机器语言则通过寻址实现,这里的地址是根据指令计算出来的。
说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
4.5 本章小结
本章分析了hello.o的ELF头,对比学习了汇编语言和机器语言。
第5章 链接
5.1 链接的概念与作用
链接就是把各个目标文件整合到一起得到可执行文件的过程。
链接使得我们可以将超大的模块分成更小的模块进行编写,在需要修改时,只需要对其中的一部分进行重新编译。
5.2 在Ubuntu下链接的命令
5.3 可执行目标文件hello的格式
如图可见,代码段的编号为16,数据段为25,.bss段的编号为26,符号表的编号为28.
5.4 hello的虚拟地址空间
程序的起始地址为0x0000562e674f8000,根据各节的偏移量(对比5.3可得到)
蓝色标注的编号为25的数据段,其余都可依据偏移量算出。
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
5.5 链接的重定位过程分析
5.5.1
链接器最开始进行符号解析,在完成了符号解析之后,链接器知道了它的输入目标模块的代码节和数据节的确切大小。接下来,链接器的重定位由重定位节和符号定义及重定位节的符号引用两步组成。
第一步,链接器将所有相同类型的节合并为同一类型的聚合节,这一步完成时,程序中的每条指令和全局变量都有唯一的运行地址。
第二步 ,链接器将修改代码节和数据节对每个符号的引用,使得它们指向正确的运行时地址。这一步基于重定位条目进行,最基本的有两种重定位类型,分为相对引用和绝对引用。
通过重定位的两种引用方法,主函数等获得了自己的虚拟地址。其他调用的标准库函数也被链接到函数中。
5.6 hello的执行流程
如图,起始位置
函数命如下:
hello!main
hello!.plt+0x70
hello!main+73
hello!main+59
libc_2.31.so!_IO_file_xsputn
libc_2.31.so!_IO_file_doallocate
5.7 Hello的动态链接分析
如图,动态链接与静态的区别在于,只有当调用了外部模块时,才开始链接,开始时,系统会记住这里有未知的符号等待解析。如图为前后地址的变化。
5.8 本章小结
本章分析了hello可执行程序的重定位节,虚拟地址,动态链接和执行过程。
第6章 hello进程管理
6.1 进程的概念与作用
进程的经典定义是执行中程序的实例。
进程提供了一个独立的逻辑控制流,私有的地址空间。
6.2 简述壳Shell-bash的作用与处理流程
Linux中的 shell就是 Linux内核的一个外层保护工具,并负责完成用户与内核之间的交互。
命令是用户向系统内核发出控制请求,与交互的文本流。而 shell是一个命令行解释器,将用户命令解析为操作系统所能理解的指令,实现用户与操作系统的交互。当需要重复执行若干命令,可以将这些命令集合起来,加入一定的控制语句,编辑成为 shell脚本文件,交给 shell批量执行。
1:读取命令
2:判断命令是否正确,并改造为系统调用函数execve()内部处理所要求的形式
3:终端进程调用fork()来创建进程,使用wait函数等待子进程完成
4:当子进程运行时,调用exceve()函数,根据命令行指定的文件到目录中查找可执行文件。
5:判断前后台命令,后台则让用户立即输入下一条命令,否则则一直等待。
6.3 Hello的fork进程创建过程
当输入./hello 2021110794 pengzi 0命令时,命令行判断这不是一个内部命令,然后按照exceve的参数设置改造它。接下来,shell调用fork函数创建子进程。新创建的子进程拥有自己的pid,拥有与父进程相同的用户级虚拟地址的一份副本,包括代码、数据、堆和共享库等。同时,子进程可以获得与父进程任何打开文件描述符相同的副本。
fork()函数被父进程调用一次,在父进程和子进程中均返回。
6.4 Hello的execve过程
根据输入的命令行参数,shell调用parseline函数解析命令。exceve()将hello作为可执行程序的名字,并在当前目录下寻找,且带有参数列表和环境变量列表envp。只有当出现错误时,exceve才返回,否则永远不返回。
6.5 Hello的进程执行
操作系统通过一个被成为上下文切换的异常控制流实现多任务。内核为每个进程维持一个上下文,上下文就是内核重新启动一个被抢占的进程所需的状态。
一般而言。Hello进程与多个进程一同进行,物理控制流被分为多个逻辑流。内核会在不同的进程之间选择切换,这种切换就是调度,这是由内核中的调度器控制的。
当内核代表用户执行系统调用时,可能会发生上下文切换,如果系统调用因为等待某个事件的发生而被阻塞,那么系统可以让当前进程休眠,切换到另外的进程。
中断也可能会引起上下文切换。
Hello程序中的exit()函数也会引起进程的上下文切换。
(
6.6 hello的异常与信号处理
1:
如图,在hello运行过程中乱按(键盘包括回车),不影响进程的执行。
2:
按下ctrl+Z之后,运行ps指令,可看到hello没被杀死
使用jobs命令可以看到此时的hello处于停止状态
使用pstree命令查看
使用fg恢复hello的运行
使用kill命令给hello发送信号,接着使用ps查看状态,发现hello接受了信号。
3:
Ctrl+c:如图,按下之后。Hello进程被杀死。
6.7本章小结
本章以hello的运行和信号接收为代表,解释了进程的概念,运行和信号处理机制。
第7章 hello的存储管理
7.1 hello的存储器地址空间
1:在有地址变换功能的计算机中,访问指令给出的地址 (操作数) 叫逻辑地址,也叫相对地址。逻辑地址由两个16位的地址分量构成,一个为段基值,另一个为偏移量。两个分量均为无符号数编码。
2:地址空间是一个非负整数地址的有序集合,如果地址空间中的整数是连续的,那么我们说它是一个线性地址空间。
3:由程序产生的地址是虚拟地址。
4:计算机系统的主存被组织成一个由M个连续的字节大小的单元组成的数组。每个字节都有一个唯一的物理地址。Hello的物理地址就是hello在内存中的实际地址
7.2 Intel逻辑地址到线性地址的变换-段式管理
机器语言指令中出现的内存地址,都是逻辑地址,需要转换成线性地址,再经过MMU(CPU中的内存管理单元)转换成物理地址才能够被访问到
在x86保护模式下,段的信息(段基线性地址、长度、权限等)即段描述符占8个字节,段信息无法直接存放在段寄存器中(段寄存器只有2字节)。Intel的设计是段描述符集中存放在GDT或LDT中,而段寄存器存放的是段描述符在GDT或LDT内的索引值(index)。
Linux中逻辑地址等于线性地址。为什么这么说呢?因为Linux所有的段(用户代码段、用户数据段、内核代码段、内核数据段)的线性地址都是从 0x00000000 开始,长度4G,这样线性地址=逻辑地址+ 0x00000000,也就是说逻辑地址等于线性地址了。
这样的情况下Linux只用到了GDT,不论是用户任务还是内核任务,都没有用到LDT。
GDT的第12和13项段描述符是(内核任务使用):
__KERNEL_CS
__KERNEL_DS,
第14和15项段描述符是(所有的用户任务共用):
__USER_CS
__USER_DS。
内核段描述符和用户段描述符虽然起始线性地址和长度都一样,但DPL(描述符特权级)是不一样的。__KERNEL_CS 和__KERNEL_DS 的DPL值为0(最高特权),__USER_CS 和__USER_DS的DPL值为3
7.3 Hello的线性地址到物理地址的变换-页式管理
P(Present)位 , 置为1表示该页是否有效,否则表示该页无效
R/W位:置为1,表示该页可读可写,否则为0表示只可读。
U/S位: 置为1,表示该页0,1,2,3环的程序都可以访问该页,置为0表示该页只能0环程序访问。
A位:表示该页是否被访问过
PS位:Page Size,为1表示一页4MB大小,此时是一个大页面。为0表示该页大小是4KB
G位:指示全局页Global
AVL:保留字段
7.4 TLB与四级页表支持下的VA到PA的变换
虚拟地址被划分为4个VPN和1个VPO,每一个VPNi都是到第i级页表的索引。我们先将VPN分为TLBI和TLBT,然后MMU从虚拟地址中抽取VPN,并且检查TLB。接下来,TLB抽取TLBI和TLBT,并进行匹配。如果匹配成功,则返回缓存的PPN,并与VPO组成PPN。如果TLB不命中,则MMU必须从页表中的PTE中取出PPN。如果取出的PTE无效,则产生缺页警告。
7.5 三级Cache支持下的物理内存访问
对于三级高速缓存而言,总是一级一级往下找,先搜索L1,接着是L2和L3,一旦找到了对应的字就立刻返回。下面是具体的寻找过程。
一般而言,高速缓存可以用元组(S,E,B,M)来描述。参数S,B,M将地址分为了三个字段。组索引位告诉我们这个字被存储在哪个组中,当且仅当设置了有效位且某行的标志位和地址的标志位匹配时,这个字才被存储。接下来,b个块偏移可以具体给出字的在B个字节中的位置。
7.6 hello进程fork时的内存映射
当fork函数被shell调用时,内核会为hello创建各种数据结构,并分配给它一个唯一的pid。为了给hello创建虚拟内存,它创建了hello的mm_struct、区域结构和页表的原样副本。它将两个进程的每个页面都标记为只读,并将两个进程的每个区域结构都标记为私有的写时复制。
当fork在新进程中返回时,新进程的虚拟地址和调用fork的虚拟地址相同,当两个进程中的任意一个进行写操作时,写时复制都会创建新页面,从而保留了每个进程的私有地址的概念。
7.7 hello进程execve时的内存映射
加载和运行hello需要进行以下几步:
1:删除已存在的用户区域。删除当前虚拟地址的用户部分中的已存在的区域结构。
2:映射私有区域。为新程序的代码、数据、bss和栈区域创建新的区域结构。所有这些都是私有的,写时复制的。
3:映射共享区域。如果hello和共享对象链接,那么共享对象都是动态链接到这个程序的,然后在链接到用户虚拟地址空间中的共享区域中。
4:设置程序计数器。设置上下文的程序计数器,使得它指向代码区域的入口点。
7.8 缺页故障与缺页中断处理
当产生缺页是,控制权转到内核中的处理程序。程序先判断这个虚拟地址和试图进行的内存访问是否合法,不合法,输出段错误或者终止程序并设定相关的量。若合法,选择一个牺牲页表,如果这个牺牲页表被修改过,则交换出去并换入新的页,更新页表。当从处理程序返回时,cpu重启引起缺页的指令,这时候可以正常运行。
7.9动态存储分配管理
动态内存管理器维护着一个进程的虚拟地址空间,称为堆。分配器将堆视为一组大小不同的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是空闲的,要么是已经分配的。已分配的块显式地保留为供应用程序使用,空闲块可被分配。一个已分配的块始终保持已分配状态,知道被释放。这种释放要么是应用程序显式执行的,要么是内存分配器隐式执行的。分配器分为显式分配器和隐式分配器两种风格。例如,malloc就是一个显式分配器
7.10本章小结
本章结合hello的内存分配过程,介绍了四种地址的一个基本概念,及其转换的方法。接下来,说明了TLB,多级页表,cache,内存映射,缺页管理,动态内存管理等各种功能的基本工作流程。
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
设备管理:unix io接口
在Linux中,所有的I/O设备都被模型化为文件,而所有的输入输出都被当作对相应文件的读和写来执行。
8.2 简述Unix IO接口及其函数
1.打开文件:内核返回一个非负整数的文件描述符,通过对此文件描述符对文件进行所有操作;
2.Linux shell创建的每个进程开始时都有三个已打开的文件:分别是标准输入(文件描述符0)、标准输出(描述符为1)以及标准出错(描述符为2)。头文件定义了常量STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO,它们可以用来代替显式的描述符值;
3.改变当前的文件位置:文件开始位置为文件偏移量,应用程序通过seek操作,可设置文件的当前位置为k;
4.读写文件,读操作:从文件复制n个字节到内存,从当前文件位置k开始,然后将k增加到k+n;
5.写操作:从内存复制n个字节到文件,当前文件位置为k,然后更新k;
6.关闭文件:当应用完成对文件的访问后,通知内核关闭这个文件。内核会释放文件打开时创建的数据结构,将描述符恢复到描述符池中。
8.3 printf的实现分析
以下是printf函数的函数体
int printf(const char *fmt, …)
{
int i;
char buf[256];
va_list arg = (va_list)((char*)(&fmt) + 4);
i = vsprintf(buf, fmt, arg);
write(buf, i);
return i;
}
printf函数的实现调用了两个函数,分别是vsprintf和write函数。
这两个函数的定义如下:
int vsprintf(char *buf, const char *fmt, va_list args)
{
char* p;
char tmp[256];
va_list p_next_arg = args;
for (p=buf;*fmt;fmt++) {
if (*fmt != ‘%’) {
*p++ = *fmt;
continue;
}
fmt++;
switch (*fmt) {
case ‘x’:
itoa(tmp, *((int*)p_next_arg));
strcpy(p, tmp);
p_next_arg += 4;
p += strlen(tmp);
break;
case ‘s’:
break;
default:
break;
}
}
return (p – buf);
}
write:
mov eax, _NR_write
mov ebx, [esp + 4]
mov ecx, [esp + 8]
int INT_VECTOR_SYS_CALL
其中vsprintf函数返回要打印出来的字符串的长度,write将buf的i个元素写入到终端。
8.4 getchar的实现分析
如果,getchar读取一行输入,且只取第一个字符,如果出错,则返回-1,将用户输入的字符输出到屏幕。
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回
8.5本章小结
本章介绍了linux的I/O管理办法,Unix I/O接口以及printf和getchar函数的实现。
结论
1:hello.c源文件的诞生
2:基于上一步文件预处理得到hello.i
3:基于上一步文件编译得到hello.s
4:基于上一步文件汇编得到hello.o
5:基于上一步文件链接得到hello
6:shell中输入./hello 2021110794 pengzi 0命令,创建进程
7:内核参与进程的运行,处理信号,多种内存机制协助
8:I/O设备保证输出到终端
9:进程被回收
附件
hello.i:将高级语言程序预处理得到;
hello.s:汇编程序
hello.o:可重定位目标程序
hello:可执行目标程序
参考文献
[1] 林来兴. 空间控制技术[M]. 北京:中国宇航出版社,1992:25-42.
[2] 辛希孟. 信息技术与信息服务国际研讨会论文集:A集[C]. 北京:中国科学出版社,1999.
[3] 赵耀东. 新时代的工业工程师[M/OL]. 台北:天下文化出版社,1998 [1998-09-26]. http://www.ie.nthu.edu.tw/info/ie.newie.htm(Big5).
[4] 谌颖. 空间交会控制理论与方法研究[D]. 哈尔滨:哈尔滨工业大学,1992:8-13.
[5] KANAMORI H. Shaking Without Quaking[J]. Science,1998,279(5359):2063-2064.
[6] CHRISTINE M. Plant Physiology: Plant Biology in the Genome Era[J/OL]. Science,1998,281:331-332[1998-09-23]. http://www.sciencemag.org/cgi/ collection/anatmorp.
[7]C语言中,编译预处理命令的作用有哪些,C语言系列——预处理命令 CSDN博客
[8]C语言:编译,预处理,宏定义与预处理 CSDN博客
[9]readelf命令使用说明 CSDN博客
[10]关于链接的较为全面的介绍 CSDN博客
[11]哈工大2022年春季学期计算机系统大作业-程序人生 CSDN博客
[12] CUP 三级缓存L1 L2 L3 cahe详解 CSDN博客