大家平時(shí)使用Linux系統(tǒng)過程中可能都見過文件系統(tǒng)里有好多帶版本號(hào)的共享庫扮饶,如下:
lrwxrwxrwx 1 root root 21 Mar 25 18:33 libDeployPkg.so.0 -> libDeployPkg.so.0.0.0
-rw-r--r-- 1 root root 31304 Mar 25 18:33 libDeployPkg.so.0.0.0
lrwxrwxrwx 1 root root 20 Mar 25 18:33 libguestlib.so.0 -> libguestlib.so.0.0.0
-rw-r--r-- 1 root root 26752 Mar 25 18:33 libguestlib.so.0.0.0
lrwxrwxrwx 1 root root 16 Mar 25 18:33 libhgfs.so.0 -> libhgfs.so.0.0.0
-rw-r--r-- 1 root root 167248 Mar 25 18:33 libhgfs.so.0.0.0
lrwxrwxrwx 1 root root 18 Mar 25 18:33 libvgauth.so.0 -> libvgauth.so.0.0.0
-rw-r--r-- 1 root root 85144 Mar 25 18:33 libvgauth.so.0.0.0
lrwxrwxrwx 1 root root 19 Mar 25 18:33 libvmtools.so.0 -> libvmtools.so.0.0.0
-rw-r--r-- 1 root root 631480 Mar 25 18:33 libvmtools.so.0.0.0
大家平時(shí)關(guān)注過這些共享庫的版本號(hào)是以什么規(guī)則制定的呢具练?
以"libname.so.x.y.z"來說:
- 最前面使用"lib"前綴,中間是庫的名字和后綴".so"甜无,后面跟著三個(gè)數(shù)字組成版本號(hào)
- x為主版本號(hào)扛点,表示庫的重大升級(jí),不同主版本號(hào)的庫之間是不兼容的毫蚓,依賴于舊版本號(hào)的程序要修改相應(yīng)的代碼來適應(yīng)新版本的庫占键,并重新編譯才可以鏈接新版本庫運(yùn)行。
- y為次版本號(hào)元潘,表示庫的增量升級(jí)畔乙,相對(duì)于舊版本只是增加了一些新的接口,并保持原有的接口符號(hào)不變翩概,完全兼容舊版本的庫牲距,即一個(gè)依賴于舊版本號(hào)的程序可以直接鏈接新版本庫來正常運(yùn)行返咱。
- z為發(fā)布版本號(hào),表示庫的一些錯(cuò)誤修正牍鞠、性能優(yōu)化等咖摹,不會(huì)增加新的接口,只是在舊版本庫的基礎(chǔ)上做一些優(yōu)化难述。
如何創(chuàng)建共享庫
首先介紹一些SO-NAME萤晴,每一個(gè)共享庫都有一個(gè)SO-NAME,即共享庫的文件名去掉次版本號(hào)和發(fā)布版本號(hào)胁后,比如"libname.so.x.y.z"的SO_NAME就是"libname.so.x"店读。在Linux系統(tǒng)中,系統(tǒng)會(huì)為每個(gè)共享庫在所在的目錄中創(chuàng)建一個(gè)與SO-NAME同名并且指向?qū)嶋H共享庫的軟鏈接攀芯。例如"libc.so.1"會(huì)指向"libc.so.1.2.3"屯断,當(dāng)目錄中有"libc.so.1.2.4"時(shí),"libc.so.1"這個(gè)軟鏈就會(huì)指向"libc.so.1.2.4"侣诺,達(dá)到升級(jí)的目的殖演。那系統(tǒng)是如何更新這個(gè)軟鏈的呢,例如我們經(jīng)常使用apt-get install更新程序時(shí)年鸳,動(dòng)態(tài)鏈接庫更換了一個(gè)新的版本趴久,那同時(shí)也需要更新一些軟鏈指向的位置,有一個(gè)程序叫ldconfig阻星,每次升級(jí)后執(zhí)行一下ldconfig朋鞍,就會(huì)自動(dòng)遍歷所有的默認(rèn)共享庫目錄,更新軟鏈妥箕。
如下代碼:
// libc.c
#include <stdio.h>
void func(int i) {
printf("func %d \n", i);
}
在gcc中通過“-Wl,-soname”參數(shù)告訴鏈接器滥酥,用于制定共享庫的SO-NAME。
gcc -fPIC -shared -Wl,-soname,libc.so.1 -o libc.so.1.2.3 lib.c
再看program.c
// program.c
#include <stdio.h>
int main() {
func(1);
return 0;
}
編譯鏈接運(yùn)行:
$ gcc -o ttt program.c ./libc.so.1.2.3
$ ./ttt
./ttt: error while loading shared libraries: libc.so.1: cannot open shared object file: No such file or directory
上面可見畦幢,程序并沒有運(yùn)行成功坎吻,因?yàn)闆]有創(chuàng)建SO-NAME的相應(yīng)軟鏈
$ ln -s libc.so.1.2.3 libc.so.1
再次運(yùn)行
$ ./ttt
./ttt: error while loading shared libraries: libc.so.1: cannot open shared object file: No such file or directory
再次運(yùn)行發(fā)現(xiàn)還是沒有運(yùn)行成功,為什么呢宇葱?因?yàn)槌绦蜻\(yùn)行時(shí)不知道去哪里找這個(gè)動(dòng)態(tài)鏈接庫瘦真,所有需要指定一下查找?guī)斓穆窂剑?/p>
$ LD_LIBRARY_PATH=. ./ttt
func 1
運(yùn)行成功。
為什么運(yùn)行"LD_LIBRARY_PATH=."后程序就可以運(yùn)行成功了呢黍瞧?這里介紹下共享庫的路徑查找相關(guān)知識(shí)點(diǎn)诸尽。
共享庫系統(tǒng)路徑
一般有三個(gè):
- /lib:主要存放系統(tǒng)最關(guān)鍵和基礎(chǔ)的共享庫,比如動(dòng)態(tài)鏈接器印颤、C語言運(yùn)行庫您机、數(shù)學(xué)庫等,/bin和/sbin下的程序需要的共享庫和系統(tǒng)啟動(dòng)需要的庫一般放在這里。
- /usr/lib:主要存放一些非系統(tǒng)運(yùn)行時(shí)所需要的關(guān)鍵性的共享庫际看,一般是用戶開發(fā)過程中用到的共享庫咸产。
- /usr/local/lib:一般存放一些非系統(tǒng)所需要的第三方庫,例如裝一個(gè)Python環(huán)境依賴的庫都可以放在這里仲闽。
總結(jié):/lib和/usr/lib存放一些常用成熟的系統(tǒng)本身需要的庫脑溢,/usr/local/lib存放一些非系統(tǒng)所需要的第三方庫。
簡單講:/lib是內(nèi)核級(jí)的赖欣,/usr/lib是系統(tǒng)級(jí)的:/usr/local/lib是用戶級(jí)的屑彻。
兩個(gè)在程序運(yùn)行時(shí)與共享庫搜索路徑相關(guān)的環(huán)境變量:
- LD_LIBRARY_PATH:通過此環(huán)境變量,可以臨時(shí)改變某個(gè)程序的共享庫查找路徑畏鼓,而不會(huì)影響系統(tǒng)中的其它程序酱酬,LD_LIBRARY_PATH默認(rèn)為空,如果某個(gè)進(jìn)程設(shè)置了此環(huán)境變量云矫,動(dòng)態(tài)鏈接器在查找共享庫時(shí),會(huì)首先查找LD_LIBRARY_PATH的指定目錄汗菜,通過這個(gè)環(huán)境變量可以測試新的共享庫让禀,因?yàn)殒溄悠麈溄訒r(shí)會(huì)鏈接最先找到的共享庫。
- LD_PRELOAD:與LD_LIBRARY_PATH類似陨界,它比LD_LIBRARY_PATH里面的目錄優(yōu)先級(jí)還高巡揍,LD_PRELOAD里面指定的共享庫和目標(biāo)文件中的全局符號(hào)會(huì)覆蓋后面出現(xiàn)的同名全局符號(hào),使得我們可以很方便的改寫標(biāo)準(zhǔn)庫里的某個(gè)函數(shù)而不影響其它函數(shù)菌瘪,對(duì)于程序調(diào)試和測試非常有用腮敌。
為什么要extern C?
前面已經(jīng)介紹了共享庫的版本升級(jí)機(jī)制俏扩,在C語言中可能升級(jí)比較方便簡單糜工,不會(huì)遇到太多問題,在C++中就比較繁瑣了录淡,因?yàn)镃++為了支持重載和namespace等捌木,編譯出來的函數(shù)符號(hào)相對(duì)于函數(shù)名字來說有很多前后綴修飾,而且不同廠家的編譯器或者不同版本的編譯器可能符號(hào)修飾規(guī)則都不同嫉戚,在更新時(shí)可能也會(huì)因?yàn)檫@種原因?qū)е虏患嫒菖亳桑院瘮?shù)導(dǎo)出時(shí)需要使用extern C修飾,讓導(dǎo)出函數(shù)的符號(hào)遵守C語言的規(guī)范彬檀。
參考資料
《程序員的自我修養(yǎng):鏈接裝載與庫》
https://blog.csdn.net/weixin_35695879/article/details/90721384