共享内存
一、共享内存的概念
进程的地址空间都是独立的,受保护的。
假设我们现在有两个进程:A进程和B进程
//A
int a = 10;
printf("%d\n",a);
//B
int a = 10;
printf("%d\n",a);
这两个进程中的a是没有关系的。那么共享内存就是内存上开辟的一块区域,能使得ptra和ptrb都指向这个空间,这样ptra可以在这个空间写东西,ptrb去查看的时候会看到A所写的东西。如下图所示:
共享内存必须有一个内核对象去指向它,不然它无法保存所写的东西。
二、相关函数
1、int shmget():用于创建或者获取共享内存
- int shmget((key_t)key, int size, int flag);
shmget()成功返回共享内存的ID,失败返回-1
key:不同的进程使用相同的key值可以获取到同一个共享内存
size:创建共享内存时,指定要申请的共享内存空间大小
flag:IPC_CREAT IPC_EXCL
2、void* shmat():将申请的共享内存的物理内存映射到当前进程的虚拟地址空间上
- void * shmat(int shmid, void * addr, int shmflag);
shmat():成功返回共享内存的首地址,失败返回NULL
addr:一般给NULL,由系统自动选择映射的虚拟地址空间
shmflag:一般给0,可以给SHM_RDONLY为只读模式,其他的为读写
3、int shmdt():断开当前进程的shmaddr指向的共享内存映射,并没有删除这个共享内存
- int shmdt( void * shmaddr)
shmdt():成功返回0,失败返回-1
4、int shmctl():控制共享内存
- int shmctl(int shmid, int cmd, struct shmid_ds * buf)
shmctl():成功返回0,失败返回-1
cmd:IPC_RMID(删除共享内存空间,并不会立即删除共享空间)
注意:不能使用shmat方法与共享存储段建立映射关系
三、系统上模拟实现
我们在系统上创建两个进程A和B
示例:进程a从键盘循环获取数据并拷贝到共享内存中,进程b从共享内存中读取并打印数据,要求进程a输入一次,进程b输出一次。进程a不输入,进程b也不输出。
//shmA.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/shm.h>
#include "sem.h"
int main()
{
int shmid = shmget((key_t)1234,128,IPC_CREAT | 0664);
assert(shmid != -1);
char *ptr = (char *)shmat(shmid,NULL,0);
assert(ptr != (char*)-1);
//0:sem1 1:sem2
int init_val[2] = {0, 1};
int semid = CreateSem(1234, init_val,2);
assert(semid != -1);
while(1)
{
SemP(semid,1); //B--->A 此处的1为下标
printf("input: ");
fgets(ptr, 127, stdin);
SemV(semid,0); //A---->B
if(strncmp(ptr,"end",3) == 0)
{
break;
}
shmdt(ptr);
shmctl(shmid,IPC_RMID,NULL);
}
}
//shmB.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/shm.h>
#include "sem.h"
#include <time.h>
int main()
{
srand((unsigned int)(time(NULL)*time(NULL)));
int shmid = shmget((key_t)1234,128,IPC_CREAT | 0664);
assert(shmid != -1);
char *ptr = (char *)shmat(shmid,NULL,0);
assert(ptr != (char*)-1);
int init_val[2] = {0, 1};
int semid = CreateSem(1234, init_val,2);
assert(semid != -1);
while(1)
{
SemP(semid,0); //A---->B
if(strncmp(ptr,"end",3)==0)
{
break;
}
printf("B process: %s",ptr);
int n = rand() % 3+1;
sleep(n);
printf("B Deal Over\n");
memset(ptr, 0, 128);
SemV(semid,1); //B--->A
}
shmdt(ptr);
shmctl(shmid,IPC_RMID,NULL);
DeleteSem(semid);
}
1、这有一个过程是这样,一个是A对B的信号量,一个是B对A的信号量。初始时,A对B中B是阻塞的,A是正常执行的。这块就需要两个信号量一个是sem1一个是sem2。系统对sem1和sem2进行P操作,如下图所示:
2、如果先执行B我们发现B是阻塞的。
3、如果执行A我们发现A中输入什么B中就输出什么,但是A不能接着输入,因为得等B处理完成之后才可以继续输入。
4、查看和删除共享内存
(1)命令删除
ipcs -s:查看信号量
ipcs -m:查看共享内存
执行命令:
ipcrm -s 98306
这样就可以删除这个信号量
(2)代码删除
我们可以在刚才的代码中调用:
DeleteSem(semid);
这样就不用手动删除,只要在A中输入end结束后,就可以删除所有东西。
四、共享内存的特点
- 它是最快的IPC,因为少了两次拷贝。
- 共享内存属于临界资源,所有AB两个进程去使用这个临界资源的时候需要进行同步控制
- B进程必须在A进程获取数据之后才能打印,A进程必须在B进程将上一次数据处理后才能再次获取
五、进程间通信的总结
1、类别
- 有名管道:通过文件描述符的方式做进程间通信,有阻塞非阻塞机制在其中,效率低。 、
- 无名管道:父子进程间的通讯
- 消息队列:进程可以根据消息类型获取不同的消息---->有个中间件MQ
- 信号量:做同步控制的,对临界资源临界区的访问或两个进程的同步,协作关系做同步控制
- 共享内存:最快的IPC
2、进程间通讯的目的
- 数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几兆字节之间。
- 共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到。
- 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知 父进程)。
- 资源共享:多个进程之间共享同样的资源。为了作到这一点,需要 内核提供锁和同步机制。
- 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
- 进程通过与内核及其它进程之间的互相通信来协调它们的行为。