前面講到socket的程序間通信方式。這樣的方式在程序間傳遞資料時首先須要從程序1位址空間中把資料複制到核心,核心再将資料複制到程序2的位址空間中,也就是資料傳遞須要經過核心傳遞。這樣在處理較多資料時效率不是非常高。而讓多個程序共享一片記憶體區則攻克了之前socket程序通信的問題。共享記憶體是最快的程序間通信 。将一片記憶體映射到多個程序位址空間中,那麼程序間的資料傳遞将不在涉及核心。
共享記憶體并非從某一程序擁有的記憶體中劃分出來的。程序的記憶體總是私有的。共享記憶體是從系統的空暇記憶體池中配置設定的,希望訪問它的每一個程序連接配接它。這個連接配接過程稱為映射。它給共享記憶體段配置設定每一個程序的位址空間中的本地位址。
mmap()系統調用使得程序之間通過映射同一個普通檔案實作共享記憶體。普通檔案被映射到程序位址空間後。程序能夠向訪問普通記憶體一樣對檔案進行訪問。不必再調用read(),write()等操作。
函數原型為:
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
當中參數addr為描寫叙述符fd應該被映射到程序空間的起始位址,當指定為NULL時核心将自己去選擇起始位址,不管addr是為NULL,函數傳回值都是fd所映射到記憶體的起始位址;
len是映射到調用程序位址空間的位元組數,它 從被映射檔案開頭offset個位元組開始算起,offset通常設定為0;
prot 參數指定共享記憶體的訪問權限。可取例如以下幾個值的或:PROT_READ(可讀) , PROT_WRITE (可寫), PROT_EXEC (可運作), PROT_NONE(不可訪問)。該值常設定為PROT_READ | PROT_WRITE 。
flags由下面幾個常值指定:MAP_SHARED , MAP_PRIVATE , MAP_FIXED,當中,MAP_SHARED(變動是共享的,對共享記憶體的改動全部程序可見) , MAP_PRIVATE(變動是私有的。對共享記憶體改動僅僅對該程序可見) 必選其一,而MAP_FIXED則不推薦使用 。
munmp() 删除位址映射關系。函數原型例如以下:
#include <sys/mman.h>
int munmap(void *addr, size_t length);
參數addr是由mmap傳回的位址。len是映射區大小。
程序在映射空間的對共享内容的改變并不直接寫回到磁盤檔案裡。往往在調用munmap()後才運作該操作。能夠通過調用msync()實作磁盤上檔案内容與共享記憶體區的内容一緻。 msync()函數原型為:
#include <sys/mman.h>
int msync(void *addr, size_t length, int flags);
參數addr和len代表記憶體區,flags有下面值指定,MS_ASYNC(運作異步寫), MS_SYNC(運作同步寫),MS_INVALIDATE(使快速緩存失效)。當中MS_ASYNC和MS_SYNC兩個值必須且僅僅能指定一個,一旦寫操作排入核心。MS_ASYNC馬上傳回,MS_SYNC要等到寫操作完畢後才傳回。假設還指定了MS_INVALIDATE,那麼與其終于拷貝不一緻的檔案資料的全部記憶體中拷貝都失效。
在使用open函數打開一個檔案之後調用mmap把檔案内容映射到調用程序的位址空間,這樣我們操作檔案内容僅僅須要對映射的位址空間進行操作。而無需再使用open。write等函數。
使用共享記憶體的步驟基本是:
open()建立記憶體段;
用 ftruncate()設定它的大小。
用mmap() 把它映射到程序記憶體,運作其它參與者須要的操作;
當使用完時,原來的程序調用 munmap()然後退出。
以下來看一個實作:
server程式建立記憶體并向共享記憶體寫入資料:
int sln_shm_get(char *shm_file, void **shm, int mem_len)
{
int fd;
fd = open(shm_file, O_RDWR | O_CREAT, 0644);//1. 建立記憶體段
if (fd < 0) {
printf("open <%s> failed: %s\n", shm_file, strerror(errno));
return -1;
}
ftruncate(fd, mem_len);//2.設定共享記憶體大小
*shm = mmap(NULL, mem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); //mmap映射系統記憶體池到程序記憶體
if (MAP_FAILED == *shm) {
printf("mmap: %s\n", strerror(errno));
return -1;
}
close(fd);
return 0;
}
int main(int argc, const char *argv[])
{
char *shm_buf = NULL;
sln_shm_get(SHM_IPC_FILENAME, (void **)&shm_buf, SHM_IPC_MAX_LEN);
snprintf(shm_buf, SHM_IPC_MAX_LEN, "hello share memory ipc! i'm server.");
return 0;
}
client程式映射共享記憶體并讀取當中資料:
int main(int argc, const char *argv[])
{
char *shm_buf = NULL;
sln_shm_get(SHM_IPC_FILENAME, (void **)&shm_buf, SHM_IPC_MAX_LEN);
printf("ipc client get: %s\n", shm_buf);
munmap(shm_buf, SHM_IPC_MAX_LEN);
return 0;
}
先執行server程式向共享記憶體寫入資料,再執行客戶程式。執行結果例如以下:
# ./server
# ./client
ipc client get: hello share memory ipc! i'm server.
#
共享記憶體不像socket那樣本身具有同步機制,它須要通過添加其它同步操作來實作同步,比方信号量等。同步相關操作在後面會有相關專欄具體叙述。