前幾天上進程間通訊的課程,看到有同學對共享內存有些不是很明白,而且在查man幫助的時候也不是很能明白系統對其的說明,故有了這篇文章。
共享內存是系統在做進程間通訊時比較常用的IPC通訊方式之一,同時也是效率高的,但是由于其的獨特性導致在使用共享內存的時候需要注意一點就是幾個進程間通訊時的互斥和同步問題,所以在使用共享內存的時候,我們一般要對其加鎖或者加一些其他的同步機制,比如信號燈之類的。
首先看下共享內存的數據結構:
共享內存區的數據結構
每個共享內存段在內核中維護著一個內部結構shmid_ds,該結構定義在linux/shm.h中,代碼如下所示:
struct shmid_ds {
struct ipc_perm shm_perm;/* 超作許可權數據結構指針 */
int shm_segsz; /* 共享存儲段大小 (bytes) */
time_t shm_atime; /* 后調用shmat時間 */
time_t shm_dtime; /* 后調用shmdt的時間 */
time_t shm_ctime; /* 后調用shmctl的改變的時間 */
unsigned short shm_cpid; /*創建者的進程ID */
unsigned short shm_lpid; /* 后對共享存儲段進行操作的進程ID */
short shm_nattch; /* 當前連接數 */
};
共享內存的創建和操作
A:共享內存區的創建
linux下使用函數shmget來創建一個共享內存區,或者訪問一個已經存在的共享內存區,該函數定義如下:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
返回:失敗-1, 成功返回非負的共享存儲段id。
第一個參數key是共享存儲關鍵字。它有特殊值IPC_PRIVATE表示總是創建一個進程私有的共享存儲段。當key值不等于IPC_PRIVATE時,shmget動作取決于后一個參數shmflg標志:
1. IPC_CREAT 單獨設置此標志,當系統中不存在相同key時,創建一個新的共享存儲段,否則返回已存在的共享存儲段。
2. IPC_EXCL 單獨設置不起作用。與 IPC_CREAT同時設置時,當系統中存在相同key時,錯誤返回。保證不會打開一個已存在的共享存儲段。
如果沒有指定IPC_CREATE并且系統中不存在相同key值的共享存儲段,將失敗返回。
第二個參數size指明要求的共享存儲段的大小。當key指定的共享存儲段已存在時,取值范圍為0和已存在共享段大小;蚝唵沃付0。成功后此函數返回共享存儲段id,同時創建于參數key相連的數據結構shmid_ds。此節構為系統內部使用。
第三個參數也可以設置共享存儲段的訪問權限,用或于上面的值操作。
B:共享內存區的操作
在使用共享內存區之前,必須通過shmat函數將其附加到進程的地址空間,進程和地址空間就建立了連接。shmat調用成功后就返回一個共享內存區指針,使用該指針就可以訪問共享內存區了。
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
返回:失敗-1并置errno, 成功返回連接的實際地址。
第一個參數,必須由shmget返回的存儲段的id。
第二個參數指明共享存儲段要連接到的地址。0,系統為我們創建一個適當的地址值。否則自己指定。
第三個參數shmflg可以設成:SHM_RND, SHM_RDONLY。SHM_RND為自己指定連接地址時用。SHM_RDONLY說明此存儲段只讀。
當進程結束使用共享內存區時,就要通過函數shmdt斷開于共享內存區的連接。函數介紹如下:
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void * shmaddr);
返回:失敗-1并置errno, 成功返回0
分離由shmaddr指定的存儲段。此值應該由shmat返回的值。
此函數不是刪除共享存儲段,而是從當前進程分離存儲段。
當進程推出時,系統會自動分離它連接的所有共享段。
共享內存區的控制
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
存儲段控制函數?色@得shmid_ds全部內容。
返回:失敗-1并置errno, 成功返回0。
第一個參數,必須由shmget返回的存儲段的id。cmd為指定要求的操作。
CMD 說明 參數
IPC_STAT 放置與shmid相連的shmid_ds結構當前值于buf所指定用戶區 buf
IPC_SET 用buf指定的結構值代替與shmid相連的shmid_ds結構值 buf
IPC_RMID 刪除制定的信號量集合
SHM_LOCK 鎖住共享存儲段。只能由超級管理員使用
SHM_UNLOCK unlock共享存儲段。只能由超級管理員使用
附例子:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <errno.h>
#define SHM_SIZE 1024
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
};
/*創建信號量函數*/
int createsem (const char *pathname,int proj_id,int members,int init_val)
{
key_t msgkey;
int index,sid;
union semun semopts;
if ((msgkey = ftok(pathname,proj_id)) == -1) {
perror ("ftok error1\n");
return -1;
}
if ((sid = semget (msgkey,members,IPC_CREAT |0666)) == -1) {
perror("semget call failed!");
return -1;
}
/*初始化操作*/
semopts.val = init_val;
for (index = 0;index < members; index ++){
semctl(sid,index,SETVAL,semopts);
}
return (sid);
}
/*打開信號量函數*/
int opensem (const char *pathname,int proj_id)
{
key_t msgkey;
int sid;
if ((msgkey = ftok(pathname,proj_id)) == -1) {
perror ("ftok error1\n");
return -1;
}
if ((sid = semget (msgkey,0,IPC_CREAT |0666)) == -1) {
perror("semget call failed!");
return -1;
}
return (sid);
}
/*P操作函數*/
int sem_p(int semid,int index)
{
struct sembuf buf = {0,-1,IPC_NOWAIT};
if (index < 0) {
perror("index of array can not equals a minus value!");
return -1;
}
buf.sem_num = index;
if (semop(semid ,&buf,1) == -1) {
perror("a wrong operations to semaphore occured!");
return -1;
}
return 0;
}
/*V操作函數*/
int sem_v(int semid,int index)
{
struct sembuf buf = {0,+1,IPC_NOWAIT};
if (index < 0) {
perror("index of array can not equals a minus value!");
return -1;
}
buf.sem_num = index;
if (semop(semid ,&buf,1) == -1) {
perror("a wrong operations to semaphore occured!");
return -1;
}
return 0;
}
/*刪除信號集函數*/
int sem_delete(int semid)
{
return (semctl(semid,0,IPC_RMID));
}
/*等待信號集函數*/
int wait_sem (int semid,int index)
{
while (semctl(semid,index,GETVAL,0) == 0) {
sleep(1);
}
return 1;
}
/*創建共享內存函數*/
int createshm(char * pathname,int proj_id,size_t size)
{
key_t shmkey;
int sid;
/*獲取鍵值*/
if ((shmkey = ftok(pathname,proj_id)) == -1 ) {
perror("shmkey ftok error! \n");
return -1;
}
if ((sid = shmget(shmkey,size,IPC_CREAT | 0666)) == -1) {
perror("shmget call failed!\n");
return -1;
}
return (sid);
}
/* Write.c */
#include <string.h>
#include "sharemem.h"
int main(void)
{
int semid,shmid;
char *shmaddr;
char write_str[SHM_SIZE];
if ((shmid = createshm(".",'m',SHM_SIZE)) == -1) {
exit (1);
}
if ((shmaddr = shmat(shmid,(char*)0,0)) == (char *)-1) {
perror("attach shared memory error!\n");
exit (1);
}
if ((semid = createsem(".",'s',1,1)) == -1) {
exit(1);
}
while(1) {
wait_sem(semid,0);
sem_p(semid,0);
printf("write:");
fgets(write_str,1024,stdin);
int len = strlen(write_str) -1;
write_str[len] = '\0';
strcpy(shmaddr,write_str);
sleep(2); /*使reader處于阻塞狀態*/
sem_v(semid,0); /*v操作*/
sleep(2); /*等待reader進行讀操作*/
}
return 0;
}
/* Read.c */
#include "sharemem.h"
int main(void)
{
int semid,shmid;
char *shmaddr;
char write_str[SHM_SIZE];
if ((shmid = createshm(".",'m',SHM_SIZE)) == -1) {
exit (1);
}
if ((shmaddr = shmat(shmid,(char*)0,0)) == (char *)-1) {
perror("attach shared memory error!\n");
exit (1);
}
if ((semid = opensem(".",'s')) == -1) {
exit(1);
}
while(1) {
printf("reader:");
wait_sem(semid,0); /*等待信號值為1*/
sem_p(semid,0);
printf("%s\n",shmaddr);
sleep(2); /*使writer處于阻塞狀態*/
sem_v(semid,0); /*v操作*/
sleep(2); /*等待write進行寫操作*/
}
return 0;
}