进程间通信之共享内存

目录

1.共享内存:

2.创建与删除

3.attch与detach

4.使用


进程间通信的本质就是:让不同的进程看见同一份资源。

上一篇博客的匿名管道适用于:有血缘关系的进程,父进程子进程兄弟进程。

                      命名管道适用于:不相关的进程,它是一种特殊的文件。

                      它们俩的本质都是一片缓冲区。

而这次的共享内存实质是物理内存的一块区域。

1.共享内存:

        不同的进程通过访问内存上的同一片区域,实现通信。

        这次学的是System V共享内存,这是时代竞争的产物。

原理:

①在内存中创建共享内存区域。

②通过特定系统接口将这块区域映射关联到进程的上下文中。

2.创建与删除

        创建我们要使用到shmget这个系统调用接口。

        它的作用是创建一个 System V级别的共享内存。

①key:标识共享内存的值。

②size:共享内存的大小。建议传(页)4KB的整数倍,因为磁盘和操作系统IO的基本单位是4KB,所以磁盘在将数据拷贝到内存时,会以4KB整数倍的大小来占用内存。

③shmflg:可以传入两个宏,IPC_CREAT  和 IPC_EXCL。传入0的话,默认是IPC_CREAT。

IPC_CREAT:创建共享内存,如果存在就获取它,如果不存在就创建一个。

IPC_EXCL:与IPC_CREAT搭配使用,如果存在就获取它,如果不存在就出错返回。

                 ->它可以保证如果调用shmget函数成功,所创建的一定是一个全新的共享内存。

return value:a valid shared memory identifier is returned.

                       一个有效的共享内存的标识符被返回。


Ps:

        我们怎么知道共享内存存在还是不存在?内核中有相应的数据结构来管理这些共享内存。

在用户层,会有这样的结构体来描述共享内存:

        这个结构体中还有一个结构体ipc_perm (IPC(Inter-Process Communication,进程间通信) perm(permission,权限,许可)),也就是进程间通信权限。

        这个结构体中第一个成员__Key,是不是很熟悉,就是shmget的一个参数。

        它表示共享内存的唯一值。这个key值一般由用户提供。

        假设:a进程和b进程要通信,a进程向shmget传入一个key值,b进程通过这个key值访问这块共享内存,这就实现了进程间通信。


        那怎么保证我们的唯一key值和系统已存在的共享内存的key值冲突呢?

        要使用ftok这个函数帮我生成。

①pathname:任意一个文件所在路径。

②proj_id:你想要设置的值。

        因为文件的inode编号是唯一的与你给的数值,依据算法,会给出一个key值。

我们先来使用下这些函数。

为了更好的打印:

#pragma once 

#include <iostream>
#include <ctime>


std::ostream& output()
{
    std::cout<<"For debug:"<<std::endl;
    std::cout<<"Time: "<< (uint64_t)time(nullptr)<<std::endl;
    return std::cout; 
}

第一步:在head.hpp中使用ftok函数创建key值

#pragma once 

#include <iostream>
#include <cstring>
#include <stdlib.h>
#include <cerrno>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define PATH "/home/Yewei/Code/study20221112"
#define PROJ_ID 0x12

key_t Creat_key()
{
    key_t key = ftok(PATH,PROJ_ID);
    if(key == -1)
    {
        std::cerr<<"ftok erro:"<<strerror(errno)<<std::endl;
        exit(1);
    }
    return key;
}

第二步:获得key值之后,调用shmget函数

const int flag = IPC_CREAT | IPC_EXCL ;

int main()
{
    //create key
    key_t key = Creat_key();
    output()<< key <<endl;

    //create shm
    int shmid = shmget(key,SIZE,flag);
    if(shmid == -1)
    {
        output()<<"shmget error :" << strerror(errno) <<endl;
        return 1;
    }

    output()<<"shmget success! shmid:"<<shmid<<endl;
}

运行结果:

 我们多运行几次:

         会发现这个共享内存总是创建失败,说明声明,随着进程结束,共享内存并不会被销毁。

         通过退出码可以看到,是在创建共享内存时报错。

结论是:
        System V下的共享内存,它的生命周期随着内核。如果不显示的删除,只能通过kernel(os)重启来解决。

如何显示的删除:

①先知道要删除哪一个

 ipcs -m


Ps:

perms:

        这是我们所创建共享内存的权限,与对操作的文件的权限相同,避免我们一会使用共享内存出现问题,所以我们现在在flag后面要"|"上对共享内存操作的权限。

nattch:

        与共享内存所连接进程的个数。

status:

        表示共享内存的状态。


 通过命令行删除

 ipcrm -m X                                                                                   X:shimd

        为什么要用shimd来删除呢,为什么不用key来删除,毕竟key值是唯一标示的共享内存。

        因为我们是在用户层,用命令行来用进程将共享内存删除。shmid是由shmget函数返回给用户的标定共享内存的值,在用户层删除当然要用shimd来删除。而key是内核中标定共享内存的值。

②通过系统接口

       这次删除就简单点。

shmid:传入shmget的返回值。

cmd:传入IPC_RMID。

buf: 如果传入IPC_RMID,此处就可以传入NULL。

        具体的大家可以在man手册中再深入研究一下。

我们使用一下:

    //delte shm
    int cnt = 3;
    while(cnt--)
    {
        cout<<"删除倒计时::"<<cnt<<endl;
    }

    int shmrm = shmctl(shmid,IPC_RMID,nullptr);
    if(shmrm == -1)
    {
        output()<<"shmctl error :" << strerror(errno) <<endl;
    }
    
    output()<<"shmctl success!"<<endl;

结果:

   

3.attch与detach

        attch我们要使用shmat。

        简单使用。

shmid: 传入shmget的返回值。

shamaddr:所要挂接到地址空间的什么位置,今天我们只用传NULL。

shmflg:传入0,就是默认读写共享内存。

return value:成功,返回挂接共享内存的地址。失败,-1。

        关键就是这个返回值,就如同malloc一样,成功创建一块堆区空间,返回地址通过地址来访问堆区。在这一样,我们可以通过返回值来访问共享内存。

代码:

    //attch shm
    int cnt = 3;
    while(cnt--)
    {
        cout<<"attach倒计时::"<<cnt<<endl;
    }
    //今天我们以一个字节一个字节来访问共享内存
    char *shm = (char*)shmat(shmid,nullptr,0);
    output()<<"shmat success!"<<endl;

结果:

        

        detach我们要使用shmdt。

        只要将我们获得的共享内存的地址传入即可。

代码:

    //detach shm
    cnt = 3;
    while (cnt--)
    {
        cout << "detach倒计时::" << cnt << endl;
    }
    shmdt(shm);
    output() << "detach success!" << endl<<endl;

结果: 

4.使用

        我们首先去编写另一个进程,用当前进程和另一个进程共同去使用共享内存。

        注意我们创建shm的文件是IpcShmCre.cpp,使用shm的文件是IpcShmUse.cpp。

        在IpcShmUse.cpp中这样写:

#include "head.hpp"
#include "output.hpp"
using namespace std;

//使用共享内存

int main()
{
    // creat key
    key_t key = Creat_key();
    cout << key << endl;

    // get shm
    int shmid = shmget(key, SIZE, IPC_CREAT);
    if (shmid == -1)
    {
        output() << "shmget error :" << strerror(errno) << endl;
        return 1;
    }

    //attch
    char* shm = (char*)shmat(shmid,nullptr,0);

    //use shm
    int index = 0;
    while(index != 26)
    {
        shm[index] = 'A' + index;
        ++index;
        shm[index] = '\0';
        sleep(1);
    }

    //detach shm
    shmdt(shm);

}

        在IpcShmCre.cpp中这样写:

    // use shm
    while(1)
    {
        printf("%s\n",shm);
        sleep(1);
    }

结果:

 运行一段时间后:

         导致这样的情况是因为共享内存的的特性,它没有任何访问控制。共享内存被用户直接看到,属于双方的用户空间。

我们还可以这样写:

在IpcShmCre.cpp中这样写:

    // use shm
    while(1)
    {
        printf("%s",shm);
        sleep(1);
    }

 在IpcShmUse.cpp中这样写:

    while(1)
    {
        printf("Please import$$");
        fflush(stdout);
        ssize_t sz = read(0,shm,SIZE);
        if(sz > 0)
        {
            shm[sz] = '\0';
        }
    }

结果:

         感谢观看。         

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值