共享存储:
在通信的进程之间存在一块可直接访问的共享空间,通过对这片共享空间进行写/读操作实现进程之间的信息交换。
共享存储是进程通信中最灵活,速度最快的通信方式。因为挂接到同一块内存区域的两个进程可以直接访问内存区域。而其他几种方式需要拷贝到内核,再从内核拷贝出去,进行通信。
需要注意的是,用户进程空间一般都是独立的,要想让两个用户进程共享空间必须通过特殊的系统调用实现,而进程内的线程是自然共享进程空间的。
需要用到的函数有:
1、获得一个共享内存的标识符
#include<sys/shm.h>
int shmget(key_t key, size_size, int flags);
参数key是唯一标识,可以由ftok函数产生
参数size是该共享存储段的长度(单位:字节)。实现通常将其向上取为系统页长的整数倍。但是,若应用指定的size值并非系统页长的整数倍,那么最后一页的余下部分是不可使用的。如果正在创建一个新段(一般是在服务器进程中),则必须指定其size。如果正在引用一个现存的段(一个客户进程),则将size指定为0。当创建一新段时,段内的内容初始化为0。
参数flags指定创建共享内存段的方式,是新建还是引用。
2、shmctl函数对共享存储段执行多种操作。
include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
//返回值:若成功则返回0,若出错则返回-1
cmd参数指定下列5中命令中一种,使其在shmid指定的段上执行。
IPC_STAT 取此段的shmid_ds结构,并将它存放在由buf指向的结构中。
IPC_SET 按buf指向结构中的值设置与此段相关结构中的下列三个字段:shm_perm.uid、shm_perm.gid以及shm_perm.mode。此命令只能由下列两种进程执行:一种是其有效用户ID等于shm_perm.cuid或shm_perm.uid的进程;另一种是具有超级用户特权的进程。
IPC_RMID 从系统中删除该共享存储段。因为每个共享存储段有一个连接计数(shmid_ds结构中的shm_nattach字段),所以除非使用该段的最后一个进程终止或与该段脱节,否则不会实际上删除该存储段。不管此段是否仍在使用,该段标识符立即被删除,所以不能再用shmat与该段连接。此命令只能由下列两种进程执行:一种是其有效用户ID等于shm_perm.cuid或shm_perm.uid的进程,另一种是具有超级用户特权的进程。
3、创建了一个共享存储段,进程就可调用shmat将其连接到它的地址空间中。
include <sys/shm.h>
void *shmat(int shmid, const void *addr, int flag);
//返回值:若成功则返回指向共享存储的指针,若出错则返回-1
共享存储段连接到调用进程的哪个地址上与addr参数以及在flag中是否指定SHM_RND位有关。
如果addr为0,则此段连接到由内核选择的第一个可用地址上。这是推荐的使用方式。
如果addr非0,并且没有指定SHM_RND,则此段连接到addr所指定的地址上。
如果addr非0,并且指定了SHM_RND,则此段连接到(addr-(addr mod ulus SHMLBA))所表示的地址上。SHM_RND命令的意思是“取整”。SHMLBA的意思是“低边界地址倍数”,它总是2的乘方。该算式是将地址向下取最近1个SHMLBA的倍数。
除非只计划在一种硬件上运行应用程序(这在当今是不大可能的),否则不应指定共享段所连接到的地址。所以一般应指定addr为0,以便由内核选择地址。
如果在flag中指定了SHM_RDONLY位,则以只读方式连接此段。否则以读写方式连接此段。
shmat的返回值是该段所连接的实际地址,如果出错则返回-1。如果shmat成功执行,那么内核将使该共享存储段shmid_ds结构中的shm_nattach计数器值加1.
当对共享存储段的操作已经结束时,则调用shmdt脱接该段。注意,这并不从系统中删除其标识符以及数据结构。该标识符仍然存在,直至某个进程(一般是服务器进程)调用shmctl(带命令IPC_RMID)特地删除它。
4、取消挂接
include <sys/shm.h>
int shmdt(void *addr);
//返回值:若成功则返回0,若出错则返回-1
addr参数是以前调用shmat时的返回值。如果成功,shmdt将使相关shmid_ds结构中的shm_nattach计数器值减1。
附测试代码:
//Makefile
cli=client
cc=gcc
ser=server
serSrc=comm.c server.c
cliSrc=comm.c client.c
.PHONY:all
all:$(cli) $(ser)
$(cli):$(cliSrc)
$(cc) -o $@ $^
$(ser):$(serSrc)
$(cc) -o $@ $^
.PHONY:clean
clean:
rm -f $(cli) $(ser)
//comm.h
#ifndef _COMM_
#define _COMM_
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#define PATHNAME "."
#define PROJID 0X6666
#define SIZE 4096*1
int creatShm();
int getShm();
int destoryShm(int shmid);
#endif
//comm.c
#include"comm.h"
static int commShm(int flags)
{
//ftok函数生成key_t值。
key_t _k=ftok(PATHNAME, PROJID);
if(_k < 0)
{
perror("ftok");
return -1;
}
//shmget函数获取共享存储标识符ID
int shmid = shmget(_k, SIZE, flags);
if(shmid < 0)
{
perror("shmget");
return -2;
}
return shmid;
}
int getShm()
{
return commShm(IPC_CREAT);
}
int creatShm()
{
return commShm(IPC_CREAT|IPC_EXCL|0666 );
}
int destoryShm(int shmid)
{
//shmctl函数对共享内存段执行多种操作,此处执行销毁操作
if(shmctl(shmid, IPC_RMID,NULL) < 0)
{
perror("shmctl");
return -1;
}
return 0;
}
//server.c
#include "comm.h"
int main()
{
int shmid = creatShm();
//调用shmath函数将次进程挂接到共享内存段(类似于malloc函数)
char *mem = (char *)shmat(shmid,NULL, 0);
while(1)
{
sleep(1);
printf("%s\n",mem);
}
//shmdt函数进行去挂接操作
shmdt(mem);
destoryShm(shmid);
return 0;
}
//client.c
#include "comm.h"
int main()
{
int shmid = getShm();
char *mem = (char *)shmat(shmid,NULL, 0);
int i = 0;
while(1)
{
sleep(1);
mem[i++]='S';
i %= (SIZE-1);
mem[i] = '\n';
}
shmdt(mem);
return 0;
}