Linux環境下,在不停止程序的情況下,更換動態鏈接庫

設計軟件架構時,爲了以後升級考慮和層次間的解耦,會將一些功能封裝到動態鏈接庫中。當需要對方法升級時,只要保證動態鏈接庫的API定義不變,就可以對局部功能進行升級。

   但是由於有些服務要求在更新時也不能停止服務,例如線上查詢功能等,如果此時爲了更新動態鏈接庫而暫停服務,會影響用戶的使用。下面的文章介紹了一種在Linux環境下,在不停止程序的情況下,更換動態鏈接庫的方法。

在替換so文件時,如果在不停程序的情況下,直接用 cp new.so old.so 的方式替換程序使用的動態庫文件會導致正在運行中的程序崩潰。解決的辦法是採用“rm+cp” 或“mv+cp” 來替代直接“cp” 的操作方法。linux系統的動態庫有兩種使用方法:運行時動態鏈接庫,動態加載庫並在程序控制之下使用。

1、爲什麼在不停程序的情況下,直接用 cp 命令替換程序使用的 so 文件,會使程序崩潰? 
很多同學在工作中遇到過這樣一個問題,在替換 so 文件時,如果在不停程序的情況下,直接用cp new.so old.so的方式替換程序使用的動態庫文件會導致正在運行中的程序崩潰,退出。這與 cp 命令的實現有關,cp 並不改變目標文件的 inode,cp 的目標文件會繼承被覆蓋文件的屬性而非源文件。實際上它是這樣實現的:

strace cp libnew.so libold.so 2>&1 |grep open.lib..so

open("libnew.so", O_RDONLY|O_LARGEFILE) = 3

open("libold.so", O_WRONLY|O_TRUNC|O_LARGEFILE) = 4

在 cp 使用“O_WRONLY|O_TRUNC” 打開目標文件時,原 so 文件的鏡像被意外的破壞了。這樣動態鏈接器 ld.so 不能訪問到 so 文件中的函數入口。從而導致 Segmentation fault,程序崩潰。ld.so 加載 so 文件及“再定位”的機制比較複雜,詳情可參見參考文獻2。

2、怎樣在不停止程序的情況下替換so文件,並且保證程序不會崩潰?

答案是採用“rm+cp” 或“mv+cp” 來替代直接“cp” 的操作方法。

在用新的so文件 libnew.so 替換舊的so文件 libold.so 時,如果採用如下方法:

rm libold.so

cp libnew.so libold.so

採用這種方法,目標文件 libold.so 的 inode 其實已經改變了,原來的 libold.so 文件雖然不能用 ”ls”查看到,但其 inode 並沒有被真正刪除,直到內核釋放對它的引用。同理,mv只是改變了文件名,其 inode 不變,新文件使用了新的 inode。這樣動態鏈接器 ld.so 仍然使用原來文件的 inode 訪問舊的 so 文件。因而程序依然能正常運行。 
到這裏,我們回想在上線操作中在替換可執行程序時,爲什麼直接使用“cp new old”這樣的命令時,系統會禁止這樣的操作,並且給出這樣的提示“cp: cannot create regular file `old': Text file busy”。這時,我們採用的辦法仍然是用“rm+cp”或者“mv+cp”來替代直接“cp”,這跟以上提到的so文件的替換有同樣的道理。 
但是,爲什麼系統會阻止 cp 覆蓋可執行程序,而不阻止覆蓋 so 文件呢?這是因爲 Linux 有個 Demand Paging 機制,所謂“Demand Paging”,簡單的說,就是系統爲了節約物理內存開銷,並不會程序運行時就將所有頁(page)都加載到內存中,而只有在系統有訪問需求時纔將其加載。 
“Demand Paging”要求正在運行中的程序鏡像(注意,並非文件本身)不被意外修改,因此內核在啓動程序後會鎖定這個程序鏡像的 inode。對於 so 文件,它是靠 ld.so 加載的,而ld.so畢竟也是用戶態程序,沒有權利去鎖定inode,也不應與內核的文件系統底層實現耦合。

+

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章