一、什么是共享内存?
共享内存:使得多个进程可以访问同一块内存空间
共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常安排为同一段物理内存。进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址。
二、共享内存的特点
(1)、共享内存是以传输数据为目的
(2)、共享内存无同步无互斥
(3)、共享内存是所有进程间通信速度最快的。
(4)、共享内存的生命周期随内核
三、共享内存的优缺点
1、优点:使用共享内存进行进程间的通信非常方便,而且函数的接口也简单,数据的共享使进程间的数据不用传送,而是直接访问内存,加快了程序的效率。同时,它也不像匿名管道那样要求通信的进程有一定的父子关系。
2、缺点:共享内存没有提供同步的机制,这使得我们在使用共享内存进行进程间通信时,往往要借助其他的手段来进行进程间的同步工作。
四、实现共享内存
在学习共享内存的代码前我们先来看几个关于共享内存的函数。
1、创建共享内存——shmget函数
函数原型为:
int shmget(key_t key,size_t size,int shmflg);
key:让两个不同进程可以访问到同一资源。
size:表示共享内存的大小。
shmflg:代表标志位,分别是IPC_CREAT,IPC_EXCL。IPC_CREAT单独使用时表示 若存在共享内存,打开返回,若不存在,创建之。IPC_EXCL一般不单独使用。若两个标志位同时使用表示若存在共享内存,出错返回,若不存在,则创建之。
2、销毁共享内存——shmctl函数
函数原型为:
int shmctl(int shmid,int cmd,struct shmid_ds *buf);
shm_id:是shmget函数返回的共享内存标识符。
cmd:command是要采取的操作,它可以取下面的三个值 :
IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。
IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值
IPC_RMID:删除共享内存段
buf:是一个结构指针,它指向共享内存模式和访问权限的结构。
shmid_ds结构至少包括以下成员:
struct shmid_ds
{
struct ipc_perm shm_perm; /* Ownership and permissions */
size_t shm_segsz; /* Size of segment (bytes) */
time_t shm_atime; /* Last attach time */
time_t shm_dtime; /* Last detach time */
time_t shm_ctime; /* Last change time */
pid_t shm_cpid; /* PID of creator */
pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */
shmatt_t shm_nattch; /* No. of current attaches */
...
};
接下来,让我们来看看如何显示共享内存的相关代码吧!
Makefile:
.PHONY:all
all:client server
client:client.c comm.c
gcc -o $@ $^
server:server.c comm.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f server client
comm.h:
#define _COMM_H_
#define PATHNAME "."
#define PROJ_ID 0x6666
#include<stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int creatShm(int size);
int getShm(int size);
int destoryShm(int shmid);
#endif //_COMM_H_
comm.c:
#include "comm.h"
static int commShm(int size,int flags)
{
key_t _key=ftok(PATHNAME,PROJ_ID);
if(_key<0)
{
perror("ftok");
return -1;
}
int shmid=shmget(_key,size,IPC_CREAT|IPC_EXCL);
if(shmid<0)
{
perror("shmget");
return -2;
}
return shmid;
}
int createShm(int size)
{
return commShm(size,IPC_CREAT|IPC_EXCL);
}
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;
}
client.c:
include "comm.h"
int main()
{
printf("hello,bit!\n");
return 0;
}
server.c :
#include "comm.h"
int main()
{
int shmid =createShm(4097);//创建共享内存
sleep(1);
printf("hello,ShareMemory\n");
sleep(4);
destoryShm(shmid);//销毁共享内存
return 0;
}
程序的运行结果为:创建内存成功后1s后输出hello,ShareMemory然后4s后共享内存被销毁!(如图所示)
没创建共享内存前:
创建后:
4s后,共享内存被销毁。
关于共享内存我们就说到这里,接下来让我们看看关于进程间通信的相关知识吧!
五、进程间通信的特点
每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC)。
IPC的方式通常有管道(包括匿名管道和命名管道)、消息队列、信号量、共享内存等。
接下来我们来看看它们各自有什么特点!
管道:
(1)匿名管道:
它的数据只能在一个方向上流动,具有固定的读端和写端。
它只能用于具有亲缘关系的进程之间的通信(例如父子进程或者兄弟进程之间)。
它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
(2)命名管道(FIFO):
与匿名管道不同。FIFO可以在无关的进程之间交换数据,与无名管道不同。
FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
消息队列:
消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
消息队列的生命周期随内核
信号量:
信号量是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作,保证信号量的原子性。
每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。
支持信号量组,拥有了信号量,就可以拥有使用临界资源。
共享内存:
共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。
共享内存是以传输数据为目的,故无同步与互斥。