linux進程空間布局


本文主要是對於linux程序執行時建立的虛擬地址空間做一定程度的描述,以及個人對於代碼到進程空間之間轉換的理解。

從操作系統的角度來看,進程最關鍵的特征就是它擁有獨立的虛擬地址空間,進程之間由此得以隔離區分。一個程序的執行主要做了三件事:

  • 創建一個獨立的虛擬地址空間。
  • 讀取可執行文件頭,並且建立虛擬空間與可執行文件的映射關系。
  • 將CPU的指令寄存器設置成為可執行文件的入口地址,啟動運行。

這三件事是《程序員的自我修養》中6.3.1 進程的建立章節中所描述,具體內容本文不會重復描述,而是講述個人對於程序執行過程中所建立的虛擬地址空間的一些淺顯的理解。

在32位的操作系統中,虛擬地址空間的地址范圍為0x00000000 ~ 0xFFFFFFFF,以下為大致的進程虛擬空間圖。

    

此處我們暫且先不去理會虛擬地址空間如何映射到物理空間,也不關心如何將可執行文件裝載到進程虛擬地址空間的過程,而是把重點放在代碼與虛擬地址空間的映射關系上。我們知道在代碼在經過預處理、編譯、匯編與鏈接之后會生成一個可執行文件,用術語來說就是ELF格式文件(Executable Linkable Format)。

ELF文件由ELF文件頭與許多段(section)組成,段中我們比較熟悉的有數據段、代碼段等。一般來說,C語言編譯之后的可執行語句變成了可執行機器代碼,保存在.text段;已經初始化的全局變量和局部靜態變量都保存在.data段;未初始化的全局變量和局部靜態變量都保存在.bss段。其中未初始化的全局變量和局部靜態變量默認值都是0,也就是說.bss段中的值都是0。空口無憑,下面用代碼證明一下。

#include<stdio.h>

int g_init_var = 123;
int g_uninit_var;

void func(int param)
{
    static int s_init_var = 456;
    static int s_uninit_var;
    printf("param address:%p, s_init_var address:%p, s_uninit_var address:%p\n", &param, &s_init_var, &s_uninit_var);
    printf("g_init_var address:%p, g_uninit_var address:%p\n", &g_init_var, &g_uninit_var);
    printf("param value:%d, s_init_var value:%d, s_uninit_var value:%d\n", param, s_init_var, s_uninit_var);
    printf("g_init_var value:%d, g_uninit_var value:%d\n", g_init_var, g_uninit_var);
}

int main(void)
{
    int a = 4;
    int b;
    func(b);    
    printf("func address:%p\n", func);
    printf("main address:%p\n", main);
    
    return 0;
}

從上面的代碼我們可以看出,有兩個全局變量,一個已經初始化,一個未初始化;兩個局部靜態變量,也是一個已經初始化,一個未初始化。我們通過linux自帶工具objdump可以查看經過編譯、匯編之后的文件內容,如下。

# gcc -c test.c
# objdump -x test.o

test.o:     file format elf32-i386
test.o
architecture: i386, flags 0x00000011:
HAS_RELOC, HAS_SYMS
start address 0x00000000

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         000000e5  00000000  00000000  00000034  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000008  00000000  00000000  0000011c  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000004  00000000  00000000  00000124  2**2
                  ALLOC
  3 .rodata       000000fe  00000000  00000000  00000124  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .comment      0000002c  00000000  00000000  00000222  2**0
                  CONTENTS, READONLY
  5 .note.GNU-stack 00000000  00000000  00000000  0000024e  2**0
                  CONTENTS, READONLY
SYMBOL TABLE:
00000000 l    df *ABS*    00000000 test.c
00000000 l    d  .text    00000000 .text
00000000 l    d  .data    00000000 .data
00000000 l    d  .bss    00000000 .bss
00000000 l    d  .rodata    00000000 .rodata
00000000 l     O .bss    00000004 s_uninit_var.1708
00000004 l     O .data    00000004 s_init_var.1707
00000000 l    d  .note.GNU-stack    00000000 .note.GNU-stack
00000000 l    d  .comment    00000000 .comment
00000000 g     O .data    00000004 g_init_var 00000004       O *COM*    00000004 g_uninit_var 00000000 g     F .text    00000097 func
00000000         *UND*    00000000 printf
00000097 g     F .text    0000004e main


RELOCATION RECORDS FOR [.text]:
// 省略...

從符號表(SYMBOL TABLE)中,我們可以清楚的看到初始化了的全局變量g_init_var與局部靜態變量s_init_var是放到了.data段中的,而未初始化的局部靜態變量s_uninit_var放到了.bss段,至於未初始化的全局變量g_uninit_var放到了一個未定義的“COMMON符號”中。前面提過,.bss段中存放的是未初始化的全局變量和局部靜態變量,而這里未初始化的全局變量g_uninit_var並未放到.bss段中,這其實是跟不同的語言與不同的編譯器實現有關,有些編譯器會將全局的的未初始化變量放到目標文件.bss段,有些則不存放,只是預留一個未定義的全局變量符號,等到最終鏈接成為可執行文件的時候再在.bss段分配空間。

從下面執行size查看test.o文件大小的結果中,我們可以清楚看到data段大小為8字節,正好等於兩個int全局變量(全局變量g_init_var與局部靜態變量s_init_var的大小)的大小,而.bss段中只有未初始化的局部靜態變量s_uninit_var,所以大小為4字節,所以也是相符的。

# size test.o
   text       data        bss        dec        hex    filename
    330          8          4        342        156    test.o

接下來我們進一步把程序鏈接成為可執行程序,並查看一下ELF可執行文件中的內容。

# gcc -o test test.o
# ./test
param address:0xbff88320, s_init_var address:0x804a018, s_uninit_var address:0x804a024
g_init_var address:0x804a014, g_uninit_var address:0x804a028
param value:134513867, s_init_var value:456, s_uninit_var value:0
g_init_var value:123, g_uninit_var value:0 func address:0x80483c4 main address:0x804845b
# objdump -x test

test:     file format elf32-i386
test
architecture: i386, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x08048310

Program Header:
// 省略...

Dynamic Section:
// 省略...

Version References:
// 省略...

Sections:
// 省略...

SYMBOL TABLE:
08048134 l    d  .interp    00000000              .interp
08048148 l    d  .note.ABI-tag    00000000              .note.ABI-tag
08048168 l    d  .note.gnu.build-id    00000000              .note.gnu.build-id
0804818c l    d  .gnu.hash    00000000              .gnu.hash
080481ac l    d  .dynsym    00000000              .dynsym
080481fc l    d  .dynstr    00000000              .dynstr
08048248 l    d  .gnu.version    00000000              .gnu.version
08048254 l    d  .gnu.version_r    00000000              .gnu.version_r
08048274 l    d  .rel.dyn    00000000              .rel.dyn
0804827c l    d  .rel.plt    00000000              .rel.plt
08048294 l    d  .init    00000000              .init
080482c4 l    d  .plt    00000000              .plt
08048310 l    d  .text    00000000              .text
0804854c l    d  .fini    00000000              .fini
08048568 l    d  .rodata    00000000              .rodata
08048670 l    d  .eh_frame    00000000              .eh_frame
08049f14 l    d  .ctors    00000000              .ctors
08049f1c l    d  .dtors    00000000              .dtors
08049f24 l    d  .jcr    00000000              .jcr
08049f28 l    d  .dynamic    00000000              .dynamic
08049ff0 l    d  .got    00000000              .got
08049ff4 l    d  .got.plt    00000000              .got.plt
0804a00c l    d  .data    00000000              .data
0804a01c l    d  .bss    00000000              .bss
00000000 l    d  .comment    00000000              .comment
00000000 l    df *ABS*    00000000              crtstuff.c
08049f14 l     O .ctors    00000000              __CTOR_LIST__
08049f1c l     O .dtors    00000000              __DTOR_LIST__
08049f24 l     O .jcr    00000000              __JCR_LIST__
08048340 l     F .text    00000000              __do_global_dtors_aux
0804a01c l     O .bss    00000001              completed.7065
0804a020 l     O .bss    00000004              dtor_idx.7067
080483a0 l     F .text    00000000              frame_dummy
00000000 l    df *ABS*    00000000              crtstuff.c
08049f18 l     O .ctors    00000000              __CTOR_END__
08048670 l     O .eh_frame    00000000              __FRAME_END__
08049f24 l     O .jcr    00000000              __JCR_END__
08048520 l     F .text    00000000              __do_global_ctors_aux
00000000 l    df *ABS*    00000000              test.c
0804a024 l O .bss 00000004              s_uninit_var.1708 0804a018 l O .data 00000004              s_init_var.1707
08049ff4 l     O .got.plt    00000000              _GLOBAL_OFFSET_TABLE_
08049f14 l       .ctors    00000000              __init_array_end
08049f14 l       .ctors    00000000              __init_array_start
08049f28 l     O .dynamic    00000000              _DYNAMIC
0804a00c  w      .data    00000000              data_start
080484b0 g     F .text    00000005              __libc_csu_fini
08048310 g     F .text    00000000              _start
00000000  w      *UND*    00000000              __gmon_start__
00000000  w      *UND*    00000000              _Jv_RegisterClasses
08048568 g     O .rodata    00000004              _fp_hw
0804854c g     F .fini    00000000              _fini
00000000       F *UND*    00000000              __libc_start_main@@GLIBC_2.0 0804a028 g O .bss 00000004 g_uninit_var 0804a014 g O .data 00000004 g_init_var
0804856c g     O .rodata    00000004              _IO_stdin_used
0804a00c g       .data    00000000              __data_start
080483c4 g F .text 00000097 func
0804a010 g     O .data    00000000              .hidden __dso_handle
08049f20 g     O .dtors    00000000              .hidden __DTOR_END__
080484c0 g     F .text    0000005a              __libc_csu_init
00000000       F *UND*    00000000              printf@@GLIBC_2.0
0804a01c g       *ABS*    00000000              __bss_start
0804a02c g       *ABS*    00000000              _end
0804a01c g       *ABS*    00000000              _edata
0804851a g     F .text    00000000              .hidden __i686.get_pc_thunk.bx
0804845b g F .text 0000004e main 08048294 g     F .init    00000000              _init

從執行結果與objdump查看的結果來看,main與func函數都位於.text段,之前在test.o中以未知的"COMMON符號"存在的未初始化的全局變量g_uninit_var也已經放到了.bss段。從初始化值來看,未初始化靜態局部變量與全局變量值都是為0,未初始化的局部變量結果未知。根據test可執行文件的內容,我們可以畫出以下的虛擬進程空間圖。

  

在這個名為test的ELF可執行文件中,ELF文件頭占據了54字節大小;程序的入口點為0x08048310,也就是.text段的起始地址,這個就是glibc的程序入口_start。在本文的最開始說過,進程的建立做的第三件事將CPU的指令寄存器設置成為可執行文件的入口地址,然后啟動運行,而此test程序中所指的入口地址也就是_start。我們常說main函數是一個程序的入口,實際上linux程序實際的入口往往指的是_start,main函數的調用需要經歷_start -> __libc_start_main,在__libc_start_main中傳入環境變量,指定棧底的地址等操作之后,開始執行main函數。

寫到這里,基本對於代碼中的實現如何對應進程虛擬空間位置做了一定程序的敘述,順便也提及了程序啟動入口執行的位置,至於如何創建虛擬地址空間,系統將執行權交還給程序之后,程序如何遞歸調用函數,就不在本文中繼續敘述了,有興趣的童鞋可以研究或是查看一下資料。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM