1. 程式人生 > >Posix共享記憶體

Posix共享記憶體

目錄

  • 1. 概述
  • 2. mmap、munmap和msync函式
    • mmap
    • munmap
    • msync
  • 3. 記憶體對映IO
    • 父子程序同步——Posix有名訊號量
    • 父子程序同步——Posix無名訊號量
  • 4. 匿名記憶體對映
  • 5. Posix共享記憶體
    • shm_open和shm_unlink函式
    • ftruncate和fstat函式
  • 6. Posix共享記憶體示例程式碼
    • 程序同步——Posix有名訊號量
      • common.h
      • server.c
      • client.c
    • 程序同步——Posix無名訊號量
      • common.h
      • server.c
      • client.c

1. 概述

共享記憶體是可用IPC機制中最快的,一旦共享記憶體區對映到共享它的程序地址空間:

  • 程序間的資料傳遞就不再執行需進入核心的系統呼叫
  • 各個程序向共享記憶體讀寫資料往往需要某種形式的同步
  • 這些程序間的同步通常使用Posix有名訊號量或無名訊號量

對比下面兩張圖所展示的例子:

  • 不使用共享記憶體,需要4次核心和程序-使用者空間的資料拷貝
  • 使用共享記憶體,只需要2次核心空間-使用者空間的資料拷貝


2. mmap、munmap和msync函式

mmap

mmap函式把一個檔案或一個Posix共享記憶體物件對映到呼叫程序的地址空間,使用該函式有三個目的:

  • 使用普通檔案以提供記憶體對映IO
  • 使用特殊檔案以提供匿名記憶體對映
  • 使用Posix共享記憶體物件以提供Posix共享記憶體區
//成功返回對映記憶體的起始地址,失敗返回MAP_FAILED
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);

mmap引數解析:

  • addr指定對映記憶體的起始地址,通常設為NULL,讓核心自己決定起始地址
  • len是被對映到呼叫程序地址空間中的位元組數,它從被對映檔案fd開頭起第offset個位元組處開始算,offset通常設為0,下圖展示了這個對映關係
  • prot指定對對映記憶體區的保護,通常設為PROT_READ | PROT_WRITE
  • flags必須在MAP_SHAREDMAP_PRIVATE這兩個標誌中選擇指定一個,程序間共享記憶體需要使用MAP_SHARED
  • 可移植的程式碼應把addr設為NULL,並且flags不指定MAP_FIXED

prot 說明 flags 說明
PROT_READ 資料可讀 MAP_SHARED 變動是共享的
PROT_WRITE 資料可寫 MAP_PRIVATE 變動是私有的
PROT_EXEC 資料可執行 MAP_FIXED 準確地解釋addr引數
PROT_NONE 資料不可訪問

mmap成功返回後,可以關閉fd,這對已建立的對映關係沒有影響。
注意,不是所有檔案都能進行記憶體對映,例如終端和套接字就不可以。

munmap

mmap建立的對映關係通過munmap刪除,其中addr是mmap返回的地址,len是對映區的大小,同mmap的引數len。

//成功返回0,失敗返回-1
int munmap(void *addr, size_len);

msync

預設情況下,核心採用虛擬記憶體演算法保持記憶體對映檔案與記憶體對映區的同步,前提是指定了MAP_SHARED標誌,但這種同步可能不是立即生效的,而是在隨後某個時間進行。
但有時候我們修改完資料並進行下一步操作之前,需要確認資料已經同步完成,這時可呼叫msync函式。

//成功返回0,失敗返回-1
int msync(void *addr, size_t len, int flags);

其中addr和len含義同munmap,flags使用下表中的常值,其中MS_ASYNCMS_SYNC這兩個常值中必須選擇指定一個。

flags 說明
MS_ASYNC 執行非同步寫,msync立即返回
MS_SYNC 執行同步寫,msync等同步完成才返回
MS_INVALIDATE 使快取記憶體的資料失效

3. 記憶體對映IO

記憶體對映IO是父子程序之間共享記憶體區的一種方法,父程序fork前以MAP_SHARED方式呼叫mmap,其建立的記憶體對映關係會被子程序繼承。
我們使用這個方法,來實現以下功能:

  • 父子程序通過記憶體對映IO共享一片記憶體
  • 父子程序共同給共享記憶體區中的一個計數器持續加1

父子程序同步——Posix有名訊號量

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <semaphore.h>
#include <sys/mman.h>

#define MMAP_FILE   "/home/delphi/mmap_file"
#define SEM_PATH    "/sem_mmap"

struct Shared
{
    int count;
};

int main(int argvc, char **argv)
{
    struct Shared shared;
    struct Shared *ptr; //mmap返回指標型別結構和對映檔案資料結構需要一致
    sem_t *mutex;       //因為是程序間同步,不能用互斥鎖,因此使用二值訊號量模擬互斥鎖
    int fd;
    int i;

    fd = open(MMAP_FILE, O_CREAT | O_RDWR, 0666);
    memset(&shared, 0, sizeof(shared));
    write(fd, &shared, sizeof(shared)); //將對映檔案內容初始化為0
    ptr = mmap(NULL, sizeof(shared), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    close(fd);

    mutex = sem_open(SEM_PATH, O_CREAT, 0666, 1); //二值訊號量模擬互斥鎖,初始值必須為1
    sem_unlink(SEM_PATH);

    setbuf(stdout, NULL); //設定標準輸出為無緩衝

    if (fork() == 0)
    {
        for (i = 0; i < 10; i++)
        {
            sem_wait(mutex);
            printf("child: %d\n", ptr->count);
            ptr->count++;
            sem_post(mutex);
        }

        exit(0);
    }

    for (i = 0; i < 10; i++)
    {
        sem_wait(mutex);
        printf("parent: %d\n", ptr->count);
        ptr->count++;
        sem_post(mutex);
    }

    return 0;
}

該示例程式碼使用Posix有名訊號量來同步父子程序,共享記憶體區中僅有一個4位元組計數器,訊號量不在共享記憶體區中。

父子程序同步——Posix無名訊號量

將上面這份程式碼修改一下,改為使用建立在共享記憶體區中的Posix無名訊號量同步父子程序,示意圖如下圖所示。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <semaphore.h>
#include <sys/mman.h>

#define MMAP_FILE   "/home/delphi/mmap_file"

struct Shared
{
    sem_t mutex;
    int count;
};

int main(int argvc, char **argv)
{
    struct Shared shared;
    struct Shared *ptr; //mmap返回指標型別結構和對映檔案資料結構需要一致
    int fd;
    int i;

    fd = open(MMAP_FILE, O_CREAT | O_RDWR, 0666);
    memset(&shared, 0, sizeof(shared));
    write(fd, &shared, sizeof(shared)); //將對映檔案內容初始化為0
    ptr = mmap(NULL, sizeof(shared), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    close(fd);

    sem_init(&ptr->mutex, 1, 1);    //Posix無名訊號量建立在ptr指向的共享記憶體中
    setbuf(stdout, NULL);          //設定標準輸出為無緩衝

    if (fork() == 0)
    {
        for (i = 0; i < 10; i++)
        {
            sem_wait(&ptr->mutex);
            printf("child: %d\n", ptr->count);
            ptr->count++;
            sem_post(&ptr->mutex);
        }

        exit(0);
    }

    for (i = 0; i < 10; i++)
    {
        sem_wait(&ptr->mutex);
        printf("parent: %d\n", ptr->count);
        ptr->count++;
        sem_post(&ptr->mutex);
    }

    sem_destroy(&ptr->mutex);

    return 0;
}

4. 匿名記憶體對映

在上面展示的記憶體對映IO示例程式碼中,可以看出編碼的前兩步都是:

  • 呼叫open建立一個普通檔案
  • 呼叫write將檔案內容初始化為0

如果僅僅是用於父子程序共享記憶體,可以使用匿名記憶體對映來避免檔案的顯式建立開啟以及初始化,其辦法是:

  • mmap呼叫時,flags設為MAP_SHARED | MAP_ANON,fd設為-1,offset設為0即可
  • 匿名記憶體對映保證這樣的記憶體區初始化為0

把基於Posix無名訊號量的示例程式碼改為匿名記憶體對映,可以看出,fork前的準備工作明顯簡化了許多。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <semaphore.h>
#include <sys/mman.h>

struct Shared
{
    sem_t mutex;
    int count;
};

int main(int argvc, char **argv)
{
    struct Shared *ptr;
    int i;

    ptr = mmap(NULL, sizeof(struct Shared), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
    sem_init(&ptr->mutex, 1, 1);
    setbuf(stdout, NULL);

    if (fork() == 0)
    {
        for (i = 0; i < 10; i++)
        {
            sem_wait(&ptr->mutex);
            printf("child: %d\n", ptr->count);
            ptr->count++;
            sem_post(&ptr->mutex);
        }

        exit(0);
    }

    for (i = 0; i < 10; i++)
    {
        sem_wait(&ptr->mutex);
        printf("parent: %d\n", ptr->count);
        ptr->count++;
        sem_post(&ptr->mutex);
    }

    sem_destroy(&ptr->mutex);

    return 0;
}

5. Posix共享記憶體

前面講的都是在父子程序間使用共享記憶體的技術,現在把共享記憶體區的概念擴充套件到任意程序之間,Posix.1提供了兩種在任意程序間共享記憶體區的方法。

  • 記憶體對映IO:該方法其實也可以用於無親緣關係程序間共享記憶體
  • Posix共享記憶體:這是Posix IPC的第三種機制

這兩種技術都需要呼叫mmap,區別在於mmap引數fd的獲取手段:

  • 記憶體對映IO通過open獲得
  • Posix共享記憶體通過shm_open獲得

shm_open和shm_unlink函式

shm_open用於建立一個新的Posix共享記憶體物件或開啟一個已存在的Posix共享記憶體物件。
shm_unlink用於從系統中刪除一個Posix共享記憶體物件。

//成功返回非負描述符,失敗返回-1
int shm_open(const char *name, int oflag, mode_t mode);

//成功返回0,失敗返回-1
int shun_unlink(const char *name);

shm_open引數說明:

  • oflag引數不能設定O_WRONLY標誌
  • 和mq_open、sem_open不同,shm_open的mode引數總是必須指定,當指定了O_CREAT標誌時,mode為使用者許可權位,否則將mode設為0

shm_open的返回值是一個描述符,它隨後用作mmap的第五個引數fd。

ftruncate和fstat函式

處理mmap的時候,普通檔案或Posix共享記憶體物件的大小都可以通過呼叫ftruncate設定。

#include <unistd.h>

//成功返回0,失敗返回-1
int ftruncate(int fd, off_t length):
  • 對於普通檔案,若檔案長度大於length,額外的資料會被丟棄;若檔案長度小於length,則擴充套件檔案大小到length
  • 對於Posix共享記憶體物件,ftruncate把該物件的大小設定成length位元組

我們呼叫ftruncate來指定新建立的Posix共享記憶體物件大小,或者修改已存在的Posix共享記憶體物件大小。

  • 建立新的Posix共享記憶體物件時指定大小是必須的,否則訪問mmap返回的地址會報bus error錯誤
  • 當開啟一個已存在的Posix共享記憶體物件時,可以呼叫fstat來獲取該物件的資訊
#include <sys/stat.h>
#include <sys/types.h>

//成功返回0,失敗返回-1
int fstat(int fd, struct stat *buf);

stat結構有12個或以上的成員,然而當fd指代一個Posix共享記憶體物件時,只有四個成員含有資訊:

struct stat
{
    mode_t st_mode;  //使用者訪問許可權
    uid_t  st_uid;   //user id of owner
    gid_t  st_gid;   //group id of owner
    off_t  st_size;  //檔案大小
};

6. Posix共享記憶體示例程式碼

示例程式碼包括一個.h檔案和兩個.c檔案:

  • common.h定義server和client共同使用的資訊
  • server.c建立並初始化Posix共享記憶體物件,以及同步需要的訊號量
  • client.c開啟Posix共享記憶體物件,然後給共享記憶體中的計數器加1

程序同步——Posix有名訊號量

common.h

#ifndef _COMMON_H_
#define _COMMON_H_

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <semaphore.h>
#include <sys/mman.h>

#define SHM_FILE    "/shm_file"
#define SEM_PATH    "/sem_mmap"

struct Shared
{
    int count;
};

#endif

server.c

#include "common.h"

int main()
{
    struct Shared *ptr;
    sem_t *mutex;
    int fd;

    shm_unlink(SHM_FILE);
    fd = shm_open(SHM_FILE, O_RDWR | O_CREAT, 0666);
    ftruncate(fd, sizeof(struct Shared));
    ptr = mmap(NULL, sizeof(struct Shared), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    close(fd);

    sem_unlink(SEM_PATH);
    mutex = sem_open(SEM_PATH, O_CREAT, 0666, 1);
    sem_close(mutex);

    pause();

    return 0;
}

client.c

#include "common.h"

int main(int argc, char **argv)
{
    struct Shared *ptr;
    struct stat buf;
    sem_t *mutex;
    int fd;
    int nloop;
    int i;

    fd = shm_open(SHM_FILE, O_RDWR, 0);
    fstat(fd, &buf);
    ptr = mmap(NULL, buf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    close(fd);

    mutex = sem_open(SEM_PATH, 0);
    nloop = atoi(argv[1]);

    for (i = 0; i < nloop; i++)
    {
        sem_wait(mutex);
        printf("pid %d: %d\n", getpid(), ptr->count++);
        sem_post(mutex);
    }

    return 0;
}

編譯並啟動server,阻塞在pasue()中。

後臺同時執行三個client程序。

擷取程序切換時的部分輸出片段,可以看到切換後計數依然是連續的。

程序同步——Posix無名訊號量

common.h

#ifndef _COMMON_H_
#define _COMMON_H_

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <semaphore.h>
#include <sys/mman.h>

#define SHM_FILE    "/shm_file"

struct Shared
{
    sem_t mutex;
    int count;
};

#endif

server.c

#include "common.h"

int main()
{
    struct Shared *ptr;
    int fd;

    shm_unlink(SHM_FILE);
    fd = shm_open(SHM_FILE, O_RDWR | O_CREAT, 0666);
    ftruncate(fd, sizeof(struct Shared));
    ptr = mmap(NULL, sizeof(struct Shared), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    close(fd);

    sem_init(&ptr->mutex, 1, 1);

    pause();

    return 0;
}

client.c

#include "common.h"

int main(int argc, char **argv)
{
    struct Shared *ptr;
    struct stat buf;
    int fd;
    int nloop;
    int i;

    fd = shm_open(SHM_FILE, O_RDWR, 0);
    fstat(fd, &buf);
    ptr = mmap(NULL, buf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    close(fd);

    //共享記憶體中的mutex已由server初始化,client直接使用就可以了,不能重複初始化
    nloop = atoi(argv[1]);

    for (i = 0; i < nloop; i++)
    {
        sem_wait(&ptr->mutex);
        printf("pid %d: %d\n", getpid(), ptr->count++);
        sem_post(&ptr->mutex);
    }

    return 0;
}

編譯並啟動server,阻塞在pasue()中。

後臺同時執行三個client程序。

擷取程序切換時的部分輸出片段,可以看到切換後計數依然是連續的。

相關推薦

IPC通訊:Posix共享記憶體

http://www.cnblogs.com/polestar/archive/2012/04/23/2466003.html  共享記憶體區是最快的可用IPC形式。它允許多個不相關的程序去訪問同一部分邏輯記憶體。如果需要在兩個執行中的程序之間傳輸資料,共享記憶體將是一

IPC之Posix共享記憶體詳解

1.概念 共享記憶體區,按標準可分為Posix共享記憶體區和System V共享記憶體區,兩者在概念上類似。 Posix 表示可移植作業系統介面(Portable Operating System Interface ,縮寫為 POSIX ),POSIX標準定義了作業系統

Linux程序間通訊之POSIX共享記憶體

共享記憶體是最高效的IPC機制,因為它不涉及程序之間的任何資料傳輸。這種高效率帶來的問題是,我們必須用其他輔助手段來同步程序對共享記憶體的訪問,否則會產生競態條件。因此,共享記憶體通常和其他程序間通訊方式一起使用。 Linux下有三種共享記憶體的IPC技術:S

54.Linux/Unix 系統程式設計手冊(下) -- POSIX 共享記憶體

1.概述 前面介紹了2種允許無關程序共享記憶體區域以便執行 IPC 的技術:System V 共享記憶體和共享檔案對映。這2種技術都有一些不足: 1.System V 共享記憶體模型使用的是鍵和識別符號,這與標準的 Unix IO 模型使用檔名和描述符的做法不一致。這種差

POSIX共享記憶體-------共享計數器

問題描述 父子程序共享一個計數器,每次父程序或子程序可以給計數器加一。 簡單實現 #include<iostream> #include<unistd.h> #include<stdio.h> #include<

IPC通訊:Posix共享記憶體1

 共享記憶體區是最快的可用IPC形式。它允許多個不相關的程序去訪問同一部分邏輯記憶體。如果需要在兩個執行中的程序之間傳輸資料,共享記憶體將是一種效率極高的解決方案。一旦這樣的記憶體區對映到共享它的程序的地址空間,這些程序間資料的傳輸就不再涉及核心。這樣就可以減少系統呼叫

【IPC】Posix共享記憶體區與mmap記憶體對映

共享記憶體是一種IPC形式,與其它IPC機制如管道、訊息佇列等相比,資料不必在程序與核心間多次交換,程序間通訊的速度更快。當共享記憶體區對映到共享它的程序的地址空間時,再加以一些同步控制,這些程序就可以進行資料傳送了。mmap函式提供了記憶體對映功能,可以把一個

linux網路程式設計之POSIX 共享記憶體和 系列函式

在前面介紹了system v 共享記憶體的相關知識,現在來稍微看看posix 共享記憶體 和系列函式。 共享記憶體簡單來說就是一塊真正的實體記憶體區域,可以使用一些函式將這塊區域對映到程序的地址空間進行讀寫,而posix 共享記憶體與system v 共享記憶體不同的是它是

Posix共享記憶體

目錄 1. 概述 2. mmap、munmap和msync函式 mmap munmap msync 3. 記憶體對映IO 父子程序同步——Po

程序間通訊機制(管道、訊號、共享記憶體/訊號量/訊息佇列)、執行緒間通訊機制(互斥鎖、條件變數、posix匿名訊號量)

(1)系統中每個訊號量的資料結構(sem)struct sem {     int semval; /* 訊號量的當前值 */     unsigned short  semzcnt;  /* # waiting for zero */     unsigned short  semncnt;  /* # w

linux進程間通信之Posix共享內存用法詳解及代碼舉例

函數 ini 復制代碼 define 進程 a.out IV 使用 init Posix共享內存有兩種非親緣進程間的共享內存方法:1). 使用內存映射文件,由open函數打開,再由mmap函數把返回的文件描述符映射到當前進程空間中的一個文件。2). 使用共享內存區對象,由

以佇列的形式使用共享記憶體

共享記憶體允許多個程序使用某一段儲存區,資料不需要在程序之間複製,多個程序都把該記憶體區域對映到自己的虛擬地址空間,直接訪問該共享記憶體區域,從而可以通過該區域進行通訊,是一種速度很快IPC。 下面是共享記憶體對映圖 一般描述使用共享記憶體,都是以普通緩衝區的形式訪問,這裡

windows 驅動開發 MDL 核心層 使用者層共享記憶體

參考資料    https://blog.csdn.net/wdykanq/article/details/7752909    http://blog.51cto.com/laokaddk/404584   核心層建立記憶體對映使用者層 PVOID

ython實現程序間的通訊有Queue,Pipe,Value+Array等,其中Queue實現多個程序間的通訊,而Pipe實現兩個程序間通訊,而Value+Array使用得是共享記憶體對映檔案的方式,所以速度比較快

1.Queue的使用 from multiprocessing import Queue,Process import os,time,random #新增資料函式 def proc_write(queue,urls): print("程序(%s)正在寫入..."%(os.getpid()))

Linux-程序通訊-訊息佇列/訊號燈/共享記憶體

訊息佇列     訊息佇列提供了程序間傳送資料塊的方法,每個資料塊都可以被認為是有一個型別,接受者接受的資料塊可以有不同的型別;我們可以通過傳送訊息來避免命名管道的同步和阻塞問題;訊息佇列與命名管道一樣,每個資料塊都有一個最大長度的限制;我們可以將每個資料塊當作是一

風河虛擬化元件使用說明(15)—— 為Windows系統安裝VNIC驅動及共享記憶體驅動(on target)

參考Guest Guide文件"Windows VNIC Driver Overview"和"Windows Shared Memory Driver Overview" 首先將Windows 10作為GuestOS啟動:  進入WindShare網站(http://wind

c/c++ linux 程序間通訊系列4,使用共享記憶體

linux 程序間通訊系列4,使用共享記憶體 1,建立共享記憶體,用到的函式shmget, shmat, shmdt 函式名 功能描述 shmget 建立共享記憶體,返回pic key

檔案記憶體對映mmap解決大檔案快速讀寫問題和程序間共享記憶體

mmap函式主要用途有三個: 1、將一個普通檔案對映到記憶體中,通常在需要對檔案進行頻繁讀寫時使用,這樣用記憶體讀寫取代I/O讀寫,以獲得較高的效能; 2、將特殊檔案進行匿名記憶體對映,可以為關聯程序提供共享記憶體空間; 3、為無關聯的程序提供共享記憶體空間,一般也是將一個普通檔案對映到

CUDA程式設計(七)共享記憶體與Thread的同步

https://blog.csdn.net/sunmc1204953974/article/details/51078818   CUDA程式設計(七) 共享記憶體與Thread的同步 在之前我們通過block,繼續增大了執行緒的數量,結果還是比較令人滿意的,但是也產生了一

CUDA 共享記憶體 bank conflict

1. bank conflict 本文所有的實驗針對 GTX980 顯示卡,Maxwell 架構,計算能力 5.2。 GPU 共享記憶體是基於儲存體切換的架構(bank-switched-architecture)。在 Femi,Kepler,Maxwell 架構的裝置上有 3