Linux---进程间通信(下)

1、System V 共享内存

原理如下图

 系统调用接口介绍

int shmget(key_t key, size_t size, int shmflg)

功能:用来创建共享内存
参数

  • key:这个共享内存段名字,内核用key来标识共享内存
  • size:共享内存大小
  • shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的(这里介绍两个:IPC_CREAT和IPC_EXCL,其中IPC_CREAT表示共享内存存在就返回,不存在就创建,IPC_EXCL不单独使用,IPC_EXCL | IPC_CREAT表示不存在就创建,存在就出错返回,作用是确保创建出来的共享内存一定是全新的)

返回值:成功返回一个非负整数,即该共享内存段的标识码(作用类似文件描述符fd);失败返回-1

void* shmat(int shmid, const void* shmaddr, int shmflg)

功能:将共享内存段连接到进程地址空间(在页表中创建映射关系)
参数

  • shmid:共享内存标识
  • shmaddr:指定连接的地址(可以直接传nullptr,让OS帮你分配)
  • shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY

返回值:成功返回一个指针,指向共享内存第一个节(类比malloc);失败返回-1

 int shmdt(const void *shmaddr)

功能:将共享内存段与当前进程脱离(删除页表中的映射关系)
参数

  • shmaddr:由shmat所返回的指针

返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段

int shmctl(int shmid, int cmd, struct shmid_ds *buf)

功能:用于控制共享内存
参数

  • shmid:由shmget返回的共享内存标识码
  • cmd:将要采取的动作(IPC_STAT---获取共享内存的相关信息、IPC_SET---设置共享内存的相关信息、IPC_RMID---释放共享内存)
  • buf:指向一个保存着共享内存的模式状态和访问权限的数据结构

返回值:成功返回0;失败返回-1

(共享内存的生命周期是随内核的,即进程结束,共享内存不会释放,需要手动释放空间)

 共享内存的通信代码如下

//comm.hpp

#include <string>
#include <iostream>
#include <sys/ipc.h>
#include <unistd.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <cstring>
using namespace std;

// 路径和项目id可以随便写,但是建议写和代码相关的
string pathname = "/shm";
int process_id=0x11223344;

const int size = 4096; // 建议设置为n*4096,Linux以4096为单位进行分配共享内存
string filename = "fifo"; //管道名,下面代码用管道是利用管道的同步机制,共享内存没有同步机制

key_t Getkey()
{
    key_t key = ftok(pathname.c_str(), process_id);// 该系统调用相当于一个hash函数,用两个参数通过算法获得一个hash值 key
    if(key < 0)
    {
        cout << "errno: " << errno << ",errstring: " << strerror(errno) << endl;
        exit(1);
    }
    
    return key;
}

string ToHex(int id) // 返回16进制表示形式 
{
    char buffer[1024];
    snprintf(buffer,sizeof(buffer),"0x%x",id);
    return buffer;
}

int CreateShmHelper(key_t key, int flag)
{
    int shmid = shmget(key, size, flag);
    if(shmid < 0)
    {
        cout << "errno: " << errno << ",errstring: " << strerror(errno) << endl;
        exit(2);
    }
    return shmid;
}

int GetShm(key_t key)
{
    return CreateShmHelper(key, IPC_CREAT);
}

int CreateShm(key_t key)
{
    return CreateShmHelper(key, IPC_CREAT|IPC_EXCL|0666);// 设置创建共享内存并设置权限0666---八进制
}


//server.cc

#include "comm.hpp"
//用一个类来初始化资源和释放资源
class Init
{
public:
    Init()
    {
        int n = mkfifo(filename.c_str(), 0666);
        if(n < 0) exit(1);

        key_t key = Getkey();
        cout << "key:" << ToHex(key) << endl;
        // sleep(5);
        // key vs shmid
        // key:内核中使用,标识共享内存的唯一性
        // shmid:应用这个共享内存的时候,我们使用shmid来使用操作共享内存
        // 即key是给内核看的,shmid是给用户看的
        shmid = CreateShm(key);
        cout << "shmid:" << shmid << endl;
        cout << "创建内存" << endl;

        p = (char*)shmat(shmid, nullptr, 0);
        cout << "将内存挂接到虚拟地址空间" << endl;
        sleep(3);
//注意资源的初始化顺序,管道的打开要放在恰当的地方,不然会出bug,这边建议放到最后
        fd = open(filename.c_str(), O_RDONLY);// 读端
    }

    ~Init()
    {
        close(fd);
        unlink(filename.c_str());// 删除管道文件

        shmdt(p);
        cout << "删除页表映射" << endl;
        sleep(3);

        shmctl(shmid, IPC_RMID, nullptr);
        cout << "删除内存" << endl;
    }
public:
    int fd;
    int shmid;
    char*p;
};

int main()
{
    Init init;
    int code = 0;
    while(1)
    {
        ssize_t n = read(init.fd, &code, sizeof(code));
        if(n > 0)
        {
            cout << "client:" << init.p << endl;
        }
        else if(n == 0)
        {
            break;
        }
    }
    return 0;
}

//client.cc
#include "comm.hpp"

int main()
{
    key_t key = Getkey();
    cout << "key:" << ToHex(key) << endl;

    int shmid = GetShm(key);
    cout << "获取shmid:" << shmid << endl;

    char *p = (char *)shmat(shmid, nullptr, 0); // 开出来的空间当数组用即可
    cout << "挂接" << endl;

    int fd = open(filename.c_str(), O_WRONLY);
    int code = 1;
    cout << "write start" << endl;
    for (int i = 0; i < 26; i++)
    {
        p[i] = 'a' + i;
        sleep(2);
        write(fd, &code, sizeof(code));
        cout << "write:" << (char)('a' + i) << endl;
    }
    shmdt(p);
    cout << "取消映射" << endl;

    return 0;
}

在看完上面这段代码的前提下,我们就能解释两个进程如何得到同一个共享内存的标识符:本质是我们事先约定了标识符key的值(在上面的代码中,我们是用ftok函数生成的key,使用的相同的参数,所以返回值也相同。这里使用ftok只是让系统帮助我们生成key,其实只要key这个值相对其他共享内存的key是唯一的即可),放在头文件中,然后两个程序通过key就能找到同一个共享内存

特点:

  • 共享内存的通信方式,不会提供同步机制,共享内存是直接裸露给所有的使用者的,一定要注意共享内存的使用安全问题
  • 共享内存可以提供较大的空间
  • 共享内存是所有进程间通信速度最快的(因为管道底层是对文件操作的复用,所以数据需要在用户缓冲区和内核文件缓冲区间来回拷贝,但是共享内存不需要,共享内存可以理解为用malloc申请了一块空间,两个进程都能看见,没有用户缓冲区和内核文件缓冲区来回拷贝的过程,所以更快)

查看管理ipc资源的指令介绍

 ipcs -m

ipcrm -m shmid   删除共享内存

2、System V 消息队列---简单介绍

消息队列提供一个进程给另一个进程发送数据块的能力

3、System V信号量---简单介绍

背景介绍

当我们在进行进程间通信的时候,可能会出现A进程和B进程通过同一份资源在进行通信,突然C进程也开始往该资源中写数据/读数据,导致数据传输出现问题的情况,换句话说,当多执行流访问同一份资源时,我们需要保护该资源,所以我们有了信号量这个概念

在介绍信号量的概念之前,我们先来看看信号量(semaphore)的系统调用接口

信号量的概念和理解

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值