操作系统:动静态库
目录
1.动静态库
1.1.如何制作一个库
1.2.静态库的使用和管理
1.3.安装和使用库
1.4.动态库
1.4.1.动态库的实现
1.4.2.动态库与静态库的区别
1.4.3.共享动态库给系统的方法
2.动态链接
2.1.操作系统层面的动态链接
1.动静态库
- 静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静 态库
- 动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。 一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文 件的整个机器码
- 在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个 过程称为动态链接(dynamic linking)
- 动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚 拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。
1.1.如何制作一个库
库(Library)是一种预先编写好的可重用代码集合,它包含了一组函数、类、数据结构或其他可执行代码的集合。库的目的是为了提供一些常用的功能,以便开发人员可以在自己的程序中直接调用这些功能,而不需要从头开始编写代码。
在我们实际开发的场景下,当我们写好一个库时,我们往往是将源代码(.c文件)经过编译后的文件(.o文件)和头文件一起打包给库的使用者,也就是我们写好的库是不包含源代码的。
文件编译
一堆源文件和头文件最终变成一个可执行程序需要经历以下四个步骤:
- 预处理: 完成头文件展开、去注释、宏替换、条件编译等,最终形成xxx.i文件。
- 编译: 完成词法分析、语法分析、语义分析、符号汇总等,检查无误后将代码翻译成汇编指令,最终形成xxx.s文件。
- 汇编: 将汇编指令转换成二进制指令,最终形成xxx.o文件。
- 链接: 将生成的各个xxx.o文件进行链接,最终形成可执行程序。
库中包含着.h文件和.o文件
接下来我们写一个demo并把它打包成库
我们以实现计算器这个库
一般来说我们要编译这个test.c这个可执行文件,需要将三个.c文件同时编译使用
gcc test.c add.c sub.c
但是这样子就需要将源代码给到别人,而开发中的源代码一般是闭源的,所以我们需要先将这几个实现.h文件的.c文件编译成.o文件
gcc -c add.c sub.c // 生成对应的.o文件
那么我们现在开始创建库的雏形,只给.h文件和.o文件(这个图add.c应该为add.h)
用户在使用时通过先将自己的测试文件转换为.o文件然后再一起编译这几个.o文件
gcc test.o add.o sub.o
假设在实际开发中,有10000个.o文件那这样子编译会不会容易漏掉,所以这时候我们需要打包文件,生成静态库
// 通过这条指令我们打包了一个静态库mycal ar -rc libmycal.a *.o
如图所示我们就打包好了一个静态库
一般来说我们通常通过makefile来打包好静态库
static-lib=libmycal.a$(static-lib):add.o sub.oar -rc $@ $^%.o:%.cgcc -c $<.PHONY:clean clean:rm -f *.o
1.2.静态库的使用和管理
我们在1.1.中制作了一个mycal的静态库,那么从库的使用角度出发我们怎么来使用这个库呢?
首先当我们写了一个库给他人使用时,此时作为第三方库,gcc无法认识这个库,需要链接指定的库
这时我们需要连接上这个静态库,输入链接指令
// gcc 需要编译的文件名 -l库名称 -L 库所在的路径 gcc test.c -lmycal -L.
注意这里的 -l 直接连接对应的库名称,且库名称需要去掉 lib 和 .a
这样我们就完成了静态库的使用了!!!
值得注意的是:一个可执行文件,可以实现动静态库的混合链接,如何连接时加入 -static 选项,表示只使用该文件编译时需要的静态库。
我们在lib文件夹中发现.h文件和.a库放在一起,在实际开发场景中,可能存在大量的这些文件,为了便于管理,我们通常是将他们分组存放后,然后打包挂到云端提供给他人使用
回到最初的起点,我们开始准备打包
这里是makefile内的代码块
static-lib=libmycal.a$(static-lib):add.o sub.oar -rc $@ $^%.o:%.cgcc -c $<.PHONY:output output:mkdir -p mycal_lib/includemkdir -p mycal_lib/lib cp -f *.h mycal_lib/includecp -f *.a mycal_lib/lib.PHONY:clean clean:rm -f *.o *.a output
接着我们make编译一下
这时就回到了我们之前讲的需要管理的场景,我们在makefile里写了一个脚本output
形成了一个目录,我们查看一下
这样子就实现了我们对这个库进行管理了
tar czf mycal_lib.tgz mycal_lib
最后的最后我们打包好这个文件就可以挂载服务器端让他人下载使用了
1.3.安装和使用库
书接上文,假设我们从网上下载一个mycal_lib这个库,我们需要如何加载进我们的库环境中呢?
首先需要解压这个包,获得库,接着就是将库安装到开发环境中!!!(本质上是拷贝)
安装开发环境的本质就是将第三方库提供的.h、.a文件分别放到系统中的指定位置,一般是拷贝到对应的include、lib文件夹里
那么当我们运行时,gcc默认就会在系统的开发环境中来寻找我们download的库和头文件了,使用时就只需要连接需要的库即可
gcc test.c -lmycal
如果我们不安装到系统里,我们需要怎么使用这个库呢?
这时我们需要输入指令来寻址(因为头文件和库文件在mycal_lib中,gcc找不到)
// -I 新增头文件搜索路径 -l为连接的库名称 -L 新增库文件的搜索路径
gcc test.c -I mycal_lib/include -lmycal -L mycal_lib/lib
那么这样子我们就完成了,对下载库使用的学习了
1.4.动态库
因为动态库的制作和静态库的制作思路一致都是形成.o文件然后打包,但是会有一些细节上的不同,接下来我们来实现一下动态库
1.4.1.动态库的实现
首先是编译文件时和打包指令不同
// 编译成.o文件时需要添加 -fPIC
gcc -fPIC -c add.c sub.c
// 打包成库时用gcc指令
gcc -shared -o libmycal.so *.o
我们只需要修改一下makefile就也可以通过脚本实现之前讲的内容
dy-lib=libmycal.so$(dy-lib):add.o sub.ogcc -shared -o $@ $^%.o:%.cgcc -fPIC -c $<.PHONY:output
output:mkdir -p mycal_lib/includemkdir -p mycal_lib/lib cp -f *.h mycal_lib/includecp -f *.so mycal_lib/lib.PHONY:clean
clean:rm -f *.o *.so output
实现的部分我们不再赘述了
1.4.2.动态库与静态库的区别
那我们通过使用静态库的方式来调用一下这个动态库
我们发现就算我们声明了路径,链接了调用的库,最后可以正常生成这个a.out文件但是我们运行时发现 -> 报错:加载共享库,无法打开这个共享库文件
这里我们解释一下: 静态库在编译后,可执行程序和静态库打包在一起相当于加载进了整个文件中,运行期间不需要找到静态库。而运行动态库是和可执行程序分离的,所以需要将动态库加载到内存中。
可执行程序 和 动态库 同时存在于内存中才可以实现动态库!!!
我们发现这时系统找不到动态库的位置,因为我们之前只给了test.c寻找到了动态库的位置,但是系统还是不知道动态库在哪里,所以需要我们把动态库的位置共享给系统。
1.4.3.共享动态库给系统的方法
- 将头文件和库文件添加到系统默认的区域,也就是拷贝到相应的include和lib
// 拷贝库中的所有.h文件到系统默认的include中 sudo cp mycal_lib/include/*.h /usr/include/ // 拷贝.so库文件 sudo cp mycal_lib/lib/*.so /lib64/
- 建立软连接,动态库会在pwd中寻找库
ln -s mycal_lib/lib/libmycal.so libmycal.so
动态库在运行时会查找当前路径是否能找到这个动态库,所以我们可以建立软连接来实现!
这时候我们可以思考一下:我们能不能把这个软连接安装到系统里面,而不是安装整个整个库呢?也是可以的!
- 添加至环境变量LD_LIBRARY_PATH中(内存级别,退出shell会被清空)
// 通过pwd找到动态库所在的路径添加到环境变量里 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/Czh_Linux/test/mycal_lib/lib
- 更改系统的配置文件(不会因为重启shell而被清空)
系统中存在一些管理动态库的配置文件
// 文件的路径 /etc/ld.so.conf.d/
接着我们就需要写入进这个目录里面,在里面创建一个自己的动态库配置文件
// 需要sudo权限或者root touch /etc/ld.so.conf.d/my_so.conf
接着在这个my_so.conf文件里面写入你库的地址,进入动态库的地址用pwd打印出地址然后拷贝进这个文件中。
// 直接拷贝这一段话到你创建的.conf文件中 /home/Czh_Linux/test/mycal_lib/lib
这时已经完成了写入动态库信息到配置文件中!!!
// 如果没有生效就需要刷新一下 sudo ldconfig
2.动态链接
在Linux中可执行文件一般以ELF的格式出现,符号表中存放着调用某些库的方法的地址,那也就是当可执行文件加载进内存时,进入内存的只有文件中对应库方法的地址,而没有库中的实现,也就对应了我们之前没有将动态库加载进内存中会出现报错!
所以进行动态链接的过程就是将对应库的方法的地址存放在可执行文件的符号表中,再配合着将对应库加载进内存中!
我们接着思考,当程序加载进内存中时是以什么形式出现的呢?从汇编语言中我们就能看出大部分的函数变量加载进内存时,最终是以“地址”的形式加载进内存的。那么这个问题引申下去就是如何对代码进行编址呢?
在编译器中,也是通过“虚拟地址空间”编址,虚拟地址空间可以看成是一种编译标准
文件通过编译后在形成进程加载进内存之前,代码和数据就已经具有了自己的地址(虚拟地址),大多数情况叫做处于磁盘文件的“逻辑地址”,核心是:基地址 + 偏移量
一般情况下:设定基地址为0,然后偏移量来进行定位区分(Linux的平坦模式)
在计算机中对程序进行编址时,一般是两种方式:绝对编址(以基地址为标准) 和 相对编址(以其他个体地址为标准)
库当中形成地址一般是通过相对编址,记录函数间的偏移量
假如:Entry的main函数地址为0x1234,进入main函数后遇到的第一个地址为0x1236那么他们的偏移量固定为(0x1236 -0x1234)
未来库在内存中的任意位置加载,那么库内部函数的相对编址始终不变,所以无论是加载在内存的某个区域,只要找到Entry我们都能通过不变的“地址”来使用库中的所有的函数。
这就是 fPIC 与位置无关码
2.1.操作系统层面的动态链接
我们知道了ELF文件(编译后的代码)也实现了类似操作系统中的虚拟地址空间,也就是操作系统和磁盘通过这个虚拟地址空间进行代码的交互,又动态库在内存中会映射在pcb的虚拟内存的共享区内,这样子就穿起来了操作系统和磁盘内的ELF可执行文件(这个不就是进程运行文件的动态链接吗)
但是我们感觉还是有一点抽象,究竟代码的地址和进程的地址是如何联系起来的呢?
我们之前强调了相对编址这个概念,在ELF的符号表中存储着各个调用库的函数的地址(只要知道main函数入口地址),我们可以计算出来他们相对的偏移量,如果main为a,printf为b,那么偏移量就为b-a,这是从库角度出发。当我们从文件的代码区出发,运行代码的本质就是地址的跳转,但是代码区相当于只存储了代码的声明,实现在库中,所以我们就需要代码区进行到某个函数时跳转到对应共享区的部分,通过偏移量,映射到内存中代码的实现!
接着我们再换一个角度,从进程的调度中出发
CPU在进行进程调度时也是通过访问虚拟地址来实现的,也就是在我们所说的代码区和共享区进行跳转通过CPU中的指令寄存器。我们知道代码在内存中拥有自己的物理地址,而CPU指令寄存器读入代码虚拟地址通过页表映射找到物理地址,因此CPU可以实现某一行代码的功能。
- 操作系统可以读取编译后的ELF程序的入口Entry地址(虚拟地址),这个地址被操作系统用于进程pcb对应的正文代码的main函数的入口,接下来操作系统将这个入口地址加载到指令寄存器中。
- 从main函数还是执行,一步一步调用不同的代码,读取对应的虚拟地址,然后通过页表映射到物理内存,实现库方法和代码的运行,接着继续跳转回CPU,读取下一条虚拟地址
- 如此往复这个循环,最终完成我们进程的调度
找代码是通过物理地址,代码之间跳转通过虚拟地址。每个程序加载进物理内存中,每个变量、函数都有自己的物理地址和虚拟地址。
核心:
- 只要获得起始地址再配合偏移量就能实现库中的任何方法
- 通过虚拟地址映射到物理地址来实现代码的定位
- 操作系统和编译器用同样的虚拟地址空间标准,实现CPU进行虚拟地址之间的转化
相关文章:
操作系统:动静态库
目录 1.动静态库 1.1.如何制作一个库 1.2.静态库的使用和管理 1.3.安装和使用库 1.4.动态库 1.4.1.动态库的实现 1.4.2.动态库与静态库的区别 1.4.3.共享动态库给系统的方法 2.动态链接 2.1.操作系统层面的动态链接 1.动静态库 静态库(.a)&…...
车载电子电器架构 —— 局部网络管理汇总
车载电子电器架构 —— 局部网络管理汇总 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明…...
网络安全 | 什么是DDoS攻击?
关注WX:CodingTechWork DDoS-介绍 DoS:Denial of Service,拒绝服务。DDoS是通过大规模的网络流量使得正常流量不能访问受害者目标,是一种压垮性的网络攻击,而不是一种入侵手段。NTP网络时间协议,设备需要…...
[Godot] 3D拾取
CollisionObject3D文档 Camera3D文档 CollisionObject3D有个信号_input_event,可以用于处理3D拾取。 Camera3D也有project_position用于将屏幕空间坐标投影到3D空间。 extends Node3D#是否处于选中状态 var selected : bool false #摄像机的前向量 var front : V…...
知识融合:知识图谱构建的关键技术
目录 一、引言二、知识图谱基础2.1 知识表示三元组属性图 2.2 知识抽取实体抽取关系抽取属性抽取 三、知识融合的核心问题3.1 实体识别与链接实体识别实体链接 3.2 重复实体合并方法示例 3.3 关系融合挑战方法示例 四、知识融合技术深度解析4.1 基于规则的方法规则设计原则规则…...
外贸建站:WordPress搭建外贸独立站零基础自建站完整教程(2024)
对于做外贸来说,拥有自己的外贸独立网站真的非常重要。在外贸领域,如今各平台竞争激烈,规则多,成本高,价格战、政策变化快,还存在封店风险等等因素。在这种情况下,拥有外贸独立站就能很好规避上…...
【教程】Kotlin语言学习笔记(五)——Lambda表达式与条件控制
写在前面: 如果文章对你有帮助,记得点赞关注加收藏一波,利于以后需要的时候复习,多谢支持! 【Kotlin语言学习】系列文章 第一章 《认识Kotlin》 第二章 《数据类型》 第三章 《数据容器》 第四章 《方法》 第五章 《L…...
C++的并发世界(三)——线程对象生命周期
0.案例代码 先看下面一个例子: #include <iostream> #include <thread>void ThreadMain() {std::cout << "begin sub thread:" << std::this_thread::get_id()<<std::endl;for (int i 0; i < 10; i){std::cout <&…...
SAD法(附python实现)和Siamese神经网络计算图像的视差图
1 视差图 视差图:以左视图视差图为例,在像素位置p的视差值等于该像素在右图上的匹配点的列坐标减去其在左图上的列坐标 视差图和深度图: z f b d z \frac{fb}{d} zdfb 其中 d d d 是视差, f f f 是焦距, b b…...
基于DWT(离散小波变换)的图像加密水印算法,Matlab实现
博主简介: 专注、专一于Matlab图像处理学习、交流,matlab图像代码代做/项目合作可以联系(QQ:3249726188) 个人主页:Matlab_ImagePro-CSDN博客 原则:代码均由本人编写完成,非中介,提供…...
【威胁情报综述阅读3】Cyber Threat Intelligence Mining for Proactive Cybersecurity Defense
【威胁情报综述阅读1】Cyber Threat Intelligence Mining for Proactive Cybersecurity Defense: A Survey and New Perspectives 写在最前面一、介绍二、网络威胁情报挖掘方法和分类A. 研究方法1) 第 1 步 - 网络场景分析:2) 第 2 步 - 数据…...
在编程中使用中文到底该不该??
看到知乎上有个热门问题,为什么很多人反对中文在编程中的使用? 这个问题有几百万的浏览热度,其中排名第一的回答非常简洁,我深以为然: 在国内做开发,用中文写注释、写文档,是非常好的习惯&…...
PyQt6从入门到放弃
PyQt6从入门到放弃 安装PyQt6 pip install PyQt6# 查看QT和PyQT的版本 from PyQt6.QtCore import QT_VERSION_STR from PyQt6.QtCore import PYQT_VERSION_STR print(QT_VERSION_STR) print(PYQT_VERSION_STR)PyQt6模块 PyQt6类由一系列模块组成包括QtCore、QtGui、QtWidgets…...
PhpWord导入试卷
规定word导入格式 1、[单选题][2024][一般]题目1 A.选项1 B.选项2 C.选项3 D.选项4 答案:D 试题图片(上传多媒体图片): 分数:2 答案解析: 2、[多选题][2024][困难]题目2 A.选项1 B.选项2 C.选项3 D.选项4 E…...
C# 运算符重载 之前的小总结
C# 中支持运算符重载,所谓运算符重载就是我们可以使用自定义类型来重新定义 C# 中大多数运算符的功能。运算符重载需要通过 operator 关键字后跟运算符的形式来定义的,我们可以将被重新定义的运算符看作是具有特殊名称的函数,与其他函数一样&…...
XenCenter 2024 创建一个虚拟机
前言 实现,创建一个虚拟机,内存,cpu,磁盘,名称,网卡,配置 Xen Center 2024 download 创建虚拟机 选择系统类型 定义虚拟机名称 选择ISO镜像库 选择主服务器 分配虚拟机内存,cpu资源…...
tomcat 知多少
Tomcat的缺省端口: 默认端口为8080,可以通过在tomcat安装包conf目录下,service.xml中的Connector元素的port属性来修改端口。 tomcat 常见 Connector 运行模式(优化): 这三种模式的不同之处如下: BIO : 一…...
【详细讲解语言模型的原理、实战与评估】
🌈个人主页:程序员不想敲代码啊🌈 🏆CSDN优质创作者,CSDN实力新星,CSDN博客专家🏆 👍点赞⭐评论⭐收藏 🤝希望本文对您有所裨益,如有不足之处,欢迎在评论区提…...
Predict the Next “X” ,第四范式发布先知AIOS 5.0
今天,第四范式发布了先知AIOS 5.0,一款全新的行业大模型平台。 大语言模型的原理是根据历史单词去不断预测下一个单词,换一句常见的话:Predict the Next “Word”。 当前对于行业大模型的普遍认知就是沿用这种逻辑,用大…...
PCL使用4PCS配准
一、代码 C++ #include <pcl/registration/ia_fpcs.h> // 4PCS算法 #include <pcl/point_types.h> #include <pcl/point_cloud.h> #include <pcl/io/pcd_io.h> #include <pcl/io/ply_io.h> #include <boost/thread/thread.hpp> #include…...
【六 (2)机器学习-机器学习建模步骤/kaggle房价回归实战】
一、确定问题和目标: 1、业务需求分析: 与业务团队或相关利益方进行深入沟通,了解他们的需求和期望。 分析业务流程,找出可能的瓶颈、机会或挑战。 思考机器学习如何帮助解决这些问题或实现业务目标。 2、问题定义:…...
vue源码解析——vue如何将template转换为render函数
Vue 将模板(template)转换为渲染函数(render function)是 Vue 编译器的核心功能,它是 Vue 实现响应式和虚拟 DOM 的关键步骤。在 Vue 中,模板(template)是开发者编写的类似 HTML 的代…...
深入理解zookeeper
如果是zookeeper的初学者,可以看: zookeeper快速入门(合集)-CSDN博客 如果想要深入理解zookeeper,并在面试中取得更好的表现,可以看下面的文章,都是偏面试向的角度写的。 三分钟明白zookeeper…...
【漏洞复现】WordPress Plugin LearnDash LMS 敏感信息暴漏
漏洞描述 WordPress和WordPress plugin都是WordPress基金会的产品。WordPress是一套使用PHP语言开发的博客平台。该平台支持在PHP和MySQL的服务器上架设个人博客网站。WordPress plugin是一个应用插件。 WordPress Plugin LearnDash LMS 4.10.2及之前版本存在安全漏洞&#x…...
phpmyadmin页面getshell
0x00 前言 来到phpmyadmin页面后如何getshell呢?下面介绍两种方法 0x01 select into outfile直接写入 1、利用条件 对web目录需要有写权限能够使用单引号(root) 知道网站绝对路径(phpinfo/php探针/通过报错等) secure_file_priv没有具体值 2、查看secure_file…...
题目:学习static定义静态变量的用法
题目:学习static定义静态变量的用法 There is no nutrition in the blog content. After reading it, you will not only suffer from malnutrition, but also impotence. The blog content is all parallel goods. Those who are worried about being cheate…...
【C++】编程规范之函数规则
对所有函数入参进行合法性检查 在编写函数时,应该始终对所有传入的参数进行合法性检查,以防止出现意外的错误或异常情况。这包括但不限于检查指针是否为空、整数是否在有效范围内、数组是否越界等等。通过对参数进行严格的合法性检查,可以避免…...
HTML常用的图片标签和超链接标签
目录 一.常用的图片标签和超链接标签: 1.超链接标签: 前言: 超链接的使用: target属性: 1)鼠标样式: 2)颜色及下划线: 总结: 2.图片标签: 前言: img的使用: 设置图片: 1.设置宽度和高度: 2.HTM…...
浏览器工作原理与实践--WebAPI:XMLHttpRequest是怎么实现的
在上一篇文章中我们介绍了setTimeout是如何结合渲染进程的循环系统工作的,那本篇文章我们就继续介绍另外一种类型的WebAPI——XMLHttpRequest。 自从网页中引入了JavaScript,我们就可以操作DOM树中任意一个节点,例如隐藏/显示节点、改变颜色、…...
TCP网络协议栈和Posix网络部分API总结
文章目录 Posix网络部分API综述TCP协议栈通信过程TCP三次握手和四次挥手(看下图)三次握手常见问题?为什么是三次握手而不是两次?三次握手和哪些函数有关?TCP的生命周期是从什么时候开始的? 四次挥手通信状态…...
外贸婚纱网站 侵权/产品如何推广
Lunix Shell编程入门 前言 要想玩转类Unix系统,仅仅会基本的常用操作命令还是远远不够的,本文介绍lunix系统下的Shell编程,通过一系列的简单Shell代码示例,一步步入门Shell编程。 一、Shell是什么? Linux系统被比作…...
网站界面布局/站长工具是什么
据国外媒体报道,Groupon周四提交IPO(首次公开募股)申请,计划最多募集7.5亿美元。Groupon向美国证券交易委员会(SEC)提交监管文件称,该公司计划通过IPO方式上市,股票交易代码为“GRPN”。 Groupon上市了,国内的很多人心…...
做网站需要花多少钱/网络广告文案
2019独角兽企业重金招聘Python工程师标准>>> 环境变量PATH: 所有的命令所在路径的集合,路径间用“:”分割;可使用 echo $PATH 查看环境变量的内容。 更改PATH想使永久生效,可将更改的命令写入到 /etc/profi…...
宿迁网站推广公司/佛山百度快速排名优化
你应该理解一下充电的概念,充电的概念不类似额定功率那样,不是恒电流就是最佳的,而是分段的~~ 简单解释的话,对于iPhone 4来说: 低于140毫安的话,相当于涓流充电 140~300毫安之间的充电电流相当于慢速充电 …...
网站建设费用分录/口碑营销什么意思
http://baike.baidu.com/view/1234431.htm转载于:https://blog.51cto.com/yerik/757540...
建站系统加盟/百度官网网址
在 Elastic Stack 7.9 之后的发布中,我们可以直接在 Elasticsearch 的配置文件中配置 Node 的角色 (node roles)。这是一个新的变化。在 7.9 发布版之前,我们使用 node.master: true 这样的方式来定义一个 master 节点,…...