首先在物理内存中开辟一段空间,然后利用内核级页表和进程的地址空间建立映射关系,当把数据写入共享内存时,即可完成进程间通信。
1.在物理内存中开辟一段空间
2.虚拟地址空间和物理内存通过页表建立映射关系
3.取消映射关系
4.释放在物理内存中开辟的地址空间
注意:这里的页表是内核级页表,我们平常在进程地址空间中说的页表是用户级页表
为什么共享内存是system V进程间通信过程中速度最快的?
在回答这个问题之前,我们和上篇博客中介绍的管道对比一下,利用管道进行进程间通信时,需要write(将数据从用户拷贝到内核)和read(将数据从内核拷贝到用户),通信需要两次数据拷贝,但是共享内存不需要,在物理内存中开辟空间并写数据,通信的两个进程立马就可以看到,省去了两次数据拷贝,所以它是最快的。
创建共享内存-----shmget函数
int shmget(key_t key, size_t size, int shmflg);
参数:
key: 标识此段共享内存,便于另一个进程查找,由ftok函数生成
size: 共享内存大小
shmflg: 可设为缺省,默认为0
返回值:
成功返回一个非负整数,即该共享内存段的标识码,失败返回-1
注意:shmflg两个重要取值:
建立映射关系的函数----shmat
void* shmat(int shmid, const void* shmaddr, int shmflg);
参数:
shmid: shmget函数的返回值
shmaddr: 指定连接的地址,为NULL,则自动选择
shmflg: 它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:
成功返回一个指针,指向共享内存第一个节,失败返回-1
共享内存段与当前进程脱离—shmdt
int shmdt(const void* shmaddr)
参数:
shmaddr:shmat所返回的指针
返回值:
成功返回0,失败返回-1
注意:将共享内存段与当前进程脱离不代表删除共享内存段
控制共享内存—shmctl
int shmctl(int shmid, int cmd, struct shmid_ds* buf);
参数:
shmid: shmget函数返回的共享内存标识码
cmd:将要采取的动作,有三个可取值
buf: 指向一个存储共享内存的模式状态和访问权限的数据结构
返回值:
成功返回0,失败返回-1
cmd说明:
//makefile
.PHONY:all
all:server client
client:client.c comm.c
gcc -o $@ $^
server:server.c comm.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f client server
//comm.h
#ifndef COMM_H
#define COMM_H
#include
#include
#include
#include
#include
#define PATHNAME "."
#define PROJ_ID 0X6666
int createShm(int size);
int destroyShm(int shmid);
int getShm(int size);
#endif
//comm.c
#include "comm.h"
static int commShm(int size, int flags)
{
key_t key = ftok(PATHNAME, PROJ_ID);
if(key < 0)
{
perror("ftok error\n");
return -1;
}
int shmid = 0;
if((shmid = shmget(key, size, flags)) < 0)
{
perror("shmget error\n");
return -2;
}
return shmid;
}
int destroyShm(int shmid)
{
if(shmctl(shmid, IPC_RMID, NULL) < 0)
{
perror("shmctl error\n");
return -1;
}
return 0;
}
int createShm(int size)
{
return commShm(size, IPC_CREAT | IPC_EXCL | 0666);
}
int getShm(int size)
{
return commShm(size, IPC_CREAT);
}
//client.c
#include "comm.h"
int main()
{
//客户端不需要再创建共享内存
int shmid = getShm(4096);
sleep(1);
char* addr = shmat(shmid, NULL, 0);
sleep(2);
int i = 0;
while(i < 26)
{
//客户端往共享内存中写
addr[i] = 'A' + i;
i++;
//保证字符串每次以\0结尾
addr[i] = 0;
sleep(1);
}
shmdt(addr);
sleep(2);
return 0;
}
//server.c
#include "comm.h"
int main()
{
//操作系统以页为单位分配共享内存,1页=4k=4096kb,但是当改成4097时并不会分配两个页,而是类似于写时拷贝
int shmid = createShm(4096);
char* addr = shmat(shmid, NULL, 0);
sleep(2);
int i = 0;
while(i++ < 26)
{
//服务器负责从共享内存中读
printf("client# %s\n", addr);
sleep(1);
}
shmdt(addr);
sleep(2);
destroyShm(shmid);
return 0;
}
输出结果:
启动服务器和客户端
数据是客户端写进共享内存的,服务器只是把数据拿出来。
但是当再次启动服务时,报以下错误:
由于共享内存的生命周期随内核,只要不删就一直在,除非重启操作系统,所以利用以下命令删除已经获得的内存:
//查看共享内存的id
ipcs -m
//删除共享共享内存
ipcrm -m +id
消息队列实际上是操作系统在内核为我们创建的一个队列,通过这个队列的标识符key,每一个进程都可以打开这个队列,每个进程都可以通过这个队列向这个队列中插入一个结点或者获取一个结点来完成不同进程间的通信。