一、什么是共享内存
共享内存就是多个进程将同一块内存区域映射到自己的进程空间之中,以此来实现数据的共享和传输,它是进程间通信方式中最快的一种。由于多个进程共享同一块内存区域,在程序设计过程中应注意进程访问的同步问题。可以和信号量结合使用。
共享内存有多种实现机制,这里介绍System V共享内存。
二、共享内存的创建
1、shmget函数
该函数用来创建共享内存。
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
参数key为共享内存的键值,与消息队列类型,它可以由用户指定,也可以调用ftok函数来生成,如果直接设为IP_RPIVATE,表示总是创建新的共享内存。
参数size为共享内存的大小,如果正在创建一块共享内存,则必须指定该参数;如果正在打开一块已经存在的共享内存,可以将该参数设为0。
参数shmflg用来创建共享内存并设定其存取权限。函数执行成功后,返回共享内存的标志符,否则返回-1.
2、shmctl函数
用户进程可以调用该函数来对共享内存进行各种操作,例如删除共享内存。
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shm_id, int command, struct shmid_ds *buf);
该函数的参数与返回值同msgctl函数非常类似。
参数shm_id是shmget函数返回的共享内存标识符。
参数command是要采取的操作,它可以取下面的三个值 :
(1) IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。
(2)IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值
(3)IPC_RMID:删除共享内存段
参数buf是一个结构指针,它指向共享内存模式和访问权限的结构。
shmid_ds结构至少包括以下成员:
struct shmid_ds
{
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
};
//使用shmget函数创建一块共享内存然后删除掉
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define SHM_SIZE 1024 //共享内存的大小
int main()
{
int shmid;
key_t key;
key=ftok("/home", 'a'); //生成共享内存的键值
if (key<0)
{
perror("ftok error");
exit(1);
}
shmid=shmget(key, SHM_SIZE, IPC_CREAT|0666); //创建一块共享内存
if (shmid<0)
{
perror("shmget error");
exit(1);
}
else
{
printf("Done!\n");
}
sleep(5);
shmid=shmctl(shmid, 0, IPC_RMID); //删除共享内存
if (shmid<0)
{
perror("shmctl error");
exit(1);
}
printf("removed!\n");
return 0;
}
三、共享内存的读写
1、shmat函数
第一次创建完共享内存时,它还不能被任何进程访问,shmat函数的作用就是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
void *shmat(int shm_id, const void *shm_addr, int shmflg);
参数shm_id是由shmget函数返回的共享内存标识。
参数shm_addr指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。
参数shmflg用来设定共享内存的访问权限,如果要求以只读方式访问,将其设为SHM_RDONLY。
调用成功时返回一个指向共享内存第一个字节的指针,通过这个地址,进程就可以像访问普通内存一样访问共享内存了,如果调用失败返回-1.
2、shmdt函数
该函数用于将共享内存从当前进程中分离。注意,将共享内存分离并不是删除它,只是使该共享内存对当前进程不再可用。一般来说,当一个进程终止时,它所映射的共享内存都会自动脱落。
#include <sys/ipc.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
参数shmaddr是shmat函数返回的地址指针,调用成功时返回0,失败时返回-1.
示例:通过共享内存来实现父进程和子进程之间的通信
//通过共享内存来实现父进程和子进程之间的通信
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define SHM_SIZE 1024 //共享内存的大小
int main()
{
int shmid;
key_t key;
pid_t pid;
int *psm;
struct shmid_ds dsbuf;
key=ftok("/home", 'a'); //生成共享内存的键值
if (key<0)
{
perror("error");
exit(1);
}
shmid=shmget(key, SHM_SIZE, IPC_CREAT|0666); //创建一块共享内存
if (shmid<0)
{
perror("shmget error");
exit(1);
}
pid=fork(); //创建子进程
if (pid<0)
{
perror("fork error");
exit(1);
}
if (pid==0) //子进程,向共享内存中写入数据
{
printf("child process:\n");
printf("PID: %d\n",getpid()); //输出子进程的标识符
psm=(int *)shmat(shmid, NULL, 0); //将共享内存映射到进程的地址空间中
if (*psm==-1)
{
perror("shmat error");
exit(1);
}
else
{
strcpy((char*)psm, "hello word!"); //向共享内存中写入数据
printf("Send message:%s\n",(char*)psm);
if((shmdt((void*)psm)<0)) //使共享内存脱离进程的地址空间
{
perror("shmdt error");
}
sleep(2);
}
}
else //父进程,从共享内存中读取数据
{
sleep(2);
printf("parent process:\n");
printf("PID:%d\n",getpid()); //输出父进程的标识符
if ((shmctl(shmid, IPC_STAT, &dsbuf))<0) //获取共享内存的内存信息
{
perror("shmctl error");
exit(1);
}
else
{
printf("shared memory information:\n");
printf("\tCreator PID:%d\n",dsbuf.shm_cpid); //输出创建共享内存的进程标识符
printf("\tSize(bytes):%ld\n",dsbuf.shm_segsz); //输出共享内存的大小
printf("\tLast operator PID:%d\n",dsbuf.shm_lpid); //输出上一次操作共享内存进程的标识符
psm=(int*)shmat(shmid, NULL, 0); //将共享内存映射到进程的地址空间中
if (*psm==-1)
{
perror("shmat error");
exit(1);
}
else
{
printf("received message:%s\n",(char*)psm); //从共享内存中读取数据
if((shmdt((void*)psm)<0)) //使共享内存脱离进程的地址空间
{
perror("shmdt error");
}
}
}
if (shmctl(shmid, IPC_RMID, NULL)<0) //删除创建的共享内存
{
perror("shmctl error");
exit(1);
}
}
return 0;
}
四、共享内存的优缺点
1、优点:我们可以看到使用共享内存进行进程间的通信真的是非常方便,而且函数的接口也简单,数据的共享还使进程间的数据不用传送,而是直接访问内存,也加快了程序的效率。同时,它也不像匿名管道那样要求通信的进程有一定的父子关系。
2、缺点:共享内存没有提供同步的机制,这使得我们在使用共享内存进行进程间通信时,往往要借助其他的手段来进行进程间的同步工作。