【Linux】-进程间通信-共享内存(SystemV),详解接口函数以及原理(使用管道处理同步互斥机制)

在这里插入图片描述

💖作者:小树苗渴望变成参天大树🎈
🎉作者宣言:认真写好每一篇博客💤
🎊作者gitee:gitee✨
💞作者专栏:C语言,数据结构初阶,Linux,C++ 动态规划算法🎄
如 果 你 喜 欢 作 者 的 文 章 ,就 给 作 者 点 点 关 注 吧!

文章目录

  • 前言
  • 一、共享内存的原理
  • 二、直接代码
    • 2.1关于共享内存的四大接口
    • 2.2如何通信
  • 三、扩展知识
    • 3.1 看看维护共享内存的结构体属性
    • 3.2 使用管道来实现同步互斥机制
  • 四、总结


前言

今天我们来讲进程间通信的的另一个通信方式,在第一篇讲解进程间通信的博客中,博主就提到了SystemV标准的通信方式,我们前面讲解的匿名管道和命名管道都是基于文件的,但是共享内存不是基于文件的,他的所有进程间通信最快的,因为他的拷贝少,共享内存的难点就在于他的接口多,复杂,因为SystemV标准下不止一个共享内存,还有消息队列和信号量,都需要类似的接口,为了可以更好的复用接口函数,接下来博主就来带大家学习共享内存。


讲解逻辑:

  1. 直接原理,讲解周边问题
  2. 通过原理,写一部分代码,认识系统接口,进行测试
  3. 扩展代码去讲解

一、共享内存的原理

使用共享内存的目的是让进程间进行通信,但是进程间通信的本质是让不同的进程看到同一份资源,由共享内存这个名字可知,这篇共享的资源是一块内存,计算机中我们一般由的地址要不是虚拟地址,要不是物理地址,想形成可执行程序里面的地址我们目前不谈,而虚拟地址是每个进程特有的,所以我们猜测这块共享内存是物理内存的一块,因为有了前面的两次通信方式的铺垫,我们已经慢慢找到规律了,那博主就以一份图给大家讲解一下共享内存的原理。
在这里插入图片描述

共享内存的原理很简单,就上幅这个图片,但是博主要讲一些周边问题:

  1. 释放共享内存,先去挂接,再释放内存,是相反的操作
  2. 上面的操作都是进程直接做的吗??不是,是直接由os去做的,原因涉及到物理内存了。
  3. 那既然有os去操作的,那么我们去创建,使用或者释放都需要经过系统调用接口去让os帮助我们实现
  4. 我们的不同进程通过共享内存进行通信,另外的进程也需要通过共享内存来进行通信,那么共享内存就不止一块,由许多快,那么这块共享内存都是需要管理起来的,所以先描述再组织,就对应我们上图的struct结构体。里面存放的是对共享内存的管理属性。

所以我们一会对共享内存的使用里面肯定会涉及到这个结构体里面的属性,等会遇到了一个讲一个,现在都讲解出来读者大概率不会理解。

二、直接代码

我们通过刚才的原理分析,而且这些操作是需要通过系统调用接口的,所以我们一步步的来介绍这些系统调用接口。

2.1关于共享内存的四大接口

一、申请共享内存接口
在这里插入图片描述

  1. 返回值(用户层)shmid:此函数申请一块共享内存,返回共享内存标识符,可以先理解为和文件描述符唯一标志文件一样的道理。
  2. 第二个参数,是申请共享内存的大小。单位是字节
  3. 第三个参数:共享内存是为了给不同的进程使用,那么使用这块内存之前,只要由一个进程创建,其他进程拿来用就行了,那这个参数就是控制对共享内存的权限操作,来看我们自己要掌握的权限在这里插入图片描述
    (1)IPC_CREAT:(单独使用)如果你申请的共享内存不存在就创建,存在就获取返回
    (2)IPC_CREAT | IPC_EXCL:如果你申请的共享内存不存在就创建,存在就报错,这是保证了你创建的共享内存是最新的。IPC_EXCL不单独使用
    (3)第三个就是传我们对应的权限,如0666

上面的方式我们再讲解文件操作的时候就讲解过了,write函数里面需要传这样的参数,这些大写字母起始就是对应的宏。

  1. 第一个参数:通过第三个参数,我们怎么知道这个共享内存存不存在,就好比你怎么保证让不同的进程看到同一份共享内存是一样的,此时就有了我们的第一个参数,接下来谈谈这个key。
    (1)key是一个数字,这个数字是多少不重要。关键在于他必须再内核中具有唯一性,能够让不同的进程进程唯一标识
    (2)第一个进程可以通过key来创建共享内存。第二个进程之后的进程,只要拿着这个key就可以和第一个进程看到同一个共享内存了
    (3)对于一个已经创好的共享内存,key在哪??大家还记得一个说管理共享内存的结构体吗,key就在共享内存的描述对象里
    (4)通过第一点想要key去唯一标识共享内存,大家再回想一下命名管道是怎么唯一标识的,是不是通过就和文件名,所以这个key应该也类似于命名管道的标识方式。
    (5)通过第二点,我们通过key创建共享内存,那么第一次创建的时候,这个key怎么有???

我们总结出四个结论和一个问题,问题来到了这个key一开始时怎么产生的了,按照第四点的结论,我们来介绍一下这个函数ftok
在这里插入图片描述
第一个参数:路径这个随便写
第二个参数,这个是工程id,我们可以随便去指定是一个数字
返回值(内核层):是一个共享内存标识符

我们上面的两个参数都是由用户自己去定义的,所以可能会和系统中的key产生冲突,这个函数是通过一个算法将两个参数进行运算的出来的这样的一个key,每次生成的结果都是不一样的,不是你每次传的参数一样计算出来的结果就是一样的。这样为什么就可以做到key是唯一的呢,我们的路径是唯一的,而且第二个参数是我们自己传,大概率也是唯一的,这样就导致我们的key是唯一的,而且一旦创建这个key就是这个共享内存所独有了,如果再生成这个key,只能获取,不会再创建一个新的了
为什么key不由os自己创建呢,我们自己创建还有可能造成key冲突的问题??
(1)再谈谈key的时候的第二点我们知道这个我们通过创建共享内存是由一个进程去创建另一个进程去使用就可以,如果这个key是os生成的,创建好的共享内存,那另一个没有关系的进程怎么获取这块共享内存,因为共享内存不是唯一的,所以os里面的key也不是唯一的,所以没有办法给另一个进程让他获取啊,有的人说传给另一个进程,这样就出现蛋生鸡的问题,另一个进程要key才能进行通信,但是要key必须先通信,如果共享内存的个数是唯一的,那么可以让os自己生成,大家自己理解一下
(2)这个key的获取可以说是用户的约定,和哪个进程通信只有用户知道,就是程序员知道,两个进程使用ftok这个相同的方式就可以获取唯一的key,因为这两个参数是唯一的
(3)有的人会说我们将系统自己生成的key通过管道传给另一个进程就可以了,答案确实可以,但是这样我们学习共享内存的成本就搞了,还要先学习管道,这样也不嫩恶搞保证共享内存是一个独立通信模块了

大家看到这里对于key的理解应该到位了,但是有一个关键的点,key vs shmid

这两个都是共享内存的标识符,他两有一个不就行了,key是内核中唯一标识的,shmid只有再进程里唯一标识的,我们操作共享共享内存的函数都是使用shmid。

通过上面的一系列分析,我们来申请一块共享内存:shmget+ftok
sham.hpp:

#ifndef __COMM_HPP__
#define __COMM_HPP__#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/types.h>
#include <sys/stat.h>#include "log.hpp"using namespace std;Log log;const int size = 4096; 
const string pathname="/home/xdh";
const int proj_id = 0x6666;key_t GetKey()
{key_t k = ftok(pathname.c_str(), proj_id);if(k < 0){log(Fatal, "ftok error: %s", strerror(errno));exit(1);}log(Info, "ftok success, key is : 0x%x", k);return k;
}int GetShareMemHelper(int flag)
{key_t k = GetKey();int shmid = shmget(k, size, flag);if(shmid < 0){log(Fatal, "create share memory error: %s", strerror(errno));exit(2);}log(Info, "create share memory success, shmid: %d", shmid);return shmid;
}int CreateShm()//创建共享内存得到标识符shmid,进行了封装
{return GetShareMemHelper(IPC_CREAT | IPC_EXCL | 0666);
}
#endif

processa.cc:

#include"sham.hpp"
//这是进程a,有这个进程创建共享内存
int main()
{//申请共享内存int shmid=CreateShm();sleep(5);return 0;
}

在这里插入图片描述

通过结果我们发现,我们第一次运行程序的时候申请了一块共享内存获得了共享内存标识符,但是第二次运行的时候显示就存在了,我们使用ipcs -m 查看共享内存,我们得出结论,进程结束了,我们的共享内存还是存在的,共享内存的生命周期是随着内核的,不是随着进程的,通过原理图也不难理解这点,没有关闭共享内存,这也可能会造成内存泄漏,类似于malloc。

这里面我们再来研究一个点,我们申请4097个字节大小的空间看看效果
在这里插入图片描述
我们看到大小是4097,在内核里面,我们的os实际上会给我们的4096*2大小的空间,但是我们只能使用4097,这个大家要记住,所以建议还是申请4096点整数倍,折合人民币我们内存的页宽有关系,大家先不用了解。

二、.挂接共享内存:shmat函数
我们的共享内存申请好了,我们就需要将其挂接到我们的地址空间上,就是原理图上的第二步
在这里插入图片描述

  1. 第一个参数:就是传刚才使用shmget函数的返回值即可是共享内存的唯一标识符
  2. 第二个参数:指定挂接到那个位置,我们申请好了共享内存,要挂接到我们进程的地址空间的共享群位置,这么多位置总要找到一个位置的其实位置吧,这样也方便我们页表进行映射,所以需要制定,我们在这里传空指针就好了,意思让系统自己决定
  3. 第三个参数:是挂接的方式

在这里插入图片描述
我们在这里传0进去就好了

  1. 返回值:我们就是把挂接到地址空间的那块位置的首地址返回出来,让用户能拿到,进行操作,所以返回值是void需要强转,和malloc类似,失败就返回(void)-1

我们来看代码实现:

//将共享内存挂接到自己的地址空间char* shmaddr=(char*)shmat(shmid,nullptr,0);if(*shmaddr<0){log(Fatal,"shmat flase:%s",strerror(errno));exit(3);}log(Info,"shmat sucessful:%s",strerror(errno));sleep(3);

在这里插入图片描述

我们来观察一下nattch这个属性,他就是表示这块共享内存当前的挂接树,没调用这个shmat函数之前为0,调用之后为1,而且当进程退出他的挂接数自然的就减少了1

三.去掉挂接关系:shmdt
刚才是因为程序结束,挂接数减少了,但我们有时候程序没结束就像去挂接,怎么做??我们通过shmdt来去挂接,来看文档
在这里插入图片描述

这个函数非常的简单,就是传刚才挂接函数返回值就可以了,我们直接来看使用效果:
我们分析,我们3秒后创建共享内存,5秒后挂接进程,挂接数变成1,3秒后,去挂接,挂接数变成1,在3秒后程序终止,

int n=shmdt(shmaddr);if(n<0){log(Fatal,"shmdt flase:%s",strerror(errno));}log(Info,"shmdt sucessful:%s",strerror(errno));sleep(3);

在这里插入图片描述

和我们预测的一样,我们的挂接数不一定非得在程序结束才会减1

四.释放共享内存:shmctl
我们想要将我们的共享概念内存释放掉使用shmctl
在这里插入图片描述

第一个参数:共享内存唯一标识符
第三个参数:是一个描述共享内存的状态和访问权限的数据结构,也就是我们开头说的描述共享内存的结构图,看到key在里面了吧,对于这个参数我们可以传一个null,因为不需要将状态获取到,这是一个输出型参数和status一样。
第二个参数:将要采取的动作,就是对第三个参数实行什么样的操作,有三个操作

在这里插入图片描述
我们关注的是最后一个,删除共享内存

来看操作:

 int n1=shmctl(shmid,IPC_RMID,nullptr);if(n1<0){log(Fatal,"shmctl flase:%s",strerror(errno));}log(Info,"shmctl sucessful:%s",strerror(errno));sleep(3);

在这里插入图片描述

通过结果验证我们的讲解,我们也可以通过ipcrm -m +shmid来删除共享内存,这个大家下去试试,但是shmctl传进去的操作不一样,功能就不一样,如果传IPC_STAT,就可以查看属性。


我们将另一个进程也挂接到这个共享内存上吧,因为申请和释放进程a帮助我们做了,我们做的就是挂接和去挂接就可以了,来看进程b的代码:
先展示进程a的代码:

#include"sham.hpp"
//这是进程a,有这个进程创建共享内存
int main()
{sleep(3);//申请共享内存int shmid=CreateShm();sleep(5);//将共享内存挂接到自己的地址空间char* shmaddr=(char*)shmat(shmid,nullptr,0);if(*shmaddr<0){log(Fatal,"shmat flase:%s",strerror(errno));exit(3);}log(Info,"shmat sucessful:%s",strerror(errno));sleep(3);//去挂接int n=shmdt(shmaddr);if(n<0){log(Fatal,"shmdt flase:%s",strerror(errno));}log(Info,"shmdt sucessful:%s",strerror(errno));sleep(3);//释放共享内存int n1=shmctl(shmid,IPC_RMID,nullptr);if(n1<0){log(Fatal,"shmctl flase:%s",strerror(errno));}log(Info,"shmctl sucessful:%s",strerror(errno));sleep(3);return 0;
}

进程b:

#include "sham.hpp"int main()
{sleep(3);int shmid=GetShm();//这个函数在sham.hpp里面写就行了,获取shmidsleep(5);//将共享内存挂接到自己的地址空间char* shmaddr=(char*)shmat(shmid,nullptr,0);if(*shmaddr<0){log(Fatal,"shmat flase:%s",strerror(errno));exit(3);}log(Info,"shmat sucessful:%s",strerror(errno));sleep(3);//去挂接int n=shmdt(shmaddr);if(n<0){log(Fatal,"shmdt flase:%s",strerror(errno));}log(Info,"shmdt sucessful:%s",strerror(errno));sleep(3);return 0;
}

在这里插入图片描述

我们也成功看到了挂接数变成了2,上面讲解的一切都是让两个不同的进程之间看到同一份资源,还没有开始通信

2.2如何通信

我们通过上面一系列的操作终于实现我们再原理图讲的内容了,该说不说,确实太复杂的,但是这一系列的操作,让他的通信显得非常的简单,我们共享内存就是一块物理内存,映射到我们进程的地址空间上,我们程序通过这块地址空间上的地址就可以直接访问这块物理空间,此时他就很想malloc申请空间,然后去使用这块空间的方法很想,我们一起来看操作,让b写,a读

a:

while(true){cout<<"a say@"<<shmaddr<<endl;sleep(1);}

b:

while(true){cout<<"b enter@";fgets(shmaddr,4096,stdin);sleep(1);}

在这里插入图片描述

结论:

  1. 我们我们两个进程对这块空间的操作是你搞你的我搞我的,两者不受任何影响,所以说明共享内存间是没有同步互斥机制的
  2. 我们的共享内存是所有进程中通信速度最快,因为拷贝少
  3. 我们的共享内存的数据是用户自己去维护的,所以这些看到和管道有不同的地方,没有清空数据,这是需要用户自己去决定的。

但是我们确实实现了两个进程间通信了,有问题我们一会来解决。

三、扩展知识

3.1 看看维护共享内存的结构体属性

我们刚才的参数都是为了描述共享内存的,所以维护共享概念给内存的属性有哪些呢,刚才其实也大致看到了一些。
在这里插入图片描述
我们通过代码看看我们刚才提到一下属性:

再a进程把通信代码改成下面的

 int count=0;struct shmid_ds shmds;while(true){sleep(1);if(count==0){shmctl(shmid, IPC_STAT, &shmds);cout << "shm size: " << shmds.shm_segsz << endl;cout << "shm nattch: " << shmds.shm_nattch << endl;printf("shm key: 0x%x\n",  shmds.shm_perm.__key);cout << "shm mode: " << shmds.shm_perm.mode << endl;}count++;}

在这里插入图片描述

3.2 使用管道来实现同步互斥机制

我们因为目前只学了System V的共享内存,我们想要解决这个问题,还可以使用信号量,但是这个我们不做重点介绍,等有机会我们在给大家讲解信号量是怎么解决共享内存的这个缺点,我们今天,就使用管道去解决这个问题吧,因为是不相关的进程,所以使用命名管道。


在这里插入图片描述

在这里插入图片描述

这个使用管道的方法其实和共享内存是一点关系没有,之根据他会阻塞就不会执行下面的代码,这样间接控制了。我们后面会简单介绍一下信号量是怎么解决这个问题的,但是知识带大家了解一下。

四、总结

今天我们学习了共享内存,学习成本和前面两个差不多,前面是原理的铺垫大家不容易理解,但是使用简单,二共享内存有了前面的原理铺垫,理解起来不难,但是后面的使用接口对大家来说可能是一个难度,大家下去好好把四大接口函数理解一下,这对博主下一篇讲解消息队列以及信号量有很大帮助,希望大家下来可以去自己实现博主这篇博客上面的内容,我们下篇再见

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue3+Vite实现工程化,插值表达式和v-text以及v-html

1、插值表达式 插值表达式最基本的数据绑定形式是文本插值&#xff0c;它使用的是"Mustache"语法&#xff0c;即 双大括号{{}} 插值表达式是将数据 渲染 到元素的指定位置的手段之一插值表达式 不绝对依赖标签&#xff0c;其位置相对自由插值表达式中支持javascript的…

XmlElement注解在Java的数组属性上,以产生多个相同的XML元素

例如&#xff0c;下面这段XML数据&#xff0c;有多个data元素&#xff0c;并且它们级别相同: <?xml version"1.0" encoding"UTF-8"?><request><reqtype>05</reqtype><secret>test</secret><body><userid&…

93.STL-系统内置仿函数

目录 算术仿函数 关系仿函数 逻辑仿函数 C 标准库中提供了一些内置的函数对象&#xff0c;也称为仿函数&#xff0c;它们通常位于 <functional> 头文件中。以下是一些常见的系统内置仿函数&#xff1a; 算术仿函数 功能描述&#xff1a; 实现四则运算其中negate是一元…

个人博客项目 - 测试报告

文章目录 一、项目背景二、测试报告功能测试1.编写测试用例2.登录测试3.编写文章测试4.查看文章测试5.删除文章测试7.注销登录测试 自动化测试性能测试1.VUG2.进行场景设计3.生成性能测试报告 总结 本文开始 一、项目背景 通过学习测试相关的知识&#xff0c;动手实践并测试一…

Linux文件

目录 一、基本概念 二、研究进程和被打开文件的关系 &#xff08;一&#xff09;w方式 &#xff08;二&#xff09;a方式 三、认识系统接口&#xff0c;操作文件 &#xff08;一&#xff09;认识文件描述符 &#xff08;二&#xff09;举例 &#xff08;三&#xff09;…

ML-Net:通过深度学习彻底改变多标签分类

一、说明 多标签分类是一项具有挑战性的机器学习任务&#xff0c;其中输入可以同时属于多个类。传统的多标签分类方法通常依赖于将问题转化为一系列二元分类任务或使用集成方法。然而&#xff0c;深度学习的出现开创了多标签分类的新时代&#xff0c;ML-Net 等模型突破了该领域…

【数据结构】动态顺序表详解

目录 1.顺序表的概念及结构 2.动态顺序表的实现 2.1创建新项目 2.2动态顺序表的创建 2.3接口的实现及测其功能 2.3.1初始化 2.3.2尾插 2.3.3头插 2.3.4尾删&头删 2.3.5打印&从任意位置插入 2.3.6删除任意位置的数据 2.3.7查找 2.3.8销毁顺序表 3.结语 He…

2018-2022年富时罗素 ESG评分数据

2018-2022年富时罗素 ESG评分数据 1、时间&#xff1a;2018-2022年 2、指标&#xff1a;证券代码、证券简称、富时罗素ESG评分、 3、说明&#xff1a; 富时罗素ESG评级体系评估了中国大陆、香港、欧洲以及美国等市场上1800家中国上市企业股票&#xff0c;评估了7200多种证券…

scss的高级用法——循环

周末愉快呀&#xff01;一起来学一点简单但非常有用的css小知识。 最近在一个项目中看到以下css class写法&#xff1a; 了解过tailwind css或者unocss的都知道&#xff0c;从命名就可以看出有以下样式&#xff1a; font-size: 30pxmargin-left: 5px;margin-top: 10px; 于是…

SpringBoot监听器解析

监听器模式介绍 监听器模式的要素 事件监听器广播器触发机制 SpringBoot监听器实现 系统事件 事件发送顺序 监听器注册 监听器注册和初始化器注册流程类似 监听器触发机制 获取监听器列表核心流程: 通用触发条件: 自定义监听器实现 实现方式1 实现监听器接口: Order(1) …

Docker Volume: 实现容器间数据共享与持久化的利器

文章目录 Docker Volume的作用Docker Volume与容器内数据的比较优势劣势 Docker Volume的创建和管理创建Docker Volume管理Docker Volume 演示Docker Volume的挂载Docker Volume的生命周期安全性考虑与Docker Volume应用场景Docker Volume与多容器协作容器迁移与Docker Volume未…

如何入驻抖音本地生活服务商,附上便捷流程!

抖音作为一款短视频社交媒体应用&#xff0c;已经成为全球范围内数以亿计的用户的首选。而在普及的同时&#xff0c;短视频领域也在不断拓展自身的业务领域&#xff0c;其中之一就是本地生活服务。继抖音本地生活服务之后支付宝、视频号也相继开展了本地生活服务&#xff0c;用…

用css实现原生form中radio单选框和input输入框的hover样式以及聚焦focus的样式

一.问题描述&#xff1a;用css实现原生form中radio单选框和input的hover已经focus的样式 在实际的开发中&#xff0c;一般公司ui都会给效果图&#xff0c;比如单选按钮radio样式&#xff0c;input输入框hover的时候样式&#xff0c;以及focus的时候样式&#xff0c;等等&#…

【并发编程】ThreadLocal详解与原理

&#x1f4eb;作者简介&#xff1a;小明Java问道之路&#xff0c;2022年度博客之星全国TOP3&#xff0c;专注于后端、中间件、计算机底层、架构设计演进与稳定性建设优化&#xff0c;文章内容兼具广度、深度、大厂技术方案&#xff0c;对待技术喜欢推理加验证&#xff0c;就职于…

java项目之社区互助平台(ssm+vue)

项目简介 社区互助平台实现了以下功能&#xff1a; 1、一般用户的功能及权限 所谓一般用户就是指还没有注册的过客,他们可以浏览主页面上的信息。但如果有中意的社区互助信息时&#xff0c;要登录注册&#xff0c;只有注册成功才有的权限。2、管理员的功能及权限 用户信息的添…

[C/C++] 数据结构 LeetCode:用队列实现栈

题目描述: 请你仅使用两个队列实现一个后入先出&#xff08;LIFO&#xff09;的栈&#xff0c;并支持普通栈的全部四种操作&#xff08;push、top、pop 和 empty&#xff09;。 实现 MyStack 类&#xff1a; void push(int x) 将元素 x 压入栈顶。int pop() 移除并返回栈顶元…

图像分割方法

常见的图像分割方法有以下几种&#xff1a; 1.基于阈值的分割方法 灰度阈值分割法是一种最常用的并行区域技术&#xff0c;它是图像分割中应用数量最多的一类。阈值分割方法实际上是输入图像f到输出图像g的如下变换&#xff1a; 其中&#xff0c;T为阈值&#xff1b;对于物体的…

如何用cmd命令快速搭建FTP服务

环境&#xff1a; Win10专业版 问题描述&#xff1a; 如何用cmd命令快速搭建FTP服务 解决方案&#xff1a; 1.输入以下命令来安装IIS&#xff08;Internet Information Services&#xff09;&#xff1a; dism /online /enable-feature /featurename:IIS-FTPServer /all …

如果文件已经存在与git本地库中,配置gitignore能否将其从git库中删除

想把项目的前后台代码放到同一个git仓库管理&#xff0c;由于未设置.gitignore&#xff0c;就使用vscode做stage操作&#xff08;相当于git add . 命令 其中【.】点表示全部文件&#xff09;&#xff0c;观察将要入库的文件发现&#xff0c;node_modules、target、.idea、log等…

webpack项目 index.html 根据不同的变量引入不同的js

项目 webpack搭建 问题&#xff1a;在入口文件index.html中根据不同的变量引入不同的js 使用插件HtmlWebpackPlugin HtmlWebpackPlugin 项目里用来生成静态文件的 这个插件每个项目基本都要用到的&#xff0c;只要全局搜一下位置 根据配置文件的指令找到执行的文件&#xff0…