宋寶華:世上最好的共享內存(Linux共享內存最透徹的一篇)上集

2022-01-25 Linux閱碼場

共享單車、共享充電寶、共享雨傘,世間的共享有千萬種,而我獨愛共享內存。

早期的共享內存,著重於強調把同一片內存,map到多個進程的虛擬地址空間(在相應進程找到一個VMA區域),以便於CPU可以在各個進程訪問到這片內存。

現階段廣泛應用於多媒體、Graphics領域的共享內存方式,某種意義上不再強調映射到進程虛擬地址空間的概念(那無非是為了讓CPU訪問),而更強調以某種「句柄」的形式,讓大家知道某一片視頻、圖形圖像數據的存在並可以藉助此「句柄」來跨進程引用這片內存,讓視頻encoder、decoder、GPU等可以跨進程訪問內存。所以不同進程用的加速硬體其實是不同的,他們更在乎的是可以通過一個handle拿到這片內存,而不再特別在乎CPU訪問它的虛擬地址(當然仍然可以映射到進程的虛擬地址空間供CPU訪問)。

只要內存的拷貝(memcpy)仍然是一個佔據內存帶寬、CPU利用率的消耗大戶存在,共享內存作為Linux進程間通信、計算機系統裡各個不同硬體組件通信的最高效方法,都將持續繁榮。關於內存拷貝會大多程度地佔據CPU利用率,這個可以最簡單地嘗試拷貝1080P,幀率每秒60的電影畫面,我保證你的系統的CPU,蛋會疼地不行。

我早就想系統地寫一篇綜述Linux裡面各種共享內存方式的文章了,但是一直被帶娃這個事業牽絆,今日我決定頂著娃娃們的山呼海嘯,也要寫一篇文章不吐不快。

共享內存的方式有很多種,目前主流的方式仍然有:

1.基於傳統SYS V的共享內存;

2.基於POSIX mmap文件映射實現共享內存;

3.通過memfd_create()和fd跨進程共享實現共享內存;

4.多媒體、圖形領域廣泛使用的基於dma-buf的共享內存。

歷史悠久、年代久遠、API怪異,對應內核代碼linux/ipc/shm.c,當你編譯內核的時候不選擇CONFIG_SYSVIPC,則不再具備此能力。

你在Linux敲ipcs命令看到的share memory就是這種共享內存:

下面寫一個最簡單的程序來看共享內存的寫端sw.c:

以及共享內存的讀端sr.c:

編譯和準備運行:

在此之前我們看一下系統的free:

下面運行sw和sr:

我們發現sr列印出來的和sw寫進去的是一致的。這個時候我們再看下free:

可以看到used顯著增大了(711632 -> 715908), shared顯著地增大了(2264 -> 6360),而cached這一列也顯著地增大326604->330716。

我們都知道cached這一列統計的是file-backed的文件的page cache的大小。理論上,共享內存屬於匿名頁,但是由於這裡面有個非常特殊的tmpfs(/dev/shm指向/run/shm,/run/shm則mount為tmpfs):

所以可以看出tmpfs的東西其實真的是有點含混:我們可以理解它為file-backed的匿名頁(anonymous page),有點類似女聲中的周深。前面我們反覆強調,匿名頁是沒有文件背景的,這樣當進行內存交換的時候,是與swap分區交換。磁碟文件系統裡面的東西在內存的副本是file-backed的頁面,所以不存在與swap分區交換的問題。但是tmpfs裡面的東西,真的是在統計意義上統計到page cache了,但是它並沒有真實的磁碟背景,這又和你訪問磁碟文件系統裡面的文件產生的page cache有本質的區別。所以,它是真地有那麼一點misc的感覺,凡事都沒有絕對,唯有變化本身是不變的。

也可以通過ipcs找到新創建的SYS V共享內存:

我對POSIX shm_open()、mmap () API系列的共享內存的喜愛,遠遠超過SYS V 100倍。原諒我就是一個懶惰的人,我就是討厭ftok、shmget、shmat、shmdt這樣的API。

上面的程序如果用POSIX的寫法,可以簡化成寫端psw.c:

讀端:

編譯和執行:

這樣我們會在/dev/shm/、/run/shm下面看到一個文件:

坦白講,mmap、munmap這樣的API讓我找到了回家的感覺,剛入行做Linux的時候,寫好framebuffer驅動後,就是把/dev/fb0 mmap到用戶空間來操作,所以mmap這樣的 API,真的是特別親切,像親人一樣。

當然,如果你不喜歡shm_open()這個API,你也可以用常規的open來打開文件,然後進行mmap。關鍵的是mmap,wikipedia如是說:

In computing, mmap(2) is a POSIX-compliant Unix system call that maps files or devices into memory. It is a method of memory-mapped file I/O. It implements demand paging, because file contents are not read from disk directly and initially do not use physical RAM at all. The actual reads from disk are performed in a "lazy" manner, after a specific location is accessed. After the memory is no longer needed, it is important to munmap(2) the pointers to it. Protection information can be managed using mprotect(2), and special treatment can be enforced using madvise(2).

POSIX的共享內存,仍然符合我們前面說的tmpfs的特點,在運行了sw,sr後,再運行psw和psr,我們發現free命令再次戲劇性變化:

請將這個free命令的結果與前2次的free結果的各個欄位進行對照:

第3次比第2次的cached大了這麼多?是因為我編寫這篇文章邊在訪問磁碟裡面的文件,當然POSIX的這個共享內存本身也導致cached增大了。

如果說POSIX的mmap讓我找到回家的感覺,那麼memfd_create()則是萬般驚豔。見過這種API,才知道什麼叫天生尤物——而且是尤物中的尤物,它完全屬於那種讓碼農第一眼看到就會兩眼充血,恨不得眼珠子奪眶而出貼到它身上去的那種API;一般人見到它第一次,都會忽略了它的長相,因為它的身材實在太火辣太搶眼了。

先不要浮想聯翩,在所有的所有開始之前,我們要先提一下跨進程分享fd(文件描述符,對應我們很多時候說的「句柄」)這個重要的概念。

眾所周知,Linux的fd屬於一個進程級別的東西。進入每個進程的/proc/pid/fd可以看到它的fd的列表:

這個進程的0,1,2和那個進程的0,1,2不是一回事。

某年某月的某一天,人們發現,一個進程其實想訪問另外一個進程的fd。當然,這只是目的不是手段。比如進程A有2個fd指向2片內存,如果進程B可以拿到這2個fd,其實就可以透過這2個fd訪問到這2片內存。這個fd某種意義上充當了一個中間媒介的作用。有人說,那還不簡單嗎,如果進程A:

fd = open();

open()如果返回100,把這個100告訴進程B不就可以了嗎,進程B訪問這個100就可以了。這說明你還是沒搞明白fd是一個進程內部的東西,是不能跨進程的概念。你的100和我的100,不是一個東西。這些基本的東西你搞不明白,你搞別的都是白搭。

Linux提供一個特殊的方法,可以把一個進程的fd甩鍋、踢皮球給另外一個進程(其實「甩鍋」這個詞用在這裡不合適,因為「甩鍋」是一種推卸,而fd的傳遞是一種分享)。我特碼一直想把我的bug甩(分)鍋(享)出去,卻發現總是被人把bug甩鍋過來。

那麼如何甩(分)鍋(享)fd呢?

欲知後事如何,請聽下回分解~~~

Linux閱碼場原創精華文章匯總

更多精彩,盡在"Linux閱碼場",掃描下方二維碼關注


你的隨手轉發或點個在看是對我們最大的支持!

相關焦點

  • Linux 高級編程 - 共享內存 Shared Memory
    共享內存 Shared MemoryHello,大紮好,這次跟大家分享的是在 Linux 中最快的一種 IPC 方式:共享內存
  • Linux的共享內存與自旋鎖
    Linux的共享內存(shared memory)是進程間通信的一種機制之一,用於在多個進程之間共享數據。共享的數據屬於多個進程的共同資源,類似於多線程的數據一樣,也要使用鎖來保護。3,最後一步是把共享內存用mmap()函數映射到當前進程裡,獲得一個共享內存的指針,就可以像正常的內存一樣讀寫它了。區別就是它是被其他進程共享的,而正常的內存是歸所屬進程獨佔的。
  • 一文搞定:Linux 共享內存原理
    但有時候為了讓不同進程之間進行通信,需要讓不同進程共享相同的物理內存,Linux通過 共享內存 來實現這個功能。下面先來介紹一下Linux系統的共享內存的使用。共享內存使用1.參數 shmflg 指定 shmget() 函數的動作,比如傳入 IPC_CREAT 表示要創建新的共享內存。函數調用成功時返回一個新建或已經存在的的共享內存標識符,取決於shmflg的參數。失敗返回-1,並設置錯誤碼。2. 關聯共享內存shmget() 函數返回的是一個標識符,而不是可用的內存地址,所以還需要調用 shmat() 函數把共享內存關聯到某個虛擬內存地址上。
  • 聊聊 Linux 的內存統計
    從 free 命令開始上面的背景介紹文章把內存相關的基礎概念講的差不多了,這裡不再贅述。本文定位是內存統計,所以從最基礎的內存統計的命令—free命令開始。執行free命令,可以看到如下的輸出:縱向是內存和Swap分區,橫向是統計項。
  • 系統內存/進程內存知識掃盲
    的,需要觀察linux的內存使用狀態,首先需要判斷整體系統的內存情況,然後是進程級別的內存情況,你真的搞清楚這裡面的方法了嘛?1、核心概念耳熟能詳的字眼有虛擬內存、共享內存、物理內存,這裡簡要說明,更多複雜的linux內存管理機制,有興趣的同學可以自己深入了解,說實話我也沒有很深入學習。物理內存:就是系統硬體提供的內存大小,是真正的內存,一般叫做內存條,是與CPU直接交換數據的內部存儲器,也叫主存(內存)。
  • 【底層原理】Linux內存管理
    作者:羅道文的私房菜原文連結:http://luodw.cc/2016/02/17/linux-memory/今天這篇文章主要是之前看linux內核相關知識和博客Gustavo Duarte我(指道文)主要是看了這篇博客,並且結合之前的知識,對內存管理的的理解又上升了一個檔次。所以想通過這篇文章總結下。我們先來看下linux內存布局,此圖比我之前寫的那篇文章寫的布局更詳細
  • 騰訊二面:大白你了解共享內存嗎?
    那就是共享內存。共享內存是進程間通信中最簡單的方式之一。站在進程的角度來說,共享內存就是可以同時被多個進程訪問的內存。由於所有進程共享同一塊內存,因此這種通信方式效率非常高。👨‍🏫 面試官:要不你再給我講講,為什麼進程間的內存不是共享的吧?👨‍💻 大白:(內心:啥?
  • 郭健:Linux內存管理系統參數配置之OOM(內存耗盡)
    按照慣例,最後一章是參考文獻,本文的參考文獻都是來自linux內核的Documentation目錄,該目錄下有大量的文檔可以參考,每一篇都值得細細品味。二、什麼是OOMOOM就是out of memory的縮寫,雖然linux kernel有很多的內存管理技巧(從cache中回收、swap out等)來滿足各種應用空間的vm內存需求,但是,當你的系統配置不合理,讓一匹小馬拉大車的時候,linux kernel會運行非常緩慢並且在某個時間點分配page frame的時候遇到內存耗盡、無法分配的狀況
  • Linux下找出吃內存的方法總結
    linux下查詢進程佔用的內存方法總結,假設現在有一個「php-cgi」的進程 ,進程id為「25282」。
  • 每個程式設計師都該了解一點 Linux 內存管理知識
    ,創下了今年閱讀量最低記錄(不算圖片),為了挽回顏面,我決定再寫一篇,還是從案例入手,介紹 Linux 內存管理機制和一個內存溢出殺手的存在感。在內存極度緊張的時候, sshd 這種不怎麼佔內存的進程也無法倖免。覆巢之下,焉有完卵。OOM Killer 的全稱叫 Out Of Memory Killer,今天簡單給大家介紹下,它是 linux 內核的內存保護機制。當應用程式大量請求內存導致內存不足的時候,通常會觸發 OOM Killer,OOM Killer 會根據相對簡單粗暴的算法殺掉某個進程以解燃眉之急。
  • KSM機制剖析 — Linux 內核中的內存去耦合
    圖 2.跨 VMs 的內存共享特性命名本文描述的特性非常新;因此,其名稱經歷了一些變化。您將發現這個 Linux 內核特性稱為 Kernel Shared Memory 或 Kernel Samepage Merging。您很快就會發現,儘管 Linux 中的內存共享在虛擬環境中有優勢(KSM 最初設計用於基於內核的虛擬機),但它在非虛擬環境中仍然有用。
  • 嵌入式Linux內存管理的一些知識點總結
    這個內存管理的知識點還真的需要我們專門的去理解一下,今天大家一起來學習學習嵌入式Linux內存管理的知識。1.不涉及linux內核的彙編知識,僅C語言層面解析1.回答:彙編主要處理的是寄存器地址(包括內容)的計算,進行一部分的地址轉換工作(當然,它是重要的);C語言處理了極大部分的系統內存管理工作。
  • Linux內存、Swap、Cache、Buffer
    在Linux系統下,我們一般不需要去釋放內存,因為系統已經將內存管理的很好,但是凡事也有例外。通過free命令看Linux內存    total:總內存大小。  used:已經使用的內存大小(這裡面包含cached和buffers和shared部分)。  free:空閒的內存大小。  shared:進程間共享內存(一般不會用,可以忽略)。
  • Linux Used內存到底哪裡去了?
    30M,但是free看到內存卻已經使用了7,8G了,已經開始swap了,請問ps aux的實際物理內存統計是不是漏了哪些內存沒算?由於linux系統採用的是虛擬內存,進程的代碼,庫,堆和棧使用的內存都會消耗內存,但是申請出來的內存,只要沒真正touch過,是不算的,因為沒有真正為之分配物理頁面。我們實際進程使用的物理頁面應該用resident set size來算的,遍歷所有的進程,就可以知道所有的所有的進程使用的內存。
  • Linux如何調試內存洩漏
    內存洩漏是指由於疏忽或錯誤造成程序未能釋放已經不再使用的內存
  • Linux 下的進程間通信:共享存儲 | Linux 中國
    例如,默認情況下,POSIX API 用內存映射文件來實現共享內存:對於一個共享的內存段,系統為相應的內容維護一個備份文件。在 POSIX 規範下共享內存可以被配置為不需要備份文件,但這可能會影響可移植性。我的例子中使用的是帶有備份文件的 POSIX API,這既結合了內存獲取的速度優勢,又獲得了文件存儲的持久性。
  • 10 張圖解再談 Linux 物理內存和虛擬內存
    img言歸正傳,上一篇文章 別再說你不懂Linux內存管理了,10張圖給你安排的明明白白! 分析了 Linux 內存管理機制,如果已經忘了的同學還可以回頭看下,並且也強烈建議先閱讀那一篇再來看這一篇。限於篇幅,上一篇沒有深入學習物理內存管理和虛擬內存分配,今天就來學習一下。
  • Linux內核中的內存管理
    這是我幾年前發在cnblogs上的一篇文章,是當時閱讀Linux內核原始碼時做的筆記。
  • 如何與ctypes數組共享內存?
    02如何與ctypes庫創建的數組共享內存空間?那麼,如何使用ctypes庫定義一個與numpy共享內存空間的數組變量呢?仍以上面的例子,定義一個uint8類型的數組b,與a數組共享內存區域,可使用下面的代碼:b = (c_uint8*len(a)).from_address(a.
  • android內存優化總結
    使用腳本每隔1s輸出對應包的PSS值PSS的定義是:Proportional Set Size 實際使用的物理內存(比例分配共享庫佔用的內存);共享內存則是:framework的代碼與資源在ram中佔有的內存。