进程间通信之共享内存
共享内存就是不同进程之间共享的一块内存区域。一般情况下,每个进程都有自己独立的内存空间,一个进程不能直接访问其他进程的内存区域,这样就使得进程与进程之间不能交流,而共享内存的出现,则使进程之间的相互访问变成了现实。
共享内存是通过多个进程之间对内存段进行映射的方法来实现内存共享的,这是进程间通信的最快的一种方式,因为此方式没有中间过程,直接将某段内存进行映射,多个进程间的共享内存是同一块物理区间,仅仅是地址不同而已,即同一物理块内存被映射到多个进程各自的进程地址空间中,此时,进程A可以即时看到进程B对共享内存中数据的更新,从而实现进程间通信。
共享内存本身不带有任何互斥与同步机制,但当多个进程对同一内存进行读写操作时会破坏该内存的内容。所以在实际应用中,同步与互斥需要用户自己来完成。
共享内存操作函数
1.int shmget(key_t key,size_t size,int shmflg);
功能:用来创建共享内存
参数:key:共享内存段的名字,可直接指定,也可通过ftok()函数产生;size:共享内存的大小,可指定,也可通过调用getpagesize()函数来使用;shmflg:共享内存标记,常取值为IPC_CREAT或IPC_EXCL,单独使用IPC_CREAT时,表示如果共享内存不存在,则创建,如果存在,则打开并返回。但是IPC_EXCL单独使用是没有意义的。通常IPC_CREAT和IPC_EXCL一起使用,表示如果共享内存不存在,则创建,如果存在,则出错,返回。
返回值:若成功,返回共享内存段的标识码,失败,返回-1。
2.void* shmat(int shmid,const void* shmaddr,int shmflg);
功能:用来将内存段映射到进程地址空间
参数:shmid:共享内存的标识码,shmget()函数的返回值;shmaddr:指定的连接的地址,通常取NULL,表示系统内核会自动选择一个地址;shmflg:共享内存标记,可能取值为SHM_RND和SHM_RDONLY,表示只读内存
返回值:成功,返回一个指针,指向共享内存的第一个字节;失败,返回-1。
3.int shmdt(const void* shmaddr);
功能:将共享内存段与当前进程脱离
参数:shmaddr:指针,shmat()函数的返回值
返回值:成功,返回0;失败,返回-1。
4.int shmctl(int shmid,int cmd,struct shmid_ds* buffer);
功能:用于控制共享内存
参数:shmid:共享内存的标识码,shmget()函数的返回值;cmd:有3个可取值,一般使用IPC_RMID;buffer:结构体指针,表示向共享内存发送命令的参数。
cmd参数的可取值:
IPC_STAT 把shmid_ds结构中的数据设置为共享内存的当前关联值
IPC_SET 进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds结构中给出的值
IPC_RMID 删除共享内存段
共享内存的效率
采用共享内存进行进程间通信的最大的一个好处就是效率高,因为进程可以直接读写内存,只拷贝两次数据,一次是从文件区到共享内存区,一次是从共享内存区到输出文件。实际上,进程之间在使用共享内存进行通信时,并不总是读写少量数据后就解除映射,有新的通信时再重新建立共享内存区域,而是一直保持这个共享区域,知道通信完成为止,这样一来,数据内容就一直保存在共享内存中,并没有被写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率很高。
共享内存的特点
共享内存就是允许两个不相关的进程访问同一块内存区域
共享内存是两个正在运行的进程之间共享和传递数据的最有效的方式
不同进程之间的共享内存通常安排为同一段物理内存
共享内存不提供任何互斥与同步机制,一般与信号量一起使用来达到对临界资源进行保护的目标
接口简单
案例
1.创建共享内存
shmget.c
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main()
{
//获取内存页大小
int size = getpagesize();
printf("pagesize = %d\n",size);
//获取键值
key_t key = ftok("/tmp",0x123);
printf("key = 0x%x\n",key);
//创建共享内存
//一般情况下,创建时直接在IPC_CREAT后跟权限
int shmid = shmget(key,size,IPC_CREAT | 0666);
if(shmid == -1)
{
perror("shmget");
return -1;
}
printf("shmid = %d\n",shmid);
return 0;
}
编译运行之后可通过ipcs –m来查看共享内存的信息
2.对共享内存的读取操作1
shmwrite1.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/shm.h>
int main()
{
key_t key=ftok("/tmp",0x66);
int pagesize = getpagesize();
int shmid=shmget(key,pagesize,IPC_CREAT | 0666);
if(shmid == -1)
{
perror("shmget");
return -1;
}
//将共享内存映射到进程的地址空间
int *pNum=(int*)shmat(shmid,NULL,0);
*pNum = 666;
if(pNum==(void*)-1)
{
perror("shmat");
return -1;
}
*(pNum+1) = 888;
return 0;
}
shmread1.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/shm.h>
int main()
{
key_t key=ftok("/tmp",0x66);
int pagesize = getpagesize();
int shmid=shmget(key,pagesize,IPC_CREAT | 0666);
if(shmid == -1)
{
perror("shmget");
return -1;
}
//将共享内存映射到进程的地址空间
int *pNum=(int*)shmat(shmid,NULL,0);
if(pNum==(void*)-1)
{
perror("shmat");
return -1;
}
printf("pNum1 = %d,pNum2 = %d\n",*pNum,*(pNum + 1));
return 0;
}
运行时,先运行shmwrite1,将内容写到共享内存上,之后再运行shmread1读取
3.对共享内存的读取操作2
shmwrite2.c
#include <stdio.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define size 4096
typedef struct _Data
{
int flag;
char szMsg[size];
}Data;
int main()
{
//创建共享内存
key_t key = ftok("/tmp",0x1234);
int shmid = shmget(key,sizeof(Data),IPC_CREAT | 0666);
if(shmid == -1)
{
perror("shmget");
return -1;
}
//将共享内存映射到进程的内存空间
void *shm = shmat(shmid,NULL,0);
if((void*)(-1) == shm)
{
perror("shmat");
return -1;
}
Data* pdata = (Data*)shm;
pdata -> flag = 0;
int i = 0;
while(1)
{
if(pdata -> flag == 0)
{
sleep(2);
snprintf(pdata -> szMsg,sizeof(pdata -> szMsg),"Hello World %d",++i);
printf("Send Msg is [%s]\n",pdata -> szMsg);
pdata -> flag = 1;
}
}
//将共享内存与进程脱离
shmdt(shm);
return 0;
}
shmread2.c
#include <stdio.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define size 4096
typedef struct _Data
{
int flag;
char szMsg[size];
}Data;
int main()
{
//创建共享内存
key_t key = ftok("/tmp",0x1234);
int shmid = shmget(key,sizeof(Data),IPC_CREAT | 0666);
if(shmid == -1)
{
perror("shmget");
return -1;
}
//将共享内存映射到进程的内存空间
void *shm = shmat(shmid,NULL,0);
if((void*)(-1) == shm)
{
perror("shmat");
return -1;
}
Data* pdata = (Data*)shm;
while(1)
{
if(pdata -> flag == 1)
{
printf("Recvive Msg is [%s]\n",pdata -> szMsg);
pdata -> flag = 0;
}
}
//将共享内存与进程脱离
shmdt(shm);
//删除共享内存
shmctl(shmid,IPC_RMID,0);
return 0;
}
运行时需要开启两个终端
4.用共享内存实现客户端与服务器端的简单通信
Server_shm.c
#include <stdio.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main()
{
//创建共享内存
key_t key = ftok("/tmp",0x1234);
if(key < 0)
{
perror("ftok");
return -1;
}
int size = getpagesize();
int shmid = shmget(key,size,IPC_CREAT | 0666);
if(shmid < 0)
{
perror("shmget");
return -1;
}
//将共享内存映射到进程的地址空间
char* address = shmat(shmid,NULL,0);
int i = 0;
while(i < 26)
{
address[i] = 'A' + i;
i++;
address[i] = 0;
sleep(1);
}
//解除共享内存的映射
shmdt(address);
sleep(3);
//删除共享内存
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
Client_shm.c
#include <stdio.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main()
{
//创建共享内存
key_t key = ftok("/tmp",0x1234);
if(key < 0)
{
perror("ftok");
return -1;
}
int size = getpagesize();
int shmid = shmget(key,size,IPC_CREAT | 0666);
if(shmid < 0)
{
perror("shmget");
return -1;
}
//将共享内存映射到进程的地址空间
char* address = shmat(shmid,NULL,0);
int i = 0;
while(i < 26)
{
printf("client:%s\n",address);
i++;
sleep(1);
}
//解除共享内存的映射
shmdt(address);
return 0;
}
运行时开俩终端,分别执行客户端程序和服务器端程序。客户端中,当i = 26时,自动退出,3秒后,共享内存段销毁。
删除共享内存
共享内存删除时有两种方法:系统命令和shmctl()函数
shmctl()函数在上面的案例中已经使用过了,接下来我们来了解如何用命令删除共享内存。首先,使用命令ipcs –m先查看一下共享内存,之后再使用ipcrm –M命令来删除
可以看到,权限为666的两个共享内存段是我们进行上面案例演示时创建的。接下来我们删除它们。
我们看到,刚才的两个权限为666的共享内存段已经被删除。