[Linux]----进程间通信之共享内存


前言

基于上篇我们利用管道进行进程间通信的使用和实现,本篇将带大家通过共享内存进行进程间通信!


正文开始

一、system V共享内存

首先回顾上篇的内容

进程进通信的前提是,先让不同的进程,看到同一份资源!

原理

在这里插入图片描述
系统提供给了我们如下接口
1.创建共享内存------删除共享内存(OS完成)
2.关联共享内存------去关联共享内存(进程完成)
在这里插入图片描述
上面函数的第二个参数建议设置成为页(4KB)的整数倍!
在这里插入图片描述
那么问题来了
1.共享内存存在哪里呢?

存在内核中—内核会给我们维护共享内存的结构!!—所以内核中一定存在描述共享内存的内核数据结构—struct shmid_ds{};
在这里插入图片描述

共享内存要被管理–>struct shmid_ds{}–>struct ipc_perm—>key(shmget)—>共享资源的唯一值。

我怎么知道,这个共享内存到底存在还是不存在呢?

先有方法表示共享内存的唯一性!!(key)–>一般是由用户提供的!

共享内存,在内核中,让不同的进程看到同一份共享内存的做法是:让他们拥有同一个key即可!!!

类比于命名管道进行理解

命名管道–>约定好使用同一个文件,来进行通信的!!

共享内存–>约定好使用同一个唯一key,来进行通信的!!

那么如何生成我们的key呢,系统给我们提供了接口!!

在这里插入图片描述

二、创建共享内存

//Comm.hpp

#pragma once
#include<iostream>
#include<sys/types.h>
#include<sys/ipc.h>
#include<cstring>
#include<cerrno>
#include<cassert>
#include<cstdlib>
#include<sys/shm.h>
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string>
#include"Log.hpp"

#define PATH_NAME "/home/hulu"
#define PROJ_ID 0x14

key_t CreateKey() 
{
    key_t key=ftok(PATH_NAME,PROJ_ID);
    if(key<0)
    {
        std::cerr<<"ftok "<<strerror(errno)<<std::endl;
        exit(1);
    }
    return key;
}

//Log.h

#pragma once

#include<iostream>
#include<ctime>

std::ostream& Log()
{
   std::cout << "For Debug |" << " timestamp: " << (uint64_t)time(nullptr) << " | ";
    return std::cout;
}

//IpcShmSer.cc

#include"Comm.hpp"
#include"Log.hpp"
#include<unistd.h>

using namespace std;
int main()
{

    key_t key=CreateKey();
    Log()<<"key: "<<key<< "\n";
    Log()<<"create share memory  begin!\n";
    int shmid=shmget(key,MEM_SIZE,IPC_CREAT | IPC_EXCL|0666);
    if(shmid<0)
    {
        Log()<<"shmget: "<<strerror(errno)<< "\n";
        return 2;
    }
    Log()<<"create shm success,shmid: "<<shmid<< "\n";
    return 0;
}

在这里插入图片描述

当我们运行完毕创建全新的共享内存的代码后(进程退出),但是第二(n)次的时候,该代码无法运行,告诉我们file存在!—>我要创建的共享内存是存在的!—>system V下的共享内存,生命周期是随内核的!!—>如果不显式的删除,只能通过Kernel(OS)重启解决!

那么我怎么知道有哪些IPC资源呢?

ipcs -m

在这里插入图片描述

这个就是我们刚刚创建的共享内存!!

如何显式的删除呢?

ipcrm -m shmid

在这里插入图片描述
还可以使用系统接口删除
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

int main()
{

    key_t key=CreateKey();
    Log()<<"key: "<<key<< "\n";

    Log()<<"create share memory  begin!\n";

    int shmid=shmget(key,MEM_SIZE,IPC_CREAT | IPC_EXCL|0666);
    if(shmid<0)
    {
        Log()<<"shmget: "<<strerror(errno)<< "\n";
        return 2;
    }
    Log()<<"create shm success,shmid: "<<shmid<< "\n";
     //删除
     shmctl(shmid,IPC_RMID,nullptr);
     Log()<<"delete shm : "<<shmid<<"success\n";
}

在这里插入图片描述
这个权限默认是0,证明其他进程无法访问,我在这里设置为666,以便我们接下来的实验!

关联和去关联共享内存

共享内存虽然是这个进程创建的,但是他不属于这个进程!

需要我们调用系统接口将他们关联起来!

在这里插入图片描述
你怎么使用malloc的空间,你就怎么使用共享内存的空间!

int main()
{

    key_t key=CreateKey();
    Log()<<"key: "<<key<< "\n";

    Log()<<"create share memory  begin!\n";

    int shmid=shmget(key,MEM_SIZE,IPC_CREAT | IPC_EXCL|0666);
    if(shmid<0)
    {
        Log()<<"shmget: "<<strerror(errno)<< "\n";
        return 2;
    }
    Log()<<"create shm success,shmid: "<<shmid<< "\n";
    sleep(10);
    //使用
    //1.将共享内存和自己的进程产生关联attach
    char* str=(char*)shmat(shmid,nullptr,0);
    Log()<<"attach shm: "<<shmid<<"success\n";
    sleep(10);


    //2.去关联
    shmdt(str);
    Log()<<"detach shm: "<<shmid<<"success\n";


    // //删除
     shmctl(shmid,IPC_RMID,nullptr);
     Log()<<"delete shm : "<<shmid<<"success\n";
}

在这里插入图片描述

进行通信

//IpcShmSer.cc

int main()
{

    key_t key=CreateKey();
    Log()<<"key: "<<key<< "\n";

    Log()<<"create share memory  begin!\n";

    int shmid=shmget(key,MEM_SIZE,IPC_CREAT | IPC_EXCL|0666);
    if(shmid<0)
    {
        Log()<<"shmget: "<<strerror(errno)<< "\n";
        return 2;
    }
    Log()<<"create shm success,shmid: "<<shmid<< "\n";
    //使用
    //1.将共享内存和自己的进程产生关联attach
    char* str=(char*)shmat(shmid,nullptr,0);
    Log()<<"attach shm: "<<shmid<<"success\n";
    while(true)
    {
        //让读端进行等待
        printf("%s\n",str);
        sleep(1);
    }
    //2.去关联
    shmdt(str);
    Log()<<"detach shm: "<<shmid<<"success\n";


    // //删除
     shmctl(shmid,IPC_RMID,nullptr);
     Log()<<"delete shm : "<<shmid<<"success\n";
}

//IpcShmCli.cc

using namespace std;
int main()
{
    key_t key = CreateKey();
    Log() << "key: " << key << "\n";

    //获取共享内存
    int shmid = shmget(key, MEM_SIZE, IPC_CREAT);
    if (shmid < 0)
    {
        Log() << "shmget: " << strerror(errno) << "\n";
        return 2;
    }
    //挂接
    char *str = (char *)shmat(shmid, nullptr, 0);

    //使用
    //竟然没有使用任何的系统调用接口
    int cnt=0;
    while(cnt<=26)
    {
        str[cnt]='A'+cnt;
        cnt++;
        str[cnt]='\0';
        sleep(1);
    }
    //去关联
    shmdt(str);
    return 0;
}

在这里插入图片描述
我们把共享内存实际上是映射到了我们的进程地址空间的用户空间了(堆->栈之间),对每一个进程而言挂接到自己上下文中的共享内存,属于自己的空间,类似于堆空间和栈空间,可以被用户直接使用!!

共享内存,因为它自身的特性,他没有任何访问控制!共享内存被双方直接看到,属于双方的用户空间,但是不安全!

共享内存是所有进程间通信,速度最快的!

三、基于管道进行共享内存通信

//Comm.hpp

#pragma once
#include<iostream>
#include<sys/types.h>
#include<sys/ipc.h>
#include<cstring>
#include<cerrno>
#include<cassert>
#include<cstdlib>
#include<sys/shm.h>
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string>
#include"Log.hpp"

#define PATH_NAME "/home/hulu"
#define PROJ_ID 0x14

key_t CreateKey() 
{
    key_t key=ftok(PATH_NAME,PROJ_ID);
    if(key<0)
    {
        std::cerr<<"ftok "<<strerror(errno)<<std::endl;
        exit(1);
    }
    return key;
}
void CreateFifo()
{
    umask(0);
    if(mkfifo(FIFO_FILE,0666)!=0)
    {
        Log()<<strerror(errno)<<"\n";
        exit(2);
    }
}

#define READER O_RDONLY
#define WRITER O_WRONLY

int Open(const std::string& filename,int flags)
{
    return open(filename.c_str(),flags);
}

int Wait(int fd)
{
    uint32_t values=0;
    ssize_t s=read(fd,&values,sizeof(values));
    return s;
}

int Signal(int fd)
{
    uint32_t cmd=1;
    write(fd,&cmd,sizeof(cmd));
}

int Close(int fd,const std::string filename)
{
    close(fd);
    unlink(filename.c_str());
}

//Log.h

#pragma once

#include<iostream>
#include<ctime>

std::ostream& Log()
{
   std::cout << "For Debug |" << " timestamp: " << (uint64_t)time(nullptr) << " | ";
    return std::cout;
}

//IpcShmSer.cc

#include"Comm.hpp"
#include"Log.hpp"
#include<unistd.h>

using namespace std;

#define FIFO_FILE ".fifo"
#define READER O_RDONLY
#define WRITER O_WRONLY

int main()
{

    key_t key=CreateKey();
    Log()<<"key: "<<key<< "\n";

    Log()<<"create share memory  begin!\n";

    int shmid=shmget(key,MEM_SIZE,IPC_CREAT | IPC_EXCL|0666);
    if(shmid<0)
    {
        Log()<<"shmget: "<<strerror(errno)<< "\n";
        return 2;
    }
    Log()<<"create shm success,shmid: "<<shmid<< "\n";

     CreateFifo();
     int fd=Open(FIFO_FILE,READER);
     assert(fd>=0);
    //使用
    //1.将共享内存和自己的进程产生关联attach
    char* str=(char*)shmat(shmid,nullptr,0);
    Log()<<"attach shm: "<<shmid<<"success\n";
    while(true)
    {
        //让读端进行等待
        if(Wait(fd)<=0) break;
        printf("%s\n",str);
        sleep(1);
    }

    //2.去关联
    shmdt(str);
    Log()<<"detach shm: "<<shmid<<"success\n";

    // //删除
     shmctl(shmid,IPC_RMID,nullptr);
     Log()<<"delete shm : "<<shmid<<"success\n";

    Close(fd,FIFO_FILE);
}

//IpcShmCli.cc

#include "Comm.hpp"
#include "Log.hpp"
#include <cstdio>
#include <unistd.h>

#define FIFO_FILE ".fifo"
#define READER O_RDONLY
#define WRITER O_WRONLY

using namespace std;
int main()
{
    int fd=Open(FIFO_FILE,WRITER);
    key_t key = CreateKey();
    Log() << "key: " << key << "\n";

    //获取共享内存
    int shmid = shmget(key, MEM_SIZE, IPC_CREAT);
    if (shmid < 0)
    {
        Log() << "shmget: " << strerror(errno) << "\n";
        return 2;
    }

    //挂接
    char *str = (char *)shmat(shmid, nullptr, 0);
    while(true)
    {
        printf("Please Enter#");
        fflush(stdout);
        ssize_t s=read(0,str,MEM_SIZE);
        if(s>0)
        {
            str[s]='\0';
        }
        Signal(fd);
    }

    //去关联
    shmdt(str);

    return 0;
}

在这里插入图片描述

四、临界资源

能被多个进程同时看到的资源----临界资源

如果没有对临界资源进行任何保护,对于临界资源的访问,双方进程在进行访问的时候,就都是乱序的,可能会因为读写交叉而导致各种的乱码和废弃数据访问控制方面的问题!!!

与之前fork()创建子进程后,父子进程打印数据是乱序的,缺乏访问控制!

对多个进程而言,访问临界资源的代码—临界区!

我的进程代码中,有大量的代码。但是只有一部分代码,会访问临界区!

我们把一件事情,要不没做,要么做完了(没有中间状态)----原子性

任何时刻,只允许一个进程访问临界资源—互斥

五、信号量

  1. 什么是信号量?
    信号量本质就是一个计数器!!!

当信号量为1的时候表示的就是互斥特性!

互斥信号量----二元信号量

常规信号量—多元信号量

每一个进程要访问临界资源,必须先申请信号量–>就要求每一个进程必须先看到这个信号量—>所以信号量本身也是临界资源!!!—>信号量对应的操作是原子的!

信号量对应的操作是PV操作!!
sem:–; 申请资源:P
sem:++;释放资源 V

共享内存没有访问控制,我们可以通过信号量进行对资源的保护!


总结

临界资源和信号量的主要内容会在线程部分着重带大家理解!
这里是带大家浅浅的认识一下!有个了解即可!

(本章完!)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

拾至灬名瑰

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

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

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

打赏作者

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

抵扣说明:

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

余额充值