當(dāng)前位置:首頁 > 嵌入式培訓(xùn) > 嵌入式學(xué)習(xí) > 講師博文 > linux開發(fā):linux內(nèi)存加載動(dòng)態(tài)庫?
linux開發(fā):linux內(nèi)存加載動(dòng)態(tài)庫?
時(shí)間:2019-03-27 來源:華清遠(yuǎn)見
1 Linux應(yīng)用程序可能會(huì)使用到兩種函數(shù)庫,一種靜態(tài)庫、一種動(dòng)態(tài)庫,靜態(tài)庫以.a為擴(kuò)展名,動(dòng)態(tài)庫以.so為擴(kuò)展名。二者都使用廣泛。
2 動(dòng)態(tài)庫和靜態(tài)庫的基本概念?
靜態(tài)庫,是在可執(zhí)行程序連接時(shí)就已經(jīng)加入到執(zhí)行碼中,在物理上成為執(zhí)行程序的一部分;使用靜態(tài)庫編譯的程序運(yùn)行時(shí)無需該庫文件支持,哪里都可以用,但是生成的可執(zhí)行文件較大。動(dòng)態(tài)庫,是在可執(zhí)行程序啟動(dòng)時(shí)加載到執(zhí)行程序中,可以被多個(gè)可執(zhí)行程序共享使用。使用動(dòng)態(tài)庫編譯生成的程序相對(duì)較小,但運(yùn)行時(shí)需要庫文件支持,如果機(jī)器里沒有這些庫文件就不能運(yùn)行。
3 如何使用動(dòng)態(tài)庫?
動(dòng)態(tài)庫也叫共享庫,如果在程序連接時(shí)使用共享庫,就必須在運(yùn)行時(shí)找到共享庫的位置。Linux的可執(zhí)行程序在執(zhí)行的時(shí)候默認(rèn)是先搜索/lib和/usr/lib這兩個(gè)目錄,然后按照/etc/ld.so.conf里面的配置搜索絕對(duì)路徑。同時(shí),linux也提供了環(huán)境變量LD_LIBRARY_PATH供用戶選擇使用,用戶可以通過它來查找默認(rèn)路徑之外的其他路徑,如要查找/work/lib路徑,你可以在/etc/rc.d/rc.local或其他系統(tǒng)啟動(dòng)后即可執(zhí)行到的腳本文件中添加如下語句:LD_LIBRARY_PATH=/work/lib:$(LD_LIBRARY_PATH)。并且LD_LIBRARY_PATH路徑優(yōu)先于系統(tǒng)默認(rèn)路徑之前查找。
不過LD_LIBRARY_PATH的設(shè)定作用是全局的,過多的使用可能會(huì)影響到其他應(yīng)用程序的運(yùn)行,所以多用在調(diào)試。
4 庫的連接路經(jīng)和運(yùn)行路經(jīng)
現(xiàn)代連接器在處理動(dòng)態(tài)庫時(shí)將鏈接時(shí)路徑(Link-time path)和運(yùn)行時(shí)路徑(Run-time path)分開,用戶可以通過-L指定連接時(shí)庫的路徑,通過-R(或-rpath)指定程序運(yùn)行時(shí)庫的路徑,大大提高了庫應(yīng)用的靈活性。比如我們做嵌入式移植時(shí)#arm-linux-gcc $(CFLAGS) –o target –L/work/lib/zlib/ -llibz-1.2.3 (work/lib/zlib下是交叉編譯好的zlib庫),將target編譯好后我們只要把zlib庫拷貝到開發(fā)板的系統(tǒng)默認(rèn)路徑下即可;蛘咄ㄟ^- rpath(或-R )、LD_LIBRARY_PATH指定查找路徑
5 動(dòng)態(tài)庫的加載使用?
基本上每個(gè)linux程序都至少會(huì)用一個(gè)動(dòng)態(tài)庫,查看某個(gè)程序使用了那些動(dòng)態(tài)庫,使用命令ldd查看
如:#ldd /bin/ls (查看系統(tǒng)中l(wèi)s用到的動(dòng)態(tài)庫)
#ldd main (查看自定義main用到的動(dòng)態(tài)庫)
#ldd -u main(查看自定義main中無用的動(dòng)態(tài)庫)
#strace ./main(查看程序啟動(dòng)時(shí)加載的所有動(dòng)態(tài)庫)
Linux程序啟動(dòng)時(shí)加載的庫有默認(rèn)的庫也有顯式手動(dòng)連接的庫
1 默認(rèn):編譯代碼時(shí)不手動(dòng)連接額外庫,那么在代碼中使用到庫函數(shù)時(shí)會(huì)在運(yùn)行時(shí)自動(dòng)加載連接需要的庫。
#gcc -o main main.c
#ldd main
linux-vdso.so.1 => (0x00007fffa1b6d000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff4fde52000)
/lib64/ld-linux-x86-64.so.2 (0x00007ff4fe230000)
2 手動(dòng):編譯代碼時(shí)手動(dòng)添加額外的庫,那么在程序運(yùn)行時(shí),既要加載默認(rèn)的庫,還要加載手動(dòng)添加的庫。加載的庫多了,會(huì)影響程序啟動(dòng)的速度。
#gcc -o main1 -lm -lrt main.c (編譯時(shí)手動(dòng)連接數(shù)學(xué)庫、線程庫)
#ldd main1
linux-vdso.so.1 => (0x00007fff88770000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f993a0cc000)
/lib64/ld-linux-x86-64.so.2 (0x00007f993a4aa000)
#strace ./main1
從程序運(yùn)行中可以看出main1運(yùn)行時(shí)即加載了默認(rèn)庫,也加載了手動(dòng)指定的庫(在此手動(dòng)指定的數(shù)學(xué)庫、線程庫是代碼不需要的,但在程序運(yùn)行時(shí)也加載了),會(huì)影響整個(gè)程序的加載速度。
大家知不知道linux從程序(program或?qū)ο螅┳兂蛇M(jìn)程(process或進(jìn)程),要經(jīng)過哪些步驟呢,簡單的說分三步:
1、fork進(jìn)程,在內(nèi)核創(chuàng)建進(jìn)程相關(guān)內(nèi)核項(xiàng),加載進(jìn)程可執(zhí)行文件;
2、查找依賴的so,一一加載映射虛擬地址
3、初始化程序變量。
可以看到,第二步中dll依賴越多,進(jìn)程啟動(dòng)越慢,并且發(fā)布程序的時(shí)候,有這些鏈接但沒有使用的so,同樣要一起跟著發(fā)布,否則進(jìn)程啟動(dòng)時(shí)候,會(huì)失敗,找不到對(duì)應(yīng)的so。所以我們不能像上面那樣,把一些毫無意義的so鏈接進(jìn)來,浪費(fèi)資源。
6 關(guān)于Linux程序連接so有兩種方式:隱式和顯示
所謂顯示就是程序主動(dòng)調(diào)用dlopen打開相關(guān)so;首先,dlopen的so使用ldd是查看不到的。其次,使用dlopen打開的so并不是在進(jìn)程啟動(dòng)時(shí)候加載映射的,而是當(dāng)進(jìn)程運(yùn)行到調(diào)用dlopen代碼地方才加載該so,也就是說,如果每個(gè)進(jìn)程顯示鏈接a.so;但是如果發(fā)布該程序時(shí)候忘記附帶發(fā)布該a.so,程序仍然能夠正常啟動(dòng),甚至如果運(yùn)行邏輯沒有觸發(fā)運(yùn)行到調(diào)用dlopen函數(shù)代碼地方。該程序還能正常運(yùn)行,即使沒有a.so. 既然顯示加載這么多優(yōu)點(diǎn),那么為什么實(shí)際生產(chǎn)中很少碼農(nóng)使用它呢, 主要原因還是起使用不是很方便,需要開發(fā)人員多寫不少代碼。所以不被大多數(shù)碼農(nóng)使用,還有一個(gè)重要原因應(yīng)該是能提前發(fā)現(xiàn)錯(cuò)誤,在部署的時(shí)候就能發(fā)現(xiàn)缺少哪些so,而不是等到實(shí)際上限運(yùn)行的時(shí)候才發(fā)現(xiàn)缺東少西。

