古之成大事者,不唯有超世之才,亦唯有堅韌不拔之志也!

            BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
            3 隨筆 :: 44 文章 :: 1 評論 :: 0 Trackbacks

          轉(zhuǎn):

          http://blog.csdn.net/zhangxinrun/article/details/5591246

          今天被朋友問及“Linux下可以替換運行中的程序么?”,以前依稀記得Linux下是可以的(而Windows就不讓),于是隨口答道“OK”。結(jié)果朋友發(fā)來一個執(zhí)行結(jié)果:(test正在運行中)

          # cp test2 test
          cp: cannot create regular file `test': Text file busy

          看起來是程序被占用,無法覆蓋。于是自己又再做了幾個實驗:

          (1)先rm刪除正在運行的test,然后cp test2 test就沒有錯誤了。
          (2)先mv改名正在運行的test,然后cp test2 test也沒有問題。

          查了查資料并動手分析了一下,找到了比較滿意的解釋。cp并不改變目標(biāo)文件的inode,事實上它的實現(xiàn)是這樣的:

          # strace cp test2 test  2>&1 | grep open.*test
          open("test2", O_RDONLY|O_LARGEFILE)     = 3
          open("test", O_WRONLY|O_TRUNC|O_LARGEFILE) = 4

          我原以為cp的實現(xiàn)是“rm + open(O_CREAT)”,不過現(xiàn)在想想上面的實現(xiàn)方式才是最可靠的(保證了時序安全和目標(biāo)文件的屬性)。這也可以解釋為什么cp的目標(biāo)文件會繼承被覆蓋文件的屬性而非源文件。

          Linux 由于Demand Paging機制的關(guān)系,必須確保正在運行中的程序鏡像(注意,并非文件本身)不被意外修改,因此內(nèi)核在啟動程序后會鎖定這個程序鏡像的inode。這就 是為什么cp在用“O_WRONLY|O_TRUNC”模式open目標(biāo)文件時會失敗。而先rm再cp的話,新文件的inode其實已經(jīng)改變了,原 inode并沒有被真正刪除,直到內(nèi)核釋放對它的引用。同理,mv只是改變了文件名,其inode不變,新文件使用了新的inode。

          問題到這里已經(jīng)水落石出,不過刨根究底的個性驅(qū)使我再做了以下一組實驗,沒想到結(jié)果完全出乎我意料之外!

          寫了一個簡單的測試程序:

          #include <stdio.h>

          int main(int argc, char * argv[])
          {
          foo();  // An export function by libtest.so.
          sleep(1000);
          return 0;
          }

          foo()是另一個測試動態(tài)庫libtest.so的導(dǎo)出接口,只打印一行提示就返回。接下來我把上面對執(zhí)行文件的測試用例對動態(tài)庫又做了一遍:

          (1)cp libtest2.so libtest.so可以直接覆蓋已加載的動態(tài)庫。
          (2)先rm刪除已加載的libtest.so,然后cp libtest2.so libtest.so成功。
          (3)先mv改名已加載的libtest.so,然后cp libtest2.so libtest.so成功。

          除了第一個用例外,結(jié)果相同。這樣看來,動態(tài)庫被加載時難道ld并沒有鎖定inode?不過想想也可以寬恕,畢竟ld也是用戶態(tài)程序,沒有權(quán)利去鎖定inode,也不應(yīng)與內(nèi)核的文件系統(tǒng)底層實現(xiàn)耦合。

          到這里都還算在情理之中,看起來Linux也都處理的很好。不過還剩下一個問題:動態(tài)庫被以cp的方式覆蓋后難道不會和Demand Paging機制產(chǎn)生沖突?

          在思考這個問題的過程中,我意識到前面這個測試程序的一個致命漏洞,稍作修改如下:

          #include <stdio.h>

          int main(int argc, char * argv[])
          {
          loop:
          foo();  // An export function by libtest.so.
          sleep(1);
          goto loop;
          return 0;
          }

          這 次,再執(zhí)行上面的三個用例后發(fā)現(xiàn),“cp libtest2.so libtest.so”雖然仍可直接覆蓋已加載的動態(tài)庫,但是測試程序馬上出現(xiàn)了“Segmentation fault”。而后兩個用例結(jié)果不變。由此可見,想要安全的替換已加載的動態(tài)庫,還是用“笨拙”的“rm + cp”吧,看似捷徑的“cp覆蓋”會直接葬送掉你的程序……

          看來,我再一次低估了Linux的健壯性,看似符合邏輯的流程也可能會帶來災(zāi) 難性的后果;“rm & cp”與“cp覆蓋”背后所隱藏的底層差異卻可以成為你的救星。Linux用得越久越是讓人覺得這是一塊充滿了荊棘和陷阱的原始叢林,只有步步為營實踏前 行才能走的更遠。

          注:以上實驗基于SuSE Linux Enterprise Server 9 SP1(Linux 2.6.5 & glibc 2.3.3)。


          posted on 2011-12-02 11:08 goto 閱讀(218) 評論(0)  編輯  收藏 所屬分類: LINUX & UNIX
          主站蜘蛛池模板: 周至县| 彩票| 兴安盟| 西峡县| 龙游县| 买车| 和顺县| 顺义区| 辉南县| 吴川市| 昌宁县| 呼图壁县| 武隆县| 城口县| 从江县| 沈阳市| 河津市| 积石山| 汽车| 都匀市| 远安县| 海盐县| 望江县| 河北省| 华阴市| 松阳县| 石家庄市| 水城县| 如皋市| 崇左市| 富裕县| 祁阳县| 巨鹿县| 湘潭县| 元江| 建德市| 肇庆市| 类乌齐县| 塔城市| 赤峰市| 枣庄市|