System V通信

news2024/10/7 7:31:44

文章目录

    • 共享内存
      • 什么是共享内存(物理内存块+属性)
      • 共享内存的接口认识
        • 查看共享内存
        • 删除共享内存
        • 共享内存的创建(ftok和shmget)
        • 挂接和去关联(shmat和shmdt)
      • 利用共享内存通信(简单的代码演示)
      • 总结共享内存
        • 共享内存的优缺点
        • 共享内存的特点
    • 消息队列(了解)
      • 接口介绍
        • 1、获取消息队列(msgget)
        • 2.删除消息队列
    • 信号量(了解)
      • 预备概念
      • 理解信号量
    • IPC资源的管理方式

之前已经讲了通过管道来进行进程间通信,匿名管道是通过子进程继承父进程的文件描述符表来使两个进程看到同一份匿名管道文件实现的,有名管道是通过文件名作为唯一标识来使两个毫不相干的进程看到同一份资源。管道通信是基于文件系统的通信方式。而System V是操作系统提供的聚焦于本地通信的通信方式,本文介绍System V主要是介绍共享内存这种通信方式。

共享内存

什么是共享内存(物理内存块+属性)

共享内存是操作系统开辟的一块内存块,开辟成功后会将内存块的地址通过页表映射到进程的进程地址空间中去,只要将这个内存块通过页表映射到两个不同的进程地址空间,就可以让两个进程实现通信(因为它们有一个相同的内存块)。

在这里插入图片描述

1.如果是申请内存块的话,malloc/new也可以做到,但是malloc和new申请的内存块只能让本进程看见,因为进程间具有独立性。

2.共享内存是操作系统设置的一种进程间通信的方式,所以操作系统内可能同时存在多组进程使用共享内存进行通信,也就是说操作系统要对共享内存做管理。

3.使用malloc时我们需要传大小,但是free时却不用传任何参数,这是因为malloc开辟的空间比我们预计要申请的空间要大,多出来的这一部分存放的是malloc出来的那块空间的属性。同理共享内存不只是一个物理的内存块,还有它的属性。

4.将共享内存通过页表和进程建立联系也叫挂接,使用完毕以后将联系销毁(不是删除共享内存只是将页表映射关系取消)又叫去关联

共享内存的接口认识

查看共享内存

ipcs -m/-q/-s   //共享内存/消息队列/信号量

在这里插入图片描述

删除共享内存

ipcrm -m +shmid

在这里插入图片描述

删除共享内存使用shmid而不是key,因为key是共享内存的内核级的标识,而我们使用指令是在用户层,所以要用用户层标识,也就是shmid

也可以通过函数来删除共享内存

#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
//cm参数使用IPC_RMID就是删除,这个函数有很多用途,但主要用于删除

这个函数在后面的代码部分有使用

共享内存的创建(ftok和shmget)

要确保两个进程使用的是同一个共享内存块,那么必须要有能唯一标识一个共享内存块的方法,共享内存的唯一标识就是key,所以在创建共享内存块之前需要获取key。

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);

要让两个进程看到同一个共享内存块,就要让两个进程拿到同一个key,只要两个进程对ftok传同样的参数就可以获得同样的key值

有了key值以后就可以申请共享内存块了

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
//最后一个参数是标志位,可以传0(相当于IPC_CREAT)
//IPC_CREAT:如果不存在就创建,存在就获取
//IPC_CREAT|IPC_EXCL:不存在就创建,存在就获取(IPC_EXCL不能单独使用)

挂接和去关联(shmat和shmdt)

#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
//挂接就是建立这个内存块和进程之间的联系,此后该进程就可以使用这个内存块了,其中第二个参数表示可以将这个内存块映射到指定的地址,但是也可以传空,第三个参数可以传0,表示默认行为(默认就可以读写)
int shmdt(void *shmaddr)//参数直接传空就行

这里有个问题,就是最开始的时候,我们使用key来创建共享内存,但是后面的函数接口使用的都是shmid。

key和shmid都是共享内存的唯一标识,它们的关系就像fd和inode的关系,key是共享内存在内核中的标识,shmid是用户层的标识。搞两套标识符主要是为了让用户层和内核层解耦,使得内核层的改变不会影响用户层。

利用共享内存通信(简单的代码演示)

两个进程,一个server负责创建共享内存块和删除(所以该进程要先运行),并从共享内存中读取数据,一个client进程用于获取共享内存并向其中写入数据。公共的代码放到一个公用的头文件中:

comm.hpp

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <cstring>
#include<cstdio>

using namespace std;

#define PATHNAME "."
#define PROJ_ID 0x66
#define MAX_SIZE 4096 // 内存是分块按页管理的,所以建议大小是4096的整数倍

// 创建共享内存:1.获取唯一标识:ftok  2.根据唯一标识申请空间

key_t getkey() // 本来要传两个参数,但是这里采用宏定义,就不用传参数了
{
    key_t key = ftok(PATHNAME, PROJ_ID);
    if (key < 0)
    {
        cerr << errno << ":" << strerror(errno) << endl;
        exit(-1);
    }
    return key;
}

//拿到唯一标识以后,可以通过这个唯一标识创建或者获取共享内存

int getShmHelper(key_t key,int flags)
{
    int shmid=shmget(key,MAX_SIZE,flags);

    if(shmid<0)
    {
        cerr << "getShmHelper"<<errno << ":" << strerror(errno) << endl;
        exit(-2);
    }
}

int getShm(key_t key)
{
    return getShmHelper(key,IPC_CREAT);
}

int createShm(key_t key)
{
    return getShmHelper(key,IPC_CREAT|IPC_EXCL|0600);//创建的时候要将共享内存的权限设置好
}

//创建完毕,可以将进程与共享内存开始挂接
void*attachShm(int shmid)
{
    void*start = shmat(shmid,nullptr,0);

    if((long long)start==-1L)//linux是64位系统,所以指针大小是八字节,强转为long long,-1L,代表该数的类型是long long
    {
        cerr <<"attachShm"<< errno << ":" << strerror(errno) << endl;
        exit(-3);
    }
}

//使用完毕以后,要去关联和删除
void*detShm(void*start)
{
    if(shmdt(start)==-1)
    {
        cerr<<"detShm" << errno << ":" << strerror(errno) << endl;
        exit(-4);
    }
}

int delShm(int shmid)
{
    if(shmctl(shmid,IPC_RMID,nullptr)==-1)
    {
        cerr<<"delShm" << errno << ":" << strerror(errno) << endl;
        exit(-5);
    }
}

server.cc

#include"comm.hpp"

int main()
{

    //服务端建立共享内存,给客户端使用,所有有关共享内存的维护工作都由服务端来进行
    key_t key=getkey();
    //cout<<key<<endl;
    //int flags=IPC_CREAT|IPC_EXCL;
    int shmid=createShm(key);
    printf("创建成功,尝试挂接\n");
    
    //共享内存本身就是一块空间,不用再开辟缓冲区了
    char*start=(char*)attachShm(shmid);
    printf("挂接成功,开始读取\n");
   
    while(true)
    {
        printf("client say:%s\n",start);
        sleep(1);
        
    }
   


    detShm(start);
    delShm(shmid);

    return 0;
}

client.cc

#include"comm.hpp"

int main()
{

    //获取共享内存,挂接共享内存,向共享内存中写入数据
    key_t key=getkey();
    int shmid=getShm(key);
    printf("获取成功,尝试挂接\n");

    //直接将共享内存作为缓冲区
    char *start_client=(char*)attachShm(shmid);
    printf("挂接成功,开始输入\n");

    const char*str="hello server,I am cilent,I want to say:你最近过的还好吗";
    int cnt=1;
    while(true)
    {
        //向共享内存中写入数据
        snprintf(start_client,MAX_SIZE,"%s[pid:%d]:消息编号:%d",str,getpid(),cnt++);
        sleep(1);

        if(cnt==10)
            break;
    }

    //使用完去关联
    detShm(start_client);
    return 0;
}

总结共享内存

共享内存的优缺点

优点:

共享内存是最快的通信方式,因为拷贝次数相比其他的通信方式要更少
在这里插入图片描述

缺点

共享内存作为最快的通信方式,但是使用的却很少,这主要是因为:

1.它的下标与文件系统完全不兼容,而Linux下一切皆文件

2.它没有同步和互斥,没有对数据进行保护

共享内存的特点

1.共享内存的声明周期不随进程,而是随操作系统(这也是所有IPC资源的特点包括消息队列和信号量),也就是说某个进程建立了一个共享内存,即使这个进程结束了,这个共享内存也不会被回收,除非操作系统关闭或重启。

在这里插入图片描述

2.共享内存的大小建议是4KB的整数倍,因为操作系统管理内存是分块管理的,而一个内存块的大小就是4KB,如果你申请4097字节的大小,虽然你只能使用4097个字节的空间,但其实操作系统给你划分了2个4096字节,而多出来的空间就被平白浪费了。


消息队列(了解)

消息队列是操作系统提供的一个内核级的队列,它两端都可以读写,为了保证不读到自己写的数据,因此它的每一个成员都可以被认为是一个结构体,该结构体中包含两个成员:数据类型和数据块缓冲区;比如一端的类型设为1,另一端的类型设为0,那么在读取的时候一端只会读取类型为0的数据,另一端只会读取类型为1的数据。

接口介绍

1、获取消息队列(msgget)

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
//这里的key也需要使用ftok函数生成,这个msgflg也与共享内存的参数一样
//如果成功,返回值将是消息队列标识符(非负整数) ,否则为 -1。用 errno 指示错误。

2.删除消息队列

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
//msqid(与共享内存的shmid一样,是用户层对消息队列的唯一标识),其他的与共享内存一样

信号量(了解)

预备概念

信号量本质是一个计数器,用来表示公共资源中,资源多少的问题,信号量主要是用与同步和互斥和原子性的(这些后面会讲)。

这里有两点需要注意:

1.公共资源就是可以被多个进程访问的资源,但是如果这个公共资源是没有被保护的,那么就会导致数据不一致的问题(很可能一个进程还没写完,另一个进程已经开始读了),我们将被保护的公共资源称之为临界资源,访问临界资源的代码部分称为临界区。

2.既然公共资源是可以被多个进程访问,所以信号量不可能是一个全局变量,因为全局变量只是在一个进程内有效

关于同步和互斥,这些后面都会讲,这里先将互斥的概念放出来:所谓的互斥就是指当两个进程想访问同一个公共资源时,不能同时一起访问,必须要等一个进程访问完了以后另一个进程才能访问。

那么什么是原子性:要么不做,要么做完。比如说你转账,要么不转,要转就一定要转成功,也就是说要么我转账成功然后扣款,要么我转账失败,但是我的钱不能少。

理解信号量

今天我去看电影,我买了一张票,于是我在该电影院的这场电影的观影厅中有了一个座位,即使我看电影迟到了,也不会发生我的座位被别人坐了这种事,因为这个座位的电影票被我买了以后,别人是不能再买到的。假设这个观影厅中有一百个座位,我买了一张以后就只剩下99个座位可售了,电影院无法卖出101张票,因为电影院没有站票。

信号量其实是一种资源预定机制,一旦某个进程预定了某个公共资源以后,信号量就要--,表示当前可用的公共资源减少一个(也叫P操作),一旦信号量减到0就表示当前的公共资源已消耗殆尽了,同理,如果信号量++就表示有进程释放资源了(也叫V操作),可用的公共资源又增多一个。此外我买到票了,电影院就一定让我进,我没买到票,电影院一定不会让我进,所以信号量也是一种保护机制。

此外公共资源还可以大致分为两类:

1.必须作为整体使用的公共资源:如管道

2.可以划分成一个个的子资源来使用(比如我买了一包湿巾,我不可能一次用一整包,而是一片一片的使用)

既然申请公共资源的时候要通过信号量,也就是说必须要保证信号量的正确性,信号量的正确性保障关键在于,对于信号量的操作无论是--还是++都是原子性的。


IPC资源的管理方式

在这里插入图片描述

与一切皆文件那块类似,还是使用了一个结构体指针数组,通过指针来指向不同的实现方式,这也是C语言模拟实现多态的方法(更准确的来说C++的多态就是因此而生)。

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

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

相关文章

Vue中如何进行数据缓存

Vue中如何进行数据缓存 Vue是一款流行的前端框架&#xff0c;它提供了许多方便的功能来处理数据。其中一个非常有用的功能是数据缓存。数据缓存可以提高应用程序的性能&#xff0c;减少网络请求&#xff0c;提高用户体验。在本文中&#xff0c;我们将介绍Vue中如何进行数据缓存…

chatgpt赋能python:Python如何取三位小数

Python 如何取三位小数 Python 是一种很强大的编程语言&#xff0c;可以应用于各个领域。其中&#xff0c;处理数字也是 Python 的一项强大功能。当我们需要对数字进行精细的操作时&#xff0c;常常需要使用到取小数的功能。本文将介绍如何使用 Python 取三位小数&#xff0c;…

Qgis中进行Shp和Excel属性连接实现百强县公共预算空间分析

前言 在之前的博文中&#xff0c;将2022的全国百强县一般公共预算收入的数据下载到了本地&#xff0c;博客原文地址&#xff1a;一种使用Java的快速将Web中表格转换成Excel的方法。对于不关注时空位置关系的一般分析&#xff0c;到此也就基本够用了。但是&#xff0c;如果站在全…

C语言函数初阶(1)

目录 1. 函数是什么 2. 库函数 3. 自定义函数 4. 函数参数 5. 函数调用 6. 函数的嵌套调用和链式访问 7. 函数的声明和定义 8. 函数递归 今天我们讲解前6个部分&#xff0c;下一个博客我们讲解后2个部分&#xff0c;因为后两个部分难度较大&#xff0c;讲解起来要花一点…

Vue中如何进行错误处理

Vue中如何进行错误处理 在Vue应用程序中&#xff0c;错误处理是必不可少的。错误可能发生在各种地方&#xff0c;例如网络请求、组件生命周期钩子函数、计算属性、方法等等。如果我们不正确地处理这些错误&#xff0c;可能会导致应用程序崩溃或无法正常工作。在本文中&#xf…

chatgpt赋能python:Python怎么反向切片

Python怎么反向切片 在Python中&#xff0c;切片是一种用于从序列中选取子序列的方法。正向切片从序列的第一个元素开始选取&#xff0c;而反向切片则从序列的最后一个元素开始选取。本文将介绍Python中如何使用反向切片。 什么是切片 在Python中&#xff0c;切片是一种操作…

IP协议的特性总结

目录 1. 地址管理 1.1 动态分配 1.2 NAT(网络地址转换)机制 1.3 IP地址的组成 1.4 IP地址网络号和主机号的划分 1.4.1 IP地址分类(ABCDE类) 1.4.2 子网掩码 1.5 特殊的IP地址 2. 路径规划 3. IP协议报文格式 3.1 分包 3.2 组包 1. 地址管理 IP地址在之前跟大家简单…

mfc读取obj格式文件初步

3dmax做一个box&#xff1b; 导出为cube1.obj&#xff1b; 记事本打开看一下该obj文件&#xff1b; # 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware # File Created: 10.06.2023 23:16:04mtllib cube1.mtl# # object Box001 #v -41.2323 0.0000 31.8849 v -4…

chatgpt赋能python:Python如何反向排序

Python如何反向排序 在Python中&#xff0c;排序是一项常见的任务。通常情况下&#xff0c;我们想对一组数据按照升序进行排序。但有时候&#xff0c;我们需要对这些数据进行反向排序&#xff0c;也就是按照降序进行排序。那么&#xff0c;Python该如何实现反向排序呢&#xf…

chatgpt赋能python:Python如何取出int内的个位数

Python如何取出int内的个位数 Python已经成为全球范围内最受欢迎的编程语言之一&#xff0c;它具有简单易学&#xff0c;可读性高和可扩展性等特点&#xff0c;因此它被广泛应用于数据科学、人工智能、网络编程、物联网和Web开发等领域。在Python编程中&#xff0c;有时需要从…

第七十天学习记录:高等数学:微分(宋浩板书)

微分的定义 基本微分公式与法则 复合函数的微分 微分的几何意义 微分在近似计算中应用 sin(xy) sin(x)cos(y) cos(x)sin(y)可以用三角形的几何图形来进行证明。 假设在一个单位圆上&#xff0c;点A(x,y)的坐标为(x,y)&#xff0c;点B(x’, y’)的坐标为(x’, y’)。则以两点…

44--Django-项目实战-全栈开发-基于django+drf+vue+elementUI企业级项目开发流程-支付宝二次封装、支付成功页面以及后台设计

一、支付宝支付介绍 需求:购买课程,付款 现在主流支付有支付宝支付、微信支持、银联支付 申请使用支付宝支付,需要有商户号(用户把钱付款到你的商户号中) 收手续费商户号要申请,需要有公司的营业执照(不需要营业执照也可以申请–》笔记)我们开发,需要商户号,公钥,…

Spring的数据访问哲学

目录 设计思路 了解Spring的数据访问异常体系 数据访问模板化 设计思路 Spring的目标之一就是允许我们在开发应用程序时&#xff0c;能够遵循面向对象(OO)原则中的“针对接口编程”Spring对数据访问的支持也不例外像很多应用程序一样&#xff0c;Spittr应用需要从某种类型的…

chatgpt赋能python:Python中使用Numpy获取数组元素的方法

Python中使用Numpy获取数组元素的方法 作为一种高级数据处理和科学计算库&#xff0c;numpy在python中被广泛使用。对于从事科研数据处理工作的工程师和研究人员来说&#xff0c;numpy已经成为必须要掌握的工具之一。 本文将讨论如何在Python中使用Numpy获取数组元素。我们将…

理解分布式锁的实现过程

背景&#xff1a;分布式锁在后端开发者会用到&#xff0c;它有哪些特点呢&#xff1f; 在分布式系统中&#xff0c;一个应用部署在多台机器当中&#xff0c;在某些场景下&#xff0c; 为了保证数据一致性&#xff0c;要求在同一时刻&#xff0c;同一任务只在一个节点上运行&am…

【计算机网络复习】第七章 物理层

物理层的位置和基本功能 u 网络体系结构的最底层&#xff0c;实现真正的数据传输 u 将二进制数据编码或调制成信号&#xff0c;发送到传输介质(传输媒体)&#xff1b; u 从传输介质接收信号&#xff0c;转换成二进制数据 物理层的主要功能 u 规定了与传输介质的接口的特…

chatgpt赋能python:判断Python中的字符类型

判断Python中的字符类型 在Python编程中&#xff0c;有时我们需要判断一个字符的类型。Python提供了几种方法来判断字符类型。本文将介绍这些方法并提供示例代码。 1. 使用内置函数ord() ord()函数可以返回一个字符的Unicode编码。使用这个方法我们可以判断一个字符是否是数…

【LGR-142-Div.4】洛谷入门赛 #13 考后分析与题解

洛谷入门赛 #Round 13 比赛分析与总结T1 魔方魔方题目背景题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 提示数据规模与约定 分析AC代码注意 T2 教学楼教学楼题目背景题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 样例 #2样例输入 #2样例输出 #2 提示样例…

尚硅谷甄选--更新中

文章目录 搭建后台管理系统模板项目初始化2.1.1环境准备2.1.2初始化项目2.2项目配置一、eslint配置1.1vue3环境代码校验插件1.2修改.eslintrc.cjs配置文件1.3.eslintignore忽略文件1.4运行脚本 二、配置**prettier**2.1安装依赖包2.2.prettierrc.json添加规则2.3.prettierignor…

Shell编程条件语句(if、case)

目录 一、Shell条件语句 1.条件表达式测试 2.文件测试 3.整数值比较 4.字符串比较 5.逻辑测试 二、if 条件语句&#xff08;串行执行&#xff09; &#xff08;1&#xff09;单分支结构 &#xff08;2&#xff09;双分支结构 &#xff08;3&#xff09;多分支结构 三…