【linux】系統程式設計-4-共享記憶體
阿新 • • 發佈:2020-12-31
目錄
前言
- 知識點
- 訊息佇列、訊號量 和 共享記憶體 被統稱為 system-V IPC
- 以上都是“持續性”資源,即它們被建立之後, 不會因為程序的退出而消失
- 訊息佇列、訊號量 和 共享記憶體 被統稱為 system-V IPC
6. 共享記憶體
6.1 概念
- 共享記憶體
- 共享記憶體是程序間通訊中最簡單的方式之一
- 是效率最高的一種IPC通訊機制
- 它允許多個不相關的程序訪問同一個邏輯記憶體
- 注意:
- 共享記憶體需要使用者自己維護,如同步、互斥等工作
- 共享記憶體屬於 臨界資源
- 所以,共享記憶體一般配合訊號量、互斥鎖等協調機制使用
- 共享記憶體優點
- 通訊方便
- 介面簡單
- 高效率
- 任意程序都可對共享記憶體進行讀寫
- 共享記憶體缺點
- 沒有提個同步、互斥等機制,需要使用者自己維護
6.2 操作函式
- 使用 shmget 函式建立或獲取一個共享記憶體物件
- 使用 shmat 函式對映到程序的虛擬地址
- 使用 shmdt 函式解除對映,解除對映後,該解除不能再訪問該共享記憶體
- 使用 shmctl 函式獲取或設定共享記憶體的相關屬性
6.2.1 shmget()
- 使用 shmget() 建立或獲取一個訊號量
- 通過命令 man 瞭解更多
- 所需要的標頭檔案:
#include <sys/ipc.h> #include <sys/shm.h>
- 函式原型:
int shmget(key_t key, size_t size, int shmflg);
- key:標識共享記憶體的鍵值
- 0 或 IPC_PRIVATE:建立一個新的共享記憶體
- 大於 0 的 32 位整數:視引數 shmflg 來確定操作
- size:
- 新建的共享記憶體大小,以位元組為單位,但是會以 頁 來取整
- shmflg:
- 0:取共享記憶體識別符號,若不存在則函式會報錯
- IPC_CREAT:如果核心中不存在該共享記憶體,則建立一個新的共享記憶體;如果存在,則呼叫
- IPC_CREAT|IPC_EXCL:如果核心中不存在該共享記憶體,則建立一個新的共享記憶體;如果存在,則報錯
- 返回:
- 成功:返回該共享記憶體的識別符號
- 失敗:返回 -1,錯誤原因記錄在變數 error 中:
- EINVAL:引數size小於 SHMMIN 或大於 SHMMAX
- EEXIST:預建立key所指的共享記憶體,但已經存在
- EIDRM:引數key所指的共享記憶體已經刪除
- ENOSPC:超過了系統允許建立的共享記憶體的最大值(SHMALL)
- ENOENT:引數key所指的共享記憶體不存在,而引數shmflg未設IPC_CREAT位
- EACCES:沒有許可權
- ENOMEM:核心記憶體不足
- key:標識共享記憶體的鍵值
6.2.2 shmat()
- 使用 shmat() 函式對映到程序的虛擬地址
- 通過命令 man 瞭解更多
- 所需要的標頭檔案:
#include <sys/types.h> #include <sys/shm.h>
- 函式原型:
void *shmat(int shmid, const void *shmaddr, int shmflg);
- shmid:共享記憶體標識
- shmaddr:
- 不為 NULL:系統會根據 shmaddr 來選擇一個合適的記憶體區域
- 為 NULL:系統會自動選擇一個合適的虛擬記憶體空間地址去對映共享記憶體
- shmflg:操作共享記憶體的方式
- SHM_RDONLY:以只讀方式對映共享記憶體(其它為讀寫模式)
- SHM_REMAP:重新對映,此時的 shmaddr 不能為 NULL
- NULLSHM:自動選擇比 shmaddr 小的最大也對齊地址
- 返回:
- 成功:返回共享記憶體的起始地址
- 失敗:返回 -1,錯誤原因記錄在變數 error 中:
- EACCES:無許可權以指定方式連線共享記憶體
- EINVAL:無效的引數shmid或shmaddr
- ENOMEM:核心記憶體不足
- 注意:
- 共享記憶體只能以 只讀和讀寫方式對映,不能以只寫方式對映
- shmat()第二個引數 shmaddr
- 一般都設為 NULL ,讓系統自動找尋合適的地址。
- 如果不為 NULL ,
- 那麼要求 SHM_RND 在 shmflg 必須被設定,系統便會將選擇比 shmaddr 小而又最大的頁對齊地址(即為SHMLBA的整數倍)作為共享記憶體區域的起始地址。(相當於自動對齊)
- 如果沒有設定 SHM_RND,那麼 shmaddr 必須是嚴格的頁對齊地址。(手動對齊)
6.2.3 shmdt()
- 使用 shmdt 函式解除對映,解除對映後,該解除不能再訪問該共享記憶體
- 通過命令 man 瞭解更多
- 所需要的標頭檔案:
#include <sys/types.h> #include <sys/shm.h>
- 函式原型:
int shmdt(const void *shmaddr);
- shmaddr:對映的共享記憶體的起始地址
- 返回:
- 成功:返回 0
- 失敗:返回 -1,錯誤原因記錄在變數 error 中:
- EINVAL:無效的引數shmaddr
- 說明:
- 本函式呼叫並不刪除所指定的共享記憶體區,而只是將先前用shmat函式連線(attach)好的共享記憶體脫離(detach)目前的程序
6.2.4 shmctl()
- 使用 shmctl 函式獲取或設定共享記憶體的相關屬性
- 通過命令 man 瞭解更多
- 所需要的標頭檔案:
#include <sys/types.h> #include <sys/shm.h>
- 函式原型:
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- shmid:共享記憶體識別符號
- cmd:控制命令
- IPC_STAT:獲取屬性資訊,放置到buf中
- IPC_SET:設定屬性資訊為buf指向的內容
- IPC_RMID:刪除該共享記憶體
- IPC_INFO:獲得關於共享記憶體的系統限制值資訊
- SHM_INFO:獲得系統為共享記憶體消耗的資源資訊
- SHM_STAT:與IPC_STAT具有相同的功能,但shmid為該SHM在核心中記錄所有SHM資訊的陣列的下標, 因此通過迭代所有的下標可以獲得系統中所有SHM的相關資訊
- SHM_LOCK:禁止系統將該SHM交換至swap分割槽
- SHM_UNLOCK:允許系統將該SHM交換至swap分。
- buf:共享記憶體管理結構體
struct shmid_ds { struct ipc_perm shm_perm; /* 所有權和許可權 */ size_t shm_segsz; /* 共享記憶體尺寸(位元組) */ time_t shm_atime; /* 最後一次對映時間 */ time_t shm_dtime; /* 最後一個解除對映時間 */ time_t shm_ctime; /* 最後一次狀態修改時間 */ pid_t shm_cpid; /* 建立者PID */ pid_t shm_lpid; /* 後一次對映或解除對映者PID */ shmatt_t shm_nattch; /* 對映該SHM的程序個數 */ ...}; struct ipc_perm { key_t __key; /* 該共享記憶體的鍵值key */ uid_t uid; /* 所有者的有效UID */ gid_t gid; /* 所有者的有效GID */ uid_t cuid; /* 建立者的有效UID */ gid_t cgid; /* 建立者的有效GID */ unsigned short mode; /* 讀寫許可權 + SHM_DEST + SHM_LOCKED 標記 */ unsigned short __seq; /* 序列號 */ };
- 返回:
- 成功:0
- 失敗:返回 -1,錯誤原因記錄在變數 error 中:
- EACCESS:引數cmd為IPC_STAT,卻無許可權讀取該共享記憶體
- EFAULT:引數buf指向無效的記憶體地址
- EIDRM:識別符號為shmid的共享記憶體已被刪除
- EINVAL:無效的引數cmd或shmid
- EPERM:引數cmd為IPC_SET或IPC_RMID,卻無足夠的許可權執行
6.3 例子
- 共享記憶體寫程序
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include "sem.h"
int main()
{
int running = 1;
void *shm = NULL; // 共享記憶體首地址(*本程序的虛擬地址*)
struct shared_use_st *shared = NULL;
char buffer[BUFSIZ + 1];
int shmid; //共享記憶體識別符號
int semid; //訊號量識別符號
//建立共享記憶體
shmid = shmget((key_t)1234, 4096, 0644 | IPC_CREAT);
if(shmid == -1)
{
fprintf(stderr, "shmget failed\n");
exit(EXIT_FAILURE);
}
//將共享記憶體連線到當前程序的地址空間
shm = shmat(shmid, (void*)0, 0);
if(shm == (void*)-1)
{
fprintf(stderr, "shmat failed\n");
exit(EXIT_FAILURE);
}
printf("Memory attached at %p\n", shm);
/* 開啟訊號量,不存在則建立一個訊號量 */
semid = semget((key_t)6666, 1, 0666|IPC_CREAT);
if(semid == -1)
{
printf("sem open fail\n");
exit(EXIT_FAILURE);
}
while(running)//向共享記憶體中寫資料
{
//向共享記憶體中寫入資料
printf("Enter some text: ");
fgets(buffer, BUFSIZ, stdin); // 阻塞等待標準輸入
strncpy(shm, buffer, 4096);
sem_v(semid);/* 釋放訊號量 */
//輸入了end,退出迴圈(程式)
if(strncmp(buffer, "end", 3) == 0)
running = 0;
}
//把共享記憶體從當前程序中分離
if(shmdt(shm) == -1)
{
fprintf(stderr, "shmdt failed\n");
exit(EXIT_FAILURE);
}
sleep(2);
exit(EXIT_SUCCESS);
}
- 共享記憶體讀程序
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include "sem.h"
int main(void)
{
int running = 1;//程式執行標誌
char *shm = NULL;// 共享記憶體首地址(*本程序的虛擬地址*)
int shmid;// 共享記憶體識別符號
int semid;// 訊號量識別符號
//建立共享記憶體
shmid = shmget((key_t)1234, 4096, 0666 | IPC_CREAT);
if(shmid == -1)
{
fprintf(stderr, "shmget failed\n");
exit(EXIT_FAILURE);
}
//將共享記憶體連線到當前程序的地址空間
shm = shmat(shmid, 0, 0);
if(shm == (void*)-1)
{
fprintf(stderr, "shmat failed\n");
exit(EXIT_FAILURE);
}
printf("\nMemory attached at %p\n", shm);
/* 開啟訊號量,不存在則建立一個訊號量 */
semid = semget((key_t)6666, 1, 0666|IPC_CREAT); /* 建立一個訊號量 */
if(semid == -1)
{
printf("sem open fail\n");
exit(EXIT_FAILURE);
}
init_sem(semid, 0); // 初始化訊號量的值為 0
while(running)// 讀取共享記憶體中的資料
{
/** 等待訊號量 */
if(sem_p(semid) == 0)
{
printf("You wrote: %s", shm);
sleep(rand() % 3);
//輸入了end,退出迴圈(程式)
if(strncmp(shm, "end", 3) == 0)
running = 0;
}
}
del_sem(semid); /* 刪除訊號量 */
//把共享記憶體從當前程序中分離
if(shmdt(shm) == -1)
{
fprintf(stderr, "shmdt failed\n");
exit(EXIT_FAILURE);
}
//刪除共享記憶體
if(shmctl(shmid, IPC_RMID, 0) == -1)
{
fprintf(stderr, "shmctl(IPC_RMID) failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
參考:
* 野火