你不知道的庫:庫的種類,作用和加載方式
📟作者主頁:慢熱的陜西人
🌴專欄鏈接:Linux
📣歡迎各位大佬👍點贊🔥關注🚓收藏,🍉留言
本博客主要內容講解了庫的概念和為什么要有庫,以及靜態庫和動態庫,最后還有最重要的庫的加載的理解以及動態庫的三種配置方法
文章目錄
- 你不知道的庫:庫的種類,作用和加載方式
- 1.了解一下庫
- 2.為什么要有庫
- 3.寫一寫----從庫的書寫者的角度
- 3.1庫的命名規則:
- 4.用一用----站在使用者的角度去使用我們寫的庫
- 4.1靜態庫
- 4.2動態庫
- 5.動態庫配置問題
- 5.1環境變量
- 5.2軟鏈接方案
- 5.3配置文件方案
- 6.動態庫加載過程的理解
- 6.1 動態庫加載問題和周邊問題:
- 6.2庫中地址的理解
1.了解一下庫
雖然我們目前沒有使用過第三方庫,但是我們一直在使用一些C/C++內部的標準庫,那么我們來在目錄中查看一下這些庫:
包括我們甚至可以找到我們經常使用的stdio.h
標準庫,并且可以使用vim編輯器進行查看的!
所以我們可以理解到:
①系統已經預裝了C/C++的頭文件和庫文件,頭文件提供方法說明,庫提供方法實現,頭和庫是有對應關系的,是要組合在一起使用的。
②頭文件是在預處理階段就引入的,鏈接的本質其實就是鏈接庫!
另外我們也可以理解一些現象了:
① 所以我們在vs2019,2022下安裝開發環境—安裝編譯器軟件,其實就是安裝要開發的語言配套的庫和頭文件
②我們在使用編譯器的時候,都會有語法的自動提醒功能,需要先包含頭文件。**語法提醒本質:**編譯器或者編輯器,它會自動的將用戶輸入的內容,不斷的在被包含的頭文件中進行搜索,自動提醒功能依賴于頭文件的。
③我們在寫代碼的時候,我們的環境怎么知道我們的代碼中有那些地有語法報錯,那些地方定義變量有問題?不要太小看編譯器,編譯器有命令行模式,也就是我們在Linux中使用gcc命令那樣,也有其他的自動化模式幫我們不斷在進行語法檢查。
2.為什么要有庫
庫可以幫助我們提高開發效率,我們學習的時候提倡去造輪子,幫我們深入理解庫的原理和實現方法。
到開發中,我們更提倡用輪子!
當然我們也可以選擇將以提供源代碼的方式,來提高開發效率,但是這對我們未來是由一些其他問題的比如我們不想讓使用者看到我們的代碼,比如我們的公司向另一個公司提供軟件服務,我們只需要將打包好的庫給他們使用即可,而不會把源碼直接給他們!
3.寫一寫----從庫的書寫者的角度
庫分為靜態庫(.a)和動態庫(.so)
另外一般云服務器,默認只會存在動態庫,不會存在靜態庫,靜態庫需要獨自安裝!
3.1庫的命名規則:
例如:
libstdc++.so.6
去掉前面的
lib
和后面的第一個.
后面包括.
的內容結惡果就是庫的名稱了!所以例子的這個庫的名稱使
stdc++
;
4.用一用----站在使用者的角度去使用我們寫的庫
4.1靜態庫
我們先簡單實現一個加減函數的庫:
我們先寫四個文件:
//myadd.c
#include"myadd.h"int myadd(int a, int b)
{return a + b;
}
//myadd.h
#pragma once
#include<stdio.h>int myadd(int a, int b);
//mysub.c
#include"mysub.h"
int mysub(int a, int b)
{return a - b;
}
// mysub.h
#pragma once#include<stdio.h>int mysub(int a, int b);
首先我們之前的做法就是在寫一個主函數的文件將他們一塊編譯:
但是這是我們只有這么兩個源文件的時候看起來還可以,但是當我們的項目非常龐大的時候,并且我們需要分享給別人使用我們的庫的時候,就非常的不方便了!
我們將.c源文件都讓其生成.o文件,也就是未鏈接前的文件:
利用ar命令生成靜態庫:ar -rc libmymath.a myadd.o mysub.o
當我們把libmymath.a
文件挪動到我們的ortherperson
目錄下的時,我們嘗試編譯的時候:
發現提示我們找不到頭文件
所以我們這樣的方式是不對的!
我們先在ortherperson
下創建兩個文件夾lib
和include
分別存放生成的靜態庫和對應的頭文件;
正確的是這樣:gcc -o mytest main.c -I./include -L./lib -lmymath
我們來解釋一下:
-I
:其中I
表示的inlcude
的意思,也就是頭文件的意思后面直接跟上我們頭文件的路徑-L
:其中L
表示的Lib
的意思,就是我們的庫的意思,后面直接跟上我們靜態庫的路徑-l
:這個參數后面緊跟我們的靜態庫的名字,就是我們前面命名規則說的那樣,去掉前面的lib
和.
后面的內容就是我們的靜態庫的名字!
那么我們接下來嘗試一下把我們的庫放到語言庫中直接去調嘗試一下:
[root@lavm-5wklnbmaja ortherperson]# cp lib/libmymath.a /lib64
[root@lavm-5wklnbmaja ortherperson]# cp include/*.h /usr/include
嘗試去編譯:
我們發現編譯成功,并且運行成功!
所以我們這里就可以引入第三方庫的使用:
①首先我們需要指定的頭文件和庫文件
②如果沒有默認安裝到系統gcc,g++默認的搜索路徑下,用戶必須指明對應的選項,告知編譯器:
? a.頭文件在哪;
? b.庫文件在哪;
? c.庫文件具體是誰
③將我們下載下來的庫和頭文件,拷貝到系統默認路徑下---- 在Linux下安裝庫!!那么卸載呢?對任何軟件而言,安裝和卸載的本質就是拷貝到系統特定的路徑。
④如果我們安裝的庫是第三方的(語言,操作系統系統接口) 庫,我們要正常使用,即便是已經全部安裝到了系統中,gcc/g++必須要用-l指明具體庫的名稱!
理解現象:
? 無論我們是從網絡中未來直接下載好的庫,或者是源代碼(編譯方法) — make install 安裝的命令 --cp,安裝到系統中,我們安裝大部分指令,庫等等都是需要sudo的或者超級用戶操作!
4.2動態庫
我們刪除掉之前的只留下最開始的源文件和頭文件:
我們重新生成.o文件:
[mi@lavm-5wklnbmaja mylib]$ gcc -c -fPIC -o myadd.o myadd.c
[mi@lavm-5wklnbmaja mylib]$ gcc -c -fPIC -o mysub.o mysub.c
其中:fPIC:產生位置無關碼(position independent code)
再生成動態庫:
gcc -shared -o libmymath.so myadd.o mysub.o
接下來我們嘗試鏈接動態庫:
gcc -o main main.c -Iinclude -Llib -lmymath
鏈接成功了,但是我們運行的時候卻報錯了,報錯說無法找到我們的動態庫文件!但是我們的鏈接卻通過了。原因是我們確實把動態庫的位置告訴了編譯器,但是卻沒告訴操作系統!
那么這里為什么沒有成功呢?這里其實就是靜態庫和動態庫的區別了,對于靜態庫將用戶使用的二進制代碼直接拷貝到可執行目標文件的程序中。但是我們的靜態庫不會。
5.動態庫配置問題
運行時,OS是如何查找我們的動態庫的?三種方法
5.1環境變量
動態庫需要再一個環境變量
LD_LIBRARY_PATH
中去尋找我們的動態庫!(告訴操作系統動態庫的位置)。我們只需要把我們當前的動態庫的路徑添加到這個環境變量中即可:
我們ldd查看一下鏈接的情況并且運行一下:
當然這種方案我們不是很推崇是一種臨時方案,原因是我們知道環境變量我們如果沒有再配置文件修改的話,我們每次重新登錄或者重啟bash都會導致環境變量的初始化。
5.2軟鏈接方案
sudo ln -s /learn/lesson11/ortherperson/lib/libmymath.so /lib64/libmymath.so
這個時候ldd我們再去查看的時候:
并且我們這時候再去運行發現是沒有任何問題的:
并且對于軟鏈接來說它不是臨時的,就算我們重啟bash也不會影響,因為他是真正寫入到磁盤內部的,而不是在內存中!
5.3配置文件方案
首先我們查看一下系統的默認配置文件:
[mi@lavm-5wklnbmaja lesson11]$ ls /etc/ld.so.conf.d/ kernel-3.10.0-957.el7.x86_64.conf mariadb-x86_64.conf
我們在這個目錄下添加以一個我們的配置文件:
xupt_lib.conf
//創建配置文件 [mi@lavm-5wklnbmaja lesson11]$ sudo touch /etc/ld.so.conf.d/xupt_lib.conf //像配置文件中寫入我們的動態庫所在的絕對路徑 [mi@lavm-5wklnbmaja lib]$ sudo vim /etc/ld.so.conf.d/xupt_lib.conf
我們ldd查看一下:
發現似乎還是沒有鏈接上,其實我們還少一步生效配置文件:
[mi@lavm-5wklnbmaja ortherperson]$ sudo ldconfig
運行一下看看:
嘗試運行:
6.動態庫加載過程的理解
靜態鏈接形成的可執行程序中本身就有靜態庫中對應的方法實現!
首先我們的靜態庫存儲在硬盤中,當多個可執行文件都要使用到這個庫的時候,他就會加載到每個可執行程序內部。會讓每個可執行程序的變大。其次因為它加載到了可執行程序內部,所以我們在外部刪除這個靜態庫的時候我們的程序依然可以正常運行。所以主流會多使用動態庫多一些:
6.1 動態庫加載問題和周邊問題:
鏈接:將可執行程序內部的外部符號,替換稱為庫中的具體地址。
只要我們把庫加載到內存,映射到進程的地址空間中之后,我們的代碼執行庫中的方法,就依舊還是在自己的地址空間內進行函數跳轉即可!
并且為什么動態庫節省資源,原因是當多個進程都要使用到同一個動態庫的時候,這個動態庫只需要在內存加載一份,每個進程只需要在自己的進程地址空間中進行映射即可!因為我們曾經在鏈接動態庫的時候程序內部已經存儲了相關數據和函數的地址信息!
有時候我們的庫很大,我們的程序并不會用到其中所有的接口,所以我們的操作系統也沒必要把庫中所有的信息都加載到內存中。
6.2庫中地址的理解
動態庫必定面臨一個問題:
不同的進程,運行的程度不同,需要使用的第三方庫是不同的,注定了每一個共享空間中空閑位置是不確定的!
在程序翻譯鏈接,形成可執行程序的時候,程序內部有沒有地址?
答案是有的:動態庫中的地址,決定不能確定,所以不能使用絕對地址!動態庫中的所有地址,都是偏移量(相對地址),默認從0開始。
當一個庫,真正的被映射進地址空間的時候,它的起始地址才會被真正的確定!當我們映射的時候是將進程地址空間的內部的代碼區包含庫的相對地址+我們的動態庫的初始地址,一塊存儲到進程地址空間對應的共享代碼空間的位置!并且這個地址一旦被確定是不變的!所以我們經過這種方式就可以去訪問動態庫中的方法了!
所以動態庫在我們的進程地址空間中隨意加載,與我們在地址空間中加載的位置毫無關系!
所以這時候我們也可以理解之前,鏈接動態庫的時候我們生成的匯編文件的時候帶了一個參數-fPIC
,被稱作與位置無關碼:也就是對應我們這里的相對地址,偏移量!
接下來我們驗證一下:
我們重新生成一個非無關碼的匯編文件:用于生成對應的靜態庫文件。
[mi@lavm-5wklnbmaja mylib]$ gcc -o myadd_static.o -c myadd.c
[mi@lavm-5wklnbmaja mylib]$ gcc -o mysub_static.o -c mysub.c
//生成對應的靜態庫
[mi@lavm-5wklnbmaja mylib]$ ar -rc libmymath.a myadd_static.o mysub_static.o
將兩個庫拷貝到我們的ortherperson/lib
目錄下:
接下來我們有一個問題,當靜態庫和動態庫同時存在的情況下,我們的可執行文件會優先使用哪個呢?答案肯定是動態庫
我們試驗一下:重新編譯文件并運行
我們ldd查看一下可執行程序的鏈接情況:很顯然是我們的動態庫!
那如果我們呢執意要靜態鏈接呢?只需要再加一個參數-static
[mi@lavm-5wklnbmaja ortherperson]$ gcc -o mytest-s main.c -I./include -L./lib -lmymath -static
當我們嘗試編譯的時候報錯了:這是因為我們沒有安裝對應的庫:sudo yum install -y glibc-static
我們分別在三種情況下編譯:
①動態庫和靜態庫都存在的情況生成:mytest-d
②動態庫和靜態庫都存在的情況下加-static參數生成:mytest-s
③刪除動態庫只留下靜態庫再生成:mytest-u
ldd查看三個文件:
所以我們可以得出結論:
①動態庫和靜態庫同時存在,系統默認使用動態庫
②編譯器,在鏈接的時候,如果庫提供了動和靜,優先動,沒有動,才使用靜!
到這本篇博客的內容就到此結束了。
如果覺得本篇博客內容對你有所幫助的話,可以點贊,收藏,順便關注一下!
如果文章內容有錯誤,歡迎在評論區指正