【Linux】从零开始认识进程间通信 —— 共享内存

在这里插入图片描述

送给大家一句话:
吃苦受难绝不是乐事一桩,但是如果您恰好陷入困境,我很想告诉您:“尽管眼前十分困难,可日后这段经历说不定就会开花结果。”请您这样换位思考、奋力前行。
-- 村上春树
🔆🔆🔆🔆🔆🔆🔆🔆

共享内存

  • 1 ❤️‍🔥前言
  • 2 ❤️‍🔥共享内存的原理
  • 3 ❤️‍🔥代码实现 -- 补充理论知识 -- 相关接口
    • 🎁创建
    • 🎁删除
    • 🎁封装
    • 🎁挂接到进程
    • 🎁开始通信
  • 5 ❤️‍🔥获取共享内存的属性
  • Thanks♪(・ω・)ノ谢谢阅读!!!
  • 下一篇文章见!!!

1 ❤️‍🔥前言

前面我们讲解了匿名管道和命名管道,通过其底层实现,我们可以发现管道是基于文件系统的通信方式。通过文件的内存缓冲区的写端和读端的文件描述符(fd),使用对应的read / write就可以支持单向通信

也就是说管道并不是一个单独的模块,还是沿用文件的管理模块,而接下来的共享内存就是一个单独设计的通信模块

共享内存是本地通信方案(System V IPC)的一种。
System V IPC包含主要有三种方式: 共享内存,消息队列 ,信号量。这些在今天逐渐被边缘化,很少再用到他们,但其中的共享内存很值得我们来学习一下,了解里面的思想。

2 ❤️‍🔥共享内存的原理

首先,共享内存是一种进程间通信的方案,那么就得满足进程间通信的需求:两个进程要看到同一块内存资源!才可以进行通信。

先来看两个进程的关系,进程具有独立性,两个进程分别会指向自己的地址空间,在通过页表映射到真的的物理地址,物理地址中储存着该进程的代码和数据。
在这里插入图片描述

接下来我们来看共享内存是如何实现的:

  1. 首先在物理内存中存在一片内存空间,这里用来管理共享内存
  2. 共享内存和管道的创建方式很像,一个进程通过对应的系统调用来创建共享内存,这个共享内存归OS管理。
  3. 再联想动态库相关知识,动态库就是加载到物理内存中,通过页表映射到进程地址空间的共享区(储存动态库函数的起止位置)。动态库可以被多个进程同时使用,就是说动态库这片内存可以被不同进程同时看到。共享内存就是类似的道理
  4. 那么在内存空间中假如存在这样一个空的内存,再在共享区申请一片空间,然后也通过页表映射到进程地址空间的共享区,那么不就可以让不同进程通过这个映射关系看到同一片内存了吗!不就可以在这片内存读取写入数据了吗!!!

在这里插入图片描述
这就是共享内存!!!

当然通过原来图也只是比较浅显的认识,接下来我们来深入探索一下!!!

  1. 上述创建物理内存 , 建立页表映射等操作,只能是操作系统来做!但是操作系统并不知道什么时候来进行操作。
  2. 所以操作系统必须提供对应操作的系统调用,供用户进程A,B来使用
  3. AB 进程可以使用共享内存来通信,那CD进程也想 ,EF进程也想— 所以共享内存在操作系统中是存在多份的,供不同个数的进程来进行通信!
  4. 既然是存在多份的共享内存,那么操作系统就要进行统一管理 — 共享内存不是简单的一段内存空间,也要有描述并管理共享内存的数据结构和匹配的算法!!!
  5. 共享内存 = 内存空间(数据) + 共享内存的属性!(和进程,文件的管理很类似啊!)

接下来我们在实践中再加深我们的理解

3 ❤️‍🔥代码实现 – 补充理论知识 – 相关接口

🎁创建

为了使用共享内存,我们先来认识一下对应的系统调用:

SHMGET(2)                                                           Linux Programmer's Manual                                                          SHMGET(2)

NAME
       shmget - allocates a System V shared memory segment

SYNOPSIS
       #include <sys/ipc.h>
       #include <sys/shm.h>

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

DESCRIPTION
       shmget() returns the identifier of the System V shared memory segment associated with the value of the argument key.  It may be used either to obtain the
       identifier of a previously created shared memory segment (when shmflg is zero and key does not have the value IPC_PRIVATE), or to create a new set.

shmget 接口:作用是申请一个system V 标准的共享内存

它有三个参数: (key值我们稍后再谈)

  • 返回值是共享内存的标识符(和 key 不同!!!)
  • size_t size : 表示要创建多大的共享内存空间(通常时候4096的N倍
  • int shmflg :这是个标记位,会有很多的标记位(比如IPC_CREATIPC_EXCL)。本质是使用位图来储存的。
    1. IPC_CREAT如果要创建的共享内存不存在,就新创建一个。如果存在了就直接回去该共享内存并返回。 — 这个总能获取一个共享内存!
    2. IPC_EXCL单独使用没有意义!!!只有和IPC_CREAT 组合才有意义!
    3. IPC_CREAT | IPC_EXCL如果要创建的共享内存不存在,就新创建一个。如果存在,就出错返回 — 这个如果成功返回了意味着共享内存(shm)是全新的!

和命名管道类似,共享内存也需要对使用者进行权限划分,创建者可以创建共享内存,使用者只需要获取即可!
那么IPC_CREAT | IPC_EXCL 就用来创建共享内存,IPC_CREAT 这个用来获取共享内存!

那么进程如何知道操作系统内存在共享内存呢???可以猜测:应该是通过struct shm属性里的用于标识共享内存的唯一性的字段!那这个字段如果让操作系统OS自动生成(类似PID)行不行呢?如果是OS创建的,那其他进程如何获取呢?

首先,如果是类似PID这种由操作系统建立的唯一性字段,只有该进程自己知道,其他进程是无法获取到的。所以为了其他进程可以获取到共享内存的信息就诞生了key_t key参数。这个key是用户设置的值,任何一个进程都可以得到这个key。通过系统调用把这个key植入到共享内存中,那么其他进程通过计算得到的key,就可以获取到该共享内存了!

ftok()就是用来创建key的函数,传入文件路径和一个项目ID,通过一个算法来得到key。所以任何一个进程都可以得到该key值,就可以找到该共享内存!

FTOK(3)                                                             Linux Programmer's Manual                                                            FTOK(3)

NAME
       ftok - convert a pathname and a project identifier to a System V IPC key

SYNOPSIS
       #include <sys/types.h>
       #include <sys/ipc.h>

       key_t ftok(const char *pathname, int proj_id);

接下来我们上代码:

  • 首先我们来实现一个获取key的函数
    1. 首先需要一个路径名字pathname(取当前路径即可) 和 一个项目ID proj_id(0x66)
    2. 然后就可以得到一个key,我们封装一个获取key 的函数GetCommKey(const std::string& pathname , int proj_id)
    3. 进行测试 — 并加入把key转换为16进制的函数 std::string ToHex(key_t key)格式化输出
shm.hpp
#ifndef __SHM__HPP__
#define __SHM__HPP__

#include <sys/ipc.h>
#include <sys/shm.h>
#include <iostream>
#include <string>
#include <cstdio>


const std::string pathname = "/home/jlx/code/shm";
const int proj_id = 0x66;
//转换为16进制
std::string  ToHex(key_t key)
{
    char buffer[128];
    snprintf(buffer , 128 , "0%x", key);
    return buffer;
}
//获取共享内存的key
key_t GetCommKey(const std::string& pathname , int proj_id)
{
    key_t key = ftok(pathname.c_str() , proj_id);
    if(key < 0)
    {
        perror("ftok");
    }
    return key;
}

#endif
运行测试一下:

在这里插入图片描述

很好,两个进程都可以获取同一个key!!!

  • 然后来对 shmget()进行封装,我们需要需要key size 标志位
//创建一个共享内存
int ShmGet(key_t key, int size)
{
    int shmid = shmget(key , size , IPC_CREAT | IPC_EXCL);
    if(shmid < 0)
    {
        perror("shmget");
    }
    return shmid;
}
我们测试一下

在这里插入图片描述
第一次创建的shmid是 0 ,key是随机数。

为什么进程退出了,共享内存不会退出呢???第一次成功创建,第二次就报错–共享内存无法创建!说明共享内存不随着进程的释放而自动释放! 创建之后一直存在,直到重启,所以释放共享内存需要一些系统调用来手动释放!
共享内存的生命周期随内核

🎁删除

我们来看看这个共享内存在哪里:使用指令ipcs -m

在这里插入图片描述

这不就找到了吗!!!

删除的指令是:ipcrm -m (shmid),用户删除需要使用shmid。
在这里插入图片描述
为什么用户删除只能使用shmid???我们对比看看

key VS shmid
key:属于用户形成,内核使用的一个字段,用户不能使用key来进行共享内存的管理。是内核进行区分shm的唯一性的!
shmid: 内核是用户返回的一个标识符,用来进行用户级对共享内存的管理的id值

保证内核与用户的解耦!

每次通过指令来删除共享内存太矬了,那有没有对应的系统调用可以让我们删除共享内存呢?
当然有了:

HMCTL(2)                                                           Linux Programmer's Manual                                                          SHMCTL(2)

NAME
       shmctl - System V shared memory control

SYNOPSIS
       #include <sys/ipc.h>
       #include <sys/shm.h>

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

int shmctl(int shmid, int cmd, struct shmid_ds *buf);也有三个参数

  1. int shmid:就是对应的共享内存的id
  2. int cmd:这是指令位(IPC_STAT IPC_RMID IPC_INFO...)用户需要什么操作就可以传入对应指令来进行操作!
  3. struct shmid_ds *buf:这是内核提供的数据结构,是输出型参数,让用户可以获取共享内存的属性信息。不需要就设置为nullptr

那么怎么删除呢?
使用指令IPC_RMID --> shmctl(int shmid , IPC_RMID , nullptr)就可以了

void ShmRemove()
{
	if (_who == gCreater)
    {
     	int res = shmctl(_shmid, IPC_RMID, nullptr);
 		std::cout << "shm remove done..." << std::endl;
	}
}

这样就可以进行删除了

🎁封装

然后我们进行对共享内存的封装,把这些函数封装为Shm类,让用户可以更加方便的使用!

封装真的是优雅
shm.hpp
#ifndef __SHM__HPP__
#define __SHM__HPP__

#include <sys/ipc.h>
#include <sys/shm.h>
#include <iostream>
#include <string>
#include <cstdio>

#define gCreater 1
#define gUser 2
const std::string pathname = "/home/jlx/code/shm";
const int proj_id = 0x66;
const int ShmSize = 4096;

class Shm
{
private:
    // 转换为16进制
    std::string ToHex(key_t key)
    {
        char buffer[128];
        snprintf(buffer, 128, "0%x", key);
        return buffer;
    }
    // 获取共享内存的key
    key_t GetCommKey()
    {
        key_t key = ftok(_pathname.c_str(), _proj_id);
        if (key < 0)
        {
            perror("ftok");
        }
        return key;
    }
    // 创建一个共享内存
    int GetShmHelper(int key, int size, int flag)
    {

        int shmid = shmget(_key, size, flag);
        if (shmid < 0)
        {
            perror("shmget");
        }
        return shmid;
    }
    bool GetShmForCreate()
    {
        if (_who == gCreater)
        {
            _shmid = GetShmHelper(_key, ShmSize, IPC_CREAT | IPC_EXCL | 0666);
            if (_shmid >= 0)
            {
                std::cout << "shm create done..." << std::endl;
                return true;
            }
        }
        return false;
    }
    bool GetShmForUse()
    {
        if (_who == gUser)
        {
            _shmid = GetShmHelper(_key, ShmSize, IPC_CREAT | 0666);
            if (_shmid >= 0)
            {
                std::cout << "shm get done..." << std::endl;
                return true;
            }
        }
        return false;
    }

public:
    Shm(const std::string &pathname, const int proj_id, int who)
        : _pathname(pathname), _proj_id(proj_id), _who(who)
    {
        _key = GetCommKey();
        if (_who == gCreater)
        {
            GetShmForCreate();
        }
        else if(_who == gUser)
        {
            GetShmForUse();
        }
        std::cout << "key:" << ToHex(_key) << std::endl;
        std::cout << "shmid:" << _shmid << std::endl;
    }
    ~Shm()
    {
        if (_who == gCreater)
        {
            int res = shmctl(_shmid, IPC_RMID, nullptr);
            std::cout << "shm remove done..." << std::endl;
        }
        
    }

private:
    key_t _key;
    int _shmid;
    const std::string _pathname;
    const int _proj_id;
    int _who;
};

#endif

这样我们在用户级别上只需要建立一个实例化的类对象,就保证了两个进程可以看到同一内存:
在这里插入图片描述

🎁挂接到进程

上面我们已经可以正常建立共享内存了,接下来就要想办法来使用共享内存:把共享内存挂接到进程地址空间的共享区!
需要使用系统调用shmat(挂载) --- shmdt(去除挂载)

SHMOP(2)                                                            Linux Programmer's Manual                                                           SHMOP(2)

NAME
       shmat, shmdt - System V shared memory operations

SYNOPSIS
       #include <sys/types.h>
       #include <sys/shm.h>

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

       int shmdt(const void *shmaddr);
 

shmat(int shmid, const void *shmaddr, int shmflg)有三个参数:

  1. int shmid: 需要挂载的共享内存的id
  2. const void *shmaddr:要挂载到的地址空间位置,一般设置为nullptr
  3. int shmflg:挂载方式—只读,只写 ,读写

返回值:挂载成功之后,会返回共享内存的起始虚拟地址(类似malloc , 在堆上申请空间,返回首地址)

我们在类中加入挂接的函数AttachShm()(使用起来很像malloc)

    void* AttachShm()
    {
        void* shmaddr = shmat(_shmid , nullptr , 0);
        if(shmaddr == nullptr)
        {
            perror("shmat");
        }
        
        return shmaddr;
    }

char* addr = (char*)shm.AttachShm();这样我们在用户端就可以使用共享内存了!
我们来看看挂载数会怎么变化:

在这里插入图片描述
这样就让共享内存挂接到了进程中的共享区!!!可以看到该共享内存的权限是666 -- 011011011 可读可写!

取出挂载的系统调用shmdt(const void *shmaddr)(类似free)传入首地址即可

    void DetachShm(void* shmaddr)
    {
        if(shmaddr == nullptr) return ;
        shmdt(shmaddr);
    }

这样就可以取消挂载了!

当然, 我们建立共享内存的时候,肯定是想要进行通信的,挂接是肯定要进行的,所以用户来进行挂载显得有些多余。我们可以在共享内存建立的时候就进行挂接,析构的时候进行取消挂接。所以我们封装一下:

private:
    void *AttachShm()
    {
        //取消挂接
        if(_shmaddr != nullptr) DetachShm();
        void *shmaddr = shmat(_shmid, nullptr, 0);
        if (shmaddr == nullptr)
        {
            perror("shmat");
        }
        std::cout << "who:" << RoleToString() << " attach shm..." << std::endl;
        return shmaddr;
    }
    void DetachShm()
    {
        if(_shmaddr == nullptr) return ;
        shmdt(_shmaddr);
    }

public:
    Shm(const std::string &pathname, const int proj_id, int who)
        : _pathname(pathname), _proj_id(proj_id), _who(who),_shmaddr(nullptr)
    {
        _key = GetCommKey();
        if (_who == gCreater)
        {
            GetShmForCreate();
        }
        else if (_who == gUser)
        {
            GetShmForUse();
        }
        //肯定是要进行挂接的
        _shmaddr = AttachShm();
        std::cout << "key:" << ToHex(_key) << std::endl;
        std::cout << "shmid:" << _shmid << std::endl;
    }
    ~Shm()
    {
        DetachShm();
        if (_who == gCreater)
        {
            int res = shmctl(_shmid, IPC_RMID, nullptr);
            std::cout << "shm remove done..." << std::endl;
        }
    }

这样在我们构造的时候就完成了挂接,析构的时候就取消挂接了,不需要用户再来进行操作了。
在这里插入图片描述

为了进行通信,我们还需要通过返回地址的函数:

    void * Addr()
    {
        return _shmaddr;
    }
    //清零函数!
    void Zero()
    {
        if (_shmaddr)
        {
            memset(_shmaddr, 0, ShmSize);
        }
    }

来测试一下:

在这里插入图片描述
非常好!!!这样就很优雅的完成了挂接的任务!!!

🎁开始通信

上面我们讲过,挂载成功之后,会返回共享内存的起始虚拟地址**(类似malloc , 在堆上申请空间,返回首地址。
所以我们使用起来也可以当成字符串来使用!
我们来进行通信试试:
在这里插入图片描述

来看效果:
在这里插入图片描述
这样就进行通信了,但是好像有些问题:

  1. 共享内存不提供对共享内存的保护机制!会造成数据不一致问题!管道是有保护机制的, 可以使用管道来辅助,管道来负责告诉进程是否写完读完。
  2. 我们在访问共享内存的时候没有使用任何系统调用!
  3. 共享内存是所有进程IPC中速度最快的,共享内存大大减少了数据的拷贝次数!

那么我们通过向共享内存写入时也向管道中写入一个“wakeup”信息,服务器端从管道读到wakeup”信息再在共享内存中读取。这样就有了保护机制:

在这里插入图片描述

我们运行看看:
在这里插入图片描述

这样就好了!可以进行通信了!!!

5 ❤️‍🔥获取共享内存的属性

int shmctl(int shmid, int cmd, struct shmid_ds *buf); 这个系统调用为我们提供了获取属性的输出型参数,我们如果想要获取共享内存的属性,就可以传入IPC_STAT

 IPC_STAT
              Copy  information from the kernel data structure associated with
              shmid into the shmid_ds structure pointed to by buf.  The caller
              must have read permission on the shared memory segment.

并传入结构体struct shmid_ds ds
我们获取到:

 struct shmid_ds {
               struct ipc_perm shm_perm;    /* Ownership and permissions */
               size_t          shm_segsz;   /* Size of segment (bytes) */
               time_t          shm_atime;   /* Last attach time */
               time_t          shm_dtime;   /* Last detach time */
               time_t          shm_ctime;   /* Creation time/time of last
                                               modification via shmctl() */
               pid_t           shm_cpid;    /* PID of creator */
               pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
               shmatt_t        shm_nattch;  /* No. of current attaches */
               ...
           };
struct ipc_perm {
               key_t          __key;    /* Key supplied to shmget(2) */
               uid_t          uid;      /* Effective UID of owner */
               gid_t          gid;      /* Effective GID of owner */
               uid_t          cuid;     /* Effective UID of creator */
               gid_t          cgid;     /* Effective GID of creator */
               unsigned short mode;     /* Permissions + SHM_DEST and
                                           SHM_LOCKED flags */
               unsigned short __seq;    /* Sequence number */
           };


这里面就包含了key shmid...等信息!

Thanks♪(・ω・)ノ谢谢阅读!!!

下一篇文章见!!!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HTTPS 原理技术

HTTPS原理技术 背景简介原理总结 背景 随着年龄的增长&#xff0c;很多曾经烂熟于心的技术原理已被岁月摩擦得愈发模糊起来&#xff0c;技术出身的人总是很难放下一些执念&#xff0c;遂将这些知识整理成文&#xff0c;以纪念曾经努力学习奋斗的日子。本文内容并非完全原创&am…

业务实战————Uibot6.0 .1多页面商品信息抓取RPA机器人

前言 【案例描述】 鲜果记水果店计划在淘宝电商平台上开设一家新店&#xff0c;小微是该企业运营部分的运营专员&#xff0c;主要负责公司商品上架和管理的工作。 公司计划在开店的新品促销活动中增加水果品类红富士苹果。小微需在商品上架前了解目前平台中销量前列的红富士苹…

【深度密码】神经网络算法在机器学习中的前沿探索

目录 &#x1f69d;前言 &#x1f68d;什么是机器学习 1. 基本概念 2. 类型 3. 关键算法 4. 应用领域 5. 工作流程 &#x1f68b;什么是神经网络 基本结构 &#x1f682;神经网络的工作原理 前向传播&#xff08;Forward Propagation&#xff09;&#xff1a; 损失函…

数据分析案例-在线食品订单数据可视化分析与建模分类

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

【Elasticsearch】IK分词器的下载及使用

安装IK分词器 网址&#xff1a;https://github.com/infinilabs/analysis-ik 3.1.在线安装ik插件&#xff08;较慢,不推荐&#xff09; # 进入容器内部 es为容器名称 docker exec -it es /bin/bash# 在线下载并安装 7.17.21为镜像版本要与之前保持一致 ./bin/elasticsearch-pl…

parallels版虚拟机Linux中安装parallels tools报错

按照一个博客的教程安装的可还是安装不了&#xff0c;请指点指点 1.先是输入name -a 输出&#xff1a;Linux user 6.6.9-arm64 #11 SMP Kali 6.6.9-1kali1 (2024-01-08) aarch64GNU/Linux2.按照版本号找对应的文件并下载 第一个文件&#xff1a; linux-headers-6.6.9-arm64_…

C语言链式二叉树、链式二叉树结构的创建、前序遍历、中序遍历、后序遍历、层序遍历来遍历二叉树、二叉树的元素个数、二叉树的高度、第K层元素的个数等的介绍

文章目录 前言一、 链式二叉树结构创建二、 手动创建二叉树三、遍历二叉树1. 前序遍历2. 中序遍历3. 后序遍历4. 层序遍历 四、二叉树的元素个数五、二叉树的高度&#xff08;深度&#xff09;六、第K层元素个数总结 前言 堆结构的实现采用的是数组实现二叉树&#xff0c;可以…

数据结构栈(C语言Java语言的实现)相关习题

文章目录 栈概念以及代码实现例题[232. 用栈实现队列](https://leetcode.cn/problems/implement-queue-using-stacks/)[1614. 括号的最大嵌套深度](https://leetcode.cn/problems/maximum-nesting-depth-of-the-parentheses/)[234. 回文链表](https://leetcode.cn/problems/pal…

【排序算法】选择排序

一、定义&#xff1a; 选择排序&#xff08;Selection sort&#xff09;是一种简单直观的排序算法。第一次从待排序的数据&#xff08;元素&#xff09;中选出最小&#xff08;或最大&#xff09;的一个元素&#xff0c;存放在数组的起始位置&#xff0c;然后再从剩余的没有排序…

Echarts报警告Legend data should be same with series name or data name.

问题排查&#xff1a; 1. 确保 legend中的data中名字和series中每一项的name要匹配。 2. 仔细查看报警规律发现次数有在变化&#xff0c;因此找到代码中是动态修改legend,series的位置&#xff0c;检查一下这两个list的赋值逻辑。 果然&#xff0c;检查发现问题出现在了遍历里…

使用 DuckDuckGo API 实现多种搜索功能

在日常生活中&#xff0c;我经常使用搜索引擎来查找信息&#xff0c;如谷歌和百度。然而&#xff0c;当我想通过 API 来实现这一功能时&#xff0c;会发现这些搜索引擎并没有提供足够的免费 API 服务。如果有这样的免费 API, 就能定时获取“关注实体”的相关内容&#xff0c;并…

线性时间选择

给定线性序集中n个元素和一个整数k&#xff0c;1≤k≤n&#xff0c;要求找出这n个元素中第k小的元素 #include<iostream> #include<cstdlib> #include<time.h> using namespace std; int a[100]; int Random(int left,int right) {srand(time(NULL));return …

微客云霸王餐v3版本正式上线 团购霸王餐+小程序多开

好久没发布更新日志了&#xff0c;上次的更新还是春节的祝福语&#xff0c;从春节结束到现在快3个月了&#xff0c;不是说没更新内容&#xff0c;其实微客云的版本迭代一直在做&#xff0c;从后台的日志看已经发布很多版本了&#xff0c;只是没有发布文章通知&#xff0c;因为我…

算法(十二)分治算法

文章目录 算法概念算法例子字符串中小写转大写求X^n问题 算法概念 分治算法&#xff08;divide and conquer&#xff09;算法的核心思想其实就是"分而治之"&#xff0c;将原问题划分成n个规模较小&#xff0c;并且结构与原问题相似的子问题&#xff0c;递归地解决这…

鸿蒙工程目录介绍

鸿蒙构建完毕生成hhvp文件。 项目结构&#xff1a; .hvigor : 是存储构建配置文件的 .idea : 是开发工具拥有的目录 AppScope : 是全局的公共资源存放位置 hvigor &#xff1a;存放前端构建配置信息 oh_modules : 存放项目用到的第三方包 build-profile.json5 : 应用级别的构…

【MySQL数据库】:MySQL复合查询

目录 基本查询回顾 多表查询 自连接 子查询 单行子查询 多行子查询 多列子查询 在from子句中使用子查询 合并查询 前面我们讲解的mysql表的查询都是对一张表进行查询&#xff0c;在实际开发中这远远不够。 基本查询回顾 【MySQL数据库】&#xff1a;MySQL基本查…

华为telnet的两种认证方式

华为telnet的两种认证方式 实验拓扑&#xff1a; 实验要求&#xff1a; 1.采用普通密码认证实现telnet 远程登录机房设备R3 2.采用AAA认证服务方式实现telnet 远程登录机房设备R3 实验步骤&#xff1a; 1.完成基本配置&#xff08;设备接口配置IP&#xff0c;此步骤略过&#…

JVM-JAVA-类加载过程

JVM源码 类加载到 JVM 的过程通过 java 命令执行代码的流程 类加载到 JVM 的过程 在运行一个 main 函数启动程序是&#xff0c;首先需要类加载起把主类加载到 JVM 中 通过 java 命令执行代码的流程 loadClass的类加载过程有如下几步&#xff1a; 类被加载到方法区中后主要包…

视频汇聚EasyCVR安防系统对接公安部GA/T 1400视图库布控、告警、订阅流程描述

随着信息技术的飞速发展&#xff0c;视频监控在公共安全领域的应用越来越广泛&#xff0c;对于视频监控系统的要求也日益严格。为了满足公安系统对视频图像信息应用的高标准需求&#xff0c;视频汇聚平台EasyCVR视频监控系统全面支持GA/T 1400标准协议&#xff0c;为公安部门提…

【C++】——string模拟实现

前言 string的模拟实现其实就是增删改查&#xff0c;只不过加入了类的概念。 为了防止与std里面的string冲突&#xff0c;所以这里统一用String。 目录 前言 一 初始化和销毁 1.1 构造函数 1.2 析构函数 二 迭代器实现 三 容量大小及操作 四 运算符重载 4.1 bool…