Android跨進(jìn)程通信-共享內(nèi)存

共享內(nèi)存的使用和原理

還是先看共享內(nèi)存的使用方法鲫趁,我主要介紹兩個函數(shù):

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg); //申請共享內(nèi)存
void *shmat(int shmid, const void *shmaddr, int shmflg); //把共享內(nèi)存映射到進(jìn)程的地址空間

通過shmget()函數(shù)申請共享內(nèi)存,它的入?yún)⑷缦?/p>

  • key:用來唯一確定這片內(nèi)存的標(biāo)識,。
  • size:就是我們申請內(nèi)存的大小
  • shmflg:讀寫權(quán)限
  • 返回值:這個操作會返回一個id, 我們一般稱為 shmid

通過shmat()函數(shù)將我們申請到的共享內(nèi)存映射到自己的用戶空間堡僻,映射成功會返回地址钉疫,有了這個地址,我們就可以隨意的讀寫數(shù)據(jù)了牲阁,我們繼續(xù)看一下這個函數(shù)的入?yún)?/p>

  • shmid :我們申請內(nèi)存時, 返回的shmid
  • shmaddr:共享內(nèi)存在進(jìn)程的內(nèi)存地址,傳NULL讓內(nèi)核自己決定一個合適的地址位置.
  • shmflg:讀寫權(quán)限
  • 返回值:映射后進(jìn)程內(nèi)的地址指針, 代表內(nèi)存的頭部地址

共享內(nèi)存的原理是在內(nèi)存中單獨開辟的一段內(nèi)存空間备燃,這段內(nèi)存空間其實就是一個tempfs(臨時虛擬文件)凌唬,tempfs是VFS的一種文件系統(tǒng)客税,掛載在/dev/shm上,前面提到的管道pipefs也是VFS的一種文件系統(tǒng)更耻。

由于共享的內(nèi)存空間對使用和接收進(jìn)程來講秧均,完全無感知,就像是在自己的內(nèi)存上讀寫數(shù)據(jù)一樣疙描,所以也是效率最高的一種IPC方式讶隐。

上面提到的IPC的方式都是在內(nèi)核空間中開辟內(nèi)存來存儲數(shù)據(jù),寫數(shù)據(jù)時效五,需要將數(shù)據(jù)從用戶空間拷貝到內(nèi)核空間炉峰,讀數(shù)據(jù)時,需要從內(nèi)核空間拷貝到自己的用戶空間戒劫,
共享內(nèi)存就只需要一次拷貝,而且共享內(nèi)存不是在內(nèi)核開辟空間巫橄,所以可以傳輸?shù)臄?shù)據(jù)量大茵典。

但是共享內(nèi)存最大的缺點就是沒有并發(fā)的控制,我們一般通過信號量配合共享內(nèi)存使用彩倚,進(jìn)行同步和并發(fā)的控制扶平。

Android中共享內(nèi)存的使用場景

共享內(nèi)存在Android系統(tǒng)中主要的使用場景是用來傳輸大數(shù)據(jù),并且Android并沒有直接使用Linux原生的共享內(nèi)存方式盯质,而是設(shè)計了Ashmem匿名共享內(nèi)存概而。

之前說到有名管道和匿名管道的區(qū)別在于有名管道可以在vfs目錄樹中查看到這個管道的文件囱修,但是匿名管道不行破镰,所以匿名共享內(nèi)存同樣也是無法在vfs目錄中查看到的,Android之所以要設(shè)計匿名共享內(nèi)存鲜漩,我覺得主要是為了安全性的考慮吧孕似。

我們來看看共享內(nèi)存的一個使用場景,在Android中喉祭,如果我們想要將當(dāng)前的界面顯示出來泛烙,需要將當(dāng)前界面的圖元數(shù)據(jù)傳遞Surfaceflinger去做圖層混合,圖層混合之后的數(shù)據(jù)會直接送入幀緩存藐唠,送入幀緩存后,顯卡就會直接取出幀緩存里的圖元數(shù)據(jù)顯示了宇立。

那么我們?nèi)绾螌?yīng)用的Activity的圖元數(shù)據(jù)傳遞給SurfaceFlinger呢泄伪?想要將圖像數(shù)據(jù)這樣比較大的數(shù)據(jù)跨進(jìn)程傳輸,靠binder是不行的蟋滴,所以這兒便用到匿名共享內(nèi)存津函。

從谷歌官方提供的架構(gòu)圖可以看到,圖元數(shù)據(jù)是通過BufferQueue傳遞到SurfaceFlinger去的涩馆,當(dāng)我們想要繪制圖像的時候允坚,需要從BufferQueue中申請一個Buffer,Buffer會調(diào)用Gralloc模塊來分配共享內(nèi)存當(dāng)作圖元緩沖區(qū)存放我們的圖元數(shù)據(jù)涯雅。

//文件-->hardware/libhardware/modules/gralloc/gralloc.cpp
static int gralloc_alloc_buffer(alloc_device_t* dev,
        size_t size, int usage, buffer_handle_t* pHandle)
{
    int err = 0;
    int fd = -1;
    size = roundUpToPageSize(size);
    // 創(chuàng)建共享內(nèi)存展运,并且設(shè)定名字跟size
    fd = ashmem_create_region("gralloc-buffer", size);
    if (err == 0) {
        private_handle_t* hnd = new private_handle_t(fd, size, 0);
        gralloc_module_t* module = reinterpret_cast<gralloc_module_t*>(
                dev->common.module);
         // 執(zhí)行mmap拗胜,將內(nèi)存映射到自己的進(jìn)程
        err = mapBuffer(module, hnd);
        if (err == 0) {
            *pHandle = hnd;
        }
    }
?
    return err;
}
?
int mapBuffer(gralloc_module_t const* module,
            private_handle_t* hnd)
{
        void* vaddr; 
        return gralloc_map(module, hnd, &vaddr);
    }
?
static int gralloc_map(gralloc_module_t const* module,
        buffer_handle_t handle,
        void** vaddr)
{
    private_handle_t* hnd = (private_handle_t*)handle;
    if (!(hnd->flags & private_handle_t::PRIV_FLAGS_FRAMEBUFFER)) {
        size_t size = hnd->size;
        //映射創(chuàng)建的匿名共享內(nèi)存
        void* mappedAddress = mmap(0, size,
                PROT_READ|PROT_WRITE, MAP_SHARED, hnd->fd, 0);
        if (mappedAddress == MAP_FAILED) {
            return -errno;
        }
        hnd->base = intptr_t(mappedAddress) + hnd->offset;
    }
    *vaddr = (void*)hnd->base;
    return 0;
}

可以看到Android的匿名共享內(nèi)存是通過ashmem_create_region() 函數(shù)來申請共享內(nèi)存的埂软,它會在/dev/ashmem下創(chuàng)建一個虛擬文件,Linux原生共享內(nèi)存是通過shmget()函數(shù)勘畔,并會在/dev/shm下創(chuàng)建虛擬文件咖杂。

匿名共享內(nèi)存是通過mmap()函數(shù)將申請到的內(nèi)存映射到自己的進(jìn)程空間,而Linux是通過*shmat()函數(shù)懦尝。

雖然函數(shù)不一樣,但是Android的匿名共享內(nèi)存和Linux的共享內(nèi)存在本質(zhì)上是大同小異的陵霉。

6.1 什么是共享內(nèi)存?

  • 共享內(nèi)存是系統(tǒng)處于多個進(jìn)程之間通訊的考慮,而預(yù)留的一塊內(nèi)存區(qū)乍桂。
  • 共享內(nèi)存允許兩個或更多的進(jìn)程訪問同一塊內(nèi)存效床,就如同malloc()函數(shù)向不同進(jìn)程返回了指向同一個物理內(nèi)存區(qū)域的指針剩檀。
  • 當(dāng)一個進(jìn)程改變了這塊地址中的內(nèi)容的時候,其他進(jìn)程都會覺察到這個更改沪猴。

6.2 關(guān)于共享內(nèi)存

  • 當(dāng)一個程序加載進(jìn)內(nèi)存后运嗜,它就被分成叫做頁的塊。
  • 通信將存在內(nèi)存的兩個頁之間或者兩個獨立的進(jìn)程之間砸民。
  • 當(dāng)一個程序想和另外一個程序通信的時候翩活,那內(nèi)存將會為這兩個程序生成一塊公共的內(nèi)存區(qū)域便贵。這塊被兩個進(jìn)程分享的內(nèi)存區(qū)域叫做共享內(nèi)存
  • 由于所有進(jìn)程共享同一塊內(nèi)存利耍,共享內(nèi)存在各種進(jìn)程間通信方式中具有最高的效率盔粹。
  • 訪問共享內(nèi)存區(qū)域和訪問進(jìn)程獨有的內(nèi)存區(qū)域一樣快舷嗡,并不需要通過系統(tǒng)調(diào)用或者其他需要切入內(nèi)核的過程來完成。同時它也也避免了對數(shù)據(jù)的跟中不必要的復(fù)制进萄。
  • 如果沒有共享內(nèi)存的概念锐峭,那一個進(jìn)程不能存取另外一個進(jìn)程的內(nèi)存部分可婶,因而導(dǎo)致共享數(shù)據(jù)或者通信失效。因為系統(tǒng)內(nèi)核沒有對訪問共享內(nèi)存進(jìn)行同步椎扬,開發(fā)者必須提供自己的同步措施具温。
  • 解決了這些問題的常用方法是是通過信號量進(jìn)行同步。不過通常我們程序只有一個進(jìn)程訪問了共享內(nèi)存钻趋,因此在集中展示了共享內(nèi)存機(jī)制的同時剂习,我們避免了讓代碼被同步邏輯搞的混亂不堪。
  • 為了簡化共享數(shù)據(jù)的完整性和避免同時存取數(shù)據(jù)失仁,內(nèi)核提供了一種專門存取共享內(nèi)存資源的機(jī)制们何。這稱為互斥體或者M(jìn)utex對象

6.3 Mutex對象

  • 例如拂封,在數(shù)據(jù)被寫入前不允許進(jìn)程從共享內(nèi)存中讀取信息鹦蠕、不允許兩個進(jìn)程同時向一個共享內(nèi)存地址寫入數(shù)據(jù)等。
  • 當(dāng)一個基礎(chǔ)想和兩一個進(jìn)程通信的時候萧恕,它將按以下順序運行:
  • 1肠阱、獲取Mutex對象屹徘,鎖定共享區(qū)域
  • 2、將要通信的數(shù)據(jù)寫入共享區(qū)域
  • 3吆视、釋放Mutex對象
  • 當(dāng)一個進(jìn)程從這個區(qū)域讀取數(shù)據(jù)的時候,它將重復(fù)同樣的步驟啦吧,只是將第二步變成讀取授滓。

6.4 內(nèi)存模型

要使用一塊共享內(nèi)存

  • 進(jìn)程必須首先分配它
  • 隨后需要訪問這個共享內(nèi)存塊的每一個進(jìn)程都必須將這個共享內(nèi)存綁定到自己的地址空間中琳水。
  • 當(dāng)完成通信之后,所有進(jìn)程都脫離共享內(nèi)存般堆,并且由一個進(jìn)程釋放該共享內(nèi)存塊在孝。
  • 在/proc/sys/kernel/目錄下,記錄著共享內(nèi)存的一些限制淮摔,如一個共享內(nèi)存區(qū)的最大字節(jié)數(shù)shmmax私沮,系統(tǒng)范圍內(nèi)最大的共享內(nèi)存區(qū)標(biāo)志符數(shù)shmmni等。

6.5 Linux系統(tǒng)內(nèi)存模型

  • 在Linux系統(tǒng)中和橙,每個進(jìn)程的虛擬內(nèi)存是被分為許多頁面的仔燕。這些內(nèi)存頁面中包含了實際的數(shù)據(jù)。每個進(jìn)程都會維護(hù)一個從內(nèi)存地址到虛擬內(nèi)存頁面之間的映射關(guān)系魔招。盡管每個進(jìn)程都有自己的內(nèi)存地址,不同的進(jìn)程可以同時將同一個頁面頁面映射到自己的地址空間办斑,從而達(dá)到共享內(nèi)存的目的外恕。
  • 分配一個新的共享內(nèi)存塊會創(chuàng)建新的內(nèi)存頁面。因為所有進(jìn)程都希望共享對同一塊內(nèi)存的訪問乡翅,只應(yīng)由一個進(jìn)程創(chuàng)建一塊新的共享內(nèi)存鳞疲。再次分配一塊已經(jīng)存在的內(nèi)存塊不會創(chuàng)建新的頁面,而只是會返回一個標(biāo)示該內(nèi)存塊的標(biāo)識符蠕蚜。
  • 一個進(jìn)程如需使用這個共享內(nèi)存塊尚洽,則首先需要將它綁定到自己的地址空間中。
  • 這樣會創(chuàng)建一個從進(jìn)程本身虛擬地址到共享頁面的映射關(guān)系波势。當(dāng)對共享內(nèi)存的使用結(jié)束之后翎朱,這個映射關(guān)系將被刪除橄维。
  • 當(dāng)再也沒有進(jìn)程需要使用這個共享內(nèi)存塊的時候尺铣,必須有一個(有且只有一個)進(jìn)程負(fù)責(zé)釋放這個被共享的內(nèi)存頁面。
  • 所有共享內(nèi)存塊的大小必須是系統(tǒng)頁面大小的整數(shù)倍争舞。系統(tǒng)頁面大小指的是系統(tǒng)中單個內(nèi)存頁面包含的字節(jié)數(shù)凛忿。在Linux系統(tǒng)中,內(nèi)存頁面大小是4KB竞川,不過您仍然應(yīng)高通過調(diào)用getPageSize獲取這個值店溢。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末叁熔,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子床牧,更是在濱河造成了極大的恐慌荣回,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件戈咳,死亡現(xiàn)場離奇詭異心软,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)著蛙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進(jìn)店門删铃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人踏堡,你說我怎么就攤上這事猎唁。” “怎么了顷蟆?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵诫隅,是天一觀的道長。 經(jīng)常有香客問我慕的,道長阎肝,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任肮街,我火速辦了婚禮风题,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嫉父。我一直安慰自己沛硅,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布绕辖。 她就那樣靜靜地躺著消别,像睡著了一般。 火紅的嫁衣襯著肌膚如雪暴备。 梳的紋絲不亂的頭發(fā)上宪迟,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天,我揣著相機(jī)與錄音树碱,去河邊找鬼肯适。 笑死,一個胖子當(dāng)著我的面吹牛成榜,可吹牛的內(nèi)容都是我干的框舔。 我是一名探鬼主播,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼刘绣!你這毒婦竟也來了樱溉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤纬凤,失蹤者是張志新(化名)和其女友劉穎福贞,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體停士,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡肚医,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了向瓷。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肠套。...
    茶點故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖猖任,靈堂內(nèi)的尸體忽然破棺而出你稚,到底是詐尸還是另有隱情,我是刑警寧澤朱躺,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布刁赖,位于F島的核電站,受9級特大地震影響长搀,放射性物質(zhì)發(fā)生泄漏宇弛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一源请、第九天 我趴在偏房一處隱蔽的房頂上張望枪芒。 院中可真熱鬧,春花似錦谁尸、人聲如沸舅踪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽抽碌。三九已至,卻和暖如春决瞳,著一層夾襖步出監(jiān)牢的瞬間货徙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工皮胡, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留痴颊,地道東北人。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓胸囱,卻偏偏與公主長得像祷舀,于是被迫代替她去往敵國和親瀑梗。 傳聞我的和親對象是個殘疾皇子烹笔,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,543評論 2 349

推薦閱讀更多精彩內(nèi)容