在乙個典型的系統中,會執行許多程式。每個程式都依賴於一些函式,其中一些是標準的c庫函式,如printf()、malloc()、write()等。
如果每個程式都使用標準的c庫,那麼每個程式通常都有這個特定庫的惟一副本。不幸的是,這導致了資源的浪費。由於c庫是公共的,所以讓每個程式引用該庫的公共例項比讓每個程式包含該庫的副本更有意義。這種方法有幾個優點,其中最重要的是節省了所需的系統總記憶體。
術語靜態鏈結意味著程式和它所鏈結的特定庫在鏈結時由鏈結器組合在一起。這意味著程式和特定庫之間的繫結是固定的,並且在程式執行之前就已經知道了。這也意味著我們不能改變這個繫結,除非我們用庫的新版本重新鏈結程式。
如果您不確定某個庫的正確版本在執行時是否可用,或者您正在測試某個庫的新版本,但又不希望將其安裝為共享的,那麼您可以考慮靜態地鏈結乙個程式。
靜態鏈結的程式是針對物件(庫)的歸檔檔案鏈結的,這些物件(庫)的副檔名通常是.a。這種物件集合的乙個例子是標準c庫libc.a
動態鏈結這個術語意味著程式和它引用的特定庫在鏈結時不會被鏈結器組合在一起。相反,鏈結器將資訊放入可執行檔案中,告訴引導程式共享物件模組**所在位置,以及應該使用哪個執行時鏈結器來查詢和繫結引用。這意味著程式和共享庫之間的繫結是在執行時完成的——在程式啟動之前,找到並繫結適當的共享庫。
這種型別的程式稱為部分繫結的可執行程式,因為它沒有被完全解析——鏈結器在鏈結時並沒有導致程式中所有引用的符號都與庫中的特定**相關聯。相反,鏈結者只是簡單地說:這個程式在乙個特定的共享物件中呼叫了一些函式,所以我只需要記下這些函式在哪個共享物件中,然後繼續。實際上,這將繫結延遲到執行時。
動態鏈結的程式被鏈結到具有副檔名的共享物件上。此類物件的乙個示例是標準c庫的共享物件版本,即libc.so。
您可以使用編譯器驅動程式qcc的命令列選項來告訴工具鏈是靜態鏈結還是動態鏈結。然後,此命令列選項確定所使用的副檔名(.a或.so)。
更進一步說,程式在執行之前可能不知道需要呼叫哪些函式。雖然這一開始看起來有點奇怪(畢竟,乙個程式怎麼可能不知道它將呼叫什麼函式呢?),但它確實是乙個非常強大的功能。
考慮乙個通用的磁碟驅動程式。它啟動、探測硬體並檢測硬碟。然後驅動程式將動態載入io-blk**來處理磁碟塊,因為它發現了乙個面向塊的裝置。現在驅動程式已經在塊級別訪問了磁碟,它發現磁碟上有兩個分割槽:乙個dos分割槽和乙個電力安全分割槽。我們沒有強制磁碟驅動程式包含它可能遇到的所有可能的分割槽型別的檔案系統驅動程式,而是保持簡單:它沒有任何檔案系統驅動程式!在執行時,它檢測到兩個分割槽,然後知道應該載入fs-dos.so和fs-qnx6.so檔案系統**來處理這些分割槽。通過延遲決定哪個函式呼叫,我們增強了磁碟驅動程式的靈活性(並減少了它的大小).
為了理解程式如何使用共享庫,我們首先檢視可執行檔案的格式,然後檢查程式啟動時發生的步驟。
qnx中微子rtos使用elf(可執行和鏈結格式)二進位制格式,該格式目前在svr4 unix系統中使用。elf不僅簡化了建立共享庫的任務,而且增強了模組在執行時的動態載入。
在下面的關係圖中,我們展示了elf檔案的兩個檢視:鏈結檢視和執行檢視。鏈結檢視,當程式或庫被鏈結時使用,涉及各種sections(包含object檔案)。section含大量的object檔案資訊:資料data、指令instruction、重定位資訊relocation info、符號symbol、除錯資訊debug info等。執行檢視,程式執行時使用,涉及各種segments。
在鏈結時,程式或庫是通過將具有相似屬性的section合併到段segments中來構建的。通常,所有可執行的和唯讀的資料section被合併到乙個「text」段segments中,而資料和「bss」被合併到「data」段segments中。這些段稱為載入段,因為它們需要在程序建立時載入到記憶體中。其他部分(如符號資訊和除錯部分)被合併到其他非載入段中。
elf載入器的大多數實現都派生於coff(common object file format)載入器。它們在載入時使用elf物件的鏈結檢視。這是低效的,因為程式載入器必須使用節來載入可執行檔案。乙個典型的程式可能包含大量的節,每個節都必須位於程式中,並分別裝入記憶體。
然而,qnx中微子完全不依賴於coff載入sections的技術。在開發我們的elf實現時,我們直接按照elf規範工作,並將效率放在首位。elf載入器使用程式的「執行檢視」。通過使用執行檢視,載入器的任務大大簡化了:它所要做的就是將程式或庫的載入段(通常是兩個)複製到記憶體中。因此,流程建立和庫載入操作要快得多。
下圖顯示了乙個典型程序的記憶體布局。程序載入段(對應於圖中的文字和資料)在程序的基本位址載入。主堆疊位於下面並向下擴充套件。建立的任何其他執行緒都有自己的堆疊,位於主堆疊之下。每個堆疊由乙個保護頁分隔,以檢測堆疊溢位。堆位於程序之上,並向上增長。
在程序位址空間的中間,為共享物件保留了乙個大區域。共享庫位於位址空間的頂部,並向下擴充套件。建立新程序時,程序管理器首先將可執行檔案中的兩個段對映到記憶體中。然後對程式的elf頭進行解碼。如果程式頭指示可執行檔案鏈結到共享庫,則程序管理器將從程式頭提取動態直譯器的名稱。動態直譯器指向乙個包含執行時鏈結共享庫。程序管理器將在記憶體中載入這個共享庫,然後將控制權傳遞給這個庫中的**。
當針對共享物件鏈結的程式啟動時,或者當程式請求動態載入共享物件時,將呼叫執行時鏈結器。執行時鏈結器包含在c執行時庫中。
執行時鏈結器在載入共享庫時執行幾個任務(.so file):
如果請求的共享庫還沒有載入到記憶體中,執行時鏈結器會載入它:
一旦找到請求的共享庫,它就會被載入到記憶體中。對於elf共享庫,這是乙個非常有效的操作:執行時鏈結器只需要使用兩次mmap()呼叫來將兩個載入段對映到記憶體中。
然後,共享庫被新增到程序已載入的所有庫的內部列表中。執行時鏈結器維護這個列表。
執行時鏈結器然後解碼共享物件的動態部分。
此動態部分向鏈結器提供關於此庫所鏈結的其他庫的資訊。它還提供了關於需要應用的重新定位和需要解析的外部符號的資訊。執行時鏈結器將首先載入任何其他需要的共享庫(它們本身可能引用其他共享庫)。然後它將處理每個庫的重新定位。其中一些重定位是庫的本地重定位,而其他重定位則需要執行時鏈結器來解析全域性符號。在後一種情況下,執行時鏈結器將在庫列表中搜尋此符號。在elf檔案中,雜湊表用於符號查詢,因此速度非常快。查詢符號庫的順序非常重要,我們將在下面的符號名稱解析一節中看到。
一旦應用了所有重定位,就會呼叫在共享庫的init部分註冊的任何初始化函式。這在c++的一些實現中用於呼叫全域性建構函式。
通過使用dlopen()呼叫,程序可以在執行時載入共享庫,該呼叫指示執行時鏈結器載入該庫。載入庫之後,程式可以使用dlsym()呼叫來確定其位址,從而呼叫庫中的任何函式。
note:請記住:共享庫只對動態鏈結的程序可用。
該程式還可以使用dladdr()呼叫來確定與給定位址相關聯的符號。最後,當程序不再需要共享庫時,它可以呼叫dlclose()從記憶體中解除安裝該庫。
當執行時鏈結器載入共享庫時,必須解析該庫中的符號。符號解析的順序和範圍很重要。如果共享庫呼叫的函式恰好在程式載入的多個庫中以相同的名稱存在,則搜尋這些庫中此符號的順序至關重要。這就是為什麼os定義了幾個載入庫時可以使用的選項。
所有具有全域性作用域的物件(可執行程式和庫)都儲存在乙個內部列表(全域性列表)中。預設情況下,任何全域性作用域物件都將其所有符號提供給任何載入的共享庫。全域性列表最初包含可執行檔案和在程式啟動時載入的任何庫。
預設情況下,當使用dlopen()呼叫載入乙個新的共享庫時,該庫中的符號通過以下順序搜尋解析:
載入的共享庫
ld_preload環境變數指定的庫列表。您可以在執行程式時使用此環境變數來新增或更改功能。對於setuid或setgid elf二進位制檔案,只載入包含在標準搜尋目錄中也setuid的庫。
全域性列表
共享庫引用的任何依賴物件(即,共享庫鏈結到的任何其他庫)
當dlopen()'ing乙個共享庫時,執行時鏈結器的作用域行為可以通過兩種方式改變:
當程式載入乙個新庫時,它可以通過將rtld_global標誌傳遞給dlopen()呼叫,指示執行時鏈結器將庫的符號放在全域性列表中。這將使庫的符號對隨後載入的任何庫都可用。
可以修改解析共享庫中的符號時搜尋的物件列表。如果將rtld_group標誌傳遞給dlopen(),則只搜尋庫直接引用的物件的符號。如果傳遞rtld_world標誌,只搜尋全域性列表中的物件。
動態鏈結 靜態鏈結
在linux系統中,ld鏈結器將彙編器編譯出來的目標檔案和靜態庫里的.a檔案鏈結生成可執行檔案。靜態庫中的.a檔案的 會在靜態鏈結過程中新增到可執行檔案中,可執行檔案會變得很大。與靜態鏈結不同,linux系統的ld鏈結器會將動態庫.so檔案進行符號重定位生成可執行檔案,動態庫.so檔案並不新增到可執...
靜態鏈結 動態鏈結
如果函式庫的乙份拷貝是可執行檔案的物理組成部分,那麼我們稱之為靜態鏈結。如果可執行檔案只是包含了檔名,讓載入器在執行時能夠尋找程式所需的函式庫,那麼稱為動態鏈結。即根據函式庫是不是可執行檔案的組成部分區分靜態鏈結和動態鏈結。1 可執行檔案的體積小。2 雖然執行速度稍慢,但是能更加有效的利用磁碟空間,...
靜態鏈結 動態鏈結
所謂靜態 動態是指鏈結。回顧一下,將乙個程式編譯成可執行程式的步驟 圖 編譯過程 靜態庫之所以成為 靜態庫 是因為在鏈結階段,會將彙編生成的目標檔案.o與引用到的庫一起鏈結打包到可執行檔案中。因此對應的鏈結方式稱為靜態鏈結。試想一下,靜態庫與彙編生成的目標檔案一起鏈結為可執行檔案,那麼靜態庫必定跟....