什么是共享内存
共享内存允许两个或更多进程访问同一块内存,就如同 malloc() 函数向不同进程返回了指向同一个物理内存区域的指针。当一个进程改变了这块地址中的内容的时候,其它进程都会察觉到这个更改。在Linux中,每个进程都有自己的P C B 和地址空间,并且都有一个对应的页表,负责将进程的地址和物理地址进行映射,通过MMU来管理。创建一段共享存储区(临界资源),被映射到不同进程的地址空间内,从而实现了高效率的资源共享。
共享内存的优缺点
优点:
共享内存在所有用于进程间通信的IPC资源中实现进程间通信的速度是 最快的,因为它少了两次资源的拷贝。
如下图
缺点:
操作系统对共享内存并没有提供同步与互斥机制,所以需要用户来实现同步与互斥机制,我们知道,信号量就可以实现。
共享内存的使用
进程间通信的本质是使不同的进程可以看到同一份资源,这份资源称之为临界资源,那么这里的共享内存即是这份临界资源。
所以,首先应该创建一段共享存储区。然后使其要访问这个共享存储区的每一个进程都要将该共享存储区连接至每个进程的地址空间。
当通信完成后,所有的进程需要对其去关联,通俗点讲,也就是说将这些进程和这块共享存储区的连接断开,并由创建共享存储区的进程释放该共享存储区。
注意:建议用户申请的共享存储块的大小最好是系统页面大小的整数倍。因为,如果用户申请的共享存储段的大小不是页面大小的整数倍的话,那么系统会采用向上对齐至页面大小的整数倍,但是用户可以使用的还是自定义的存储段。在linux系统下,页面大小默认为4kb。
指令
- 查看系统中的共享存储段
ipcs -m
- 删除系统中的共享存储段
ipcrm -m shmid
- 删除系统中的共享存储段
共享内存实现进程间通信
下面一幅图来解释一下,实现共享内存间的通信
实现通信的接口函数
- shmght:创建一个新的共享存储段或引用一个现有的共享存储段。
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg)
参数说明:
*key:由ftok函数得到的IPC键值,标识系统中唯一的IPC资源。
size:该共享存储段的长度,以字节为单位。通常需要将其向上取为系统页长的整数倍。(一页为4k)。
如果在创建一个新段,那么必须指定size值,如果引用一个现存的段,则将size指定为0。当创建一个新段时,段内的内容初始化为0.
shmflg:
PC_CREAT||IPC_EXCL|0666 保证返回一个新的共享内存标识符,并且指定该共享内存的劝降为0666.
PC_CREAT 如果共享内存不存在,则创建并返回,如果存在,则打开并返回其标识符。
返回值:
成功时返回一个新创建的共享内存标识符或者一个已存在的共享内存标识符。取决于shmglg参数。
失败时返回-1并设置错误码。
- shmat:挂接
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void shmaddr, int shmflg)
功能:进程可调用该函数将共享存储段连接到它的地址空间
若成功,返回指向共享存储段的指针(该段所连接的虚拟地址),并且内核将使其与该共享存储段相关的shmid_ds结构中的shm_nattch计数器加1(类似于引用计数);若出错,返回-1.
参数:
shmid:共享存储段标识符。
shmaddr:
shmaddr = 0,则存储段连接到由内核选择的第一个可用地址上。一般推荐使用该方法,因为内核最清楚进程的地址空间。
shmflg:
若指定了SHM_RDONLY位,则以只读方式连接此段,否则以读写方式连接此段。
- shmdt(去关联)
函数说明:
当对共享存储的操作已经结束时,则需调用shmdt与该段分离。与shmat函数相反,是用来断开与共享内存附加点的地址,禁止本进程访问此片共享内存。
需要注意的是,该函数并不删除所指定的共享内存区,而是将之前用shmat函数连接好的共享内存区脱离目前的进程。
#include <sys/types.h>
#include <sys/shm.h>
void *shmdt(const void* shmaddr);
返回值:
若成功,返回0,并将shmid_ds结构中的shm_nattch计数器值减一;若出错,返回-1.
- shmctl:删除共享内存段。
#include <sys/types.h>
#Include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds* buf);
返回值:若删除成功,返回0;
若出错,返回-1。
参数:
shmid:共享存储段标识符;
cmd:指定的操作。
执行删除:设置为IPC_RMID。
buf:删除时忽略该参数,设置为NULL
key值与shmid有什么区别?
key值与shmid值都是标识唯一的一份IPC资源,那么它们之间又有什么区别呢?
key值:在操作系统层面,用来标识唯一的一份IPC资源。
shmid:在用户层面,即就是在代码层面,用来标识一份IPC资源。 —–>这个是用户所关注的
代码实现共享内存间通信
//comm.h
#ifndef __COMM_H__
#define __COMM_H__
#include<stdio.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<sys/ipc.h>
#define PATHNAME "."
#define ID 65
int CreateShm(int size);
int GetShm(int size);
int DestoryShm(int shmid);
#endif
//comm.c
#include"comm.h"
static int CommShm(int size,int flags)
{
key_t _key = ftok(PATHNAME,ID);
if(_key<0)
{
perror("ftok");
return -1;
}
int shmid = shmget(_key,size,flags);
if(shmid<0)
{
perror("shmid");
return -2;
}
return shmid;
}
int CreateShm(int size)
{
return CommShm(size,IPC_CREAT|IPC_EXCL|0666);
}
int GetShm(int size)
{
return CommShm(size,IPC_CREAT);
}
int DestoryShm(int shmid)
{
if(shmctl(shmid,IPC_RMID,NULL)<0)
{
perror("shmctl");
return -1;
}
return 0;
}
//server.c
#include"comm.h"
int main()
{
//chuangjiangongxiangcunchuduan
int shmid = CreateShm(4095);
char* buf;
buf = shmat(shmid,NULL,0);//guanlian
sleep(4);
int count = 0;
while(count<20)
{
buf[count++] = 'a'+count;
buf[count] = '\0';
sleep(1);
}
shmdt(buf);//quguanlian
DestoryShm(shmid);
return 0;
}
//client.c
#include"comm.h"
int main()
{
int shmid = GetShm(0);
char *buf;
buf = shmat(shmid,NULL,0);
int count = 0;
while(count++<15)
{
printf("client# %s\n",buf);
sleep(1);
}
shmdt(buf);
return 0;
}
运行结果 如下图,并且由监视窗口可以看到,关联的进程数由2变为1,然后删除