进程间通信(三)
一、System V共享内存
1.什么是共享内存:
共享内存区是最快的IPC形式。一旦这样的内存映射到共享他的进程地址空间,这些进程间进行传输数据不再涉及到内核,换句话说就是进程不再通过执行进入内核的系统调用来传递彼此的数据。
2.共享内存示意图:
3.共享内存的数据结构:
4.共享内存的原理:
内存共享: 两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁和信号量都可以。
效率: 采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据[1]: 一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建 立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回 文件的。因此,采用共享内存的通信方式效率是非常高的。
共享内存实现机制
共享内存是通过把同一块内存分别映射到不同的进程虚拟地址空间中实现进程间通信。而共享内存本身不带任何互斥与同步机制,但当多个进程同时对同一内存进行读写操作时会破坏该内存的内容,所以,在实际中,同步与互斥机制需要用户来完成。
-
相对以管道来说,共享内存更加高效,因为他们直接访问内存即可完成通信,而管道涉及到用户态和内核态的交互,以完成数据的相互拷贝,所以效率比较低。
-
共享内存的使用方式:
a.先在内核中创建出共享内存对象。
b.多个进程附加到这个共享内存上。
c.直接读写这块共享内存 -
共享内存的生命周期随内核,即进程结束,共享内存依然存在,直到用户手动释放或者重启主机
-
共享内存就是允许两个不想关的进程访问同一个内存
-
共享内存是两个正在运行的进程之间共享和传递数据的最有效的方式
-
不同进程之间共享的内存通常安排为同一段物理内存
-
共享内存不提供任何互斥和同步机制,一般用信号量对临界资源进行保护。
-
接口简单
二、共享内存函数:
1.shmget函数
int shmget(key_t key, size_t size, int shmflg);
- 功能:是用来创建共享内存
- 参数:key_t key:这个共享内存段的名字
size:共享内存的大小
shmflg:由权限标志构成,用法和创建文件使用mode时一样 - 返回值:成功返回一个非负整数,代表共享内存段的标识码。失败返回-1.
2.shmat函数
void *shmat(int shmid, const void *shmaddr, int shmflg)
-
功能:将共享内存段链接到进程地址空间
-
参数:shmid:共享内存标识
void shmaddr*:指定链接的地址,一般为NULL(
shmaddr为NULL,核心自动选择一个地址
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr -
(shmaddr % SHMLBA)shmflg=SHM_RDONLY,表示连接操作用来只读共享内存)
shmflg:他的两个可能取值为SHM_RND,SHM_RDONLY,一般添0即可 -
返回值:成功返回一个指针,指向共享内存的第一个节,失败返回-1
3.shmdt函数
int shmdt(const void *shmaddr);
- 功能:将共享内存段与当前进程脱离
- 参数:shmaddr:由shmat函数所返回的指针
- 返回值:成功返回0;失败返回-1(注意⚠️,将共享内存和当前进程脱离不等于删除共享内存段
4.shmctl函数
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
-
功能:用于控制共享内存
-
参数:shmid:由shmget函数返回的共享标识码
cmd:将要采取的动作(IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值;IPC_SET:在进程由足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构中给出的值;IPC_RMID:删除共享内存段
buf:指向一个保存着共享内存的模式状态的和访问权限的数据结构 -
返回值:成功返回0;失败返回-1;
5.ftok函数
key_t ftok(const char *pathname, int proj_id);
- 共功能是生成一个key,给shmget函数使用
- 返回值:失败返回-1
三、测试代码
comm.h
#pragma once
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define PATHNAME "."
#define PROJ_ID 0x6666
int createShm(int size);
int destroyShm(int shmid);
int getShm(int size);
comm.c
#include "comm.h"
static int commShm(int size,int flags)
{
key_t _key = ftok(PATHNAME,PROJ_ID);
if(_key < 0)
{
perror("ftok is perror");
return -1;
}
int shmid = 0;
if((shmid = shmget(_key,size,flags)) < 0)
{
perror("shmget is error");
return -2;
}
return shmid;
}
int destroyShm(int shmid)
{
if(shmctl(shmid,IPC_RMID,NULL) < 0)
{
perror("shmctl is error");
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);
}
server.c
#include "comm.h"
int main()
{
int shmid = createShm(4096);
char* addr = (char*)shmat(shmid,NULL,0);
sleep(2);
int i = 0;
while(i++ < 26)
{
printf("client say : %s\n",addr);
sleep(1);
}
shmdt(addr);
sleep(2);
destroyShm(shmid);
return 0;
}
client.c
#include "comm.h"
int main()
{
int shmid = getShm(4096);
sleep(1);
char* addr = (char*)shmat(shmid,NULL,0);
sleep(2);
int i = 0;
while(i < 26)
{
addr[i] = 'A' + i;
i++;
addr[i] = 0;
sleep(2);
}
shmdt(addr);
sleep(2);
return 0;
}
结果:
注意共享内存没有进行同步与互斥
四、相关知识:
- System V消息队列
a.消息队列提供了一个从一个进程向另外一个进程发送一块数据 的方法
b.每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值
c.特性方面IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核 - System V信号量
a.相当于一个计数器,描述资源的个数,每当有进程想申请资源时,计数器的值减1,每当有进程释放资源时,计数器加1。主要用于进程同步与互斥
b.进程互斥:由于个进程要求共享资源,而有些资源需要互斥使用,因此个进程之间是竞争使用这些资源,进程间的关系就是互斥关系。系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源。在进程中涉及但互斥资源的程序段叫临界区,