在linux系統中,文件名并不直接指向磁盤上的文件,而是通過inode進行關聯。多個文件名可以指向同一個inode,這在linux中被稱為硬鏈接。
例如,文件abc和def具有相同的鏈接狀態,它們都是指向inode 789665的硬鏈接。這個inode的硬鏈接數為2。當刪除文件時,系統會執行兩個操作:1. 從目錄中刪除相應的記錄;2. 將硬鏈接數減1,如果硬鏈接數變為0,則釋放對應的磁盤空間。
1.2 軟鏈接
硬鏈接通過inode引用另一個文件,而軟鏈接則是通過文件名來引用另一個文件。軟鏈接和被引用的文件擁有不同的inode,可以看作是文件系統中的快捷方式。在Shell中,創建軟鏈接的方式如下:
1.3 軟硬鏈接的對比
軟鏈接是一個獨立的文件,而硬鏈接只是文件名與目標文件inode之間的映射關系。
1.4 軟硬鏈接的用途
硬鏈接:
- 常用于備份文件。
- 例如.和..就是硬鏈接。
軟鏈接:
- 類似于快捷方式,常用于創建指向其他文件的引用。
2. 什么是庫
庫是已經編寫好的、成熟的、可復用的代碼。在實際應用中,每個程序都依賴于許多基礎的底層庫,不可能從零開始編寫所有代碼,因此庫的存在非常重要。
庫本質上是可執行代碼的二進制形式,可以被操作系統加載到內存中執行。庫分為兩種類型:
C語言:
C++語言:
3. 制作動靜態庫
預備工作:
3.1 靜態庫
靜態庫(.a文件)在程序編譯鏈接時將其代碼鏈接到可執行文件中,程序運行時不再需要靜態庫。一個可執行程序可能使用多個庫,這些庫可以是靜態庫或動態庫。默認情況下,編譯器會使用動態鏈接庫,只有在找不到相應的.so文件時,才會使用同名的靜態庫。可以使用gcc的-Static選項強制使用靜態庫。
3.1.1 靜態庫的生成
t選項列出靜態庫中的文件,v選項提供詳細信息。
3.1.2 靜態庫的使用
在任意目錄下,新建main.c文件并引入庫頭文件:
#include "my_stdio.h" #include "my_string.h" #include <stdio.h> int main() { const char *s = "abcdefg"; printf("%s: %dn", s, my_strlen(s)); mFILE *fp = mfopen("./log.txt", "a"); if(fp == NULL) return 1; mfwrite(s, my_strlen(s), fp); mfwrite(s, my_strlen(s), fp); mfwrite(s, my_strlen(s), fp); mfclose(fp); return 0; }
場景一:頭文件和庫文件都已安裝到系統路徑下。
頭文件安裝到系統路徑:
庫文件安裝到系統路徑:
此時直接編譯gcc main.c會出現鏈接錯誤,因為系統路徑下gcc默認只會鏈接C的標準庫,第三方庫需要明確指定:
$ gcc main.c -lmystdio // -l 表示指定鏈接的庫,空格可帶可不帶
場景二:頭文件和庫文件與源文件在同一路徑下。
頭文件查找規則是先查找同級目錄,再查找系統目錄,因此頭文件不會報錯,但庫文件只會在系統路徑下查找,需要指定庫路徑:
$ gcc main.c -L. -lmymath // -L表示指明庫的路徑
場景三:頭文件和庫文件都有各自獨立的路徑。
找不到頭文件,因為頭文件和main.c不在同一級目錄下,也不位于系統目錄:
此時找到頭文件,但庫文件找不到:
有庫的路徑但不知道鏈接哪個庫:
此時編譯成功:
$ gcc main.c -I頭文件路徑 -L庫文件路徑 -lmymath // -I指明頭文件路徑
總結:
測試目標文件生成后,刪除靜態庫,程序仍然可以運行。庫文件名稱和引入庫文件名稱:去掉前綴lib,去掉后綴.so或.a,例如libc.so -> c。
3.2 動態庫
動態庫(.so文件)在程序運行時才進行鏈接,多個程序可以共享使用庫的代碼。一個與動態庫鏈接的可執行文件僅包含它所用到的函數入口地址表,而非外部函數所在目標文件的整個機器碼。在可執行文件開始運行前,外部函數的機器碼由操作系統從磁盤上的動態庫中復制到內存中,這個過程稱為動態鏈接(dynamic linking)。動態庫可以在多個程序間共享,因此動態鏈接使得可執行文件更小,節省了磁盤空間。操作系統通過虛擬內存機制允許物理內存中的一份動態庫被所有使用該庫的進程共享,節省了內存和磁盤空間。
3.2.1 動態庫的生成
shared選項表示生成共享庫格式,fPIC選項產生位置無關碼(position independent code)。庫名規則為libxxx.so:
3.2.2 動態庫的使用
場景一:頭文件和庫文件安裝到系統路徑下。
此時直接編譯會導致鏈接錯誤,與靜態庫類似:
$ gcc main.c -lmystdio
場景二:頭文件和庫文件與源文件在同一路徑下。
$ gcc main.c -L. -lmymath // 從左到右搜索-L指定的目錄
場景三:頭文件和庫文件都有各自獨立的路徑。
$ gcc main.c -I頭文件路徑 -L庫文件路徑 -lmymath
使用ldd查看可執行文件依賴哪些庫:
問題1:
如果刪除庫中的文件,會導致如下錯誤:
這是因為動態鏈接的特性,刪除庫文件會導致鏈接錯誤,而靜態鏈接則不會。
解決方案:
- 拷貝.so文件到系統共享路徑下,通常是/usr/lib, /usr/local/lib, /lib64等。
- 在系統共享路徑下建立同名的軟鏈接。
Linux系統會查找動態庫和環境變量,可以通過更改環境變量LD_LIBRARY_PATH來指定動態庫路徑:
但重啟Xshell后,導入的環境變量會丟失,可以將環境變量添加到.bashrc文件中:
使用ldconfig方案:配置/etc/ld.so.conf.d/,然后使用ldconfig更新:
將庫的路徑寫入配置文件,系統會在配置文件中的路徑查找庫,自行創建一個配置文件:
然后在命令行輸入ldconfig,即可生效。
程序運行時與gcc無關,gcc是告訴編譯器接下來操作系統要加載你的程序。
如何讓系統指定路徑查找自己的動態庫,上述就是解決方案。
問題2:如果同時提供.so和.a,gcc/g++默認使用什么庫?
默認使用動態庫:
如果想要靜態鏈接,需要添加-static選項:
注意:
如果你要強制靜態鏈接,必須提供對應的靜態庫。如果只提供靜態庫但連接方式是動態鏈接,gcc/g++沒有選擇,只能針對你的.a文件局部采用靜態鏈接。因此,如果從系統路徑中刪除庫文件,程序仍然可以運行。
完,您的點贊和收藏是我更新的最大動力!!!