【Android Linux內存及性能優化】(四) 進程內存的優化 - 動態庫


本文接着
【Android Linux內存及性能優化】(一) 進程內存的優化 - 堆段
【Android Linux內存及性能優化】(二) 進程內存的優化 - 棧段 - 環境變量 - ELF
【Android Linux內存及性能優化】(三) 進程內存的優化 - 數據段 - 代碼段


一、內存篇

1.3 進程內存優化

1.3.1 ELF執行文件

1.3.2 動態庫

動態庫技術是當前程序中經常採用的技術,其目的是減小程序的大小,節省空間,提高效率,具有很高的靈活性。
與靜態庫不同,動態庫裏面的函數不是執行程序本身的一部分,
而是根據執行需要按需載入,其執行代碼可以同時在多個程序中共享。

動態庫加載方式有兩種:

  1. 靜態加載
    在程序編譯時,加上“ -l ”選項,指定其所依賴的動態庫,這個庫名字將記錄在 ELF 文件的 .dynamic 節。
    在程序運行時,loader 會預先將程序所依賴的所有動態庫都加載在進程空間中。
    優點: 動態庫的接口調用簡單,可以直接調用。
    缺點: 動態庫的生存週期等於進程的生存週期,其加載時機不靈活。

  2. 動態加載
    在程序中調用函數(dlopen、dlclose)來控制動態庫加載與卸載。
    優點:動態庫加載的時機非常靈活,可以非常細緻地定義動態庫搞錯生存週期。
    缺點:動態庫的接口調用起來比較麻煩,同時還要關注動態庫的生存週期。

前面介紹進程時,分別包括: 只讀的代碼段、可修改的數據段、堆段 和 棧段。
對於共享庫來說,分爲兩個段:只讀的代碼段 、 可修改的數據段。
如果你在共享庫的函數裏,動態分配一塊內存,這段內存將被算在調用該函數的進程的堆中。

對於共享庫的代碼段 和 數據段:

  • 代碼段由於其是隻讀的,內容 不會改變,對每個進程都是一樣的,所以它在系統中是唯一的,系統只爲其分配一塊內存,多個進程之間共享。
  • 數據段由於其內容在進程運行期間是變化的,每個進程都要對其進行改寫,所以在鏈接到進程空間後,系統會爲每個進程創建相應的數據段。也就是說如果一個共享庫,被 N 個進程鏈接,當這N 個進程同時運行時,同時共享一個代碼段,每個進程擁有一個數據段,系統中共有該動態鏈接庫的1 個代碼段 和 N 個數據段。

1.3.2.1 數據段

1.3.2.1.1 共享庫中的.bss 節

前面講過,在進程中的bss,如果數據段容不下,它將使用mmap 在堆段內存分配大內存。
那 共享庫中是怎麼分配的呢?

例:

a.c
#include <stdlib.h>
#include <stdio.h>

int bss[1024*128] = {0};
void a1(){
	printf("bss: %p\n",bss);
	return;
}
編譯成動態庫: gcc -shared -fPIC  a.c  -o  liba.so

hello.c
#include <stdlib.h>
#include <stdio.h>

extern int bss[1024*128];

int main(){
	a1();		// 調用a1()
	int pid = getpid();
	printf("pid: %d\n",pid);
	funca();
	pause();
}
編譯成可執行文件: gcc -L./  -Ia  hello.c -o hell

在這裏插入圖片描述

編譯執行後,使用 cat maps 和 cat memmap 可以看看出,
對於共享庫的 bss 節的數據,如果數據段不能容納的話,進程將會創建一內存段來容納bss 節的數據,
其中 bss 數組起始地址位於動態庫的數據段。
loader 在加載動態庫時會自動將數據段最後一個頁面剩餘的地址自動清零,留給 bss節的變量使用,故其數據段使用了一個物理頁而。

如果在進程中引用了共享庫的全局變量,進程將會擴展它的堆棧段,並將 bss 段的數據複製到堆棧段中來。

因此,不要在進程中通過extern 的方式,引用共享庫中的變量,一旦引用,不論其是否使用,都會將佔用物理內存。
同時還會增加系統啓動時內存複製的代價,會導致性能下降


1.3.2.1.2 共享庫中的.data 節

對於未賦值或初值爲0 的全局變量在共享庫中的聲明,
在進程中使用,則該變量被複制到進程的數據段,同時修改使用該變量的共享庫的指向。

當主程序鏈接了一個共享庫的全局變量時,它會爲該變量定義一個地址,但它不會影響數據段的大小,將該值複製到這個地址上。如果地址段不夠用,它將佔用堆段,系統將調用brk 來擴展堆段。

總之,可執行文件(動態庫)儘量不要直接去操作位於其他動態庫的全局變量,跨動態庫的直接訪問全局變量的代價很高,可以在動態庫中編寫接口函數來操作全局變量,並將這些接口導出,供其他進程(或動態庫)使用。


1.3.2.2 代碼段

動態庫的代碼段會被多個不同的進程所引用,所以動態庫的代碼段與執行文件的代碼段有所不同。

1.3.2.2.1 符號解析

前面講解過的ELF文件的主體結構,ELF 其中有兩個section:.rel.dyn 和 .rel.plt 。
主要負責共享庫的重定向工作,現在就來看看它們的內容 。
在這裏插入圖片描述
可以看到變量 b 在 .rel.dyn 中,而 hello中用到的外部函數 funca 、printf 在 .rel.plt 中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章