親愛的讀者朋友們😃,此文開啟知識盛宴與思想碰撞🎉。
快來參與討論💬,點贊👍、收藏?、分享📤,共創活力社區。
????????在 Linux 系統編程中,動靜態庫是重要的組成部分,理解它們的原理、制作方法和使用技巧,對于開發者來說至關重要。
目錄
一、動靜態庫基礎概念
二、靜態庫的原理與操作
2.1 原理剖析
2.2 制作流程
2.3 使用方法及路徑問題
2.4 errno 的作用
2.5 庫的安裝
三、動態庫的原理與操作
3.1 原理與命令
3.2 動靜態庫分離實踐
3.3 動態庫加載問題
3.4 解決加載問題的方法
3.5 ncurses 庫介紹
四、動態加載的底層原理
4.1 動態庫加載機制
4.2 進程地址空間詳解
4.3 動態庫地址處理
一、動靜態庫基礎概念
????????庫是已編寫好、可復用的代碼,以二進制形式存在,能被操作系統載入內存執行,分為靜態庫和動態庫。
- 靜態庫(.a)在程序編譯鏈接時,代碼被鏈接到可執行文件中(因此一般文件內存會更大),程序運行時不再依賴靜態庫;
- 動態庫(.so)即 “Shared Object” 的縮寫,在程序運行時才鏈接代碼,多個程序可共享使用。
📌注意:?
- 一個可執行程序可能用到許多的庫,這些庫運行時有的是靜態庫,有的是動態庫。我們的編譯默認使用動態鏈接庫,只有在該庫下找不到動態?
.so
?文件時,才會采用同名靜態庫。我們也可以使用?gcc
?的?-static
?選項強制設置鏈接靜態庫。
gcc -static main.c -o main_static
二、靜態庫的原理與操作
2.1 原理剖析
?
????????靜態庫的原理是將相關源文件編譯為.o 目標文件,再打包成 libXXX.a 文件。
????????當主程序的 main.c 編譯為 main.o 時,會自動與靜態庫鏈接形成可執行程序。在這個過程中,ar 命令用于生成靜態庫,如ar -rc libmymath.a add.o sub.o
,其中-rc
選項表示若目標靜態庫文件存在則替換,不存在則創建。
📌解釋:?
ar -rc libmymath.a add.o sub.o
?這條命令的作用是,把?add.o
?和?sub.o
?這兩個目標文件添加到?libmymath.a
?靜態庫中。如果?libmymath.a
?庫文件不存在,就會創建一個新的;如果已經存在,就把?add.o
?和?sub.o
?插入到庫中,若庫中已有同名文件,會用新文件替換舊文件。而且,在創建或更新庫文件的過程中不會顯示提示信息。?
2.2 制作流程
以制作一個簡單的數學庫為例,先編寫源文件,如 mymath.c 定義數學運算函數,mymath.h 聲明函數。然后編寫 Makefile 文件,內容如下:
# 定義一個變量 lib,其值為靜態庫的文件名 libmymath.a
lib=libmymath.a# 這是一個目標規則,目標是生成靜態庫 libmymath.a
# 此規則表明,生成 libmymath.a 依賴于 mymath.o 文件
$(lib):mymath.o# 使用 ar 命令將 mymath.o 文件打包成靜態庫 libmymath.a# $@ 代表目標文件,即 libmymath.a# $^ 代表所有的依賴文件,即 mymath.oar -rc $@ $^# 這是一個目標規則,目標是生成目標文件 mymath.o
# 此規則表明,生成 mymath.o 依賴于 mymath.c 文件
mymath.o:mymath.c# 使用 gcc 編譯器將 mymath.c 文件編譯成目標文件 mymath.o# -c 選項表示只進行編譯,不進行鏈接# $^ 代表所有的依賴文件,即 mymath.cgcc -c $^# .PHONY 聲明了一些偽目標,偽目標不是真正的文件,而是一個動作
# 這里聲明了 clean 為偽目標,即使當前目錄下存在名為 clean 的文件,make 也會執行 clean 對應的命令
.PHONY:clean# 這是一個偽目標規則,用于清理生成的中間文件和靜態庫文件
# 執行 make clean 命令時,會刪除當前目錄下所有的 .o 文件和 .a 文件
clean:rm -f *.o *.a
????????執行 Makefile 文件,就能生成靜態庫。之后可將庫文件和頭文件整理到相應目錄,方便分發使用。
2.3 使用方法及路徑問題
???? 使用靜態庫時,需注意頭文件和庫文件的路徑設置。若頭文件路徑未指定,gcc 默認在系統路徑(如 /usr/include)和當前目錄查找。可通過-I
選項指定頭文件搜索路徑,如gcc main.c -I./lib/include
?。對于庫文件,gcc 默認在系統路徑(如 /lib64、/usr/lib)和當前目錄查找,若找不到,需用-L
選項指定庫路徑,如gcc main.c -L./lib/mymathlib
。此外,還需用-l
選項指明要鏈接的庫名(去掉前綴 lib 和后綴.a),如gcc main.c -I./lib/include -L./lib/mymathlib -lmymath
。
-I
: 指定頭文件搜索路徑-L
: 指定庫路徑-l
: 指定庫名- 測試目標文件生成后,靜態庫刪掉,程序照樣可以運行
- 庫文件名稱和引入庫的名稱:去掉前綴?
lib
,去掉后綴?.so
、.a
,如:libc.so
?->?c
2.4 errno 的作用
????????庫中常提供全局變量 errno 用于指示運行時錯誤。如在數學庫的除法函數中,當除數為 0 時,可設置 errno 表示錯誤,方便調用者判斷結果正確性及獲取錯誤原因。
2.5 庫的安裝
????????庫的安裝本質上是將庫文件和頭文件放置到系統可查找的路徑下,可通過拷貝文件或建立軟連接實現。如將頭文件拷貝到 /usr/include,庫文件拷貝到 /lib64 ,之后編譯時只需用-l
選項指定庫名即可。但不建議隨意將第三方庫安裝到系統路徑,以免污染系統庫。
三、動態庫的原理與操作
3.1 原理與命令
????????動態庫的制作同樣先將源文件編譯為.o 文件,但編譯時需添加-fPIC
選項,用于生成位置無關碼,使庫能在虛擬內存的任意位置加載。生成動態庫使用gcc -shared -o libmymethod.so *.o
命令,-shared
選項告訴 gcc 生成共享庫。
📌解釋:?
- 假設當前目錄下有?
add.o
?和?sub.o
?兩個目標文件,執行?gcc -shared -o libmymethod.so *.o
?命令后,gcc
?會將?add.o
?和?sub.o
?鏈接成一個名為?libmymethod.so
?的動態鏈接庫。
3.2 動靜態庫分離實踐
????????在實際開發中,可將動靜態庫分離管理。通過編寫 Makefile 文件,分別生成靜態庫和動態庫,如:
# 定義動態庫變量,值為動態庫文件名libmymethod.so
dy-lib=libmymethod.so
# 定義靜態庫變量,值為靜態庫文件名libmymath.a
static-lib=libmymath.a# 聲明all為偽目標,偽目標不是真正的文件,用于定義一組操作
.PHONY:all
# all目標,依賴于動態庫和靜態庫的生成,執行make或make all時會構建這兩個庫
all: $(dy-lib) $(static-lib)# 構建靜態庫libmymath.a的規則,依賴于mymath.o文件
$(static-lib):mymath.o# 使用ar工具將mymath.o打包成靜態庫libmymath.a# $@代表目標文件,即libmymath.a# $^代表所有的依賴文件,即mymath.oar -rc $@ $^# 從mymath.c生成mymath.o的規則
mymath.o:mymath.c# 使用gcc編譯器將mymath.c編譯成目標文件mymath.o# -c選項表示只編譯不鏈接gcc -c $^# 構建動態庫libmymethod.so的規則,依賴于mylog.o和myprint.o文件
$(dy-lib):mylog.o myprint.o# 使用gcc編譯器生成共享庫(動態庫)libmymethod.so# -shared選項用于生成共享庫# $@代表目標文件,即libmymethod.so# $^代表所有的依賴文件,即mylog.o和myprint.ogcc -shared -o $@ $^# 從mylog.c生成mylog.o的規則
mylog.o:mylog.c# 使用gcc編譯器將mylog.c編譯成目標文件mylog.o,并生成位置無關碼# -fPIC選項用于生成位置無關碼,使生成的目標文件可用于創建共享庫gcc -fPIC -c $^# 從myprint.c生成myprint.o的規則
myprint.o:myprint.c# 使用gcc編譯器將myprint.c編譯成目標文件myprint.o,并生成位置無關碼# -fPIC選項用于生成位置無關碼,使生成的目標文件可用于創建共享庫gcc -fPIC -c $^# 聲明clean為偽目標,用于清理生成的文件
.PHONY:clean
# clean目標,執行make clean時會刪除所有的.o、.a、.so文件以及mylib目錄
clean:rm -rf *.o *.a *.so mylib# 聲明output為偽目標,用于整理和輸出庫文件及頭文件
.PHONY:output
# output目標,執行make output時會創建mylib目錄結構,并將頭文件、靜態庫和動態庫文件復制到相應位置
output:# 創建mylib/include目錄,如果目錄已存在則不報錯mkdir -p mylib/include# 創建mylib/lib目錄,如果目錄已存在則不報錯mkdir -p mylib/lib# 將所有.h頭文件復制到mylib/include目錄cp *.h mylib/include# 將所有.a靜態庫文件復制到mylib/lib目錄cp *.a mylib/lib# 將所有.so動態庫文件復制到mylib/lib目錄cp *.so mylib/lib
????????這樣可清晰管理不同類型的庫,提高項目的可維護性。
?
3.3 動態庫加載問題
????????生成可執行程序后,運行時可能出現找不到動態庫的情況,如這是因為系統加載器在運行時找不到動態庫路徑,即使編譯時指定了庫路徑,運行時也需重新告知系統。?
3.4 解決加載問題的方法
?
- 解決動態庫加載問題有多種方法。可將動態庫拷貝到系統默認庫路徑(如 /usr/lib64);
- 也可在系統默認庫路徑建立軟連接,如
sudo ln -s /home/whb/108/Lesson24/test/mylib/lib/libmymethod.so /lib64/libmymethod.so
;- 還能將庫所在路徑添加到環境變量 LD_LIBRARY_PATH 中,但該方法在關閉 shell 后失效,若想長久生效,需將環境變量寫入系統啟動配置文件(如~/.bash_profile);
- 另外,可在 /etc/ld.so.conf.d 目錄下創建配置文件,添加動態庫路徑后執行 ldconfig 重新加載。
3.5 ncurses 庫介紹
?????????ncurses 是基于終端的圖形庫,可用于控制終端界面。在 CentOS 系統中,可通過
sudo yum install ncurses-devel
命令安裝,安裝后開發者能利用其豐富功能創建交互式終端應用程序,提升用戶體驗。
四、動態加載的底層原理
4.1 動態庫加載機制
????????當 CPU 執行代碼時,若調用庫函數,會先在共享區查找。若庫文件未加載進內存,會觸發缺頁中斷,系統將動態庫文件加載進來,并建立與頁表的映射關系。此后,程序在進程地址空間中執行。系統會管理多個動態庫的加載情況,確保程序正確運行。
?進程如何看到動態庫的:
?
?進程間如何共享庫的:
4.2 進程地址空間詳解
????????程序編譯好后,內部已有地址概念,采用平坦模式編址(0 - 4GB),此時的地址為邏輯地址,也是虛擬地址。編譯形成可執行程序時,會生成存儲各段地址的表,表頭地址即 main 函數地址,CPU 從該地址開始執行。執行過程中,CPU 讀取的指令可能包含數據或虛擬地址。若虛擬地址在頁表中無映射關系,會引發缺頁中斷,將所需內容加載進內存并建立映射。
4.3 動態庫地址處理
????????由于動態庫可能有多個,難以保證每個庫都加載到固定位置,因此采用相對編址方式。gcc 的-fPIC
選項讓編譯器用偏移量對庫中函數編址,庫加載時,通過起始地址加偏移量的方式找到庫函數,實現庫在虛擬內存任意位置的加載!
????????動靜態庫的各個環節緊密相連,就像一條完整的 “生產線”,每個環節都不可或缺,共同支撐著 Linux 系統編程的高效運行🚀。
歡迎關注我👉【A charmer】?