關(guān)于 Linux 共享內(nèi)存,寫得最好的應(yīng)該是宋寶華的 《世上最好的共享內(nèi)存》一文。
本文可以說是對這篇文章的學(xué)習(xí)筆記,順手練習(xí)了一下 rust libc —— shichaoyuan/learn_rust/linux-shmipc-demo
按照宋寶華的總結(jié),當(dāng)前有四種主流的共享內(nèi)存方式:
- 基于傳統(tǒng) SYS V 的共享內(nèi)存;
- 基于 POSIX mmap 文件映射實(shí)現(xiàn)共享內(nèi)存;
- 通過 memfd_create() 和 fd 跨進(jìn)程共享實(shí)現(xiàn)共享內(nèi)存;
- 多媒體、圖形領(lǐng)域廣泛使用的基于 dma-buf 的共享內(nèi)存。
前兩種方式比較符合傳統(tǒng)的用法,共享內(nèi)存做為進(jìn)程間通信的媒介。
第三種方式更像是通過傳遞內(nèi)存“句柄”進(jìn)行數(shù)據(jù)傳輸。
第四種方式是為設(shè)備間傳遞數(shù)據(jù)設(shè)計(jì),避免內(nèi)存拷貝,直接傳遞內(nèi)存“句柄”。
這里嘗試了一下第二種和第三種方式。
1. POSIX mmap
這套 API 應(yīng)該是最普遍的 —— shm_open + mmap,本質(zhì)上來說 Aeron 也是用的這種方式(關(guān)于 Aeron 可以參考我之前的文章)。
看一下 glibc 中 shm_open 函數(shù)的實(shí)現(xiàn)就一清二楚了:

shm_open 函數(shù)就是在 /dev/shm 目錄下建文件,該目錄掛載為 tmpfs,至于 tmpfs 可以簡單理解為存儲(chǔ)介質(zhì)是內(nèi)存的一種文件系統(tǒng),更準(zhǔn)確的理解可以參考官方文檔 tmpfs.txt。
然后通過 mmap 函數(shù)將 tmpfs 文件映射到用戶空間就可以隨意操作了。

優(yōu)點(diǎn):
這種方式最大的優(yōu)勢在于共享的內(nèi)存是有“實(shí)體”(也就是 tmpfs 中的文件)的,所以多個(gè)進(jìn)程可以很容易通過文件名這個(gè)信息構(gòu)建共享內(nèi)存結(jié)構(gòu),特別適合把共享內(nèi)存做為通信媒介的場景(例如 Aeron)。
缺點(diǎn):
如果非要找一個(gè)缺點(diǎn)的話,可能是,文件本身獨(dú)立于進(jìn)程的生命周期,在使用完畢后需要注意刪除文件(僅僅 close 是不行的),否則會(huì)一直占用內(nèi)存資源。
2. memfd_create
memfd_create 函數(shù)的作用是創(chuàng)建一個(gè)匿名的文件,返回對應(yīng)的 fd,這個(gè)文件當(dāng)然不普通,它存活在內(nèi)存中。更準(zhǔn)確的理解可以參考官方文檔 memfd_create(2)。
直觀理解,memfd_create 與 shm_open 的作用是一樣的,都是創(chuàng)建共享內(nèi)存實(shí)體,只是 memfd_create 創(chuàng)建的實(shí)體是匿名的,這就帶了一個(gè)問題:如何讓其它進(jìn)程獲取到匿名的實(shí)體?shm_open 方式有具體的文件名,所以可以通過打開文件的方式獲取,那么對于匿名的文件怎么處理呢?
答案是:通過 Unix Domain Socket 傳遞 fd。

rust 的 UDS 實(shí)現(xiàn):
rust 在 std 中已經(jīng)提供了 UDS 的實(shí)現(xiàn),但是關(guān)于傳遞 fd 的 send_vectored_with_ancillary 函數(shù)還屬于 nightly-only experimental API 階段。所以這里使用了一個(gè)三方 crate —— sendfd,坦白說可以自己實(shí)現(xiàn)一下,使用 libc 構(gòu)建好 SCM_RIGHTS 數(shù)據(jù),sendmsg 出去即可,不過細(xì)節(jié)還是挺多,我這里就放棄了。
這套 API 設(shè)計(jì)更靈活,直接拓展了我的思路,本來還是受限于 Aeron 的用法,如果在這套 API 的加持下,是否可以通過傳遞數(shù)據(jù)包內(nèi)存塊(fd)真正實(shí)現(xiàn)零拷貝呢?
優(yōu)點(diǎn):
靈活。
缺點(diǎn):
無