第1 5章 在应用程序中使用虚拟内存

Wi n d o w s提供了3种进行内存管理的方法,它们是:

• 虚拟内存,最适合用来管理大型对象或结构数组。

• 内存映射文件,最适合用来管理大型数据流(通常来自文件)以及在单个计算机上运行的多个进程之间共享数据。

• 内存堆栈,最适合用来管理大量的小对象。

本章将要介绍第一种方法,即虚拟内存。内存映射文件和堆栈分别在第 1 7章和第1 8章介绍。用于管理虚拟内存的函数可以用来直接保留一个地址空间区域,将物理存储器(来自页文件)提交给该区域,并且可以设置你自己的保护属性。

5.1 在地址空间中保留一个区域

通过调用Vi r t u a l A l l o c函数,可以在进程的地址空间中保留一个区域:

第一个参数p v A d d r e s s包含一个内存地址,用于设定想让系统将地址空间保留在什么地方。在大多数情况下,你为该参数传递NU L L。它告诉Vi r t u a l A l l o c,保存着一个空闲地址区域的记录的系统应该将区域保留在它认为合适的任何地方。系统可以从进程的地址空间的任何位置来保留一个区域,因为不能保证系统可以从地址空间的底部向上或者从上面向底部来分配各个区域。可以使用M E M _ TO P _ D O W N标志来说明该分配方式。这个标志将在本章的后面加以介绍。

对大多数程序员来说,能够选择一个特定的内存地址,并在该地址保留一个区域,这是个非同寻常的想法。当你在过去分配内存时,操作系统只是寻找一个其大小足以满足需要的内存块,并分配该内存块,然后返回它的地址。但是,由于每个进程有它自己的地址空间,因此可以设定一个基本内存地址,在这个地址上让操作系统保留地址空间区域。

例如,你想将一个从50 MB开始的区域保留在进程的地址空间中。这时,可以传递52 428 800(5 0 × 1 0 2 4 × 1 0 2 4)作为p v A d d r e s s参数。如果该内存地址有一个足够大的空闲区域满足你的要求,那么系统就保留这个区域并返回。如果在特定的地址上不存在空闲区域,或者如果空闲区域不够大,那么系统就不能满足你的要求,Vi r t u a l A l l o c函数返回N U L L。注意,为p v A d d r e s s参数传递的任何地址必须始终位于进程的用户方式分区中,否则对 Vi r t u a l A l l o c函数的调用就会失败,导致它返回N U L L。

第1 3章讲过,地址空间区域总是按照分配粒度的边界来保留的(迄今为止在所有的Wi n d o w s环境下均是6 4 K B )。因此,如果试图在进程地址空间中保留一个从19 668 992(300 × 65 536 + 8192)这个地址开始的区域,系统就会将这个地址圆整为 6 4 K B的倍数,然后保留从19 660 800(3 0 0× 65 536)这个地址开始的区域。

如果Vi r t u a l A l l o c函数能够满足你的要求,那么它就返回一个值,指明保留区域的基地址。如果传递一个特定的地址作为 Vi r t u a l A l l o c的p v A d d r e s s参数,那么该返回值与传递给Vi r t u a l A l l o c的值相同,并被圆整为(如果需要的话)6 4 K B边界值。

Vi r t u a l A l l o c函数的第二个参数是d w S i z e,用于设定想保留的区域的大小(以字节为计量单位)。由于系统保留的区域始终必须是 C P U页面大小的倍数,因此,如果试图保留一个跨越6 2 K B的区域,结果就会在使用 4 KB、8 KB或16 KB页面的计算机上产生一个跨越 6 4 K B的区域。

Vi r t u a l A l l o c函数的第三个参数是f d w A l l o c a t i o n Ty p e,它能够告诉系统你想保留一个区域还是提交物理存储器(这样的区分是必要的,因为Vi r t u a l A l l o c函数也可以用来提交物理存储器)。若要保留一个地址空间区域,必须传递 M E M _ R E S E RV E标识符作为F d w A l l o c a t i o n Ty p e参数的值。

如果保留的区域预计在很长时间内不会被释放,那么可以在尽可能高的内存地址上保留该区域。这样,该区域就不会从进程地址空间的中间位置上进行保留。因为在这个位置上它可能导致区域分成碎片。如果想让系统在最高内存地址上保留一个区域,必须为 p v A d d r e s s参数和f d w A l l o c a t i o n Ty p e参数传递 N U L L,还必须逐位使用 O R将M E M _ TO P _ D O W N标志和M E M _ R E S E RV E标志连接起来。

注意 在Windows 98下,M E M _ TO P _ D O W N标志将被忽略。

最后一个参数是f d w P r o t e c t,用于指明应该赋予该地址空间区域的保护属性。与该区域相关联的保护属性对映射到该区域的已提交内存没有影响。无论赋予区域的保护属性是什么,如果没有提交任何物理存储器,那么访问该范围中的内存地址的任何企图都将导致该线程引发一个访问违规。

当保留一个区域时,应该为该区域赋予一个已提交内存最常用的保护属性。例如,如果打算提交的物理存储器的保护属性是 PA G E _ R E A D W R I T E(这是最常用的保护属性),那么应该用PA G E _ R E A D W R I T E保护属性来保留该区域。当区域的保护属性与已提交内存的保护属性相匹配时,系统保存的内部记录的运行效率最高。

可以使用下列保护属性中的任何一个: PA G E _ N O A C C E S S、PA G E _ R E A D W R I T E、PA G E _ R E A D O N LY、PA G E _ E X E C U T E、PA G E _ E X E C U T E _ R E A D或PA G E _ E X E C U T E _R E A D W R I T E。但是,既不能设定 PA G E _ W R I T E C O P Y属性,也不能设定PA G E _ E X E C U T E _W R I T E C O P Y属性。如果设定了这些属性,Vi r t u a l A l l o c函数将不保留该区域,并且返回N U L L。另外,当保留地址空间区域时,不能使用保护属性标志 PA G E _ G U A R D,PA G E _ N O C A C H E或PA G E _ W R I T E C O M B I N E,这些标志只能用于已提交的内存。

注意 Windows 98只支持PA G E _ N O A C C E S S、PA G E _ R E A D O N LY和PA G E _ R E A D W R I T E保护属性。如果试图保留使用 PA G E _ E X E C U T E或PA G E _ E X E C U T E _ R E A D两个保护属性的区域,将会产生一个带有 PA G E _ R E A D O N LY保护属性的区域。同样,如果保留一个使用 PA G E _ E X E C U T E _ R E A D W R I T E保护属性的区域,就会产生一个带有PA G E _ R E A D W R I T E保护属性的区域。

15.2 在保留区域中的提交存储器

当保留一个区域后,必须将物理存储器提交给该区域,然后才能访问该区域中包含的内存地址。系统从它的页文件中将已提交的物理存储器分配给一个区域。物理存储器总是按页面边界和页面大小的块来提交的。

若要提交物理存储器,必须再次调用Vi r t u a l A l l o c函数。不过这次为f d w A l l o c a t i o n Ty p e参数传递的是M E M _ C O M M I T标志,而不是M E M _ R E S E RV E标志。传递的页面保护属性通常与调用Vi r t u a l A l l o c来保留区域时使用的保护属性相同(大多数情况下是 PA G E _ R E A D W R I T E),不过也可以设定一个不同的保护属性。

在已保留的区域中,你必须告诉Vi r t u a l A l l o c函数,你想将物理存储器提交到何处,以及要提交多少物理存储器。为了做到这一点,可以在 p v A d d r e s s参数中设定你需要的内存地址,并在d w S i z e参数中设定物理存储器的数量(以字节为计量单位)。注意,不必立即将物理存储器提交给整个区域。

下面让我们来看一个如何提交物理存储器。比如说,你的应用程序是在 x86 CPU上运行的,该应用程序保留了一个从地址 5 242 880开始的512 KB的区域。你想让应用程序将物理存储器提交给已保留区域的6 KB部分,从2 KB的地方开始,直到已保留区域的地址空间。为此,可以调用带有M E M _ C O M M I T标志的Vi r t u a l A l l o c函数,如下所示:

在这个例子中,系统必须提交8 KB的物理存储器,地址范围从5 242 880到5 251 071 (5 242880 + 8 KB  - 1字节)。这两个提交的页面都拥有PA G E _ R E A D W R I T E保护属性。保护属性只以整个页面为单位来赋予。同一个内存页面的不同部分不能使用不同的保护属性。然而,区域中的一个页面可以使用一种保护属性(比如 PA G E _ R E A D W R I T E),而同一个区域中的另一个页面可以使用不同的保护属性(比如PA G E _ R E A D O N LY)。

15.3 同时进行区域的保留和内存的提交

有时你可能想要在保留区域的同时,将物理存储器提交给它。只需要一次调用 Vi r t u a l A l l o c函数就能进行这样的操作,如下所示:

这个函数调用请求保留一个 99 KB的区域,并且将99 KB的物理存储器提交给它。当系统处理这个函数调用时,它首先要搜索你的进程的地址空间,找出未保留的地址空间中一个地址连续的区域,它必须足够大,能够存放100 KB(在4 KB页面的计算机上)或104 KB(在8 KB页面的计算机上)。

系统之所以要搜索地址空间,原因是已将 p v A d d r e s s参数设定为N U L L。如果为p v A d d r e s s设定了内存地址,系统就要查看在该内存地址上是否存在足够大的未保留地址空间。如果系统找不到足够大的未保留地址空间,Vi r t u a l A l l o c将返回N U L L,

如果能够保留一个合适的区域,系统就将物理存储器提交给整个区域。无论是该区域还是提交的内存,都将被赋予PA G E _ R E A D W R I T E保护属性。

最后需要说明的是,Vi r t u a l A l l o c将返回保留区域和提交区域的虚拟地址,然后该虚拟地址被保存在p v M e m变量中。如果系统无法找到足够大的地址空间,或者不能提交该物理存储器,Vi r t u a l A l l o c将返回N U L L。

当用这种方式来保留一个区域和提交物理存储器时,将特定的地址作为 p v A d d r e s s参数传递给 Vi r t u a l A l l o c当然是可能的。否则就必须用 O R将M E M _ TO P _ D O W N 标志与f d w A l l o c a t i o n Ty p e参数连接起来,并为p v A d d r e s s参数传递N U L L,让系统在进程的地址空间的顶部选定一个适当的区域。

15.4 何时提交物理存储器

假设想实现一个电子表格应用程序,这个电子表格为 2 0 0行 x 256列。对于每一个单元格,都需要一个C E L L D ATA结构来描述单元格的内容。若要处理这种二维单元格矩阵,最容易的方法是在应用程序中声明下面的变量:

如果C E L L D ATA结构的大小是1 2 8字节,那么这个二维矩阵将需要6 553 600(200 x 256 x1 2 8)个字节的物理存储器。对于电子表格来说,如果直接用页文件来分配物理存储器,那么这是个不小的数目了,尤其是考虑到大多数用户只是将信息放入少数的单元格中,而大部分单元格却空闲不用,因此显得有些浪费。内存的利用率非常低。

传统上,电子表格一直是用其他数据结构技术来实现的,比如链接表等。使用链接表,只需要为电子表格中实际包含数据的单元格创建C E L L D ATA结构。由于电子表格中的大多数单元格都是不用的,因此这种方法可以节省大量的内存。但是这种方法使得你很难获得单元格的内容。如果想知道第5行第1 0列的单元格的内容,必须遍历链接表,才能找到需要的单元格,因此使用链接表方法比明确声明的矩阵方法速度要慢。

虚拟内存为我们提供了一种兼顾预先声明二维矩阵和实现链接表的两全其美的方法。运用虚拟内存,既可以使用已声明的矩阵技术进行快速而方便的访问,又可以利用链接表技术大大节省内存的使用量。

如果想利用虚拟内存技术的优点,你的程序必须按照下列步骤来编写:

1) 保留一个足够大的地址空间区域,用来存放 C E L L D ATA结构的整个数组。保留一个根本不使用任何物理存储器的区域。

2) 当用户将数据输入一个单元格时,找出 C E L L D ATA结构应该进入的保留区域中的内存地址。当然,这时尚未有任何物理存储器被映射到该地址,因此,访问该地址的内存的任何企图都会引发访问违规。

3) 就C E L L D ATA结构来说,只将足够的物理存储器提交给第二步中找到的内存地址(你可以告诉系统将物理存储器提交给保留区域的特定部分,这个区域既可以包含映射到物理存储器的各个部分,也可以包含没有映射到物理存储器的各个部分)。

4) 设置新的C E L L D ATA结构的成员。

现在物理存储器已经映射到相应的位置,你的程序能够访问内存,而不会引发访问违规。

这个虚拟内存技术非常出色,因为只有在用户将数据输入电子表格的单元格时,才会提交物理存储器。由于电子表格中的大多数单元格是空的,因此大部分保留区域没有提交给它的物理存储器。

虚拟内存技术存在的一个问题是,必须确定物理存储器在何时提交。如果用户将数据输入一个单元格,然后只是编辑或修改该数据,那么就没有必要提交物理存储器,因为该单元格的C E L L D ATA结构的内存在数据初次输入时就已经提交了。

另外,系统总是按页面的分配粒度来提交物理存储器的。因此,当试图为单个 C E L L D ATA结构提交物理存储器时(像上面的第二步那样),系统实际上提交的是内存的一个完整的页面。这并不像它听起来那样十分浪费:为单个C E L L D ATA结构提交物理存储器的结果是,也要为附近的其他C E L L D ATA结构提交内存。如果这时用户将数据输入邻近的单元格(这是经常出现的情况),就不需要提交更多的物理存储器。

有4种方法可以用来确定是否要将物理存储器提交给区域的一个部分:

• 始终设法进行物理存储器的提交。每次调用 Vi r t u a l A l l o c函数的时候,不要查看物理存储器是否已经映射到地址空间区域的一个部分,而是让你的程序设法进行内存的提交。系统首先查看内存是否已经被提交,如果已经提交,那么就不要提交更多的物理存储器。

这种方法最容易操作,但是它的缺点是每次改变 C E L L D ATA结构时要多进行一次函数的

调用,这会使程序运行得比较慢。

• (使用Vi r t u a l Q u e r y函数)确定物理存储器是否已经提交给包含C E L L D ATA结构的地址空间。如果已经提交了,那么就不要进行任何别的操作。如果尚未提交,则可以调用Vi r t u a l A l l o c函数以便提交内存。这种方法实际上比第一种方法差,它既会增加代码的长度,又会降低程序运行的速度(因为增加了对Vi r t u a l A l l o c函数的调用)。

• 保留一个关于哪些页面已经提交和哪些页面尚未提交的记录。这样做可以使你的应用程序运行得更快,因为不必调用 Vi r t u a l A l l o c函数,你的代码能够比系统更快地确定内存是否已经被提交。它的缺点是,必须不断跟踪页面提交的信息,这可能非常简单,也可能非常困难,要根据你的情况而定。

• 使用结构化异常处理( S E H)方法,这是最好的方法。 S E H是一个操作系统特性,它使系统能够在发生某种情况时将此情况通知你的应用程序。实际上可以创建一个带有异常

处理程序的应用程序,然后,每当试图访问未提交的内存时,系统就将这个问题通知应用程序。然后你的应用程序便进行内存的提交,并告诉系统重新运行导致异常条件的指令。这时对内存的访问就能成功地进行了,程序将继续运行,仿佛从未发生过问题一样。这种方法是优点最多的方法,因为需要做的工作最少(也就是说要你编写的代码比较少),同时,你的程序可以全速运行。关于 S E H的全面介绍,请参见第2 3、2 4和2 5章。第2 5章中的电子表格示例应用程序说明了如何按照上面介绍的方法来使用虚拟内存。

15.5 回收虚拟内存和释放地址空间区域

若要回收映射到一个区域的物理存储器,或者释放这个地址空间区域,可调用 Vi r t u a l F r e e函数:

首先让我们观察一下调用Vi r t u a l F r e e函数来释放一个已保留区域的简单例子。当你的进程不再访问区域中的物理存储器时,就可以释放整个保留的区域和所有提交给该区域的物理存储器,方法是一次调用Vi r t u a l F r e e函数。

就这个函数的调用来说, p v A d d r e s s参数必须是该区域的基地址。此地址与该区域被保留时Vi r t u a l A l l o c函数返回的地址相同。系统知道在特定内存地址上的该区域的大小,因此可以为d w S i z e参数传递0。实际上,必须为d w S i z e参数传递0,否则对Vi r t u a l F r e e的调用就会失败。对于第三个参数f d w F r e e Ty p e,必须传递M E M _ R E L E A S E,以告诉系统将所有映射的物理存储器提交给该区域并释放该区域。当释放一个区域时,必须释放该区域保留的所有地址空间。例如不能保留一个128 KB的区域,然后决定只释放它的64 KB。必须释放所有的128 KB。

当想要从一个区域回收某些物理存储器,但是却不释放该区域时,也可以调用 Vi r t u a l F r e e函数,若要回收某些物理存储器,必须在Vi r t u a l F r e e函数的p v A d d r e s s参数中传递用于标识要回收的第一个页面的内存地址,还必须在 d w S i z e参数中设定要释放的字节数,并在 f d w F r e e Ty p e参数中传递M E M _ D E C O M M I T标志。

与提交物理存储器的情况一样,回收时也必须按照页面的分配粒度来进行。这就是说,设

定页面中间的一个内存地址就可以回收整个页面。当然,如果 pvAddress + dwSize的值位于一个页面的中间,那么包含该地址的整个页面将被回收。因此位于 pvAddress 至pvAddress +d w S i z e范围内的所有页面均被回收。

如果d w S i z e是0,p v S d d r e s s是已分配区域的基地址,那么 Vi r t u a l F r e e将回收全部范围内的已分配页面。当物理存储器的页面已经回收之后,已释放的物理存储器就可以供系统中的所有其他进程使用,如果试图访问未回收的内存,将会造成访问违规。

15.6 改变保护属性

虽然实践中很少这样做,但是可以改变已经提交的物理存储器的一个或多个页面的保护属性。例如,你编写了一个用于管理链接表的代码,将它的节点存放在一个保留区域中。可以设计一些函数,以便处理该链接表,这样,它们就可以在每个函数开始运行时将已提交内存的保护属性改为 PA G E _ R E A D W R I T E,然后在每个函数终止运行时将保护属性重新改为PA G E _ N O A C C E S S。

通过这样的设置,就能够使链接表数据不受隐藏在程序中的其他错误的影响。如果进程中的任何其他代码存在一个迷失指针,试图访问你的链接表数据,那么就会引发访问违规。当试图寻找应用程序中难以发现的错误时,利用保护属性是极其有用的。

若要改变内存页面的保护属性,可以调用Vi r t u a l P r o t e c t函数:

这里的p v A d d r e s s参数指向内存的基地址(它必须位于进程的用户方式分区中),d w S i z e参数用于指明你想要改变保护属性的字节数,而 f l N e w P r o t e c t参数则代表PA G E _ *保护属性标志中的任何一个标志,但PA G E _ W R I T E C O P Y和PA G E _ E X E C U T E _ W R I T E C O P Y这两个标志除外。

最后一个参数p f l O l d P r o t e c t是D W O R D的地址,Vi r t u a l P r o t e c t将用原先与p v A d d r e s s位置上的字节相关的保护属性填入该D W O R D。尽管许多应用程序并不需要该信息,但是必须为该参数传递一个有效地址,否则该函数的运行将会失败。

当然,保护属性是与内存的整个页面相关联的,而不是赋予内存的各个字节的。因此,如

果要使用下面的代码来调用 4 KB 页面的计算机上的 Vi r t u a l P r o t e c t函数,其结果是把PA G E _ N O A C C E S S保护属性赋予内存的两个页面:

Windows 98 Windows 98只支持PA G E _ R E A D O N LY和PA G E _ R E A D W R I T E两个保护属性。如果试图将页面的保护属性改为PA G E _ E X E C U T E或PA G E _ E X E C U T E _ R E A D,该页面可得到PA G E _ R E A D O N LY保护属性。同样,如果试图将页面的保护属性改为PA G E _ E X E C U T E _ R E A D W R I T E,那么该页面将得到PA G E _ R E A D W R I T E保护属性。

Vi r t u a l P r o t e c t函数不能用于改变跨越不同保留区域的页面的保护属性。如果拥有相邻的保留区域并想改变这些区域中的一些页面的保护属性,那么必须多次调用 Vi r t u a l P r o t e c t函数。

Windows核心编程 第十五章 在应用程序中使用虚拟内存的更多相关文章

  1. 《Windows核心编程系列》十三谈谈在应用程序中使用虚拟内存

    在应用程序中使用虚拟内存 Windows提供了以下三种机制对内存进行操控: 一:虚拟内存.最适合来管理大型对象数据或大型结构数组. 二:内存映射文件.最适合用来管理大型数据流,以及在同一机 器上运行的 ...

  2. Windows核心编程 第十二章 纤程

    第1 2章 纤 程 M i c r o s o f t公司给Wi n d o w s添加了一种纤程,以便能够非常容易地将现有的 U N I X服务器应用程序移植到Wi n d o w s中.U N I ...

  3. Windows核心编程 第十九章 DLL基础

    第1 9章 D L L基础 这章是介绍基本dll,我就记录一些简单应用,dll的坑点以及扩展后面两章会说,到时候在总结. 自从M i c r o s o f t公司推出第一个版本的Wi n d o w ...

  4. 《Windows核心编程》第五章——作业

    #include <windows.h> #include<iostream> #include <tchar.h> using namespace std; ty ...

  5. Windows核心编程 第十四章 虚拟内存

    第1 4章 虚 拟 内 存 <这一章没啥,是说的几个内存相关的函数 > 14.1 系统信息 许多操作系统的值是根据主机而定的,比如页面的大小,分配粒度的大小等.这些值决不应该用硬编码的形式 ...

  6. “全栈2019”Java第八十五章:实现接口中的嵌套接口

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  7. “全栈2019”Java第二十五章:流程控制语句中循环语句while

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  8. 【windows核心编程】 第六章 线程基础

    Windows核心编程 第六章 线程基础 欢迎转载 转载请注明出处:http://www.cnblogs.com/cuish/p/3145214.html 1. 线程的组成 ①    一个是线程的内核 ...

  9. Windows核心编程:第4章 进程

    Github https://github.com/gongluck/Windows-Core-Program.git //第4章 进程.cpp: 定义应用程序的入口点. // #include &q ...

随机推荐

  1. SpringMVC-04 数据处理及跳转

    SpringMVC-04 数据处理及跳转 结果跳转方式 1.ModelAndView 设置ModelAndView对象 , 根据view的名称 , 和视图解析器跳到指定的页面 . 页面 : {视图解析 ...

  2. Docker 三剑客 到 k8s 介绍

    一.Docker 三剑客 Docker-Compose Docker-Compose 是用来管理你的容器的,有点像一个容器的管家,想象一下当你的Docker中有成百上千的容器需要启动,如果一个一个的启 ...

  3. H5 简单实现打砖块游戏

    实现效果如图所示: 1.布局 在html中,声明  div1 作为作为带有边框的父物体,一切行为都要在 div1 中进行.创建小球ball.左右可滑动的板子bat,以及存放要销毁的砖块的父物体 bri ...

  4. web实现时钟效果

    纯原生开发时钟效果,话不多说直接上代码. HTML标签部分 <div class="cricles">         <div class="poin ...

  5. 自定义校验注解ConstraintValidator

    一 前言 系统执行业务逻辑之前,会对输入数据进行校验,检测数据是否有效合法的.所以我们可能会写大量的if else等判断逻辑,特别是在不同方法出现相同的数据时,校验的逻辑代码会反复出现,导致代码冗余, ...

  6. python 常用库收集

    读者您好.今天我将介绍20个属于我常用工具的Python库,我相信你看完之后也会觉得离不开它们.他们是: Requests.Kenneth Reitz写的最富盛名的http库.每个Python程序员都 ...

  7. 利用eigen库简单实现矩阵功能

    eigen是目前运行速度较快的C++矩阵运算库,而且其轻便小巧安装方便的特点简直是吸引人啊!特做此笔记,记录一下这个安装简单.体积轻巧.功能强大的C++库. 1. Download and Insta ...

  8. .Net5下WebRequest、WebClient、HttpClient是否还存在使用争议?

    WebRequest.WebClient.HttpClient 是C#中常用的三个Http请求的类,时不时也会有人发表对这三个类使用场景的总结,本人是HttpClient 一把梭,也没太关注它们的内部 ...

  9. PReact10.5.13源码理解之hook

    hook源码其实不多,但是实现的比较精巧:在diff/index.js中会有一些optison.diff这种钩子函数,hook中就用到了这些钩子函数.   在比如options._diff中将curr ...

  10. 使用 docker 进行 ElasticSearch + Kibana 集群搭建

    在Docker容器中运行Elasticsearch Kibana和Cerebro 机器信息 10.160.13.139 10.160.9.162 10.160.11.171 1. 安装docker和d ...