上一篇博文提到的系統調用mmap通過映射一個普通文件實現共享內存。那麼本文中介紹的System V 共享內存則是通過映射特殊文件系統shm中的文件實現進程間的共享內存通信。也就是說,每個共享內存區域對應特殊文件系統shm中的一個文件。執行過程是先調用shmget,再調用shmat。對於每個共享的內存區,內核維護如下的信息結構,定義在
//System V 共享內存基本數據結構 struct shmid_ds { struct ipc_perm shm_perm; /* Ownership and permissions: System V IPC所共有的數據結構 */ size_t shm_segsz; /* Size of segment (bytes): 共享內存段的大小 */ time_t shm_atime; /* Last attach time */ time_t shm_dtime; /* Last detach time */ time_t shm_ctime; /* Last change time */ pid_t shm_cpid; /* PID of creator */ pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */ shmatt_t shm_nattch; /* No. of current attaches */ ... };
System V共享內存常用API
#include#include /* 創建一個新的內存共享區或者訪問一個已經存在的共享內存區 返回共享內存區標識符 */ int shmget(key_t key, size_t size, int shmflg); /* 創建或打開一個共享內存區後,調用shmat把它連接到調用進程的地址空間 */ void *shmat(int shmid, const void *shmaddr,int shmflg); /*
當一個進程完成某個共享內存區的使用時,調用shmdt斷開這個內存區 */ int shmdt(const void *shmaddr); /* 對內存區進行多種操作 cmd取值: IPC_RMID:從系統中刪除由shmid標識的共享內存區並拆除它 IPC_SET:給指定的共享內存區設置其shmid_ds結果成員 IPC_STAT:通過buff參數向調用者返回所指定共享內存區當前的shmid_ds結構 */ int shmctl(int shmid, int cmd, struct shmid_ds *buf);
調用shmget函數使用指定的路徑名和長度創建一個共享內存區,如下:
int shmget(key_t key, size_t size, int shmflg);
創建共享內存,並將該內存的內容初始化為0;
打開一個已經存在共享內存, 如果打開時不知道共享內存的大小, 可以將size指定為0, shmflg可以指定為0(按照默認的權限打開);
參數:
key:這個共享內存段名字;
size:共享內存大小(bytes);
shmflg:用法類似msgget中的msgflg參數;
返回值:
成功返回一個非負整數,即該共享內存段的標識碼;失敗返回-1
/**示例: 創建並打開一個共享內存 **/ int main(int argc,char **argv) { const int SHM_SIZE = 1024; int shmid = shmget(0x1234, SHM_SIZE, 0666|IPC_CREAT); if (shmid == -1) err_exit("shmget error"); cout << "share memory get success" << endl; }shmat
void *shmat(int shmid, const void *shmaddr, int shmflg);
連接到本進程地址空間, 成功連接之後, 對該內存的操作就與malloc來的一塊內存非常類似了, 而且如果這塊內存中有數據, 則就可以直接將其中的數據取出來。
參數:
shmaddr:指定連接的地址(大小不知道的話推薦使用NULL)
shmflg:一般指定為0, 表示可讀,可寫; 而它的另外兩個可能取值是SHM_RND和SHM_RDONLY(見下)
返回值:
成功返回一個指針,指向共享內存起始地址;失敗返回(void *) -1
shmaddr與shmflg組合說明
shmaddr為NULL
Linux內核自動為進程連接到進程的內存(推薦使用)
shmaddr不為NULL且shmflg無SHM_RND標記
以shmaddr為連接地址
shmaddr不為NULL且shmflg設置了SHM_RND標記
連接的地址會自動向下調整為SHMLBA的整數倍;
公式:shmaddr - (shmaddr % SHMLBA)
SHMLBA為內存頁面的大小(4K)
shmflg=SHM_RDONLY
只讀共享內存, 不然的話就是可讀,可寫的, 注意: 此處沒有可讀,可寫這個概念
shmdt當一個進程完成某個共享內存區的使用時,調用shmdt斷開這個內存區:
int shmdt(const void *shmaddr);
參數:
shmaddr: 由shmat所返回的指針
注意:將共享內存段與當前進程脫離不等於刪除共享內存段
/** 示例: 將數據寫入/讀出共享內存 程序write: 將數據寫入共享內存 程序read: 將數據讀出共享內存(當然, 可以讀取N多次) **/ //write程序 struct Student { char name[26]; int age; }; int main(int argc,char **argv) { int shmid = shmget(0x1234, sizeof(Student), 0666|IPC_CREAT); if (shmid == -1) err_exit("shmget error"); // 以可讀, 可寫的方式連接該共享內存 Student *p = (Student *)shmat(shmid, NULL, 0); if (p == (void *)-1) err_exit("shmat error"); strcpy(p->name, "xiaofang"); p->age = 22; shmdt(p); }
//read程序 int main(int argc,char **argv) { int shmid = shmget(0x1234, 0, 0); if (shmid == -1) err_exit("shmget error"); // 以只讀方式連接該共享內存 Student *p = (Student *)shmat(shmid, NULL, 0); if (p == (void *)-1) err_exit("shmat error"); // 直接將其中的內容打印輸出 cout << "name: " << p->name << ", age: " << p->age << endl; shmdt(p); }shmctl
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
IPC_RMID:從系統中刪除由shmid標識的共享內存區並拆除它 IPC_SET:給指定的共享內存區設置其shmid_ds結果成員 IPC_STAT:通過buff參數向調用者返回所指定共享內存區當前的shmid_ds結構
System V共享內存小結:
1)如果共享內存已經與所有訪問它的進程斷開了連接,則調用IPC_RMID子命令後,系統將立即刪除共享內存的標識符,並刪除該共享內存區,以及所有相關的數據結構;
2)如果仍有別的進程與該共享內存保持連接,則調用IPC_RMID子命令後,該共享內存並不會被立即從系統中刪除,而是被設置為IPC_PRIVATE狀態,並被標記為"已被刪除"(使用ipcs命令可以看到dest字段);直到已有連接全部斷開,該共享內存才會最終從系統中消失。
3)另外:一旦通過shmctl對共享內存進行了刪除操作,則該共享內存將不能再接受任何新的連接,即使它依然存在於系統中!所以,可以確知,在對共享內存刪除之後不可能再有新的連接,則執行刪除操作是安全的;否則,在刪除操作之後如仍有新的連接發生,則這些連接都將可能失敗。
/** 示例: 刪除共享內存 **/ int main(int argc,char *argv[]) { int shmid = shmget(0x1234, 0, 0); if (shmid == -1) err_exit("shmget error"); if (shmctl(shmid, IPC_RMID, NULL) == -1) err_exit("shmctl IPC_RMID error"); cout << "share memory remove success" << endl; }