程序員的自我修養筆記之裝載

可執行文件的裝載與進程

介紹ELF文件在Linux下的裝載過程,探尋可執行文件裝載的本質git

  • 什麼是進程的虛擬地址空間
  • 爲何進程要有本身獨立的虛擬地址空間
  • 幾種裝載方式
  • 進程虛擬地址空間的分佈狀況

進程虛擬地址空間

32位硬件平臺決定了虛擬地址空間的地址爲0 到2^32 -1,即0x00000000 ~ 0xFFFFFFFF,也就是4GB的虛擬空間大小;而63位的硬件平臺具備64位尋址能力,它的虛擬地址空間達到了2^64 字節,即0x0000000000000000 ~ 0xFFFFFFFFFFFFFFFF,總共17179869184GB程序員

而從程序的角度看,C語言中的指針所佔空間可用於計算虛擬地址空間的大小,通常狀況下,C語言指針大小的位數與虛擬空間的位數相同,如32位平臺下的指針爲32位,4字節。github

如下以32爲地址空間爲主,64位做爲擴展。算法

默認狀況下Linux系統將進程的虛擬地址空間做以下分配:windows

1550220847534

其中的操做系統使用的空間,進程是不被容許訪問的,且進程並不能徹底使用剩下的3GB虛擬空間,其中一部分是預留給其餘用途的。bash

1550220996919

PAE

Linux下Intel在1995年的Pentium Pro CPU便開始使用36位的物理地址,便可以訪問64GB的物理內存。這時,操做系統只能有4GB的虛擬地址空間,沒法所有讀取完64GB的物理內存,而PAE就是爲了解決這個問題出現的。數據結構

PAE(Physical Address Extension)是一種地址擴展方式,Inter修改了頁映射的方式後,使得新的映射方式能夠訪問更多的物理內存。操做系統提供一個窗口映射的方法,將額外的內存映射進地址空間中。應用程序根據須要選擇申請和映射。好比應用程序中的0x10000000~0x20000000這一段256MB的虛擬地址空間做爲窗口,程序從高於4GB的物理空間中申請多個大小爲256MB的物理空間,編號爲A,B,C,而後根據須要將窗口映射到不一樣的物理空間塊,用到A時將0x10000000~0x20000000映射到A,用到B,C時在映射過去,如此重複。在Windows下,這種內存操做方式爲AWE(Address Windows Extensions)。像Linux等UNIX系統則採用mmp()系統調用來實現app

裝載方式

覆蓋裝入

沒有發明虛擬存儲以前使用得比較普遍,現已幾乎被淘汰。函數

覆蓋裝入的方法把挖掘內存潛力的任務交給了程序員程序員在編寫程序時必須手動將程序分割成若干塊,而後編寫小的輔助代碼管理這些模塊什麼時候駐留在內存,什麼時候被替換。這個輔助代碼被稱爲覆蓋管理器(Overlay Manager),好比下圖ui

1550223485233

模塊A與B之間相互沒有調用依賴關係,所以兩模塊共享內存區域,當

使用A時則覆蓋該內存,使用B時覆蓋該內存,覆蓋管理器則做爲常駐內存。

多模塊則以下,程序員須要手工將模塊按照它們之間的調用依賴關係組織成樹狀結構

1550223740492

所以覆蓋管理器須要保證一下亮點。

  • 樹狀結構中從任何一個模塊到樹的根(main)都叫調用路徑,當模塊被調用時,這個調用路徑上的模塊必須在內存之中。好比C模塊正在執行時,B和main都須要在內存中,確保E執行完畢後能正確返回到模塊B和main。

  • 禁止跨樹間調用

    任意模塊不容許跨樹狀結構進行調用,好比A不能夠調用B,E,F。但不少時候兩個模塊都依賴於同一個模塊,如模塊E和模塊C須要另一個模塊G,則最方便的方法就是把模塊G併入到main模塊中,這樣G就在E和C的調用路徑上了。

頁映射

頁映射是虛擬存儲機制的一部分,隨着虛擬存儲的發明而誕生。

頁映射將內存和全部磁盤中的數據及指令按照**頁(Page)**爲單位劃分若干頁,之後全部的裝載和操做單位就是頁。

1550224180737

假設程序全部的指令和數據總共32KB,那麼程序被分爲8頁,並編號P0~P7。但16KB內存沒法將32KB程序裝入,此時將按照動態裝入的原理進行裝入過程。若是程序執行入口在P0則裝載管理器發現程序的P0不在內存中,則將內存F0分配給P0,並將P0的內存扎un購入F0中。運行後使用P5,則將P4裝入F1,以此類推,以下所示:

1550224497404

若是程序繼續運行須要訪問P4,則裝載管理器必須選擇放棄目前正在使用的4哥內存頁中的其中一個來裝載P4,放棄的算法有不少,如:

  • FIFO先進先出,則放棄F0,P4裝入F0
  • LUR最少使用,則放棄F2,P4裝入F2

等算法。而這裏所謂的裝載管理器就是現代的操做系統,準確說影視就是操做系統的存儲管理器。

從操做系統角度看可執行文件的裝載並在進程中執行

進程的創建

進程關鍵特徵在於它擁有獨立的虛擬地址空間。一個程序被執行,每每在最開始時須要作三件事:

  • 建立獨立的虛擬地址空間

    即建立映射函數所須要的相應的數據結構,而在i386的Linux下,建立虛擬地址空間實際上只是分配一個頁目錄,甚至不須要設置映射關係。也就是完成虛擬空間到物理內存的映射關係。

  • 讀取可執行文件頭,創建虛擬空間與可執行文件的映射關係

    完成虛擬空間與可執行文件的映射關係,這一步是整個裝載過程當中最重要的一步,也就是傳統意義上的「裝載」。

    如圖,考慮最簡單的例子,虛擬地址如圖,文件大小爲0x000e1,對齊爲0x1000。因爲.text段大小不到0x1000,所以須要對齊。

    1550650714552

    這種映射關係是保存在操做系統內部的一個數據結構。Linux將進程虛擬空間中的一個段叫作虛擬內存區域(VMA.Virtual Memory Area)。windows叫作虛擬段(Virtual Section)。在上面例子中,會在進程相應的數據結構中設置有一個.text段的VMA,它在虛擬空間中的地址爲0x08048000~0x08049000對應ELF文件中偏移爲0的.text,屬性爲只讀。

  • 將CPU的指令寄存器設置成可執行文件的入口地址,啓動運行

頁錯誤

上述步驟執行完後,只是經過可執行文件頭部信息創建起可執行文件和進程虛存之間的映射關係,並無將可執行文件的指令和數據裝入內存。

假設在上面的例子中,程序入口地址爲0x08048000,恰好是.text段的其實地址,CPU打算執行時發現爲空頁面時,便認爲這是個頁錯誤(Page Fault)。CPU將控制權交給操做系統,操做系統將查詢上面說到的數據結構,找到空頁面所在VMA,計算出相應的頁面在可執行文件中的偏移,再在物理內存中分配一個物理頁面,將進程中該虛擬頁與分配的物理頁之間創建映射關係,再把控制權還給進程,進程從剛纔頁錯誤的位置從新開始執行,以下圖所示,爲可執行文件,進程虛存與物理內存之間的關係:

1550995358009

進程虛存空間分佈

ELF文件連接視圖和執行視圖

在實際場景裏面,ELF文件段數量是比較多的,但因爲須要進行頁對齊等操做,若是以一個段進行頁的分配的話,勢必會形成較大的浪費。

可是在操做系統的角度來看裝載可執行文件(操做系統並不須要知道哪一個段名稱是什麼,做用如何等等信息),發現並不關心可執行文件實際內容,而只是關心跟裝載相關的問題,最主要的就是段的權限問題。

段的權限組合基本是如下三種

  • 以代碼段爲表明的可讀可執行段
  • 以數據段和BSS段爲表明的可讀可寫段
  • 以只讀數據段爲表明的權限爲只讀的段

所以找到一個以權限種類爲劃分,將相同權限的段合併在一塊兒進行映射的方案,而合併後的數據稱之爲Segment,如.text段和.init段合在一塊兒看做爲一個Segment,那麼在裝載時即可以把他們看做一個個總體進行裝載,這樣就能夠達到明顯減小頁面內部碎片化的問題,從而節省空間。

對好比下圖,左邊爲按段裝載,又邊爲合併後按Segment裝載

1550997079148

下面編寫一個例子程序:

SectionMapping.c
#include <stdlib.h>

int main(){
	while(1){
		sleep(1000);
	}
	return 0;
}
gcc -static SectionMapping.c -o SectionMapping.elf
複製代碼

而後再使用readelf

readelf -S SectionMapping.elf
複製代碼

獲得以下信息

There are 33 section headers, starting at offset 0xc4d50:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .note.ABI-tag     NOTE             0000000000400190  00000190
       0000000000000020  0000000000000000   A       0     0     4
  [ 2] .note.gnu.build-i NOTE             00000000004001b0  000001b0
       0000000000000024  0000000000000000   A       0     0     4
readelf: Warning: [ 3]: Link field (0) should index a symtab section.
  [ 3] .rela.plt         RELA             00000000004001d8  000001d8
       00000000000001f8  0000000000000018  AI       0    24     8
  [ 4] .init             PROGBITS         00000000004003d0  000003d0
       0000000000000017  0000000000000000  AX       0     0     4
  [ 5] .plt              PROGBITS         00000000004003e8  000003e8
       00000000000000a8  0000000000000000  AX       0     0     8
  [ 6] .text             PROGBITS         0000000000400490  00000490
       00000000000874fc  0000000000000000  AX       0     0     16
  [ 7] __libc_freeres_fn PROGBITS         0000000000487990  00087990
       00000000000024e9  0000000000000000  AX       0     0     16
  [ 8] __libc_thread_fre PROGBITS         0000000000489e80  00089e80
       00000000000003d7  0000000000000000  AX       0     0     16
  [ 9] .fini             PROGBITS         000000000048a258  0008a258
       0000000000000009  0000000000000000  AX       0     0     4
  [10] .rodata           PROGBITS         000000000048a280  0008a280
       000000000001bd7c  0000000000000000   A       0     0     32
  [11] __libc_subfreeres PROGBITS         00000000004a6000  000a6000
       0000000000000048  0000000000000000   A       0     0     8
  [12] __libc_IO_vtables PROGBITS         00000000004a6060  000a6060
       00000000000006a8  0000000000000000   A       0     0     32
  [13] __libc_atexit     PROGBITS         00000000004a6708  000a6708
       0000000000000008  0000000000000000   A       0     0     8
  [14] .stapsdt.base     PROGBITS         00000000004a6710  000a6710
       0000000000000001  0000000000000000   A       0     0     1
  [15] __libc_thread_sub PROGBITS         00000000004a6718  000a6718
       0000000000000010  0000000000000000   A       0     0     8
  [16] .eh_frame         PROGBITS         00000000004a6728  000a6728
       0000000000009c38  0000000000000000   A       0     0     8
  [17] .gcc_except_table PROGBITS         00000000004b0360  000b0360
       0000000000000085  0000000000000000   A       0     0     1
  [18] .tdata            PROGBITS         00000000006b0b40  000b0b40
       0000000000000020  0000000000000000 WAT       0     0     8
  [19] .tbss             NOBITS           00000000006b0b60  000b0b60
       0000000000000040  0000000000000000 WAT       0     0     8
  [20] .init_array       INIT_ARRAY       00000000006b0b60  000b0b60
       0000000000000010  0000000000000008  WA       0     0     8
  [21] .fini_array       FINI_ARRAY       00000000006b0b70  000b0b70
       0000000000000010  0000000000000008  WA       0     0     8
  [22] .data.rel.ro      PROGBITS         00000000006b0b80  000b0b80
       0000000000000464  0000000000000000  WA       0     0     32
  [23] .got              PROGBITS         00000000006b0fe8  000b0fe8
       0000000000000008  0000000000000008  WA       0     0     8
  [24] .got.plt          PROGBITS         00000000006b1000  000b1000
       00000000000000c0  0000000000000008  WA       0     0     8
  [25] .data             PROGBITS         00000000006b10c0  000b10c0
       0000000000001af0  0000000000000000  WA       0     0     32
  [26] .bss              NOBITS           00000000006b2bc0  000b2bb0
       0000000000001718  0000000000000000  WA       0     0     32
  [27] __libc_freeres_pt NOBITS           00000000006b42d8  000b2bb0
       0000000000000028  0000000000000000  WA       0     0     8
  [28] .comment          PROGBITS         0000000000000000  000b2bb0
       0000000000000025  0000000000000001  MS       0     0     1
  [29] .note.stapsdt     NOTE             0000000000000000  000b2bd8
       0000000000001408  0000000000000000           0     0     4
  [30] .symtab           SYMTAB           0000000000000000  000b3fe0
       000000000000a6c8  0000000000000018          31   693     8
  [31] .strtab           STRTAB           0000000000000000  000be6a8
       0000000000006532  0000000000000000           0     0     1
  [32] .shstrtab         STRTAB           0000000000000000  000c4bda
       0000000000000176  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)
複製代碼

查看ELF的Segment信息,正如稱Section屬性的結構叫作段表,描述Segment的結構爲程序頭(Program Header),它描述ELF文件該如何被操做系統映射進進程的虛擬空間:

readelf -l SectionMapping.elf
複製代碼

結果以下:

Elf file type is EXEC (Executable file)
Entry point 0x400a00
There are 6 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x00000000000b03e5 0x00000000000b03e5  R E    0x200000
  LOAD           0x00000000000b0b40 0x00000000006b0b40 0x00000000006b0b40
                 0x0000000000002070 0x00000000000037c0  RW     0x200000
  NOTE           0x0000000000000190 0x0000000000400190 0x0000000000400190
                 0x0000000000000044 0x0000000000000044  R      0x4
  TLS            0x00000000000b0b40 0x00000000006b0b40 0x00000000006b0b40
                 0x0000000000000020 0x0000000000000060  R      0x8
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     0x10
  GNU_RELRO      0x00000000000b0b40 0x00000000006b0b40 0x00000000006b0b40
                 0x00000000000004c0 0x00000000000004c0  R      0x1

 Section to Segment mapping:
  Segment Sections...
   00     .note.ABI-tag .note.gnu.build-id .rela.plt .init .plt .text __libc_freeres_fn __libc_thread_freeres_fn .fini .rodata __libc_subfreeres __libc_IO_vtables __libc_atexit .stapsdt.base __libc_thread_subfreeres .eh_frame .gcc_except_table 
   01     .tdata .init_array .fini_array .data.rel.ro .got .got.plt .data .bss __libc_freeres_ptrs 
   02     .note.ABI-tag .note.gnu.build-id 
   03     .tdata .tbss 
   04     
   05     .tdata .init_array .fini_array .data.rel.ro .got 
複製代碼

從裝載的角度看,咱們只須要關心兩個「LOAD」類型的Segment,其餘只是在裝載中起輔助做用。這裏能夠看到,文件被從新劃分紅三個部分:

  • 可讀可執行LOAD段
  • 可讀寫的LOAD段
  • 沒有被映射的段

所以全部相同屬性的Section被歸類到了同一個Segment,並映射到同一個VMA裏面。因此說Segment和Section在不一樣的角度給ELF文件進行劃分。

  • 連接視圖:從Section的角度進行劃分
  • 執行視圖:從Segment的角度進行劃分

能夠以下圖表示可執行文件的段與進程虛擬空間的映射關係:

1550998021300

ELF可執行文件和共享庫文件有個專門的數據結構叫**程序頭表(Program Header Table)**用來保存Segment信息,由於ELF文件不須要被裝載,所以沒有程序頭表具體結構以下,並與readelf -l讀出來的數據一一對應

typedef struct{
    Elf32_Word        p_type;
    Elf32_Off            p_offset;
    Elf32_Addr         p_vaddr;
    Elf32_Addr         p_paddr;
    Elf32_Word        p_filesz;
    Elf32_Word        p_memsz;
    Elf32_Word        p_flags;
    Elf32_Word        p_align;
}Elf32_Phdr;
複製代碼

基本含義以下

1550998603509

其中,p_memsz >= p_filesz但若是p_memsz <= p_filesz則表示Segment段在內存中分配的空間大小超過文件中實際的大小,這部分多餘的空間則被所有填充爲」0「。這樣咱們構造ELF可執行文件時就不須要再額外設立BSS的Segment了,能夠把數據Segment的p_memsz擴大,那些額外的部分就是BSS。數據段和BSS的區別在於,數據段從文件中初始化內容,而BSS則所有被初始化爲0。所以能夠看到前面的BSS其實已經被併入數據類型段裏面,而沒有顯示出來。

堆和棧

查看進程虛擬空間分佈如圖所示:

1550999813925

其中意義能夠見我另一篇文章,《/proc/{pid}/maps的文件結構解析》

其中,主設備號和次設備號及文件節點號都是0,表示沒有映射到文件,這種VMA叫作匿名虛擬內存區域(Anonymous Virtual Memory Area)。咱們目前關注Heap和Stack這兩個VMA幾乎在全部進程中都存在,malloc()函數內存分配就是從堆裏面進行分配的,堆由系統庫管理,而vsyscall位於內核空間,具體做用待深究,不過在名稱來看猜想應該是和內核通訊相關的VMA了。

一個進程可主要分以下幾個區域:

  • 代碼VMA,權限只讀,可執行;有映像文件
  • 數據VMA,權限可讀寫;有映像文件
  • 堆VMA,可讀可寫不可執行;匿名,可向上擴展
  • 棧VMA,可讀可寫不可執行;匿名,可向下擴展

具體如圖:

1551000603318

Linux內核裝載ELF過程

  • bash進程調用fork()系統調用建立一個新的進程

  • 調用execve()系統調用執行指定的ELF文件,原先的bash進程繼續返回等待剛纔啓動的新進程結束,而後等待用戶輸入命令。execve()被定義在unsitd.h文件中,原型以下

    int execve(const char * filename,char * const argv[ ],char * const envp[ ]);

    三個參數分別是被執行的程序文件名,執行參數,環境變量

啊~~~這裏步驟比較多且複雜,沒有實際實驗也看不太懂,就先直接貼圖將就着看吧;

1551002209326

1551002228233

而後最後獲得的結果就是返回地址改爲被裝載的ELF程序入口地址:

  • 靜態連接:ELF文件的頭文件中e_entry所指的地址
  • 動態連接:動態連接器地址

至此,可執行文件的裝載部分已介紹完畢

相關文章
相關標籤/搜索