在 Linux中如何使用動態連結模組庫?

大雄45發表於2022-07-23
導讀 學習如何用動態連結庫將多個 C 目標檔案結合到一個單個的可執行檔案之中。當使用 C 程式語言編寫一個應用程式時,你的程式碼通常有多個原始檔程式碼。

最終,這些檔案必須被編譯到一個單個的可執行檔案之中。你可以透過建立靜態或動態庫(後者也被稱為 共享shared 庫)來實現這一點。這兩種型別的庫在建立和連結的方式上有所不同。兩者都有缺點和優點,這取決於你的使用情況。

動態連結是最常見的方法,尤其是在   系統上。動態連結會保持庫模組化,因此,很多應用程式可以共享一個庫。應用程式的模組化也允許單獨更新其依賴的共享庫。

在這篇文章中,我將演示動態連結是如何工作的。在後期的文章中,我將演示靜態連結。

連結器

連結器linker是一個 ,它將一個程式的數個部分結合在一起,併為它們重新組織記憶體分配。

連結器的功能包括:

  • 整合一個程式的所有的部分
  • 計算出一個新的記憶體組織結構,以便所有的部分組合在一起
  • 恢復記憶體地址,以便程式可以在新的記憶體組織結構下執行
  • 解析符號引用

連結器透過這些功能,建立了一個名為可執行檔案executable的可以執行的程式。在你建立一個動態連結的可執行檔案前,你需要一些用來連結的庫,和一個用來編譯的應用程式。準備好你 最喜歡的文字編輯器 並繼續。

建立目標檔案

首先,建立帶有這些函式簽名的標頭檔案 mymath.h :

int add(int a, int b);
int sub(int a, int b);
int mult(int a, int b);
int divi(int a, int b);
使用這些函式定義來建立 add.c 、sub.c 、mult.c 和 divi.c 檔案。我將把所有的程式碼都放置到一個程式碼塊中,請將其分為四個檔案,如註釋所示:
 // add.c int add(int a, int b){ return (a+b); } //sub.c int sub(int a, int b){ return (a-b); } //mult.c int mult(int a, int b){ return (a*b); } //divi.c int divi(int a, int b){ return (a/b); }
現在,使用 GCC 來建立目標檔案 add.o、sub.o、mult.o 和 divi.o :

(LCTT 校注:關於“目標檔案object file”,有時候也被稱作“物件檔案”,對此,存在一些譯法混亂情形,稱之為“目標檔案”的譯法比較流行,本文采用此譯法。)

$ gcc -c add.c sub.c mult.c divi.c
建立一個共享的目標檔案

在最終的可執行檔案的執行過程中將連結動態庫。在最終的可執行檔案中僅放置動態庫的名稱。實際上的連結過程發生在執行時,在此期間,可執行檔案和庫都被放置到了主記憶體中。

除了可共享外,動態庫的另外一個優點是它減少了最終的可執行檔案的大小。在一個應用程式最終的可執行檔案生成時,其使用的庫只包括該庫的名稱,而不是該庫的一個多餘的副本。

你可以從你現有的示例程式碼中建立動態庫:

$ gcc -Wall -fPIC -c add.c sub.c mult.c divi.c

選項 -fPIC 告訴 GCC 來生成位置無關的程式碼position-independent code(PIC)。-Wall 選項不是必需的,並且與程式碼的編譯方式是無關的。不過,它卻是一個有價值的選項,因為它會啟用編譯器警告,這在排除故障時是很有幫助的。
使用 GCC ,建立共享庫 libmymath.so :

$ gcc -shared -o libmymath.so add.o sub.o mult.o divi.o

現在,你已經建立了一個簡單的示例數學庫 libmymath.so ,你可以在 C 程式碼中使用它。當然,也有非常複雜的 C 庫,這就是他們這些開發者來生成最終產品的工藝流程,你和我可以安裝這些庫並在 C 程式碼中使用。

接下來,你可以在一些自定義程式碼中使用你的新數學庫,然後連結它。

建立一個動態連結的可執行檔案

假設你已經為數學運算編寫了一個 。建立一個名稱為 mathDemo.c 的檔案,並將這些程式碼複製貼上至其中:

nclude <stdlib.h>
int main()
{
int x, y;
printf("Enter two numbers\n");
scanf("%d%d",&x,&y);
printf("\n%d + %d = %d", x, y, add(x, y));
printf("\n%d - %d = %d", x, y, sub(x, y));
printf("\n%d * %d = %d", x, y, mult(x, y));
if(y==0){
printf("\nDenominator is zero so can't perform division\n");
exit(0);
}else{
printf("\n%d / %d = %d\n", x, y, divi(x, y));
return 0;
}
}

注意:第一行是一個 include 語句,透過名稱來引用你自己的 libmymath 庫。要使用一個共享庫,你必須已經安裝了它,如果你沒有安裝你將要使用的庫,那麼當你的可執行檔案在執行並搜尋其包含的庫時,將找不到該共享庫。如果你需要在不安裝庫到已知目錄的情況下編譯程式碼,這裡有 一些方法可以覆蓋預設設定。不過,對於一般使用來說,我們希望庫存在於已知的位置,因此,這就是我在這裡演示的東西。

複製檔案 libmymath.so 到一個標準的系統目錄,例如:/usr/lib64, 然後執行 ldconfig 。ldconfig 命令建立所需的連結,並快取到標準庫目錄中發現的最新共享庫。

$ sudo cp libmymath.so /usr/lib64/
$ sudo ldconfig
編譯應用程式

從你的應用程式原始檔程式碼(mathDemo.c)中建立一個名稱為 mathDemo.o 的目標檔案:

$ gcc -I . -c mathDemo.c

-I 選項告訴 GCC 來在其後所列出的目錄中搜尋標頭檔案(在這個示例中是 mymath.h)。在這個示例中,你指定的是當前目錄,透過一個單點(.)來表示。建立一個可執行檔案,使用 -l 選項來透過名稱來引用你的共享數學庫:

$ gcc -o mathDynamic mathDemo.o -lmymath

GCC 會找到 libmymath.so ,因為它存在於一個預設的系統庫目錄中。使用 ldd 來查證所使用的共享庫:

$ ldd mathDemo
linux-vdso.so.1 (0x00007fffe6a30000)
libmymath.so => /usr/lib64/libmymath.so (0x00007fe4d4d33000)
libc.so.6 => /lib64/libc.so.6 (0x00007fe4d4b29000)
/lib64/ld-linux-x86-64.so.2 (0x00007fe4d4d4e000)

看看 mathDemo 可執行檔案的大小:

$ du ./mathDynamic
24 ./mathDynamic

當然,它是一個小的應用程式,它所佔用的磁碟空間量也反映了這一點。相比之下,相同程式碼的一個靜態連結版本(正如你將在我後期的文章所看到的一樣)是 932K !

$ ./mathDynamic
Enter two numbers
25
5
25 + 5 = 30
25 - 5 = 20
25 * 5 = 125
25 / 5 = 5

你可以使用 file 命令來查證它是動態連結的:

$ file ./mathDynamic
./mathDynamic: ELF 64-bit LSB executable, x86-64,
dynamically linked,
interpreter /lib64/ld-linux-x86-64.so.2,
with debug_info, not stripped

成功!

動態連結

因為連結發生在執行時,所以,使用一個共享庫會產生一個輕量型的可執行檔案。因為它在執行時解析引用,所以它會花費更多的執行時間。不過,因為在日常使用的 Linux 系統上絕大多數的命令是動態連結的,並且在現代硬體上,所能節省的時間是可以忽略不計的。對開發者和使用者來說,它的固有模組性是一種強大的功能。

在這篇文章中,我描述瞭如何建立動態庫,並將其連結到一個最終可執行檔案。在我的下一篇文章中,我將使用相同的原始檔程式碼來建立一個靜態連結的可執行檔案。

原文來自:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69955379/viewspace-2907277/,如需轉載,請註明出處,否則將追究法律責任。

相關文章