【小叶】编译和链接【未完待续】

构建:编译和链接合并到一起的过程

一、预处理Prepressing

你们随便找篇博客、找本书吧,都有

二、编译Compilation

词法分析、语法分析、语义分析及优化后生成相应的汇编代码文件

扫描(词法分析)-语法分析(检查表达式是否合法)-语义分析(静态语义通常包括声明和类型匹配、类型的转换;动态语义:运行期出现的语义相关问题,比如除0)-源代码优化-代码生成-目标代码优化

三、汇编Assembly

将汇编代码转换成机器可以执行的指令。输出目标文件

四、链接Linking

把各个模块间相互引用的部分处理好,能够正确地衔接。主要包括:地址和空间分配、符号决议、重定位。A模块使用B模块函数func(),单独编译时A不知道func()地址,链接时由链接器将目标地址修正,填入正确的func()地址。

4.1 静态链接

链接器为变量、函数分配地址

4.2 动态链接

要解决的问题

多个模块用同一个函数func(),同时跑多个模块的进程时,不希望内存加载多份func();

将公共的函数放在一个文件中,只加载到内存中一次(.dll, .so)。

动态库数据段、代码段靠在一起,没有像可执行程序的数据段、代码段合并。在不同进程的虚拟地址,并不一定相同。动态库文件中的符号,加载时才解析,也就是动态链接。

地址无关代码

重定位-加载期间或运行期间。加载期间的重定位依赖地址无关代码PIC (position independent code),尤其是两个共享库有引用关系。

GOT

五、加载

可执行文件(经过编译器和链接器处理后的二进制文件,假设叫a.out)加载进内存时,不同的section(ELF中标记为LOAD的),会被加载进内存中不同的segment。

5.1 虚拟内存到物理内存

主要是怎么映射。要了解MMU、也要了解页表的作用和变化。

进程使用页表。

5.2 可执行文件到虚拟内存

为了简化,我们现在只考虑程序怎么在进程中跑起来。

int val = 10;
int *pval = &val; 

int *pmalloc_data = (int *)malloc(sizeof(int) * 2000u); 
  • pval 其实是虚拟地址  /* 前提支持虚拟内存 */
  • pmalloc_data 其实也是虚拟地址;
  • pmalloc_data[0]~pmalloc_data[999] 在虚拟内存上是连续的,在物理内存上是连续的吗?不一定哦(操作系统的事,你别管)

简化的方法就是只考虑虚拟内存,忽略掉虚拟内存与物理内存的转换过程,忽略掉数据从磁盘到L3-L2-L1缓存的过程,不然就太复杂了。建议在脑袋里,按模块隔离掉各个系统的知识点,在需要的时候,又能通过“接口”串起来。

我们的代码,经过编译、链接变成了二进制,变成了a.out。这些二进制已经被解析成代码(.text)、数据(.data(又分很多种,就很烦)),是有意义的。

当我们执行可执行文件时,二进制再加载进内存(其实是加载进物理内存,为了简化我们构建了一个虚拟内存“层”,使得地址摆的好看、用的方便)。加载也不是扔到一起,放得下就行,而是要映射到不同的内存上。同时,不同的segment还区分了可读、可写权限。

虚拟内存的排布,由a.out决定,又要考虑程序运行过程中的数据计算、指令操作等。同时,进程跑起来,调用函数的过程中,“堆”、“栈”也要加入进来。这个过程是动态的,真正用到时,操作系统才会为其映射真正的物理内存,而且用多少才映射多少。所以malloc可能会失败,递归可能会堆栈溢出,因为我们一开始是无法估计他们到底要用多少的,我们只是告诉他们能用的最大值是多少(操作系统的事)。

一个经典的虚拟内存排布图,如下:(进程的虚拟地址空间分布(@ JoeChenzzz))

【小叶】编译和链接【未完待续】_第1张图片

不是吓唬大家,这个图上的每个字,都能聊很久,这里点到为止。

5.3 虚拟内存与进程

虚拟内存,可以为各个进程提供统一的地址排布方式,但实际映射的物理地址不同,由此进程间隔离,不会相互影响。

一个进程可以理解为打包了一批内存,给进程中的各个线程使用,有些每个线程公用。比如合租房(603)里的厨房、客厅、阳台、卫生间,三个卧室都能访问。各个卧室(线程)又有独立的空间,甚至有的还有独立卫浴,另外两个人公用外面的卫浴。如果一个人把客厅的桌子掀了,那这套房(进程)中每个卧室的主人,也都用不了。但是不会影响到隔壁602继续吃饭、睡觉、打游戏。

你可能感兴趣的:(计算机体系知识,c++)