linux 下有動態庫和靜態庫,動態庫以.so為擴展名,靜態庫以.a為擴展名。二者都使用廣泛。本文主要講動態庫方面知識。
基本上每一個linux 程序都至少會有一個動態庫,查看某個程序使用了那些動態庫,使用ldd 命令查看
#?ldd?/bin/ls
linux-vdso.so.1?=>?(0x00007fff597ff000)
libselinux.so.1?=>?/lib64/libselinux.so.1?(0x00000036c2e00000)
librt.so.1?=>?/lib64/librt.so.1?(0x00000036c2200000)
libcap.so.2?=>?/lib64/libcap.so.2?(0x00000036c4a00000)
libacl.so.1?=>?/lib64/libacl.so.1?(0x00000036d0600000)
libc.so.6?=>?/lib64/libc.so.6?(0x00000036c1200000)
libdl.so.2?=>?/lib64/libdl.so.2?(0x00000036c1600000)
/lib64/ld-linux-x86-64.so.2?(0x00000036c0e00000)
libpthread.so.0?=>?/lib64/libpthread.so.0?(0x00000036c1a00000)
libattr.so.1?=>?/lib64/libattr.so.1?(0x00000036cf600000)
這么多so,是的。使用ldd顯示的so,并不是所有so都是需要使用的,下面舉個例子
#include?
#include?
#include?
using?namespace?std;
int?main?()
{
???cout?<"test"?<endl;
???return?0;
}
使用缺省參數編譯結果
#?g++?-o?demo?main.cpp
#?ldd?demo
linux-vdso.so.1?=>?(0x00007fffcd1ff000)
libstdc++.so.6?=>?/usr/lib64/libstdc++.so.6?(0x00007f4d02f69000)
libm.so.6?=>?/lib64/libm.so.6?(0x00000036c1e00000)
libgcc_s.so.1?=>?/lib64/libgcc_s.so.1?(0x00000036c7e00000)
libc.so.6?=>?/lib64/libc.so.6?(0x00000036c1200000)
/lib64/ld-linux-x86-64.so.2?(0x00000036c0e00000)
如果我鏈接一些so,但是程序并不用到這些so,又是什么情況呢,下面我加入鏈接壓縮庫,數學庫,線程庫
#?g++?-o?demo?-lz?-lm?-lrt?main.cpp
#?ldd?demo
????????linux-vdso.so.1?=>?(0x00007fff0f7fc000)
????????libz.so.1?=>?/lib64/libz.so.1?(0x00000036c2600000)
????????librt.so.1?=>?/lib64/librt.so.1?(0x00000036c2200000)
????????libstdc++.so.6?=>?/usr/lib64/libstdc++.so.6?(0x00007ff6ab70d000)
????????libm.so.6?=>?/lib64/libm.so.6?(0x00000036c1e00000)
????????libgcc_s.so.1?=>?/lib64/libgcc_s.so.1?(0x00000036c7e00000)
????????libc.so.6?=>?/lib64/libc.so.6?(0x00000036c1200000)
????????libpthread.so.0?=>?/lib64/libpthread.so.0?(0x00000036c1a00000)
????????/lib64/ld-linux-x86-64.so.2?(0x00000036c0e00000)
看看,雖然沒有用到,但是一樣有鏈接進來,那看看程序啟動時候有沒有去加載它們呢
#?strace?./demo
????execve("./demo",?["./demo"],?[/*?30?vars?*/])?=?0
????...?=?0
????open("/lib64/libz.so.1",?O_RDONLY)?=?3
????...
????close(3)?=?0
????open("/lib64/librt.so.1",?O_RDONLY)?=?3
????...
????close(3)?=?0
????open("/usr/lib64/libstdc++.so.6",?O_RDONLY)?=?3
????...
????close(3)?=?0
????open("/lib64/libm.so.6",?O_RDONLY)?=?3
????...
????close(3)?=?0
????open("/lib64/libgcc_s.so.1",?O_RDONLY)?=?3
????...
????close(3)?=?0
????open("/lib64/libc.so.6",?O_RDONLY)?=?3
????...
????close(3)?=?0
????open("/lib64/libpthread.so.0",?O_RDONLY)?=?3
????...
????close(3)?=?0
????...
看,有加載,所以必定會影響進程啟動速度,所以我們最后不要把無用的so編譯進來,這里會有什么影響呢?
大家知不知道linux從程序(或對象)變成進程(或進程),要經過哪些步驟呢,這里如果詳細的說,估計要另開一篇文章。簡單的說分三步:
1、fork進程,在內核創建進程相關內核項.so文件 隱式鏈接,加載進程可執行文件; 2、查找依賴的so,一一加載映射虛擬地址 3、初始化程序變量。
可以看到,第二步中dll依賴越多,進程啟動越慢,并且發布程序的時候,這些鏈接但沒有使用的so,同樣要一起跟著發布,否則進程啟動時候,會失敗,找不到對應的so。所以我們不能像上面那樣,把一些毫無意義的so鏈接進來,浪費資源。但是開發人員寫 一般有沒有那么細心,圖省事方便.so文件 隱式鏈接,那么有什么好的辦法呢。繼續看下去,下面會給你解決方法。
先使用 ldd -u demo 查看不需要鏈接的so,看下面,一面了然,無用的so全部暴露出來了吧
#?ldd?-u?demo
Unused?direct?dependencies:
????????/lib64/libz.so.1
????????/lib64/librt.so.1
????????/lib64/libm.so.6
????????/lib64/libgcc_s.so.1
??使用?-Wl,--as-needed?編譯選項
#?g++?-Wl,--as-needed?-o?demo?-lz?-lm?-lrt?main.cpp
#?ldd?demo
????????linux-vdso.so.1?=>?(0x00007fffebfff000)
????????libstdc++.so.6?=>?/usr/lib64/libstdc++.so.6?(0x00007ff665c05000)
????????libc.so.6?=>?/lib64/libc.so.6?(0x00000036c1200000)
????????libm.so.6?=>?/lib64/libm.so.6?(0x00000036c1e00000)
????????/lib64/ld-linux-x86-64.so.2?(0x00000036c0e00000)
????????libgcc_s.so.1?=>?/lib64/libgcc_s.so.1?(0x00000036c7e00000)
#?ldd?-u?demo
Unused?direct?dependencies:
我們知道linux鏈接so有兩種途徑:顯示和隱式。
所謂顯示就是程序主動調用打開相關so;這里需要補充的是,如果使用顯示鏈接,上面討論的那些問題都不存在。首先,的so使用ldd是查看不到的。其次,使用打開的so并不是在進程啟動時候加載映射的,而是當進程運行到調用代碼地方才加載該so,也就是說,如果每個進程顯示鏈接a.so;但是如果發布該程序時候忘記附帶發布該a.so,程序仍然能夠正常啟動,甚至如果運行邏輯沒有觸發運行到調用函數代碼地方。該程序還能正常運行,即使沒有a.so.
既然顯示加載這么多優點,那么為什么實際生產中很少碼農使用它呢, 主要原因還是起使用不是很方便,需要開發人員多寫不少代碼。所以不被大多數碼農使用,還有一個重要原因應該是能提前發現錯誤,在部署的時候就能發現缺少哪些so,而不是等到實際上限運行的時候才發現缺東少西。
下面舉個工作中最常碰到的問題。
寫一個最簡單的so,tmp.cpp
int?test()
{
???return?20;
}
編譯=>鏈接=》運行,下面main.cpp
$?g++?-fPIC?-c?tmp.cpp
$?g++?-shared?-o?libtmp.so?tmp.o
$?mv?libtmp.so?/tmp/
$?g++?-o?demo?-L/tmp?-ltmp?main.cpp
$?./demo
./demo:?error?while?loading?shared?libraries:?libtmp.so:?cannot?open?shared?object?file:?No?such?file?or?directory
$?ldd?demo
linux-vdso.so.1?=>??(0x00007fff7fdc1000)
????????libtmp.so?=>?not?found
這個錯誤是最常見的錯誤了。運行程序的時候找不到依賴的so。一般人使用方法是修改這個環境變量
?export?LD_LIBRARY_PATH=/tmp
$?./demo
test
這樣就OK了, 不過這樣 只對當前shell有效,當另開一個shell時候,又要重新設置。可以把 =/tmp 語句寫到~/.中,這樣就對當前用戶有效了,寫到/etc/中就對所有用戶有效了。
前面鏈接時候使用-L/tmp/ -ltmp 是一種設置相對路徑方法,還有一種絕對路徑鏈接方法。
$?g++?-o?demo??/tmp/libtmp.so?main.cpp
$?./demo
??test
$?ldd?demo
????????linux-vdso.so.1?=>??(0x00007fff083ff000)
????????/tmp/libtmp.so?(0x00007f53ed30f000)?
絕對路徑雖然申請設置環境變量步驟,但是缺陷也是致命的,這個so必須放在絕對路徑下,不能放到其他地方,這樣給部署帶來很大麻煩。所以應該禁止使用絕對路徑鏈接so。
搜索路徑分兩種,一種是鏈接時候的搜索路徑,一種是運行時期的搜索路徑。像前面提到的-L/tmp/是屬于鏈接時期的搜索路徑,即給ld程序提供的編譯鏈接時候尋找動態庫路徑;而則既屬于鏈接期搜索路徑,又屬于運行時期的搜索路徑。
這里需要介紹鏈-rpath鏈接選項,它是指定運行時候都使用的搜索路徑。聰明的同學馬上就想到,運行時搜索路徑,那它記錄在哪兒呢。也像. 那樣,每部署一臺機器就需要配一下嗎。呵呵,不需要..,因為它已經被硬編碼到可執行文件內部了。看看下面演示
$?g++?-o?demo?-L?/tmp/?-ltmp?main.cpp
$?./demo
./demo:?error?while?loading?shared?libraries:?libtmp.so:?cannot?open?shared?object?file:?No?such?file?or?directory
$?g++?-o?demo?-Wl,-rpath?/tmp/?-L/tmp/?-ltmp?main.cpp
$?./demo
test
$?readelf?-d?demo
?Dynamic?section?at?offset?0xc58?contains?26?entries:
????Tag????????Type?????????????????????????Name/Value
???0x0000000000000001?(NEEDED)?????????????Shared?library:?[libtmp.so]
???0x0000000000000001?(NEEDED)?????????????Shared?library:?[libstdc++.so.6]
???0x0000000000000001?(NEEDED)?????????????Shared?library:?[libm.so.6]
???0x0000000000000001?(NEEDED)?????????????Shared?library:?[libgcc_s.so.1]
???0x0000000000000001?(NEEDED)?????????????Shared?library:?[libc.so.6]
???0x000000000000000f?(RPATH)??????????????Library?rpath:?[/tmp/]
???0x000000000000001d?(RUNPATH)????????????Library?runpath:?[/tmp/]
看看是吧,編譯到elf文件內部了,路徑和程序深深的耦合到一起