一、Linux可執(zhí)行文件結(jié)構(gòu)
在 Linux 下,程序是一個普通的可執(zhí)行文件,以下列出一個二進制可執(zhí)行文件的基本情況:
可以看出,此可執(zhí)行文件在存儲時(沒有調(diào)入到內(nèi)容)分為代碼區(qū)(text)、數(shù)據(jù)區(qū)(data)和未初始化數(shù)據(jù)區(qū)(bss)3 個部分。各段基本內(nèi)容說明如下:
代碼區(qū):
存放 CPU 執(zhí)行的機器指令。通常代碼區(qū)是可共享的(即另外的執(zhí)行程序可以調(diào)用它),使其可共享的目的是對于頻繁被執(zhí)行的程序,只需要在內(nèi)存中有一份代碼即可。代碼區(qū)通常是只讀的,使其只讀的原因是防止程序意外地修改了它的指令。另外,代碼區(qū)還規(guī)劃了局部變量的相關(guān)信息。
代碼區(qū)的指令包括操作碼和操作對象(或?qū)ο蟮刂芬?。如果是立即數(shù)(即是具體的數(shù)值),將直接包含在代碼中,如果是局部數(shù)據(jù),將在運行時在棧區(qū)分配空間,然后再引用該數(shù)據(jù)的地址,如果是未初始化數(shù)據(jù)區(qū)和數(shù)據(jù)區(qū),在代碼中同樣將引用該數(shù)據(jù)的地址。
全局初始化數(shù)據(jù)區(qū)/靜態(tài)數(shù)據(jù)區(qū)(數(shù)據(jù)段):
該區(qū)包含了在程序中明確被初始化的全局變量、已經(jīng)初始化的靜態(tài)變量(包括全局靜態(tài)變量和局部靜態(tài)變量)和常量數(shù)據(jù)(如字符串常量)。
例如,一個不在任何函數(shù)內(nèi)聲明(全局變量),如下:
int count = 100;
使得變量 count 根據(jù)其初始值被存儲初始化數(shù)據(jù)區(qū)中。
在任意位置定義靜態(tài)變量方式如下:
static int num = 200;
這聲明了一個靜態(tài)數(shù)據(jù)并初始化,如果在任意函數(shù)體外聲明,則表示其為一個靜態(tài)全局變量,如果在函數(shù)體內(nèi)(局部),則表示其為一個局部靜態(tài)變量。另外,如果在一個函數(shù)名前加上 static,則表示此函數(shù)只能再當前文件中被調(diào)用。
未初始化數(shù)據(jù)區(qū)(又叫 BSS 區(qū)):
存入的是全局未初始化變量和未初始化靜態(tài)變量。未初始化數(shù)據(jù)區(qū)的數(shù)據(jù)在程序開始執(zhí)行之前被內(nèi)核初始化為 0 或者空(NULL)。
例如,一個不在任何函數(shù)內(nèi)聲明的未初始化變量。
long sum[1000];
將 sum 存儲到未初始化數(shù)據(jù)
二、Linux進程結(jié)構(gòu)
在 Linux 系統(tǒng)下,如果將某個可執(zhí)行文件加載到內(nèi)存運行,則將演變成一個或多個進程(多個進程的原因是進程在運行時可以再創(chuàng)建新的進程,但加載時只有一個進程)。進程是 Linux 事務(wù)管理的基本單元,所有的進程均擁有自己獨立的處理環(huán)境和系統(tǒng)資源。進程的環(huán)境由當前系統(tǒng)狀態(tài)及其父進程信息決定和組成的。
下圖為可執(zhí)行文件存儲結(jié)構(gòu)和 Linux 進程基本結(jié)構(gòu)(部分)的對照圖。
一個進程是一個運行著的程序段,一個進程主要包括在內(nèi)存中申請的空間,代碼(加載的程序,包括代碼段,數(shù)據(jù)段,BSS),堆,棧,以及內(nèi)核提供的內(nèi)核進程信息結(jié)構(gòu)體task_struct (位置在 /usr/include/linux/sched.h)、打開的文件、上下文(指進程執(zhí)行活動全過程的靜態(tài)描述)信息以及掛起的信號等。
(1)代碼區(qū)(text segment)。加載的是可執(zhí)行文件代碼段,其加載到內(nèi)存中的位置由加載器完成。
(2)全局初始化數(shù)據(jù)區(qū)/靜態(tài)數(shù)據(jù)區(qū)(Data Segment)。加載的是可執(zhí)行文件數(shù)據(jù)段,存儲于數(shù)據(jù)段(全局初始化,靜態(tài)初始化數(shù)據(jù))的數(shù)據(jù)的生存周期為整個程序運行過程。
(3)未初始化數(shù)據(jù)區(qū)(BSS)。加載的是可執(zhí)行文件BSS段,位置可以分開亦可以緊靠數(shù)據(jù)段,存儲于數(shù)據(jù)段的數(shù)據(jù)(全局未初始化,靜態(tài)未初始化數(shù)據(jù))的生存周期為整個程序運行過程。
(4)棧區(qū)(stack)。由編譯器自動分配釋放,存放函數(shù)的參數(shù)值、返回值、局部變量等。在程序運行過程中實時加載和釋放,因此,局部變量的生存周期為申請到釋放該段??臻g。
(5)堆區(qū)(heap)。用于動態(tài)內(nèi)存分配。堆在內(nèi)存中位于BSS區(qū)和棧區(qū)之間。一般由程序員分配和釋放,若程序員不釋放,程序結(jié)束時有可能由OS回收。
系統(tǒng)之所以分成這么多個區(qū)域,主要基于以下考慮:
代碼段和數(shù)據(jù)段分開,運行時便于分開加載,在哈佛體系結(jié)構(gòu)的處理器將取得更好得流水線效率。
代碼時依次執(zhí)行的,是由處理器 PC 指針依次讀入,而且代碼可以被多個程序共享,數(shù)據(jù)在整個運行過程中有可能多次被調(diào)用,如果將代碼和數(shù)據(jù)混合在一起將造成空間的浪費。
臨時數(shù)據(jù)以及需要再次使用的代碼在運行時放入棧中,生命周期短,便于提高資源利用率。
堆區(qū)可以由程序員分配和釋放,以便用戶自由分配,提高程序的靈活性。
三、各存儲類型比較