共享存储
所谓共享存储,就是一块共享的内存区域,它可被多个进程以读或写的形式访问,以达到进程间通信的目的。
因为这个通信方式,发送端与接收端不需要来回复制要发送接收的信息,所以共享存储是最快的一种IPC(进程间通信方式)。
既然共享存储允许多个进程去访问,那么它必须满足同步与互斥原则,在发送端正在写的时候,接收端不应该去读。通常我们使用信号量或者互斥量来达到这一目的。
共享内存同消息队列,信号量相同,同属于XSI IPC。内核都为他们维护了一套数据结构。
共享内存的数据结构:
struct shmid_ds
{
struct ipc_perm shm_perm;
size_t shm_segsz;
pid_t shm_lpid;
pid_t shm_cpid;
shmatt_t shm_nattch;//shmatt_t定义为无符号整型,他至少和unsigned short一样大。
time_t shm_atime;
time_t shm_dtime;
time_t shm_ctime;
...
}
同样,系统会给每一个共享内存资源设立一个唯一的标识符key,我们可以通过函数ftok得到这个key。数据类型为key_t。
#include<sys/ipc.h>
key_t ftok(const char* path,int id);
path指向一个字符串,表示路径名,我们一般设为当前路径“.”,id为项目id,一般指定为一个0~255之间的数。
操作函数
获得共享存储
#include<sys/shm.h>
int shmget(key_t key,size_t size,int flag)
返回值:成功返回共享存储区的shmid,若失败,返回-1。
参数key即为我们调用ftok函数得到的标识符,作为系统对此共享存储资源的唯一标识。
参数size是要申请的共享存储区的大小,以字节为单位,这个数一般为系统页长的整数倍(4K的整数倍)。若申请的size不是页长的整数倍,系统也是按整数倍进行分配的,但是最后一页余下的部分不可用。如果创建一个新的资源,则必须指定size,且段的内容会被初始化为0。若引用一个已经存在的,则将size置为0。
参数flag与消息队列相同,有两个选项,IPC_CREAT和IPC_EXCL。使用的时候有两种情况:
1. IPC_CREAT和IPC_EXCL一起使用(IPC_CREAT|IPC_EXCL),表示申请创建一个新的IPC资源,若要申请的资源已经存在,则错误返回。若不存在,则创建。
2. IPC_CREAT单独使用,表示申请创建一个IPC资源,若要申请的IPC资源已经存在,则直接使用;若不存在,则创建新的。
一般我们还会在后面加上资源的默认权限(如0666)。
删除共享存储
#include<sys/shm.h>
int shmctl(int shmid,int cmd,struct shmid_ds *buf);
返回值:成功返回0,失败返回-1。
参数shmid即为我们调用shmget得到的共享存储的id。
参数cmd指定了要执行了命令,当cmd被设置为IPC_RMID时,该函数执行删除动作。此时,第三个参数设为NULL。
除此之外,cmd还有IPC_STAT,IPC_SET等命令。
将进程挂接到共享存储上
#include<sys/shm.h>
void* shmat(int shmid,const void* attr,int flag);
返回值:若成功,返回指向共享内存存储的指针,若失败,返回-1。
shmid不再多说,attr参数决定了共享存储段连接到调用进程的那个地址。一般我们设置为NULL或0,表示此段连接到由内核选择的第一个可用地址上。这是推荐的使用方式。
如果参数flag指定了SHM_RDONLY,则以只读方式连接,否则以读写的方式连接。
如果此函数成功执行的话,那么内核将使与该共享存储段相关的shmid_ds结构中的shm_nattch计数器值加1。
将进程从共享存储上卸载
#include<sys/shm.h>
int shmdt(const void* addr)
返回值:成功返回0,失败返回-1。
参数addr是之前调用shmat的返回值。如果函数执行成功的话,shmdt将使相关shmid_ds结构中的shm_nattch计数器减1。
当使用完共享存储后,调用此函数令进程与它分离,但共享存储并不会消失,其标识符仍然还在,直到有进程调用shmctl将其删除。
查看系统的共享存储资源
系统同样为我们提供了查看共享内存IPC资源的指令:
ipcs -m
删除共享内存资源:
ipcrm -m [shmid]
以共享存储实现客户机-服务器模式
头文件comm.h
#ifndef __COMM__
#define __COMM__
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#define PATHNAME "."
#define PROJID 0X666
#define SIZE 4096*1
int commMem(int flags);
int creatMem();
int getMem();
int destoryMem(int shmid);
#endif //__COMM__
源文件comm.c
#include"comm.h"
int commMem(int flags)
{
key_t _k = ftok(PATHNAME,PROJID);
if(_k < 0)
{
perror("ftok");
return -1;
}
int shmid = shmget(_k,SIZE,flags);
if(shmid < 0)
{
perror("shmget");
return -1;
}
return shmid;
}
int creatMem()
{
return commMem(IPC_CREAT | IPC_EXCL | 0666);
}
int getMem()
{
return commMem(IPC_CREAT);
}
int destoryMem(int shmid)
{
int ret = shmctl(shmid,IPC_RMID,NULL);
if(ret < 0)
{
perror("destory shmctl");
return -1;
}
return 0;
}
客户机文件client.c
#include"comm.h"
int main()
{
printf("client\n");
int shmid = getMem();
if(shmid < 0)
{
printf("getMem error !");
return -1;
}
printf("shmid : %d\n",shmid);
sleep(5);
char* addr = shmat(shmid,NULL,0);
if(addr == NULL)
{
printf("NULL!!!!!!!\n");
}
//printf("Debug addr %s",addr);
if(addr < 0)
{
perror("client shmat");
return -1;
}
int i = 0;
while(i < SIZE - 1)
{
addr[i] = 'L';
addr[i+1] = '\0';
sleep(1);
i++;
}
int ret = shmdt(addr);
printf("Debug shmdt \n");
if(ret < 0)
{
perror("client shmdt");
return -1;
}
return 0;
}
服务器文件server.c
#include"comm.h"
int main()
{
int shmid = creatMem();
sleep(10);
if(shmid < 0)
{
printf("CreatMem error !\n");
return -1;
}
char *addr = shmat(shmid,NULL,0);
if(addr < 0)
{
perror("shmat");
return -1;
}
while(1){
printf("#\n");
printf("%s\n",addr);
printf("#\n");
sleep(1);
}
int ret = shmdt(addr);
if(ret < 0)
{
perror("shmdt");
return -1;
}
int retdes = destoryMem(shmid);
if(retdes < 0)
{
printf("destoryMem error !\n");
return -1;
}
return 0;
}