Linux段管理,BSS段,data段,.rodata段,text段

时间:2021-05-11 16:13:56

 最近在解决一个编译问题时,一直在考虑一个问题,那就是Linux下可执行程序运行时内存是什么状态,是按照什么方式分配内存并运行的。查看了一下资料,就此总结一下,众所周知,linux下内存管理是通过虚存管理的,在分配内存是并非在物理内存开辟了一段空间,而是在使用时才分配的,而且是通过段页式管理。以上比较废话,开始看看程序运行时内存会是什么状态。

        在linux下内存分配是以页为单位的,而页是通过段管理,各个段之间是独立的,方便管理。linux程序运行时,可以分为以下几个内存段:

一、BSS段 bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。

    该段用于存储未初始化的全局变量或者是默认初始化为0的全局变量,它不占用程序文件的大小,但是占用程序运行时的内存空间。

view plaincopy to clipboardprint?
  1. #define DEBUG "debug"int space[1024][1024];int main(){  char *a = DEBUG;  return 1;}  

上面声明了一个space的二维数组,是一个全局变量,没有被初始化,通过nm命令可以查看程序中的符号信息如下:

view plaincopy to clipboardprint?
  1. 0000000000600660 d _DYNAMIC00000000006007f8 d _GLOBAL_OFFSET_TABLE_0000000000400578 R _IO_stdin_used                 w _Jv_RegisterClasses0000000000600640 d __CTOR_END__0000000000600638 d __CTOR_LIST__0000000000600650 D __DTOR_END__0000000000600648 d __DTOR_LIST__0000000000400630 r __FRAME_END__0000000000600658 d __JCR_END__0000000000600658 d __JCR_LIST__000000000060081c A __bss_start0000000000600818 D __data_start0000000000400530 t __do_global_ctors_aux00000000004003e0 t __do_global_dtors_aux0000000000400580 R __dso_handle                 w __gmon_start__0000000000600634 d __init_array_end0000000000600634 d __init_array_start0000000000400490 T __libc_csu_fini00000000004004a0 T __libc_csu_init                 U __libc_start_main@@GLIBC_2.2.5000000000060081c A _edata0000000000a00840 A _end0000000000400568 T _fini0000000000400358 T _init0000000000400390 T _start00000000004003bc t call_gmon_start0000000000600820 b completed.63470000000000600818 W data_start0000000000600828 b dtor_idx.63490000000000400450 t frame_dummy0000000000400474 T main0000000000600840 B space  

最后一行的B表示是BSS段,也就表示space是存在于BSS段中的。

二、data段 该段用于存储初始化的全局变量,初始化为0的全局变量出于编译优化的策略还是被保存在BSS段,对上面的程序做一下更改就可以看到是如何分配的了。

view plaincopy to clipboardprint?
  1. #define DEBUG "debug"int space[1024][1024];int data = 1;int no_data = 0;int main(){  char *a = DEBUG;  return 1;}  

使用nm查看后

view plaincopy to clipboardprint?
  1. 0000000000600660 d _DYNAMIC00000000006007f8 d _GLOBAL_OFFSET_TABLE_0000000000400578 R _IO_stdin_used                 w _Jv_RegisterClasses0000000000600640 d __CTOR_END__0000000000600638 d __CTOR_LIST__0000000000600650 D __DTOR_END__0000000000600648 d __DTOR_LIST__0000000000400630 r __FRAME_END__0000000000600658 d __JCR_END__0000000000600658 d __JCR_LIST__0000000000600820 A __bss_start0000000000600818 D __data_start0000000000400530 t __do_global_ctors_aux00000000004003e0 t __do_global_dtors_aux0000000000400580 R __dso_handle                 w __gmon_start__0000000000600634 d __init_array_end0000000000600634 d __init_array_start0000000000400490 T __libc_csu_fini00000000004004a0 T __libc_csu_init                 U __libc_start_main@@GLIBC_2.2.50000000000600820 A _edata0000000000a00840 A _end0000000000400568 T _fini0000000000400358 T _init0000000000400390 T _start00000000004003bc t call_gmon_start0000000000600820 b completed.6347000000000060081c D data0000000000600818 W data_start0000000000600828 b dtor_idx.63490000000000400450 t frame_dummy0000000000400474 T main0000000000600830 B no_data0000000000600840 B space  

可以看到变量data被分配在data段,而被初始化为0的no_data被分配在了BSS段。

三、.rodata段

该段也叫常量区,用于存放常量数据,ro就是Read Only之意。但是注意并不是所有的常量都是放在常量数据段的,其特殊情况如下:
1)有些立即数与指令编译在一起直接放在代码段。

view plaincopy to clipboardprint?
  1. int main(){  int a = 10;  return 1;}  
a是常量,但是它没有被放入常量区,而是在指令中直接通过立即数赋值

Linux段管理,BSS段,data段,.rodata段,text段

2)对于字符串常量,编译器会去掉重复的常量,让程序的每个字符串常量只有一份。

view plaincopy to clipboardprint?
  1. char *str = "123456789";char *str1 = "helloworld";int main(){  char* a = "helloworld";  char b[10] = "helloworld";  return 1;}  
汇编代码如下:

view plaincopy to clipboardprint?
  1. .file   "hello.c".globl str        .section        .rodata.LC0:        .string "123456789"        .data        .align 8        .type   str, @object        .size   str, 8str:        .quad   .LC0.globl str1        .section        .rodata.LC1:        .string "helloworld"        .data        .align 8        .type   str1, @object        .size   str1, 8str1:        .quad   .LC1        .text.globl main        .type   main, @functionmain:.LFB0:        .cfi_startproc        pushq   %rbp        .cfi_def_cfa_offset 16        .cfi_offset 6, -16        movq    %rsp, %rbp        .cfi_def_cfa_register 6        movq    $.LC1, -8(%rbp)        movl    $1819043176, -32(%rbp)        movl    $1919907695, -28(%rbp)        movw    $25708, -24(%rbp)        movl    $1, %eax        leave        .cfi_def_cfa 7, 8        ret        .cfi_endproc.LFE0:        .size   main, .-main        .ident  "GCC: (GNU) 4.4.6 20110731 (Red Hat 4.4.6-3)"        .section        .note.GNU-stack,"",@progbits  
可以看到str1和a同时指向.rodata段中同一个LC1,而是用数组初始化的字符串常量是没有放入常量区的,另外用const修饰的全局变量是放入常量区的,但是使用cons修饰的局部变量只是设置为只读起到防止修改的效果,没有放入常量区。

3)有些系统中rodata段是多个进程共享的,目的是为了提高空间的利用率。

四、text段

text段是用于存放程序代码的,编译时确定,只读。更进一步讲是存放处理器的机器指令,当各个源文件单独编译之后生成目标文件,经连接器链接各个目标文件并解决各个源文件之间函数的引用,与此同时,还得将所有目标文件中的.text段合在一起,但不是简单的将它们“堆”在一起就完事,还需要处理各个段之间的函数引用问题。

五、stack段

也就是栈段,常说的堆栈段之一,是由系统负责申请释放,其操作方式类似stack,用于存储参数变量及局部变量,其实函数的执行也是stack的方式,所以才有了递归

六、heap段

也就是俗称的堆,它由用户申请和释放,申请时至少分配虚存,当真正存储数据时才分配相应的实存,释放时也并非立即释放实存,而是可能被重复利用,待后续会再仔细介绍相关的知识。

Linux段管理,BSS段,data段,.rodata段,text段

可以看到堆和栈的内存增长方向是相反的,后续会对linux的内存管理做详细的介绍