Linux-共享内存

文章目录


前言


在这之前我们已经学习了两种进程间通信方式:匿名管道和命名管道。

从我们之前的学习已经知道,想让多个进程间进行通信就需要让他们一起看到同一份资源。

匿名管道是通过fork子进程来让子进程继承父进程的fd。

命名管道是通过生成命名管道文件,并一起打开管道文件。

一、system V共享内存

共享内存相对于我们之前的管道通信有一定区别:

1.共享内存是要让多个进程看到同一份内存.

根据之前我们介绍过的冯洛伊曼体系,对于内存级别的通信特性就代表了共享内存其通信效率要高于管道通信!

2.进程想要看到同一份共享内存,需要key

这里生成的key方式与哈希字符串类似,通过算法来形成key。所以要想要形成同样的key,就必须确保pathname和porj_id相同,不同进程凭借同样的key来访问同一份共享内存!

申请共享内存

参数key 代表如果要访问该共享内存需要的key。

参数size代表申请的共享内存大小,这里需要注意的是,共享内存的大小是以4096个字节为单位,所以size最好是4096的倍数。

参数shmflg是模式选项,有 IPC_CREAT 和 IPC_EXCL , IPC_CREAT单独使用代表 如果没有该共享内存则创建,有则使用已经存在的。 IPC_EXCL单独使用没有意义,如果和IPC_CREAT一起使用代表如果没有该共享内存则创建,如果已经存在则报错。

返回值是共享内存的id,就跟文件一样,我们的共享内存也需要进行管理,所以就也有id。

挂载共享内存

由于我们的共享内存的通信方式是让多个进程看到同一份内存,从我们之前学习地址空间的知识,进程需要通过虚拟地址空间->页表->物理内存,所以,要想看到看到位于物理内存的共享内存,就需要修改页表来做到,所以提供了挂载共享内存的接口函数

参数shmid是我们刚刚讲的共享内存id。

参数shmaddr 可以指定shmaddr的地址为挂载的共享内存地址,一般设置为nullptr。

参数shmflg是模式选项,SHM_RND和SHM_RDONLY,SHM_RND与shmaddr相关,SHM_RDONLY指定该进程只允许对共享内存进行读操作。

返回值为挂载的共享内存地址。

删除共享内存挂载

注意:这里是删除挂载,不是删除共享内存!!!

参数shmaddr为共享内存在该进程的地址。

返回值若为1则删除成功,-1则发生错误。

删除共享内存

参数shmid为共享内存id。

参数cmd为模式选项,其中IPC_RMID为删除选项

参数buf这里暂时不讨论。

返回值若为1则删除成功,-1则发生错误

我们要想删除共享内存也不止这一种方式

通过输入ipcs -m 查看存在的共享内存属性

通过输入ipcrm -m shmid 来删除共享内存

二、示例代码

cpp 复制代码
#Server端
#include "comm.hpp"
#include "Log.hpp"

int main()
{
    // 1.创建创建token
    key_t key = ftok(PATH_NAME, PROJ_ID);
    Log(Debug) << "共享秘钥创建成功! step 1"
               << " [key:" << getKey(key) << "]" << std::endl;


    // 2.申请共享内存
    int shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);
    if (shmid == -1)
    {
        Log(Error) << "共享内存创建失败!!!!! step 2" << std::endl;

        perror("shmget");
        exit(1);
    }
    Log(Debug) << "共享内存创建成功! step 2" << std::endl;

    //sleep(10);

    char *shmaddr = (char *)shmat(shmid, nullptr, SHM_RDONLY);
    if ((void *)shmaddr == (void *)-1)
    {
        Log(Error) << "共享内存挂载失败!!!!!! step 3" << std::endl;

        perror("shmat");
        exit(2);
    }
    Log(Debug) << "共享内存挂载成功! step 3" << std::endl;

   // sleep(5);



    //开始访问共享内存
    while(1)
    {
        printf("%s\n",shmaddr);
        sleep(1);
        if(strcmp(shmaddr,"quit") == 0) break;
    }


    int n = shmdt(shmaddr);
    if (n == -1)
    {
        Log(Error) << "共享内存挂载删除失败! step 4" << std::endl;

        perror("shmdt");
        exit(3);
    }

    Log(Debug) << "共享内存挂载删除! step 4" << std::endl;

    //sleep(5);

    n = shmctl(shmid, IPC_RMID, nullptr);
    if (n == -1)
    {
        Log(Error) << "共享内存删除失败! step 5" << std::endl;

        perror("shmctl");
        exit(4);
    }
    Log(Debug) << "共享内存删除成功! step 5" << std::endl;

    return 0;
}
cpp 复制代码
#Client端
#include "Log.hpp"
#include "comm.hpp"

int main()
{
    key_t key = ftok(PATH_NAME, PROJ_ID);
    Log(Debug) << "共享秘钥创建成功!step 1"
               << " [key:" << getKey(key) << "]" << std::endl;

    int shmid = shmget(key, SHM_SIZE, 0);
    if (shmid == -1)
    {
        Log(Error) << "共享内存获取失败!!!!! step 2" << std::endl;
        perror("shmget");
        exit(1);
    }
    Log(Debug) << "共享内存获取成功!step 2" << std::endl;

    //sleep(10);


    char *shmaddr = (char *)shmat(shmid, nullptr, 0);
    if ((void *)shmaddr == (void *)-1)
    {
        Log(Error) << "共享内存挂载失败!!!!!! step 3" << std::endl;   

        perror("shmat");
        exit(2);
    }
    Log(Debug) << "共享内存挂载成功!step 3" << std::endl;

    //sleep(5);

    while(1)
    {
        //std::cout << "请输入:->" ;
        ssize_t n = read(0, shmaddr, SHM_SIZE - 1);
        if(n > 0)
        {
            shmaddr[n - 1] = 0;
            if(strcmp(shmaddr,"quit") == 0) break;
        }
    }



    int n = shmdt(shmaddr);
    if (n == -1)
    {
        Log(Error) << "共享内存挂载删除失败! step 4" << std::endl;

        perror("shmdt");
        exit(3);
    }

    Log(Debug) << "共享内存挂载删除!step 4" << std::endl;

    //sleep(5);

    return 0;
}
cpp 复制代码
comm.hpp
#include <iostream>
#include <cstdio>
#include <sys/types.h>
#include <sys/ipc.h>
#include <assert.h>
#include <sys/shm.h>
#include <unistd.h>
#include <cstring>
#define PROJ_ID 10086
#define SHM_SIZE 4096

char buffer[514] = {0};

const char *getKey(key_t key)
{

    sprintf(buffer, "0x%x", key);
    return buffer;
}


#define PATH_NAME "/home/fengjunzi/test"
cpp 复制代码
Log.hpp
#include <iostream>
#include <time.h>
#include <string>

#define Debug 0
#define Error 1

const std::string com[] = {
    "Debug",
    "Error"};

std::ostream &Log(int command)
{
    std::cout << "[" << (unsigned)time(nullptr) << "]:"
              << "[" << com[command] << "]" <
        " ";
    return std::cout;
}

三.运行效果

它的缺陷从运行就可以看出来,共享内存没有进行同步与互斥。

不能像管道一样具有访问控制,就会出现写端只写了一半,但是读端已经开始读了的情况。

相关推荐
2301_14725836941 分钟前
7月2日作业
java·linux·服务器
格调UI成品2 小时前
预警系统安全体系构建:数据加密、权限分级与误报过滤方案
大数据·运维·网络·数据库·安全·预警
盘古开天16663 小时前
如何用废弃电脑变成服务器搭建web网站(公网访问零成本)
服务器·电脑·免费公网ip
xuanzdhc5 小时前
Linux 基础IO
linux·运维·服务器
愚润求学5 小时前
【Linux】网络基础
linux·运维·网络
bantinghy6 小时前
Linux进程单例模式运行
linux·服务器·单例模式
小和尚同志7 小时前
29.4k!使用 1Panel 来管理你的服务器吧
linux·运维
帽儿山的枪手7 小时前
为什么Linux需要3种NAT地址转换?一探究竟
linux·网络协议·安全
shadon1789 天前
回答 如何通过inode client的SSLVPN登录之后,访问需要通过域名才能打开的服务
linux
AWS官方合作商9 天前
AWS ACM 重磅上线:公有 SSL/TLS 证书现可导出,突破 AWS 边界! (突出新功能的重要性和突破性)
服务器·https·ssl·aws