【1++的Linux】之进程间通信(共享内存)

news2024/11/16 7:40:50

👍作者主页:进击的1++
🤩 专栏链接:【1++的Linux】

我们在前面的文章中提到过,进程间的通信本质都是先看到同一块资源,然后通过这同一块资源进行通信,并且是单向的通信,只能一端发,一端进行读,共享内存也是基于这样的原理而进行的通信,与管道有异曲同工之处,管道是基于文件,拿到同一个文件的文件描述符而进行的通信,需要调用对文件的读写操作函数,因此要经过内核。而共享内存是不会的,其是内存级的通信,因此它的效率非常高。

ps:共享内存因为数据不需要在客户机和服务器端之间复制,数据直接写到内存,不用若干次数据拷贝,所以这是最快的一种IPC。

那么是什么是共享内存呢?
我们其实早已经和它见过面了!!!
在这里插入图片描述
我们的共享内存就在共享区中。
在这里插入图片描述

我们通过OS申请一块物理内存,作为共享内存,进程间是独立的,有自己的页表,进程地址空间。两进程通过页表将这块物理内存映射到各自的进程地址空间,此时他们就能够看到同一块资源啦!
共享内存的提供者是OS,共享内存不止有一块,此时就需要将他们进行管理,怎么管理呢?六字真言:先描述,后组织!!!
那么共享内存实质是什么呢?----共享内存块+对应的内核数据结构。

共享内存的建立:

shmget()函数

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

它的参数都是指什么呢?

key : 通信的双方要保证看到的是同一块共享内存,,它是几不重要,只要它在系统里唯一就行。相同的key就可以看到同一块空间了。
size : 共享内存的大小
shmflg :标志位 IPC_CREAT 创建共享内存:若存在则获取,不存在则创建 IPC_CREAT | IPC_EXCL 创建共享内存:若存在,报错返回,若不存在,创建。
IPC_EXCL单独使用没有意义。

返回值是一个整数 ,其类似于文件描述符一样,是共享内存用户层的标识符。

那么key 值怎么获取,才能使系统中唯一的呢?
key_t ftok(const char *pathname, int proj_id) 我们调用ftok函数去生成一个唯一的key。
pathname:指定的文件,此文件必须存在且可存取
proj_id:计划代号(project ID)

ftok的典型实现是调用stat函数,然后组合以下三个值:
① pathname所在的文件系统的信息(stat结构的st_dev成员)。
② 该文件在本文件系统内的索引节点号(stat结构的st_ino成员)。
③ proj_id的低序8位(不能为0)。
上述三个值的组合产生一个32位键。

创建出共享内存后,我们需要将其挂接到我们需要进行通信的地址空间上,我们用shmat()来实现。
void *shmat(int shm_id, const void *shm_addr, int shmflg);

第一个参数,shm_id是由shmget()函数返回的共享内存标识。
第二个参数,shm_addr指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。
第三个参数,shm_flg是一组标志位,通常为0。
调用成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回

挂接完成后我们就可以进行通信了。
若向结束通信,我们可以选择让进程和共享内存分离
我们使用shmdt() 函数实现。
int shmdt(const void *shmaddr);

参数shmaddr是shmat()函数返回的地址指针,调用成功时返回0,失败时返回-1.

该函数只是让进程和其分离,并不会删除共享内存。
删除共享内存我们用shmctl()函数。
int shmctl(int shm_id, int command, struct shmid_ds *buf);

第一个参数,shm_id是shmget()函数返回的共享内存标识符。
第二个参数,command是要采取的操作,它可以取下面的三个值 :
IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。
IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值 IPC_RMID:删除共享内存段
第三个参数,buf是一个结构指针,它指向共享内存模式和访问权限的结构。

出来使用函数删除共享内存,我们还可以用命令手动去删除:

ipcs -m 用来查询存在的共享内存
ipcrm -m+shmid 可以删除对应的共享内存

共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取。所以我们通常需要用其他的机制来同步对共享内存的访问

下面我们利用管道来实现一个同步的共享内存:

#pragma once
#include<iostream>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<cassert>
#include<unistd.h>
#include<cstring>
#include<sys/stat.h>
#include<fcntl.h>
#include"LOG.hpp"
#define IPC_PATH "/home/hyp"
#define PROJ_id 0x66  //8位
#define SHM_SIZE 4096 //最好是页的整数倍
#define Fifo_Name "./fifo"

class Init
{
    public:
    Init()
    {
        umask(0);
         int n=mkfifo(Fifo_Name,0666);//创建管道
         if(n==-1)
         {
            perror("mkfifo");
            exit(1);
         }
    }

    ~Init()
    {
        unlink(Fifo_Name);
    }


};


int OPen_fifo(std::string fifo_name,std::string flag)
{
    int fd=-1;
    if(flag=="READ")
    {
        fd=open(fifo_name.c_str(),O_RDONLY);

    }
    else if(flag=="WRITE")
    {
        fd=open(fifo_name.c_str(),O_WRONLY);
    }
    else{
        exit(2);
    }
    assert(fd!=-1);
    return fd;
}
void Sendmessage(int fd)
{
    int commd=0;
    int s=write(fd,&commd,sizeof commd);
    log("发送中.....",DEBUG)<<std::endl;
    assert(s!=-1);
    
}

void Wait(int fd)
{
    log("等待中.....",DEBUG)<<std::endl;
    int commd=1;
    int s=read(fd,&commd,sizeof(commd));
    assert(s!=-1);
}

//日志打印


#pragma once
#include<iostream>
#include<string>
#ifndef _LOG_H_
#define _LOG_H_

#define DEBUG 0
#define NOTICE 1
#define WARNING 2
#define ERROR 3

std::string mes[4]={
    "DEBUG","NOTICE","WARNING","ERROR"
};

std::ostream& log(const std::string& message,int level)
{
   std::cout<<"|"<<mes[level]<<"|"<<message<<"|";
   return std::cout;
}

#endif

//发送端

#include"comm.hpp"
#include"LOG.hpp"

int main()
{
    key_t k=ftok(IPC_PATH,PROJ_id);
    if(k==-1)
    {
        perror("ftok");
        exit(1);
    }
    log("creat key done",DEBUG)<<" client# k:"<<k<<std::endl;

    int shmid=shmget(k,SHM_SIZE,0);
    assert(shmid>0);
    log("creat shm done",DEBUG)<<std::endl;
    //将创建出的共享内存挂接到自己的地址空间中
    char* shmaddr=(char*)shmat(shmid,nullptr,0);
    if(shmaddr==nullptr)
    {
        perror("shmat");
        exit(1);
    }
    log("attach shm success",DEBUG)<<std::endl;

    //使用
   int fd= OPen_fifo(Fifo_Name,"WRITE");
    while(true)
    {
        //sleep(1);
        //char puts[1024];
        //fgets(shmaddr,10,stdin);
        //Sendmessage(shmaddr);
        Sendmessage(fd);
        int s=read(0,shmaddr,SHM_SIZE-1);
        if(s>0)
        {
            shmaddr[s-1]='\0';
           if(strcmp(shmaddr,"quit")==0)
            {
                break;
            }
        }

    }
    close(fd);

     int n=shmdt(shmaddr);
    if(n==-1)
    {
        perror("shmdt");
        exit(1);
    }
    log("detach shm success",DEBUG)<<std::endl;


    return 0;
}

//收端

#include"comm.hpp"
#include"LOG.hpp"

Init init;
int main()
{
    key_t k=ftok(IPC_PATH,PROJ_id);
    if(k==-1)
    {
        perror("ftok");
        exit(1);
    }
    log("creat key done",DEBUG)<<" server# k:"<<k<<std::endl;

    int shmid=shmget(k,SHM_SIZE,IPC_CREAT|IPC_EXCL|0666);//通信的发起者
    assert(shmid>0);
    log("creat shm done",DEBUG)<<std::endl;
    //将创建出的共享内存挂接到自己的地址空间中
    char* shmaddr=(char*)shmat(shmid,nullptr,0);
    if(shmaddr==nullptr)
    {
        perror("shmat");
        exit(1);
    }
    log("attach shm success",DEBUG)<<std::endl;
    //使用

    int fd=OPen_fifo(Fifo_Name,"READ");
    while(true)
    {
        Wait(fd);
        printf("%s\n",shmaddr);
        if(strcmp(shmaddr,"quit")==0) break;
       
    }

    int x=shmctl(shmid,IPC_RMID,0);
    assert(x!=-1);
    log("delete shm success",DEBUG)<<std::endl;

    

    return 0;
}

为了让进程之间能够通信,我们让其能够看到同一份资源,但看到同一份资源也会带来一些时序问题,从而造成数据不一致的问题。
我们上述的代码中是用加入管道的方式从而保证其同步性使得:只有一个进程写完后另一个进程才能够去读。信号量也是解决同步机制的一种方法。

我们将多个进程看到的公共的一份资源称为临界资源。
把进程访问临界资源的代码称为临界区
多个执行流运行时互相干扰,主要是我们不加保护的访问了临界资源(在非临界区是没有影响的),为了更好的进行临界区的保护,我们让多执行流在任何时刻都只有一个进程能够进入临界区----我们将其称为互斥。
原子性:要么不做,要么做完,没有中间状态。

关于信号量我们在后面会有更加详细的解读。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1141187.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

可视化 | (四)可视化降维

文章目录 &#x1f4da;降维的重要性&#x1f4da;MDS、PCA&#x1f407;MDS&#x1f407;PCA &#x1f4da;SNE&#x1f407;总述&#x1f407;SNE&#x1f407;Symmetric-SNE&#x1f407;T-SNE &#x1f4da;降维的重要性 降维在数据分析和可视化领域中扮演着重要的角色。当…

19、Python -- 关键字参数 与 参数默认值,参数收集 与 逆向参数收集

目录 关键字参数位置参数&#xff1a;关键字参数位置参数和关键字参数的混合使用关键字参数注意点 参数默认值使用参数默认值 普通参数收集&#xff08;*xxx&#xff09;注意点 关键字参数收集(**xxx)逆向参数收集注意点&#xff1a; dict&#xff08;字典&#xff09;的逆向参…

J2EE项目部署与发布(Windows版本)

目录 一.会议OA单体项目Windows部署 1.实操 二.spa前后端分离项目Windows部署 1.部署后端 2.部署前端 配置node.js 3.从实施的角度 4.从开发的角度 ​编辑 一.会议OA单体项目Windows部署 我们从实施的角度来看&#xff0c;拿到项目之后一定要问开发人员提供数据库脚…

SpringCloud复习:(8)Zuul内置过滤器

过滤器的执行顺序&#xff1a;根据filterOrder方法的返回值&#xff0c;返回值&#xff08;包含负数&#xff09;越小&#xff0c;越早执行 。 FilterProcessor类中会调用filter的runFilter方法 ZuulFilter中的runFilter方法会调用run方法&#xff1a;

在ffmpeg中,网络视频流h264为什么默认的转为YUV而不是其他格式

在做网络视频的时候&#xff0c;有些视频的编程概念&#xff0c;早点知道&#xff0c;早点弄清楚会少走很多的弯路。对应视频的转码&#xff0c;传输&#xff0c;一开始如果直接跟着代码跑的话&#xff0c;很容易觉得自己都明白了&#xff0c;但是为什么这样做&#xff0c;好像…

使用WebStorm创建和配置TypeScript项目

创建 这里我用的是WebStorm 2019.2.2版本 首先&#xff0c;创建一个空项目 File -> New -> Project->Empty Project生成配置文件 自动配置&#xff1a; 打开终端输入tsc --init&#xff0c;即可自动生成tsconfig.json文件 手动配置&#xff1a; 在项目根目录下新建一…

第四章 文件管理 八、文件保护

目录 一、口令保护 1、定义&#xff1a; 2、优点&#xff1a; 3、缺点: 二、加密保护 1、定义&#xff1a; 2、例子&#xff1a; 2、优点&#xff1a; 3、缺点: 三、访问控制 1、定义&#xff1a; 2、精简的访问控制表&#xff1a; &#xff08;1&#xff09;定义&a…

海南海口大型钢结构件3D扫描全尺寸三维测量平面度平行度检测-CASAIM中科广电

高精度三维扫描技术已经在大型工件制造领域发挥着重要作用&#xff0c;特别是在质量检测环节&#xff0c;高效、高精度&#xff0c;可以轻松实现全尺寸三维测量。本期&#xff0c;CASAIM要分享的应用是在大型钢结构件的关键部位尺寸及形位公差检测。 钢结构件&#xff0c;是将…

38 深度学习(二):tensorflow基础介绍

文章目录 tensorflow基础介绍基础张量自定义损失函数自定义模型和激活函数图函数&#xff08;略&#xff09;自动求导机制自定义fit tensorflow基础介绍 基础张量 import tensorflow as tf import numpy as np import pandas as pd# constant是常量张量 不能进行再次assign改…

【ETL工具】Datax-ETL-SqlServerToHDFS

&#x1f984; 个人主页——&#x1f390;个人主页 &#x1f390;✨&#x1f341; &#x1fa81;&#x1f341;&#x1fa81;&#x1f341;&#x1fa81;&#x1f341;&#x1fa81;&#x1f341; 感谢点赞和关注 &#xff0c;每天进步一点点&#xff01;加油&#xff01;&…

lazada获得lazada商品详情 API 返回值说明

item_get-获得lazada商品详情 lazada.item_get 公共参数 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&#xff09;获取key和secret接入secretString是调用密钥api_nameString是API接口名称&#xff08;包括在请求地址中&#xff09;[item_sear…

德思特新闻 | 走进德思特——通过创新帮助客户成功

文章来源&#xff1a;德思特测试测量 阅读原文&#xff1a;德思特新闻 | 走进德思特——通过创新帮助客户成功 走进德思特 2023年上半年&#xff0c;ChatGPT红遍全球&#xff0c;人工智能、B5G/6G、物联网、云计算、软件自动化等新兴技术的快速发展进一步推动科技行业的复苏…

vue核心面试题汇总【查缺补漏】

给大家推荐一个实用面试题库 1、前端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;web前端面试题库 很喜欢‘万变不离其宗’这句话&#xff0c;希望在不断的思考和总结中找到Vue中的宗&#xff0c;来解答面试官抛出的…

gd32部分映射1/2,完全映射,备用功能选择等

一、重映射与部分映射问题 参考相应用户手册&#xff1b; 打开&#xff1a;I/O 重映射功能和调试配置&#xff1b; AFIO 端口配置寄存器 0&#xff08;AFIO_PCF0&#xff09;&#xff1b; AFIO 端口配置寄存器 1&#xff08;AFIO_PCF1&#xff09;&#xff1b; 【e.g】以定时器…

Java练习题2021-4

"某游戏公司设计了一个奖励活动&#xff0c;给N个用户(1≤N≤10^7)连续编号为1到N&#xff0c;依据用户的编号S发放奖励。 发放奖励规则为&#xff1a; 公司随机设定三个非零正整数x&#xff0c;y&#xff0c;z。 如果S同时是x、y的倍数&#xff0c;奖励2张卡片&#xff1…

QCI利用量子计算为飞行汽车提供优化飞行路径和改进设计的功能

​&#xff08;图片来源&#xff1a;网络&#xff09; 长期以来&#xff0c;飞行汽车和量子计算机一直是“未来技术”的简写&#xff0c;它们将彻底改变交通世界。预计到2032年&#xff0c;量子计算的市场价值将达到1700亿美元&#xff0c;当前有很多实际用例已经证明了它的价…

如何用 Zabbix 监控 Radius 服务?

网管小贾 / sysadm.cc Radius 对于搞系统的小伙伴们来说应该并不陌生&#xff0c;它是提供身份认证的服务程序。 Radius 应用广泛&#xff0c;最简单的场景之一就是无线连接&#xff0c;使用用户名密码或者证书等方式通过它的验证后无线网络连接就可以建立起来。 如此重要的一…

1496. 判断路径是否相交

1496. 判断路径是否相交 java代码&#xff1a; class Solution {public boolean isPathCrossing(String path) {int x 0;int y 0;HashSet<String> hashSet new HashSet<>();hashSet.add("0-0");for (int i 0; i < path.length(); i) {switch (pa…

Vue-cli 搭建 SPA 项目,Vue 项目结构说明,开发示例,以及如何修改端口号,install 命令中的 -g,-S,-D 参数,嵌套路由,package.json 详解

目录 1. vue-cli安装 1.1 安装前提 1.2 什么是vue-cli 1.3 安装vue-cli 2. 使用vue-cli构建项目 2.1 使用脚手架创建项目骨架 2.2 到新建项目目录&#xff0c;安装需要的模块 2.3 如何修改端口号 2.4 添加element-ui模块 2.5 package.json详解 3. install命令中的-g…

FLStudio21完整版编曲软件多少钱?要不要购买

水果&#xff0c;全称Fruity Loop Studio&#xff0c;简称FL Studio。是一款全能的音乐制作软件&#xff0c;经过二十多年的演化更迭&#xff0c;其各项功能非常的先进。其开创性的Pat\song模式&#xff0c;也为初学者的学习提供了便利。那么水果音乐制作软件需要多少钱呢&…