同一塊物理內存被映射到進程A、B各自的進程地址空間。進程A可以即時看到進程B對共享內存中數據的更新,反之亦然。所以必須要某種同步機制來保證該共享內存中資源的準確性。
共享內存相關函數
shmget()創建共享內存並返回標識符
頭文件 #include<sys/shm.h>
int shmget(key_t key, size_t size, int shmflag);
第一個參數key,與信號量一樣,程序需要提供一個參數key,有效地爲共享內存段命名。有一個特殊的鍵值:IPC_PRIVATE,它用於創建一個只屬於創建進程的共享內存,通常你不會用到這個值。
第二個參數size,以字節爲單位指定需要共享的內存容量。
第三個參數shmflag,與創建文件時使用的mode、標誌一樣,由IPC_CREAT定義的一個特殊比特必須和權限標誌按位或才能創建一個新的共享內存段。如果無需用到IPC_CREAT標誌,該標誌就會被悄悄忽略。
如果共享內存創建成功,shmget返回一個共享內存標識符(非負整數),該標識符將用於後續的共享內存函數。如果失敗了就返回-1.
shmat()函數將共享內存鏈接到當前進程
void* shmat(int shm_id, const void* shm_addr, int shmflg);
第一個參數shm_id是由shmget函數返回的共享內存標識符
第二個參數shm_addr指定的是共享內存鏈接到當前進程中的地址位置。它通常是一個空指針,表示讓系統來選擇共享內存出現的地址
第三個參數shmflg十一組位標誌。它的兩個可能取值是SHM_RND和SHM_RDONLY。其中SHM_RND與shm_addr聯合使用,用來控制共享內存的鏈接地址。SHM_RDONLY使得鏈接的內存只讀。
我們很少需要控制共享內存鏈接的地址,通常都是讓系統來選擇一個地址,否則就會使應用程序對硬件的依賴過高。
如果shmat調用成功,它返回一個指向共享內存第一個字節的指針:如果失敗,它就返回-1。
共享內存的訪問權限類似於文件的訪問權限,即共享內存的讀寫權限由它的屬主(共享內存的創建者)、它的訪問權限和當前進程的屬主決定。這個規則的一個例外是:當shmflg&SHM_RDONLY爲true的時,即使該共享內存的訪問權限允許寫操作,它都不能被寫入。
shmdt()函數將共享內存從當前進程中分離。
它的參數是shmat返回的地址指針。
成功時返回0,失敗時返回-1
注意:將共享內存分離並未刪除它,只是使得該共享內存對當前進程不再可用。
shmctl()共享內存控制函數
int shmctl(int shm_id, int command, struct shmid_ds *buf);
第一個參數shm_id是shmget返回的共享內存標識符。
第二個參數command是要採取的動作,它可以取三個值:
IPC_STAT 把shmid_ds結構中的數據設置爲共享內存的當前關聯值
IPC_SET 如果進程有足夠的權限,就把共享內存當前的關聯值設置爲shmid_ds結構中給出的值
IPC_RMID 刪除共享內存段
第三個參數buf是一個指針,它指向包含共享內存模式和訪問權限的結構。
成功時返回0,失敗時返回-1
shmid_ds 結構至少包含以下成員:
struct shmid_ds
{
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
}
具體應用舉例
我們首先申請一塊256字節的共享內存,讓進程一往其中寫數據,進程B從中讀數據。並且在進程一寫數據時,進程二不能讀取這塊共享內存的數據,而進程二讀取的時候,進程一不能向其中寫數據。
這裏我們就又用到了之前講過的信號量,再通過以下代碼回顧一下:
/*
sem.c
*/
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<sys/sem.h>
#include"sem.h"
static int semid = -1;
void sem_init()
{
semid = semget((key_t)1234, 1,IPC_CREAT | IPC_EXCL | 0600);
if(semid == -1)
{
semid = semget((key_t)1234, 1, 0600);
if(semid == -1)
{
perror("semget error");
}
}
else
{
union semun a;
a.val = 0;
//注意:這裏的初始值和之前的不同
if(semctl(semid, 0, SETVAL, a) == -1)
{
perror("semctl error");
}
}
}
void sem_p()
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = -1;
buf.sem_flg = SEM_UNDO;
if(semop(semid, &buf, 1) == -1)
{
perror("semop p error");
}
}
void sem_v()
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = 1;
buf.sem_flg = SEM_UNDO;
if(semop(semid, &buf, 1) == -1)
{
perror("semop v error");
}
}
void sem_destroy()
{
if(semctl(semid, 0, IPC_RMID) == -1)
{
perror("del error");
}
}
然後我們把pv操作的頭文件也寫好:
/*
sem.h
*/
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<sys/sem.h>
union semun
{
int val;
};
void sem_init();
void sem_p();
void sem_v();
void sem_destroy();
接下來就是兩個進程的代碼了,我們把shma.c作爲寫入的程序,shmb.c作爲讀取的程序,則:
/*
shma.c 往共享內存中寫入數據
*/
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<sys/sem.h>
#include<sys/shm.h>
#include"sem.h"
int main()
{
int shmid = shmget((key_t)2345, 256, IPC_CREAT | 0600); //創建共享內存
assert(shmid != -1);
char*s = (char*)shmat(shmid, NULL, 0); //把共享內存連接到該進程空間
sem_init(); //信號量初始化
while(1)
{
char buff[128] = {0};
printf("input:\n");
fgets(buff, 128, stdin);
strcpy(s,buff);
sem_v(); //進行v操作,把信號量的值改爲1,通知shmb可以訪問共享內存了
if(strncmp(buff, "end", 3) == 0) //如果寫入的是end,就結束該進程
{
break;
}
}
shmdt(s);
exit(0);
}
/*
shmb.c 從共享內存中讀取數據
*/
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<sys/sem.h>
#include<sys/shm.h>
#include"sem.h"
int main()
{
int shmid = shmget((key_t)2345, 256, IPC_CREAT | 0600);
assert(shmid != -1);
char* p = (char*)shmat(shmid, NULL, 0);
sem_init();
while(1)
{
sem_p(); //從共享內存讀取數據時,不允許其他人訪問
if(strncmp(p, "end", 3) == 0)
{
break;
}
printf("%s\n", p);
}
shmdt(p); //將共享內存與進程空間的鏈接斷開
sem_destroy();
shmctl(shmid, IPC_RMID, NULL); //刪除該塊共享內存
exit(0);
}
注意編譯時要帶上sem.c,如:gcc -o shma shma.c sem.c
並且我們可以使用 ipcs -m 命令查看申請好的共享內存。
這時,我們再用ipcs -m 命令查看時,之前申請的共享內存已經沒有了。