916-关于共享内存

我们知道各进程之间是独立存在,互不影响的。有没有一种方式让这些进程之间产生联系呢?当然有!那就是共享内存。共享内存是进程间通信中最简单的方式之一。

站在进程的角度来说,共享内存就是可以同时被多个进程访问的内存。由于所有进程共享同一块内存,因此这种通信方式效率非常高。

为什么进程间的内存不是共享的吧?
举一个例子,假设有 2 个进程同时想让某一物理地址保存一个值,A 进程想让这个物理地址保存 1,B 进程想让这个物理地址保存 2。那么这个物理地址到底应该保存哪个值?所以,为了将每个进程隔离开,设计者就想到一个办法,操作系统会给每个进程分配一个虚拟地址。然后将不同进程的虚拟地址和不同内存的物理地址进行映射。

每次进程想要写入数据,先访问虚拟地址,然后内存再将这个地址转换成物理地址,这样不同进程运行的时候,写入的是不同物理地址,就不会有冲突了。这就是进程独享内存空间的原理。我下面给您画个图。实际中虚拟内存和物理内存都会被分成大小相等的页,然后进行映射。但是由于我们这次面试的重点不在此,图就简略一点,表明关系就好。
在这里插入图片描述
为什么要引入虚拟地址呢?运行过程中还得进行虚拟地址和物理地址进行转换。我看看物理内存有多大,直接把一段物理空间交给一个进程不好吗?然后这段空间不允许别的进程进行操作。这样不更省事?

1、操作系统是不希望一个普通的进程可以直接对物理地址写数据的。如果一个普通的进程可以随意的向物理地址中写数据。那么一个恶意进程一旦知道别的进程的物理地址,那不是很容易就把别的进程的数据篡改了嘛。
2、每个进程在创建之初,它所需要的内存大小都是不确定的。如果按照您的说法直接给进程分配固定的物理内存,假如两个进程在创建之初都直接各自分配了 1G 的物理空间。但实际运行起来,A 进程只用了 100M,而 B 进程需要 1.9 G。那么给 A 进程分配的空间就浪费了,而给 B 进程分配的空间又不够。都采用虚拟地址,表面看上去每个进程都可以独占内存的所有空间。在进程运行的途中再对虚拟地址和物理地址进行转换,可以有效的利用空间。甚至在内存不足的情况下,还可以把进程的内存存到硬盘里,切换到该进程时再从硬盘读取。
3、虚拟内存可以为每个进程提供一个一致的地址空间,这样程序员就不需要管理内存了,这也降低了编程的复杂度。

进程通信为什么又要共享内存?
因为有时候两个进程需要进行大量的通信,并且传递的都是比较大的数据。那么采用管道或者消息队列的方式就不方便了。这不如两个进程都拿出一块虚拟地址,映射到相同的物理内存中。这样进程间需要传送的数据就不需要来回拷贝了,这边一写那边立马看到了。共享内存理论上是最快的进程通信方式,不过有个弊端就是不能跨物理机进程通信,如果需要跨物理机进行进程通信,建议用套接字。
在这里插入图片描述

我们需要通过一些手段保证在数据被写入之前不允许其他进程从共享内存中读取。比较常见的解决办是通过 信号量 来进行同步

用代码实现一下共享内存可以不?

(1)既然需要用共享内存,首先需要创建一个共享内存或者得到一个共享内存。这一步要用到一个函数就是 shmget。

int shmget(key_t key,size_t size, int flag);
//key:用来定位共享内存
//size:用来指定共享内存的大小
//flag:用来表示创建共享内存的方式,如果赋值是 IPC_CREAT 表示创建一个新的
//返回值:共享内存标识符

(2)通过第一步创建好了共享内存,但是如果一个进程想要访问这段共享内存,那么就需要将共享内存加载到自己的虚拟地址空间中。而加载的这个过程就需要用到下面这个函数。

void *shmat(int shmid, const void *shmaddr, int shmflag);
//shmid:传入共享内存标识符
//shmaddr:指定共享内存映射的地址
//shmflag:标识内存关联后的读写权限
//返回值:返回共享内存映射到进程空间的起始地址。

(3)经过前两步,所有与共享内存进行关联的进程,就可以进行通信了。这一步不需要什么特殊的函数,直接往共享内存中写入,或者从中读取就可以啦。

(4)如果内存共享使用完毕,那么就需要解除绑点,然后再删除共享内存对象。这需要用到下面两个函数。

int shmdt(void *addr);
//addr:共享内存的起始地址
void *shmctl(int shm_id, int cmd, struct shmid_ds *buf);
//shm_id:共享内存标识符
//cmd:对共享内存的操作,如果用IPC_RMID表示要将共享内存删去。
//buf:共享内存管理结构体。
//shared_memory.h
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>   //刚才介绍的几个函数都在这个库中
#include <unistd.h>
#define PATHNAME "/home/dabai/server.c" //路径名,用它来获取共享内存标识符的key
#define PROJ_ID 0x6666 //整数标识符
#define SIZE 4096 //共享内存的大小

//server.c
#include "shared_memory.h"
int main()
{
 key_t key = ftok(PATHNAME, PROJ_ID); //建立共享内存需要一个区域标识符来标识共享内存区域,ftok把已经存在的路径名和整数标识符转换成一个整数 IPC 键值。
 //如果key创建失败则返回值小于0,应该有个打印错误并结束程序的操作,为了代码简洁我就不写啦。
 int shm = shmget(key, SIZE, IPC_CREAT | IPC_EXCL | 0666); //创建新的共享内存,返回共享内存标识符
 //如果共享内存创建失败则返回值小于0,应该有个打印错误并结束程序的操作,为了代码简洁我就不写啦。
 printf("key: %x\n", key);
 printf("shm: %d\n", shm);
 char* mem = shmat(shm, NULL, 0); //关联共享内存
    //这里还是应该检查下是否关联成功为了代码简洁我就省略了
    int i = 0;
 while (1){
  mem[i] = 'a';    //进程可以根据自己的需要在这里对共享内存进行写入或读出。
     i++;
 }
 shmdt(mem); //共享内存去关联
 shmctl(shm, IPC_RMID, NULL); //释放共享内存
 return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林林林ZEYU

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值