1. 程式人生 > >mmap和shm共享內存的區別和聯系

mmap和shm共享內存的區別和聯系

size key uid 屬於 class 情況下 一段 sha unmap

共享內存的創建

根據理論:

1. 共享內存允許兩個或多個進程共享一給定的存儲區,因為數據不需要來回復制,所以是最快的一種進程間通信機制。共享內存可以通過mmap()映射普通文件(特殊情況下還可以采用匿名映射)機制實現,也可以通過系統V共享內存機制實現。應用接口和原理很簡單,內部機制復雜。為了實現更安全通信,往往還與信號燈等同步機制共同使用。

mmap的機制如:就是在磁盤上建立一個文件,每個進程存儲器裏面,單獨開辟一個空間來進行映射。如果多進程的話,那麽不會對實際的物理存儲器(主存)消耗太大。

技術分享圖片

shm的機制:每個進程的共享內存都直接映射到實際物理存儲器裏面。

技術分享圖片

結論:

1、mmap保存到實際硬盤,實際存儲並沒有反映到主存上。優點:儲存量可以很大(多於主存);缺點:進程間讀取和寫入速度要比主存的要慢。

2、shm保存到物理存儲器(主存),實際的儲存量直接反映到主存上。優點,進程間訪問速度(讀寫)比磁盤要快;缺點,儲存量不能非常大(多於主存)

使用上看:如果分配的存儲量不大,那麽使用shm;如果存儲量大,那麽使用shm。

mmap就是一個文件操作。

mmap函數是unix/linux下的系統調用,來看《Unix Netword programming》卷二12.2節有詳細介紹。

mmap系統調用並不是完全為了用於共享內存而設計的。它本身提供了不同於一般對普通文件的訪問方式,進程可以像讀寫內存一樣對普通文件的操作。而Posix或系統V的共享內存IPC則純粹用於共享目的,當然mmap()實現共享內存也是其主要應用之一。
mmap系統調用使得進程之間通過映射同一個普通文件實現共享內存。普通文件被映射到進程地址空間後,進程可以像訪問普通內存一樣對文件進行訪問,不必再 調用read(),write()等操作。mmap並不分配空間, 只是將文件映射到調用進程的地址空間裏, 然後你就可以用memcpy等操作寫文件, 而不用write()了.寫完後用msync()同步一下, 你所寫的內容就保存到文件裏了. 不過這種方式沒辦法增加文件的長度, 因為要映射的長度在調用mmap()的時候就決定了.

簡單說就是把一個文件的內容在內存裏面做一個映像,內存比磁盤快些。
基本上它是把一個檔案對應到你的virtual memory 中的一段,並傳回一個指針。

系統調用mmap()用於共享內存的兩種方式:
(1)使用普通文件提供的內存映射:適用於任何進程之間;此時,需要打開或創建一個文件,然後再調用mmap();典型調用代碼如下:
fd=open(name, flag, mode);
if(fd<0)
...
ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0); 通過mmap()實現共享內存的通信方式有許多特點和要註意的地方,我們將在範例中進行具體說明。
(2)使用特殊文件提供匿名內存映射:適用於具有親緣關系的進程之間;由於父子進程特殊的親緣關系,在父進程中先調用mmap(),然後調用fork()。那麽在調用fork()之後,子進程繼承父進程匿名映射後的地址空間,同樣也繼承mmap()返回的地址,這樣,父子進程就可以通過映射區域進行通信了。註意,這裏不是一般的繼承關系。一般來說,子進程單獨維護從父進程繼承下來的一些變量。而mmap()返回的地址,卻由父子進程共同維護。

1、mmap()系統調用形式如下:

void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset )

參數fd為即將映射到進程空間的文件描述字,一般由open()返回,同時,fd可以指定為-1,此時須指定flags參數中的MAP_ANON,表明進行的是匿名映射(不涉及具體的文件名,避免了文件的創建及打開,很顯然只能用於具有親緣關系的進程間通信)。len是映射到調用進程地址空間的字節數,它從被映射文件開頭offset個字節開始算起。prot 參數指定共享內存的訪問權限。可取如下幾個值的或:PROT_READ(可讀) , PROT_WRITE (可寫), PROT_EXEC (可執行), PROT_NONE(不可訪問)。flags由以下幾個常值指定:MAP_SHARED , MAP_PRIVATE , MAP_FIXED,其中,MAP_SHARED , MAP_PRIVATE必選其一,而MAP_FIXED則不推薦使用。offset參數一般設為0,表示從文件頭開始映射。參數addr指定文件應被映射到進程空間的起始地址,一般被指定一個空指針,此時選擇起始地址的任務留給內核來完成。函數的返回值為最後文件映射到進程空間的地址,進程可直接操作起始地址為該值的有效地址。這裏不再詳細介紹mmap()的參數,讀者可參考mmap()手冊頁獲得進一步的信息。

三、mmap()範例

下面將給出使用mmap()的兩個範例:範例1給出兩個進程通過映射普通文件實現共享內存通信;範例2給出父子進程通過匿名映射實現共享內存。系統調用mmap()有許多有趣的地方,下面是通過mmap()映射普通文件實現進程間的通信的範例,我們通過該範例來說明mmap()實現共享內存的特點及註意事項。

範例1:兩個進程通過映射普通文件實現共享內存通信

/*-------------map_normalfile1.c-----------*/
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
typedef struct{
    char name[4];
    int  age;
}people;
main(int argc, char** argv) // map a normal file as shared mem:
{
    int fd,i;
    people *p_map;
    char temp;
    
    fd=open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);
    lseek(fd,sizeof(people)*5-1,SEEK_SET);
    write(fd,"",1);
    
    p_map = (people*) mmap( NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0 );
    close( fd );
    temp = ‘a‘;
    for(i=0; i<10; i++)
    {
        temp += 1;
        memcpy( ( *(p_map+i) ).name, &temp,2 );
        ( *(p_map+i) ).age = 20+i;
    }
    printf(" initialize over \n ");
    sleep(10);
    munmap( p_map, sizeof(people)*10 );
    printf( "umap ok \n" );
}
/*-------------map_normalfile2.c-----------*/
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
typedef struct{
    char name[4];
    int  age;
}people;
main(int argc, char** argv)    // map a normal file as shared mem:
{
    int fd,i;
    people *p_map;
    fd=open( argv[1],O_CREAT|O_RDWR,00777 );
    p_map = (people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    for(i = 0;i<10;i++)
    {
    printf( "name: %s age %d;\n",(*(p_map+i)).name, (*(p_map+i)).age );
    }
    munmap( p_map,sizeof(people)*10 );
}

  

範例2:父子進程通過匿名映射實現共享內存

#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
typedef struct{
    char name[4];
    int  age;
}people;
main(int argc, char** argv)
{
    int i;
    people *p_map;
    char temp;
    p_map=(people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
    if(fork() == 0)
    {
        sleep(2);
        for(i = 0;i<5;i++)
            printf("child read: the %d people‘s age is %d\n",i+1,(*(p_map+i)).age);
        (*p_map).age = 100;
        munmap(p_map,sizeof(people)*10); //實際上,進程終止時,會自動解除映射。
        exit();
    }
    temp = ‘a‘;
    for(i = 0;i<5;i++)
    {
        temp += 1;
        memcpy((*(p_map+i)).name, &temp,2);
        (*(p_map+i)).age=20+i;
    }
    sleep(5);
    printf( "parent read: the first people,s age is %d\n",(*p_map).age );
    printf("umap\n");
    munmap( p_map,sizeof(people)*10 );
    printf( "umap ok\n" );
}

  

四、shm內存共享的主要函數講解

shmget函數

該函數用來創建共享內存:
int shmget(key_t key, size_t size, int shmflg);
參數:
key : 和信號量一樣,程序需要提供一個參數key,
      它有效地為共享內存段命名。
      
      有一個特殊的鍵值IPC_PRIVATE, 
      它用於創建一個只屬於創建進程的共享內存,
      通常不會用到。
size: 以字節為單位指定需要共享的內存容量。
shmflag: 包含9個比特的權限標誌,
         它們的作用與創建文件時使用的mode標誌是一樣。
         由IPC_CREAT定義的一個特殊比特必須和權限標誌按位或
         才能創建一個新的共享內存段。

NOTE:
權限標誌對共享內存非常有用,
因為它允許一個進程創建的共享內存可以被共享內存的創建者所擁有的進程寫入,
同時其它用戶創建的進程只能讀取共享內存。

我們可以利用這個功能來提供一種有效的對數據進行只讀訪問的方法,
通過將數據放共享內存並設置它的權限,
就可以避免數據被其他用戶修改。

返回值:
創建成功,則返回一個非負整數,即共享內存標識;
如果失敗,則返回-1.

  

shmat函數

第一次創建共享內存段時,它不能被任何進程訪問。要想啟動對該內存的訪問,必須將其連接到一個進程的地址空間。
這個工作由shmat函數完成:
void *shmat(int shm_id, const void *shm_addr, int shmflg);
參數:
shm_id : 由shmget返回的共享內存標識。
shm_add: 指定共享內存連接到當前進程中的地址位置。
         它通常是一個空指針, 
         表示讓系統來選擇共享內存出現的地址。
shmflg : 是一組標誌。
         它的兩個可能取值是:
         SHM_RND, 和shm_add聯合使用,
                  用來控制共享內存連接的地址。
         SHM_RDONLY, 它使連接的內存只讀

返回值:
如果調用成功, 返回一個指向共享內存第一個字節的指針;
如果失敗,返回-1.

共享內存的讀寫權限由它的屬主(共享內存的創建者),
它的訪問權限和當前進程的屬主決定。
共享內存的訪問權限類似於文件的訪問權限。

  

shmdt函數

將共享內存從當前進程中分離。
int shmdt(const void *shm_addr);
shm_addr: shmat返回的地址指針。

成功時,返回0,
失敗時,返回-1.

NOTE:
共享內存分離並未刪除它,
只是使得該共享內存對當前進程不再可用。

  

shmctl函數

共享內存的控制函數
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
shmid_ds結構至少包含以下成員:
struct shmid_ds {
  uid_t shm_perm.uid;
  uid_t shm_perm.gid;
  mode_t shm_perm.mode;
}

參數:
shm_id : 是shmget返回的共享內存標識符。
command: 是要采取的動作,
         它可以取3個值:

IPC_STAT  把shmid_ds結構中的數據設置為共享內存的當前關聯值
IPC_SET   如果進程有足夠的權限,
          就把共享內存的當前關聯值設置為shmid_ds結構中給出的值
IPC_RMID  刪除共享內存段

buf    : 是一個指針,
         包含共享內存模式和訪問權限的結構。

返回值:
成功時,返回0,
失敗時,返回-1.

  

shm內存共享可參考:http://blog.chinaunix.net/uid-26000296-id-3421346.html

mmap和shm共享內存的區別和聯系