一、引言
之前的管道学习中,我们利用命名管道生成了两个.fifo的文件,如果用户不懂技术,以为这个文件没用就可能把它们删除了。此外,因为管道(不管是命名管道还是匿名管道)能够容纳的最大数据是有限的(65535字节),你不断的往管道里面写数据, 如果超过这个容量就可能导致管道损坏。(就像水管承载的水量是有限的,超过一定的水压就可能爆裂),所以总结起来就是管道容易被误删,管道容量有上限。那么接下来就涉及到了共享内存。
二、共享内存概述
共享内存是由IPC为一个进程创建的一个特殊的地址范围,它将出现在进程的地址空间中。 其他进程可以把同一段共享内存段“连接到”它们自己的地址空间里去。如下图所示
①上图中的共享内存它有可能是A或者B其中一个进程创建的。但是它不属于任何进程(不属于A也不属于B),属于操作系统本身。
②A、B进程都可以访问共享内存地址,就好像它们是有malloc分配的一样。
③如果A进程向这段共享内存写了数据,所做的改动会立刻被有权访问同一段共享内存的其他进程看到,如这里的B进程。
可以想象成现实当中的共享单车、共享充电宝。首先,这两者都不属于用户;其次,大家都有权利使用;最后,如果前一个用户把它用坏了,后一个用户如果碰巧也用上这个,那么它的状态就是上次弄坏的状态。 再具体点,就是如果前一个用户用的共享充电宝用了百分之三十的电量,那么后一个用户如果立马也使用的话,就只有百分之七十的电量可供使用,当然是假设这个充电宝还没有充上电的时候
所以简单来说,共享有以下三个特点:这个东西不属于你;谁都能用;后一个人使用一定能看到前一个人的操作结果
再回到共享内存这个概念,即这段内存不属于任何进程;所有进程都能使用这段内存;有一个进程对内存进行了改变,那么其他进程都能看见操作结果。
通过ipcs命令可以查看共享内存、消息队列、信号量。信号和管道都属于某一进程空间,但这三种都属于操作系统本身,进程存不存在与这三种没关系。即这三种东西是对于进程来说的独立的。进程没开,共享内存仍可以存在(如下图)。
ipcrm -m shmid 删除指定id的共享内存
ipcm -a 把所有创建出来的共享内存,信号量等全部删除(除root外)
三、共享内存函数
1、shmget函数——创建
作用:用来创建共享内存
原型:int shmget(key_t key,size_t size, int shmflg);
参数说明:
- key: 共享内存段的名字
- size: 共享内存大小
- shmflg:由九个权限标志构成(读、写、执行——0777),用法和创建文件时使用的mode模式标志是一样的,但是不需要用umask(0)
返回值:如果共享内存创建成功,返回一个非负整数,即该段共享内存的标识码(shm_id);如果失败,则返回“-1”
注意:该函数有两层含义:如果不存在则创建;如果存在(根据key判断)则不做操作
2、shmat函数——连接
作用:共享内存段刚被创建的时候,任何进程还都不能访问它,为了建立对这个共享内存段的访问渠道,必须由我们来把它连接到自己的进程的地址空间(就像共享单车你要扫码支付完才能使用)
原型:void* shmat(int shm_id, const void *shm_addr, int shmflg);
参数说明:
- shm_id: shmget返回的共享内存标识
- shm_addr:把共享内存连接到当前进程去的时候准备放置它的那个地址
- shmflg是一组按位OR(或)在一起的标志。它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:调用成功,返回一个指针,指针指向共享内存的第一个字节;如果失败,则返回“-1”(void* 则表示可以存任何类型,因为刚连接自己也不知道是什么类型)
3、shmdt函数——断开
作用:把共享内存与当前进程脱离开(就像共享单车的归还动作)
原型:int shmdt(const void *shm_addr);
参数说明:shm_addr: 由shmat返回的地址指针
返回值:操作成功,返回“0”,失败则返回“-1”
脱离共享内存并不等于删除它,只是当前进程不能再继续访问它而已
4、shmctl函数——删除
作用:共享内存的控制函数(一般用不到,因为数据可以共享就可以覆盖,没必要删除)
原型:int shmctl(int shm_id,int command,struct shmid_ds *buf);
参数说明:
- shm_id: 由shmget返回的共享内存标识码
- command:将要采取的动作(三个取值:IPC_STAT、IPC_SET、IPC_RMID)
- buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:操作成功,返回“0”,失败则返回“-1”
内存操作:
- memset 通常为新申请的内存进行初始化工作(清空——bzero函数)
- memcpy 内存拷贝,只是将数据拷贝出来,原本数据不会清空
四、示例
创建两个应用程序,一个读一个写
1、代码
#include <iostream>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <string.h>
using namespace std;
//写端
typedef struct student
{
char stuid[10];
char name[20];
}STU;
int main()
{
void* shmaddr = NULL;
int shmid = 0;
STU stu1 = { "1001","zqw"};
//创建
shmid = shmget((key_t)1001,2048,IPC_CREAT|0777);
if (shmid == -1)
{
perror("shmget error");
}
else
{
//连接共享内存 获取到共享内存的首地址shmaddr
shmaddr = shmat(shmid,NULL,0);
//写入数据 = 内存拷贝
memcpy(shmaddr, &stu1,sizeof(STU));
/*cout <<"res_stu id" << res_stu.stuid<< endl;
cout << "res_stu name" << res_stu.name << endl;*/
//断开共享内存的连接
shmdt(shmaddr);
}
return 0;
}
//读端
typedef struct student
{
char stuid[10];
char name[20];
}STU;
int main()
{
void* shmaddr = NULL;
int shmid = 0;
STU res_stu = { 0 };
//创建
shmid = shmget((key_t)1001, 2048, IPC_CREAT | 0777);
if (shmid == -1)
{
perror("shmget error");
}
else
{
//连接共享内存 获取到共享内存的首地址
shmaddr = shmat(shmid, NULL, 0);
//读取数据 = 内存拷贝
memcpy(&res_stu, shmaddr, sizeof(STU));
cout << "读端 id = " << res_stu.stuid << endl;
cout << "读端 name = " << res_stu.name << endl;
//断开共享内存的连接
shmdt(shmaddr);
}
return 0;
}
2、运行结果
- 因为在读端没有对数据进行更改、清空等操作,只是单纯的拷贝,所以共享内存的数据依然还在,读多次的结果还是一样
- 进程结束,共享内存依然还在,说明了共享内存属于操作系统直接管理而不属于进程
- 共享内存足够用就好,创建完会一直占用内存,直到你手动删除
转载请注明出处