楊 倩,楊明趙
(1.重慶理工大學,重慶 400054;2.云從科技集團股份有限公司,重慶 401120)
Linux遵循傳統(tǒng)UNIX系統(tǒng)“一切皆文件”的理念,進程已打開的文件,稱為“文件描述符(File Descriptor,簡稱:FD)”,將文件描述符從一個進程傳遞到另外一個進程,對應的使用文件描述符所關聯(lián)資源的能力也就被傳遞到了新的進程。在軟件設計中,這種方式可以實現(xiàn)在一組進程中的不同進程對同一資源不同階段共享訪問,可以利用進程的相互隔離,帶來安全性、 穩(wěn)定性和健壯性等方面的好處, 文件描述符的進程間傳遞是Linux操作系統(tǒng)的一種重要的進程間能力傳遞手段,本文將對其底層原理和典型應用場景進行分析。
現(xiàn)代操作系統(tǒng)的進程一般工作在虛擬內(nèi)存地址空間通過MMU地址轉換映射到物理內(nèi)存,進程無法訪問其他進程的虛擬內(nèi)存,這樣就達到了相互隔離的安全目的。 進程之間的交互操作只能通過比如將不同進程的某一部分虛擬地址區(qū)間映射到相同的物理內(nèi)存塊,達到共享內(nèi)存的目的;也可以通過進程處于內(nèi)核特權模式上下文中,將數(shù)據(jù)從一個進程的內(nèi)存拷貝到另外一個進程的內(nèi)存,達到內(nèi)存間消息交互的目的。文件描述符通常通過整數(shù)表示,該整數(shù)代表在文件描述符表(File Descriptor Table)中的索引,進程每次打開文件,會從該表空閑項中分配一項。 文件描述符是進程操作該進程描述符所關聯(lián)資源的句柄,有了進程描述符也就具備了操作這些資源的能力(Capability)。如果能在進程之間傳遞文件描述符,也就能夠?qū)崿F(xiàn)能力的傳遞而不是直接傳輸數(shù)據(jù),這樣可以減少數(shù)據(jù)的拷貝,提高數(shù)據(jù)使用效率。但是,文件描述符表為各個進程私有,互相之間無法訪問。要實現(xiàn)進程之間文件描述符的傳遞,實際上是要實現(xiàn)該文件描述符所關聯(lián)資源在不同進程的內(nèi)存空間的重新映射。文件描述符的映射關系如圖1所示[1]。
圖1 文件描述符和進程的關系
文件描述符的傳遞目前在不同的Linux衍生系統(tǒng)上有不同的實現(xiàn),原生的Linux系統(tǒng)使用基于UNIX域套接字的sendmsg()和recvmsg()系統(tǒng)調(diào)用可以實現(xiàn)上述能力,從Linux內(nèi)核5.6版本開始, 引入了一種新的系統(tǒng)調(diào)用叫做pidfd_getfd(),使得進程間傳遞文件描述符更加安全可靠,并且使得開發(fā)相關應用更加簡潔方便;而安卓系統(tǒng),為了使其應用框架更加簡潔,讓不同的進程可以通過RPC的方式進行交互,在內(nèi)核層面添加了Binder IPC機制,Binder也支持文件描述符的傳遞。后面的章節(jié)將對這種系統(tǒng)能力的實際應用場景進行一些分析。
多數(shù)操作系統(tǒng)圖形子系統(tǒng)采用客戶端/服務端模型,負責操作顯存的進程作為服務端,稱為“顯示服務器”,其他具有圖像界面的應用進程作為客戶端。 所有客戶端將繪制完成自身圖像數(shù)據(jù)(比如GUI)傳輸?shù)椒斩?,由服務端對圖像進行轉換、裁剪、混合等操作后,將最終圖像送到顯存進行顯示[2]。
圖2 圖形系統(tǒng)客戶端/服務端模型
圖像數(shù)據(jù)具有數(shù)據(jù)量大的特點,采用共享內(nèi)存池實現(xiàn)數(shù)據(jù)零拷貝必不可少,然而,這里的服務端和眾多客戶端進程通常并沒有進程上的父子關系,進程之間的內(nèi)存共享只能通過文件描述符傳遞來實現(xiàn)。
以安卓及桌面Linux系統(tǒng)所采用的Wayland架構為例,客戶端在啟動階段會和服務端建立會話,會話通道不同的操作系統(tǒng)有所不同,比如安卓采用其特有的Binder IPC、 Wayland采用UNIX域套接字,所采用的這些通道底層都提供了文件描述符的傳遞能力,通過會話一端將內(nèi)存句柄以文件描述符的形式傳遞給對端,這樣就完成了共享內(nèi)存池的建立。 后續(xù)客戶端完成一幀操作后,通過會話通道喚醒服務端。服務端進行后續(xù)處理??梢钥吹剑^程是在共享的內(nèi)存中進行的,IPC機制只是起到處理雙方同步訪問機制的作用,保證一方開始讀的時候另外一方已經(jīng)寫入完成[3]。
一般操作系統(tǒng)需要對權限進行管理,系統(tǒng)資源需要特殊的權限才能進行訪問,如果這些特權進程發(fā)生錯誤或者用戶不受信任代碼運行于特權進程,將給不法攻擊者帶來可乘之機。安卓操作系統(tǒng)對這些應用使用系統(tǒng)資源進行了限制,采用了授權訪問機制,如果應用需要訪問這些設備,需要向系統(tǒng)申請對應的權限,系統(tǒng)通過對話框提示用戶同意后,才能進行進一步操作[4]。
這類需求一般實現(xiàn)方式是,通過系統(tǒng)服務作為代理,介于用戶進程和設備之間做訪問限制,帶來效率和實時性上的問題。所以,安卓在架構設計上使用了文件描述符傳遞機制,首先限制設備文件節(jié)點只能由特定系統(tǒng)服務訪問,確保了安全性。經(jīng)過用戶允許的程序,可以由系統(tǒng)服務將所涉及到的設備節(jié)點打開,然后將文件描述符通過Binder機制發(fā)送到用戶程序,從而用戶程序得到了訪問該設備的“能力”。在這種場景下,通過文件描述符的傳遞,用戶進程與設備之間的交互完全沒有第三方的參與,在不破壞設備訪問協(xié)議、不增加額外的數(shù)據(jù)拷貝開銷以及不增加數(shù)據(jù)延遲的情況下,很簡潔地實現(xiàn)了訪問權限限制和授權訪問[5]。
互聯(lián)網(wǎng)基礎設施需要保證互聯(lián)網(wǎng)請求都能及時得到響應。其中負載均衡器需要管理來自用戶的大量TCP連接和數(shù)據(jù)分發(fā),如果過程中需要升級程序或者更新配置,則需要重新啟動該服務,這種情況下,已經(jīng)建立的TCP連接需要被強制RESET,以及重啟過程中客戶端發(fā)起的SYN請求也會被拒絕。這樣短暫的中斷在一些關鍵的互聯(lián)網(wǎng)基礎設施是不被允許的,所以提出了“零停機運維”的概念。
“零停機運維”要求服務即使在不得不重啟的情況下,仍然保持連接活躍,不讓客戶端請求無響應或者響應錯誤。這種條件下,文件描述符進程間傳遞發(fā)揮了作用。知名開源負載均衡器Haproxy配置熱加載以及互聯(lián)網(wǎng)公司FaceBook提出的“Socket Takeover”均使用了這種技術。其核心原理是,服務新的實例先啟動,完成初始化,與即將關閉的舊實例建立連接,舊實例將所有活躍的TCP套接字文件描述符發(fā)送到新的實例,新的服務接管這些活躍的連接,然后舊服務實例退出,完成了整個重啟過程。過程中,活躍的連接狀態(tài)不會受到影響,客戶端的連接不會被中斷,從客戶端感覺不到服務端的重啟活動,即實現(xiàn)了“零停機”[6,7]。
本文章對文件描述符的跨進程傳遞原理進行了原理分析,并對已有的一些應用場景進行了描述。相信未來在其他的一些場景下,文件描述符的傳遞也會有更加廣泛的應用場景。