一、What is Prelink?
1.1 Prelink 簡介
Prelink 是 Red Hat 開發者 Jakub Jelinek 所設計的工具。正如其名字所示,Prelink 利用事先鏈接代替運行時鏈接的方法來加速共享庫的加載。它不僅可以加快起動速度,還可以減少部分內存開銷,是各種 Linux 架構上用于減少程序加載時間、縮短系統啟動時間和加快應用程序啟動的很受歡迎的一個工具。
Linux 系統運行時的動態鏈接尤其是重定位 (Relocation) 的開銷,對于大型系統來說是很大的。相比之下,早期 UNIX 下的 a.out 格式的老式鏈接方法在速度和占用內存方面有明顯的優勢(但不如ELF格式更靈活,能方便的構建動態共享庫)。Prelink 工具是試圖在保持一部分靈活性的基礎上,借鑒 a.out 格式在速度和占用內存方面的優點,對 ELF 文件進行一些改進。
Prelink 工具的原理主要基于這樣一個事實:動態鏈接和加載的過程開銷很大,并且在大多數的系統上,函數庫并不會常常被更動,每次程序被執行時所進行的鏈接動作都是完全相同的,對于嵌入式系統來說尤其如此。因此,這一過程可以改在運行時之前就可以預先處理好,即花一些時間利用 Prelink 工具對動態共享庫和可執行文件進行處理,修改這些二進制文件并加入相應的重定位等信息,節約了本來在程序啟動時的比較耗時的查詢函數地址等工作,這樣可以減少程序啟動的時間,同時也減少了內存的耗用。
Prelink 的這種做法當然也有代價:每次更新動態共享庫時,相關的可執行文件都需要重新執行一遍 Prelink 才能保證有效,因為新的共享庫中的符號信息、地址等很可能與原來的已經不同了。這種代價對于嵌入式系統的開發者來說可能稍微帶來一些復雜度,不過好在對用戶來說幾乎是可以忽略的。
很多 Linux 發行版上已經預裝了或者已經使用了 Prelink 工具,不過我們需要適用于嵌入式平臺,比如 ARM 的版本,這樣我們需要到下載 Prelink 的源代碼并重新編譯。
1.2 Prelink 機理
從我們最熟悉的 hello world 程序開始分析:
#include 《stdio.h》
int main(int argc, const char* argv[]) {
printf(“Hello, World!\n”);
return 0;
}
我們知道,printf 是在 c語言運行庫 libc 中定義的。如果不使用動態庫,也就是使用glibc 的靜態庫版本,鏈接到 a.out 中的話,那么 printf 函數的地址在運行之前就是已知的,很簡單的一句地址轉移就可以完成了。
可是使用動態庫的話,在程序編譯階段,我們是無法得知 printf 的函數地址,因為動態庫的加載的內存地址是隨機的。那么對于動態庫的情況,針對 printf 是如何尋址的呢?
在程序啟動時,當調用 printf 的時候,程序會將處理權交給 loader,由其負責在進程以及其鏈接的動態庫中查找 printf 的函數地址。由于 loader 不知道 printf 是在哪個動態庫,所以它將在整個進程和動態庫的范圍內查找。更糟糕的是在 C++ 程序中,符號的命名是類名+函數名,這導致在做字符串比較時,往往直到字符串的結尾才能獲得結果。
這就導致了,在進程啟動過程中,符號查找往往占據了大部分時間。據統計,在 Linux 的 KDE 進程中啟動過程中,符號查找表竟占據了進程啟動 80% 的時間。有沒有辦法來改進呢?
如果進程在運行前,就能獲知動態庫的加載地址,那么函數調用的地址就應該是已知的,我們就可以通過修改執行程序,來避免符號的查找。從而節省進程啟動的時間。
實際上 Prelink 正是這么做的。Prelink 最早是在 Redhat 中引用的,用來加速 KDE 的啟動速度。那時侯 Prelink 作為系統的一個進程,不定期的啟動,對系統中的進程和動態庫進行優化,這在系統中進程和動態庫不怎么變化的情況下非常有用。
在做 Prelink 時,需要為其指定需要做 Prelink 的進程和動態庫的目錄。Prelink 需要做以下幾件事情:
分析所有的進程和動態庫,為每個動態庫指定一塊唯一的(虛擬)內存地址;
分析進程和動態庫中,所有需要重定位的函數、全局變量等,用 loader 進行符號查找,對齊地址進行解析;
修改進程中和動態庫的二進制文件;
眾所周知,在 32 位 Linux 操作系統上有 4G 的地址空間,3G 以上為操作系統使用,0000000~4000000 歸進程的代碼段、數據段和堆段使用,從 3G 往下歸棧段使用。基本上我們可以認為從 1G~3G 的地址空間可以用來指定動態庫的加載地址,地址空間還是很豐富的。
凡事總有萬一,如果地址空間不夠怎么辦呢?Prelink 關于這個問題,做了兩個約定:
總是一同出現的動態庫,其動態庫的加載地址一定不能重疊;
總是不同時間段出現的動態庫,其動態庫的加載地址可以重疊;
有了這兩個約定之后,基本上就可以保證,為每個動態庫指定加載地址,從而在運行前就能獲知函數和全局變量等符號的地址。
-
Linux
+關注
關注
87文章
11329瀏覽量
209968 -
Ha-VIS preLink
+關注
關注
0文章
2瀏覽量
1592 -
權重定位
+關注
關注
0文章
2瀏覽量
1297
發布評論請先 登錄
相關推薦
評論