汪敏


摘要:Linux操作系統廣泛運用在服務器運維、嵌入式軟件設計和移動端應用開發中,而對于Linux進程間通信機制的理解和研究又決定了軟件之間數據通信的效率。Linux有多種方式實現進程之間的通信,包括管道、消息隊列、信號量、socket、內存共享等,其中內存共享是效率最高的一種方式,實現了不同進程對同一塊物理內存的訪問,不需要陷入內核態中進行內核空間和用戶空間的數據拷貝,大大提高了通信效率。文章介紹了在Linux操作系統中內存共享的4種不同實現方式,同時介紹了不同實現方式在原理和應用場景上的區別。
關鍵詞:Linux操作系統:內存共享:進程間通信:dmabuf框架
中圖分類號:TP31
文獻標志碼:A
0 引言
Linux是一種開源的操作系統,可以自由裁剪系統功能、性能高效安全,具有強大的技術社區的運維,在嵌入式軟件設計、服務器運維、移動端應用開發等場景中有著廣泛的運用。內存共享作為一種高效的進程間通信機制[1-5],決定著Linux應用開發的效率。目前,內存共享機制主要有4種,它們在技術實現和應用場景上面有很多的不同,特別是Linux3.3版本中加入的DMABUF框架不僅解決了進程之間大量數據的通信問題,還支持多設備之間的數據高效共享,從而避免了內存拷貝問題的存在,在圖形開發、多媒體領域有著越來越多的運用。
1 內存共享簡介
內存共享機制是將不同進程的虛擬地址空間映射到同一片物理內存上,多個進程可以共享同一片物理內存。Linux操作系統存在多種進程間通信的方式,包括管道、消息隊列、信號等。不同于管道、消息隊列需要在內核空間和用戶空間進行多次數據拷貝,內存共享僅需要兩次數據拷貝,大大提高了進程之間通信的效率,適合通信數據量比較大的場景。
在Linux操作系統中,提供了內存共享機制的多種實現方式。存在內存共享機制多種實現方式的原因是多方面的,既有多種歷史版本不同實現方式的原因,又有針對多媒體、圖形領域等特定場景不同實現方式的原因。內存共享作為最高效的進程間通信和計算機硬件組件間數據通信的一種方式,值得研究人員深入研究。目前,Linux操作系統中主要存在4種內存共享的實現方式,有System V內存共享、POSIX內存共享、通過memfd_create()實現的內存共享和基于DMABUF框架實現的內存共享。下面分別介紹這4種實現方式的區別以及具體的應用場景。
2 Linux的內存管理
Linux操作系統采用的是虛擬內存管理技術,該技術使Linux中進程的所有操作都是基于虛擬地址展開的。只有在需要訪問內存資源的時候,才會進行虛擬地址和物理地址的映射,通過slab分配器和伙伴系統申請實際的物理內存。虛擬內存管理技術有很多好處,首先,擴展了Linux中進程的地址空間,每個進程都有了4G的虛擬地址空間:其次,進程不直接訪問物理內存,提高了操作系統的安全性和穩定性;最后,每個進程擁有了獨立的地址空間,易于用戶程序的開發。Linux中進程在執行的時候,先加載部分程序,將剩余部分留在外部磁盤中;當執行過程中需要的部分在外部磁盤的時候,發生中斷:內核將需要的部分加載到內存中。對Linux內存管理機制的深入理解有助于編寫更加高效的內存共享程序,特別是能夠理解mmap()函數的工作方式,對于文件或者其他對象映射進入調用進程的虛擬地址空間將會有更加透徹的認識。
3 System V內存共享
System V內存共享可以設置多個進程共享物理內存的同一塊區域,這一共享的內存區域將成為進程用戶空間的一個部分。“寫進程”將數據復制到共享內存區域,“讀進程”將從共享內存區域中讀出數據。不同于消息隊列,在消息隊列中要求“寫進程”將數據從用戶空間的緩沖區復制到內核空間中,也要求“讀進程”將數據從內核空間復制到用戶空間的緩沖區中。也不同于管道,“寫進程”的寫入數據緩存在內核中,“讀進程”從內核緩存中讀取數據,同時數據交互的過程遵循先進先出的原則。System V內存共享機制數據拷貝不經過內核空間,意味著更高的通信效率。由于存在多個進程對同一片內存區域數據的讀寫,在實際使用中通常使用信號量來完成System V內存共享機制的數據同步過程。
System V內存共享機制的一般過程如下:首先,調用shmget()創建新的共享內存段,返回共享內存標識符;然后,使用shmat()附著共享內存段,將該內存段成為調用進程的虛擬內存的一部分,shmat()返回的addr值可以供程序使用.addr值是該共享內存段起始點的指針,共享內存段使用完畢后,調用shmdt()分離共享內存,調用該函數后,該共享內存段將無法被繼續引用;最后,調用shmctl()刪除共享內存段。如圖1所示,用free和ipcs命令查看當前內存運行情況和進程間通信的狀態。
4 POSIX內存共享
Svstem V內存共享是基于標識符實現的,不同于通用的Linux l/0模型使用文件描述符的方式,這意味著Svstem V內存共享需要一整套單獨的系統調用,導致了System V內存共享在易用性方面的劣勢,正因為這樣.POSIX.1b設置了新的內存共享方式。Linux在實現POSIX內存共享機制時,使用文件系統來標識共享內存對象,將共享對象創建為/dev/shm目錄下面的一個文件,/dev/shm目錄下使用tmpfs專用文件系統,該文件系統下的內存對象具有持久性,共享內存對象將一直存活,直到系統關閉之后才會丟失。
mmap()函數經常將文件映射為進程虛擬地址空間的一部分[6-10].完成映射之后,可以通過操作內存地址完成文件內容的修改。如果操作的內容不在內存頁上面,則會從相應的外部磁盤中來加載該頁完成文件的最終修改過程。mmap()函數經常被使用在POSIX內存共享機制中.POSIX內存共享機制一般過程如下:先調用shm_open()創建且打開一個命名的共享內存對象,shm_open()函數將返回一個引用共享內存對象的文件描述符,共享內存對象初始長度為0。調用mmap()函數傳人文件描述符參數,同時指定flags為MAP—SHARED.mmap()函數將該內存對象映射為進程的虛擬地址空間。可以使用既有的Linux系統調用(例如fchmod(),fstat())進行共享內存對象的操作和查看。在使用完共享內存對象后,可以使用shm_unlink()函數刪除共享內存對象。刪除單個的共享內存對象不會影響現有的共享內存對象的映射關系,但是后續shm_open()打開這個共享內存對象會失敗。當所有的進程都刪除共享對象后,對象將最終被刪除。
Svstem V和POSIX內存共享機制在使用上有著很多差異。首先是在持久化上的區別,POSIX內存共享對象與文件相映射意味著共享內存中的數據能夠持久化地保存:其次是在標識內存共享對象上的差異,由于使用了獨立的標識符模型.Svstem V需要單獨的系統調用實現內存共享機制,而POSIX內存共享機制可以使用標準的Linux I/O模型實現內存共享操作;再次是共享內存大小的創建時間,System V在使用shmget()的時候就確定了共享內存的大小,而POSIX內存共享可以通過ftruncate()獨立調整對象的大小,然后重新建立新的映射,在靈活性方面.POSIX內存共享機制要更加高效:最后是支持程度方面,由于發展歷史的原因.System V的支持廠商更多一些,但是目前來看.Linux通用的發行版本都已經支持POSIX內存共享機制。從以上System V和POSIX內存共享機制的區別可以看出,大部分情況下.POSIX內存共享機制應該作為使用進程間通信機制的首選。
5 通過memfd_create()實現的內存共享
在進行進程間通信的編程過程中,經常會出現這樣的場景,就是A進程想要訪問B進程的文件描述符,要訪問B進程文件描述符的原因是因為該文件描述符指向需要訪問的內存,這樣A進程就可以通過B進程的文件描述符訪問B進程的內存。下一步就是需要完成文件描述在A、B進程之間的共享,文件描述符是進程級別的概念,不同的進程有不同的文件描述符列表。文件描述符不能作為一個變量直接傳輸給A進程,原因是open()函數返回的數字僅代表了在B進程中文件描述符的引用,即使是傳輸給A進程,A進程也不能通過該數字訪問B進程的內存。這時候就需要通過cmsg在socket上傳輸控制信息,完成文件描述的傳遞過程。
memfd_create()函數主要的功能是創建一個匿名文件,然后返回這個匿名文件的文件描述符,這樣就可以通過這個函數直接返回一個匿名內存文件的文件描述符。同時使用SCM_RIGHTS,進程就可以透過socket將文件描述符傳遞給另外一個進程,進而可以實現以fd為中間媒介,溝通兩個不相關的進程共享同一片內存區域。
通過memfd_creace()函數實現進程之間的內存共享,最大的好處是這種編程模型比較清晰、簡單和通用。進程之間共享內存的大小、數量、目的地都簡單可控,只要使用socket來傳遞需要的文件描述符即可。例如在網絡視頻播放領域,可以將收到的視頻畫面多創建幾個緩存,然后單獨將緩存的fd遞交給另外的進程進行解碼,即可實現視頻畫面高效的處理。
6 基于DMABUF框架的內存共享
DMABUF框架主要解決的是多個不同設備之間的緩存共享問題,而緩存共享問題的本質是避免內存拷貝。作為一個通用的框架,在2012年2月匯入Linux3.3版本,解決了長期困擾Linux驅動開發人員的“緩存共享”問題。
DMABUF框架的一般應用如圖2所示,緩存使用者1通過訪問緩存分配者獲得緩存的文件描述符,獲取文件描述符之后,通過socket將文件描述符發送給緩存使用者2。緩存使用者2在收到文件描述符之后,將文件描述符信息導入緩存使用者3(內核空間)中,此時緩存使用者3就獲得了緩存的共享訪問能力。
通過DMABUF框架實現緩存共享有很多好處,例如,在網絡流媒體場景中,要實現內存零拷貝,需要先將幀緩存里面的內容通過多媒體組件來轉變為視頻碼流,在轉變為視頻碼流之后,再廣播到網絡上。這一系列的過程中,需要避免出現內存拷貝的過程,這樣才能夠保障流媒體傳輸的效率。在類似的場景中.DMABUF框架有著很多的運用,特別是多進程、多組件之間協同處理數據的時候,更加需要考慮數據處理的效率問題。
從設計思想上來看,內核一般只提供機制而不提供具體的策略,具體的策略應該由用戶空間來構建,上面DMABUF框架的一般使用流程正是遵循的這樣的原則。基于DMABUF框架的內存共享方式以文件描述符為句柄,實現了設備間、進程間的緩存共享訪問,從而避免了內存拷貝問題。
7 內存共享的應用場景
7.1 生產者和消費者內存共享場景
生產者、消費者模型作為經典的模型,經常被用在各種進程間通信的案例中來介紹,這里主要介紹的是大數據量的生產者和消費者模型的構建過程。生產者主要負責數據的產出,消費者主要負責數據的處理過程。在大數據量的生產者、消費者模型中使用共享內存有很多好處。首先,具有內存共享的生產者消費者模型支持數據的異步傳輸。在使用共享內存前,生產者只能一直等待消費者處理完成數據之后,才能生產數據;在使用了內存共享機制后,生產者可以將消費者來不及處理的數據放置到共享內存中,實現了生產者和消費者異步處理數據的過程。其次,內存共享支持了生產者和消費者之間的交互解耦。生產者和消費者之間不需要直接交互,避免了依賴關系的生成。最后,支持數據處理流量控制的實現。當出現消費者處理數據過快或者過慢的時候,可以增加或者減少生產者線程同時配合信號量,完成消費者和生產者之間數據的處理節奏。
7.2 無關進程間內存共享場景
無關進程之間經常需要進行只讀文件的共享過程,例如配置文件、圖像幀緩存等只讀數據。例如,有一個500 k大小的配置文件,本地機器有80個進程需要進行配置文件的讀取過程,那么一共就需要分配4M的內存空間進行數據的讀取過程,這對于計算機來說,是巨大的內存資源的浪費。如果使用了內存共享技術,就可以由一個進程完成在共享內存中加載配置文件的過程,同時將這份共享的配置文件同步給其他進程,就實現了多個進程使用同一份只讀配置文件的過程。無關進程間的內存共享更多的應用在多媒體領域中,多媒體領域經常會存在多個畫面的緩存。為了實現內存的零拷貝,會更多地使用內存共享技術。
7.3 父子進程間內存共享場景
管道是父子進程間經常使用的一種通信方式,但是使用管道作為父子進程間的通信方式有兩方面的弊端。第一,通信的數據量比較小,管道經常作為shell中兩個命令間數據溝通快捷的方式而存在,同時交互的數據量比較小,不像共享內存那樣可以按照需求來分配通信數據量的大小;第二,管道寫入的數據都是緩存在內核中,嚴格遵守先進先出的原則,需要在內核空間和用戶空間來回切換,才能完成數據的拷貝過程,因此通信的效率不高。基于這兩個原因,在對通信數據量大小有要求的情況下,建議使用共享內存的方式來完成父子進程間的數據通信過程。
8 結語
本文主要討論了System V內存共享、POSIX內存共享、基于memfd_create()內存共享和基于DMABUF框架內存共享4種進程間通信方式的優缺點以及應用場景,前3種更加強調進程之間的內存共享功能,而基于DMABUF框架則更加突出設備間的緩存共享功能,雖然基于DMABUF框架也支持多進程之間的內存共享。目前廣泛應用于圖形編程領域的內存共享方式,已經從進程虛擬地址空間映射方式轉變為以文件描述符為句柄共享內存的方式,這種趨勢的轉變,也讓decoder.GPU等可以跨越不同的進程來訪問內存。
參考文獻
[1]黃茹.淺析Linux環境下的進程間通信機制[J].科技信息,2014( 14):96-97.
[2]姚珉.理解Linux環境下SYSTEM V進程間通信[J].電腦開發與應用,2005( 10):50-52.
[3]陳浩.Linux下用戶態和內核態內存共享的實現[J].電腦編程技巧與維護,2011(4):25-27.
[4]張華,孫傳偉,李靖誼.基于Linux的同步共享內存的研究與實現[J].湖南工業職業技術學院學報,2004(4):19-21.39.
[5]劉斌,朱程榮.Linux內核與用戶空間通信機制研究[J].電腦知識與技術,2012( 16):3816-3817.3849.
[6]楊宇音,李志淮.Linux中用戶空間與內核空間的通信實現[J].微機發展,2005(5):75-76,130.
[7]李妍.消息隊列在Linux線程或進程間通信中的應用[J].電子世界,2019 (17):146-147.
[8]許豪,陳可.Linux下進程間通信機制的探討[J].科技與創新,2016(3):83.
[9]黃向平,彭明田,楊永凱.基于內存映射文件的高性能庫存緩存系統[J].電子技術應用,2020(7):113-117. 126.
[10]黃向平,彭明田,楊永凱.基于內存映射文件的復雜對象快速讀取方法[J].計算機技術與發展,2020(3):82-87.
(編輯沈強)