【Linux】进程间通信 -- System V共享内存

前言

本篇博客介绍第二种进程间通信的方式 – System V
System V 有三种方式:
共享内存
消息队列
信号量
本篇博客对于系统调用的函数,会进行一定的封装

在这里插入图片描述

一. System V 共享内存

二. 共享内存的原理

进程间具有独立性,不同进程之间无法直接获取对方的数据,所有通信都需要有第三方的介入,管道是以文件的方式,提供共享的文件。
匿名管道建立在子进程继承父进程的进程信息的特性,可以获得父进程创建的匿名管道的文件描述符,仅适用于具有“亲戚关系”的进程;
命名管道本质同匿名管道一样,只不过是在当前目录下创建一个管道文件,不同进程可以以不同方式打开这个管道文件,进行读写适用于任何不同进程
两个管道都是内存级文件不会进行刷盘

那么System V 共享内存是以什么方式使得不同进程进行通信的呢?

进程间通信的前提都是:让不同进程看到同一份“资源”
管道通过文件,而System V通过内存实现
在这里插入图片描述
System V创建的共享内存通过页表映射放到不同进程的共享区

三. 共享内存的创建

创建共享内存的函数是shmget() shared memory get
在这里插入图片描述

  1. 第一个参数稍后详细讲解
  2. 第二个参数是创建的共享内存的大小
  3. 第三个参数是一个位图,可传的参数有三种情况:
    (1) IPC_CREAT:没有共享内存就创建,有,就返回这个共享内存的shmid
    (2) IPC_CREAT | IPC_EXCL :没有共享内存就创建,有,就直接报错,返回-1并设置错误码。其作用是确保创建的共享内存是最新的
    (3) 以上两种 | 权限
    注意:IPC_EXCL不能单独使用,没有效果

返回值是创建的共享内存的共享内存的标识符(和文件描述符不同)

下面我们详细讲解一下第一个参数

我们创建的共享内存,需要不同进程双方都能找到,并且创建新的共享内存时不和其他共享内存冲突,所有需要由系统生成一个key值,尽量的避免冲突,保证不同进程的通信

生成key值的函数是:ftok()
在这里插入图片描述
内存中可能同时有多个共享内存,操作系统也会对其有相应的结构体,对他们进行管理。这也是先描述,再组织思想的体现。
所以我们就需要确保不同进程能找到同一个共享内存

第一个参数:路径字符串;第二个参数:项目ID
ftok的这两参数,在创建后会保存在共享内存结构体的属性中,这样操作系统才能帮我们找到我们要使用的共享内存。
而需要进行通信的进程,需要确保这两个参数传的一样,才能找到同一个共享内存。
具体内容是什么其实不重要(pathname需要是能找到的文件路径),只要确保要通信的进程传的一样就好

获取key值的函数

#define PATHNAME "." 
#define PROJID 0x6666

//获取key值
key_t getKey()
{
    key_t k=ftok(PATHNAME,PROJID);
    if(k==-1)
    {
        cerr<<errno<<":"<<strerror(errno)<<endl;
        exit(1);
    }
    return k;
}

//将key值转换成十六进制
string toHex(key_t k)
{
    char buffer[64];
    snprintf(buffer,sizeof(buffer),"0x%x",k);
    return buffer;
}

简单测试一下
在这里插入图片描述

这样我们就得到了相同的key值。
接下来就是创建内存空间了。

因为创建共享内存和获取共享内存只有最后一个参数有所不同,所以我们可以进行一定的封装

//进行封装
static int createShmHelper(key_t key,int size,int flag)
{
    int shmid = shmget(key,size,flag);

    //出错判断
    if(shmid==-1)
    {
        cerr<<"error: "<<errno<<" : "<<strerror(errno)<<endl;
        exit(2);
    }
    return shmid;
}

//创建共享内存
int createShm(key_t key,int size)
{
    //因为是创建,我们就用,保证创建最新的内存空间
    return createShmHelper(key,size,IPC_CREAT | IPC_EXCL);
}

//获取共享内存
int getShm(key_t key,int size)
{
    //只要获取就好,所以只要IPC_CREAT
    return createShmHelper(key,size,IPC_CREAT);
}

简单测试一下
在这里插入图片描述

四. 共享内存的查看和删除(指令)

我们成功创建了共享内存,那么接下来有个疑问:我们这两个程序很简单,没有任务循环,运行一下就结束了,那相应创建的共享内存还存在吗?
我们如果再运行shmServer会发现以下现象
在这里插入图片描述
因为在shmServer.cpp中,我们调用shmget()使用的是IPC_CREAT | IPC_EXCL,所以当共享内存已经存在时,会创建失败,返回-1,设置错误码。


共享内存的查看

我们还可以使用ipcs指令查看进程间通信的相关信息
在这里插入图片描述
第一个是消息队列,第二个共享内存,第三个是信号量数组
ipcs -m指令表示只查看共享内存
在这里插入图片描述

key:共享内存对应的key值,类比文件inode编号,内核使用
shmid:共享内存的id,类比文件描述符fd,用户使用
owner:创建该共享内存的用户
perms:该共享内存的权限(同文件)
bytes:大小(以字节为单位)
nattch:关联(链接)该共享内存的进程数
status

以上两种现象,都表明,即使创建共享内存的进程已经退出,相应的共享内存其实还存在。


共享内存的删除
我们可以通过ipcrm -m shmid删除我们创建的共享内存
在这里插入图片描述
我们再创建共享内存就会分配新的shmid给我们创建的共享内存了
在这里插入图片描述

五. 共享内存的修改/删除

函数是:shmctl()
在这里插入图片描述
第一个参数是shmid;第二个是对共享内存的操作,操作有多种,删除,修改个别属性...;第三个参数是输出型参数,是内核中管理共享内存的结构体,可以通过传结构体获取该共享内存的属性

这是结构体的部分属性
在这里插入图片描述

共享内存的删除
修改操作的宏是IPC_RMID

//删除共享内存
void delShm(int shmid)
{

    int n=shmctl(shmid,IPC_RMID,NULL);
    assert(n!=-1);
    (void)n;
}

获取共享内存属性

//查看共享内存属性
struct shmid_ds ds;
int n=shmctl(shmid,IPC_STAT,&ds);
if(n!=-1)
{
    cout<<"perm: "<<toHex(ds.shm_perm.__key)<<endl;
    cout<<"create pid: "<<ds.shm_cpid<<" : "<<getpid()<<endl;
}

在这里插入图片描述
注意:查看共享内存需要有权限
调用者必须要有这个共享内存的读权限
在这里插入图片描述

六. 共享内存的权限

我们用以上方法创建一个共享内存,perms是权限,默认为0
在这里插入图片描述

权限需要在创建时确定,其实只需要在shmget的第三个参数加上对应的权限就好

//创建共享内存
int createShm(key_t key,int size)
{
    //因为是创建,我们就用,保证创建最新的内存空间
    umask(0);//将掩码设置为0,保证预期的权限
    return createShmHelper(key,size,IPC_CREAT | IPC_EXCL | 0666);
}

在这里插入图片描述
这样就成功设置权限了。

七. 共享内存的关联

以上我们完成了进程间通信的周边工作,但是我们还没有真正实现通信。我们只创建了共享内存,但其实此时共享内存还并没有在我们进程的task struct中,我们只是成功的创建了。为此我们还需要关联这个共享内存,才会将共享内存映射到进程的共享区,才可以真正的使用。

关联的函数是shmat()
在这里插入图片描述

第一个参数是shmid;第二个参数是共享区的虚拟地址,可以通过这个参数指定将共享内存挂接到指定位置;第三个参数是挂接方式(读写)
返回值是挂接的共享区的虚拟地址

//关联共享内存
char * attchShm(int shmid)
{
    char*start=(char*)shmat(shmid,NULL,0);
    return start;
}

在这里插入图片描述
在(三)中,我们就讲了,nattch是关联该共享内存的进程数,这个测试也验证了这一点。0->1->2->1->0

八. 共享内存的取关联

取关联的函数是shmdt()

头文件和shmat()一样
在这里插入图片描述
传参是shmat的返回值

//取关联共享内存
void detachShm(char*start)
{
    int n=shmdt(start);
    assert(n!=-1);
    (void)n;
}

九. 共享内存的通信

接下来,我们就可以使用共享内存进行通信了。
不过我们还可以将上述的代码再进行一个封装,封装成一个类

#define SERVER 1
#define CLIENT 0
const int gsize=4096;

class Init
{
public:
    Init(int t):type(t)
    {
        //获取key值
        key_t k = getKey();
        //创建/获取共享内存
        if(type == SERVER)
        {
            //服务端创建
            shmid = createShm(k, gsize);
        }
        else
        {
            //客户端获取
            shmid = getShm(k, gsize);
        }
        //关联共享内存
        start = attachShm(shmid);
    }

    //获取读写字符串
    char *getStart()
    { 
        return start; 
    }

    ~Init()
    {
        //取关联
        detachShm(start);

        if(type == SERVER)
        {
            //如果是服务端,还需要销毁共享内存
            delShm(shmid);
        }
    }
private:
    char *start;//读写字符串
    int type; //表示是服务端还是客户端
    int shmid;//共享内存的id
};

使用如下:
在这里插入图片描述
在这里插入图片描述
成功完成通信

十. 小知识

  1. 共享内存的大小分配
    在这里插入图片描述
    PAGE_SIZE的大小是4KB,刚好对应CPU访问的数据块的大小。
    但是当我们创建一个,比如5000字节的共享内存,那操作系统是分配8KB吗
    在这里插入图片描述
    查看出来的共享内存的大小还是5000,而不是8KB。
    但是这个5000是其实只是允许访问的大小,实际上操作系统是分配了8KB的

  2. 共享内存因为是直接在内存中读写数据,没有像管道那样的缓冲区,一方写,另一方立刻就可以读到,所以是所有进程通信速度最快的

  3. 共享内存因为是直接在内存中通信,没有系统调用接口,没有任何保护机制,是同步互斥

结束语

本篇知识记录较杂,请多谅解。本着记笔记分享的目的,望佬指点。

如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值