Linux进程地址空间布局
一个Liunx应用程序进程在内存中有不同的分段(Segment),而进程地址空间布局中,从物理上,数据(user data, sys data)和指令,都是“数据”,但是从逻辑上来讲,是有区别的,属性不一样。有些数据是指令,只读,不可写,有些数据可写,有些数据只可读。
【题外话】关于CPU如何区分内存中数据到底是“指令”还是“数据”,就需要一些基础汇编知识了。举例来说,CPU中有几个寄存器专门用来指向“数据”或“指令”,CPU将会根据该寄存器指向的内存地址来确定内存中的数据到底需要被解释为“指令”还是“数据”。例如,DS(Data Segment)寄存器指向的内存地址被CPU认为是“数据”,而CS(Code Segment)寄存器和IP(Instruction Pointer)寄存器则共同决定了CPU将要执行的”指令“(其本质上也是数据,但是这些数据可以被CPU所理解,是CPU可以执行的动作)。
从逻辑层面(操作系统)把数据分成不同的段(不同的区域)来存储:
这里我们不做过多讨论,因为网上的资料已经非常全面,具体可以参考如下文章,都是非常不错的总结:
但是笔者想要补充的一点是:
无论是.bss段或是.rodata段或是上图中的“数据段”(更严谨一点来说,应该是经过初始化的数据段),都应该属于“数据段”,只是因为其中每段中存放的数据有各自的特点而分开了。
何为BSS段?
关于BSS段的出处以及BSS这个缩写的的全称,可以参考如下文章,讲的非常不错:
深入理解BSS(Block Started by Symbol) - veli - 博客园
下面挑一些重点来“赘述”一下,为下文的实验环节建立理论基础。
根据上图应该可以明显地看出来,BSS段是“未初始化的数据”,但是注意,不是随便一个未初始化的变量都放在了BSS段,放入BSS段中的变量需要满足以下条件的其中之一:
- 所有未被显示地初始化的全局变量和静态变量(包括局部静态变量)
- 所有被显式地初始化为0的全局变量和静态变量(包括局部静态变量)
举例:
全局声明 int g_si;或 全局声明 int g_si = 0; 此时g_si是放在BSS段中的。
static int si; 或static int si = 0; 此时si是存放在BSS段中的。注意,无论该声明语句是全局的还是在某个代码块中,si都是存放在BSS段的。
在这里加粗部分是笔者想要补充的,具体验证请查看下面的实验部分。
验证
笔者使用WSL(Windows SubSystem Linux)来进行验证,编译器GCC版本9.3.0。
使用size命令可以查看ELF文件(或二进制文件)的各个分区(Section)大小。首先写一个空白程序,如下:
使用gcc编译后,使用size命令查看a.out(由该源文件生成的可执行程序)的分区大小:
可以看出,一个几乎”空白“的程序中,BSS段目前的大小是8个字节(这里虽然我们没有声明任何变量,BSS段也会占用,具体占用大小笔者暂未研究)。
本验证环节主要是验证:在代码块中的静态变量何时会存放在BSS段中,所以全局非静态变量本环节不做演示,可以自己验证一下。
修改程序如下:
编译后使用size命令查看的结果如下:
可以看出,此时data段没有发生任何变化,而BSS段却增长了8个字节,从8变成了16。这就说明,gsi1,gsi2,gsi3确实是位于BSS段中的。
再次修改程序:
编译后使用size命令查看结果:
与未显式赋值为0的情况相同,BSS段仍然是16。
但是如果我们给定一个初始值呢?
再次修改程序:
结果如下:
惊奇地发现,BSS段没有增加,反而从8个字节变成了4个字节(减少的原因笔者暂未研究),但是data段的大小从544变成了556,增加了12个字节(3个int型数据,即3*4)。
看到这里,上面的结论中,“未显式初始化或初始化为0的静态变量存放在BSS段”已经被验证了一半,因为我们只是测试了全局静态变量,而对于局部的静态变量是否也成立,需要进一步验证:
以上两种程序经过编译后,得出的结果是一样的,相比较“空源程序”生成的可执行文件,BSS段增加了8个字节,而data段大小没有发生改变。至此,可以验证出“未显式初始化或初始化为0的静态变量存放在BSS段”这句话的正确性了。这句话中提到的的静态变量,无论是全局静态变量还是局部静态变量,都是存放在BSS段中的。
那么,如果局部静态变量在声明时被赋予了一个非零值呢?
可以看出,此时si1,si2和si3已经不在BSS段中了,而又是data段增加了12个字节。
总结:
- 所有未被显示地初始化的全局变量和静态变量(包括局部静态变量)
- 所有被显式地初始化为0的全局变量和静态变量(包括局部静态变量)
符合上述两种条件的变量被存放在BSS段中。