>>轉載請注明來源:
飄零的代碼 piao2010 ’s blog
,謝謝!^_^
>>本文鏈接地址:
Linux共享庫(so)動態加載和升級
學習Linux共享庫動態加載緣于一個生產環境升級apache so文件常見錯誤操作:apache在運行中直接cp覆蓋目標so文件,一段時間后錯誤日志里面出現關鍵詞:
Segmentation fault (段錯誤)
,一個個worker進程就這樣漸漸退出,最后無法處理HTTP請求。
首先了解一下共享庫的創建,
源文件test.c
#include<stdio.h> #include<unistd.h> ? void test1 ( void ) { printf ( "This is do test1 \n " ) ; sleep ( 10 ) ; printf ( "End of test1 \n " ) ; } ? void test2 ( void ) { printf ( "This is do test2 \n " ) ; sleep ( 10 ) ; printf ( "End of test2 \n " ) ; } |
執行gcc -fPIC -shared -o libtest.so test.c 會生成共享庫文件 libtest.so
參數含義:
-fPIC/-fpic: Compiler directive to output position independent code, a characteristic required by shared libraries. 創建共享庫必須的參數
-shared: Produce a shared object which can then be linked with other objects to form an executable.
然后使用共享庫:源文件main2.c
#include <stdio.h> ? int main ( ) { test1 ( ) ; test2 ( ) ; return 0 ; } |
動態庫鏈接:gcc -o main2 -L . -ltest main2.c 生成二進制程序main2
參數含義:
-L 指定動態庫目錄為當前目錄
-l 指定動態庫名test,不要寫libtest.so
執行main2程序發現報錯:
error while loading shared libraries: libtest.so: cannot open shared object file: No such file or directory
原因是共享庫不在系統默認的路徑里面,可以在shell執行
export LD_LIBRARY_PATH=./
添加當前路徑或者在
/etc/ld.so.conf 增加路徑并ldconfig生效
。
執行main2成功輸出:
This is do test1
End of test1
This is do test2
End of test2
接下來是主角:動態加載,源文件main.c
#include <stdio.h> #include <dlfcn.h> /* 必須加這個頭文件 */ ? int main ( ) { void * lib_handle ; void ( * fn1 ) ( void ) ; void ( * fn2 ) ( void ) ; char * error ; ? lib_handle = dlopen ( "libtest.so" , RTLD_LAZY ) ; if ( ! lib_handle ) { fprintf ( stderr , "%s \n " , dlerror ( ) ) ; return 1 ; } ? fn1 = dlsym ( lib_handle , "test1" ) ; if ( ( error = dlerror ( ) ) != NULL ) { fprintf ( stderr , "%s \n " , error ) ; return 1 ; } ? fn1 ( ) ; ? fn2 = dlsym ( lib_handle , "test2" ) ; if ( ( error = dlerror ( ) ) != NULL ) { fprintf ( stderr , "%s \n " , error ) ; return 1 ; } ? fn2 ( ) ; ? dlclose ( lib_handle ) ; ? return 0 ; } |
接口函數介紹:
(1) dlopen
函數原型:void *dlopen(const char *libname,int flag);
功能描述:dlopen必須在dlerror,dlsym和dlclose之前調用,表示要將庫裝載到內存,準備使用。
如果要裝載的庫依賴于其它庫,必須首先裝載依賴庫。如果dlopen操作失敗,返回NULL值;如果庫已經被裝載過,則dlopen會返回同樣的句柄。
參數中的libname一般是庫的全路徑,這樣dlopen會直接裝載該文件;如果只是指定了庫名稱,在dlopen會按照下面的機制去搜尋:
a.根據環境變量LD_LIBRARY_PATH查找
b.根據/etc/ld.so.cache查找
c.查找依次在/lib和/usr/lib目錄查找。
flag參數表示處理未定義函數的方式,可以使用RTLD_LAZY或RTLD_NOW。RTLD_LAZY表示暫時不去處理未定義函數,先把庫裝載到內 存,等用到沒定義的函數再說;RTLD_NOW表示馬上檢查是否存在未定義的函數,若存在,則dlopen以失敗告終。
(2) dlerror
函數原型:char *dlerror(void);
功能描述:dlerror可以獲得最近一次dlopen,dlsym或dlclose操作的錯誤信息,返回NULL表示無錯誤。dlerror在返回錯誤信息的同時,也會清除錯誤信息。
(3) dlsym
函數原型:void *dlsym(void *handle,const char *symbol);
功能描述:在dlopen之后,庫被裝載到內存。dlsym可以獲得指定函數(symbol)在內存中的位置(指針)。
如果找不到指定函數,則dlsym會返回NULL值。但判斷函數是否存在最好的方法是使用dlerror函數,
(4) dlclose
函數原型:int dlclose(void *);
功能描述:將已經裝載的庫句柄減一,如果句柄減至零,則該庫會被卸載。如果存在析構函數,則在dlclose之后,析構函數會被調用。
編譯gcc -o main main.c -ldl 生成二進制程序main,執行輸出
This is do test1
End of test1
This is do test2
End of test2
到這里共享庫動態加載就介紹完了:)
最后模擬一下升級so故障:
在執行main的時候,趁sleep期間cp 另外的so文件覆蓋libtest.so,一會就出現Segmentation fault。
但是如果是mv 另外的so文件覆蓋libtest.so,則無此問題,或者先rm libtest.so 再cp/mv 也不會有問題,因此升級方法就是這兩種,當然最好是先停應用再升級。
至于原因,可以參考我前一篇博客
《Linux cp mv rm ln 命令對于 inode 和 dentry 的影響》
。
12.5更新:
今天咨詢了維揚同學,可以用strace觀察程序運行期間的系統調用,發現有不少mmap操作:
省略前面 open ( "/root/so/libtest.so" , O_RDONLY ) = 3 read ( 3 , " \177 ELF \1 \1 \1 \3 \0 \0 \0 \0 \0 \0 \0 \0 \3 \0 \3 \0 \1 \0 \0 \0 \240 \3 \0 \000 4 \0 \0 \0 " ... , 512 ) = 512 brk ( 0 ) = 0x8227000 brk ( 0x8248000 ) = 0x8248000 fstat64 ( 3 , { st_dev = makedev ( 253 , 0 ) , st_ino = 17559 , st_mode = S_IFREG | 0755 , st_nlink = 1 , st_uid = 0 , st_gid = 0 , st_blksize = 4096 , st_blocks = 16 , st_size = 4348 , st_atime = 2012 / 05 / 13 - 14 : 13 : 18 , st_mtime = 2012 / 05 / 13 - 14 : 13 : 01 , st_ctime = 2012 / 05 / 13 - 14 : 13 : 01 } ) = 0 mmap2 ( NULL , 5772 , PROT_READ | PROT_EXEC , MAP_PRIVATE | MAP_DENYWRITE , 3 , 0 ) = 0x6e6000 mmap2 ( 0x6e7000 , 4096 , PROT_READ | PROT_WRITE , MAP_PRIVATE | MAP_FIXED | MAP_DENYWRITE , 3 , 0 ) = 0x6e7000 close ( 3 ) = 0 munmap ( 0xb7753000 , 15020 ) = 0 fstat64 ( 1 , { st_dev = makedev ( 0 , 11 ) , st_ino = 3 , st_mode = S_IFCHR | 0620 , st_nlink = 1 , st_uid = 0 , st_gid = 5 , st_blksize = 1024 , st_blocks = 0 , st_rdev = makedev ( 136 , 0 ) , st_ atime = 2012 / 05 / 13 - 14 : 56 : 03 , st_mtime = 2012 / 05 / 13 - 14 : 56 : 03 , st_ctime = 2012 / 05 / 13 - 14 : 53 : 31 } ) = 0 mmap2 ( NULL , 4096 , PROT_READ | PROT_WRITE , MAP_PRIVATE | MAP_ANONYMOUS , - 1 , 0 ) = 0xb7756000 write ( 1 , "This is do test1 \n " , 17 ) = 17 rt_sigprocmask ( SIG_BLOCK , [ CHLD ] , [ ] , 8 ) = 0 rt_sigaction ( SIGCHLD , NULL , { SIG_DFL , [ ] , 0 } , 8 ) = 0 rt_sigprocmask ( SIG_SETMASK , [ ] , NULL , 8 ) = 0 nanosleep ( { 10 , 0 } , 0xbfd63fe4 ) = 0 write ( 1 , "End of test1 \n " , 13 ) = 13 write ( 1 , "This is do test2 \n " , 17 ) = 17 rt_sigprocmask ( SIG_BLOCK , [ CHLD ] , [ ] , 8 ) = 0 rt_sigaction ( SIGCHLD , NULL , { SIG_DFL , [ ] , 0 } , 8 ) = 0 rt_sigprocmask ( SIG_SETMASK , [ ] , NULL , 8 ) = 0 nanosleep ( { 10 , 0 } , 0xbfd63fe4 ) = 0 --- SIGSEGV ( Segmentation fault ) @ 0 ( 0 ) --- +++ killed by SIGSEGV +++ |
SIGSEGV信號估計和mmap只讀映射之后寫入(覆蓋)文件有關?
詳見續篇
《為何cp覆蓋進程的動態庫(so)會導致coredump》
。
參考資料:
http://www.yolinux.com/TUTORIALS/LibraryArchives-StaticAndDynamic.html
http://hi.baidu.com/luoxsbupt/item/a9d346b7653a2771254b09bc
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
