架构整洁之道-组件构建原则

news2024/11/20 12:18:15

5 组件构建原则

  大型软件系统的架构过程与建筑物修建很类似,都是由一个个小组件组成的。所以,如果说SOLID原则是用于指导我们如何将砖块砌成墙与房间的,那么组件构建原则就是用来指导我们如何将这些房间组合成房子的。

5.1 组件

  组件是软件的部署单元,是整个软件系统在部署过程中可以独立完成部署的最小实体。例如,对于Java来说,它的最小组件是Jar文件。在编译运行语言中,组件是一组二进制文件的集合,而在解释运行语言中,组件则是一组源代码文件的集合。无论采用什么编程语言来开发软件,组件都是该软件在部署过程中的最小单元。

  在早期的软件开发中,程序员可以完全掌握自己编写的程序所处的内存地址和存放格式,在那里,程序中的第一条语句被称为起源(origin)语句,它的作用是声明该程序应该被加载到的内存位置。由于那时候没有重定位(relocate)技术,因此设计程序加载的内存地址是早期程序员在编程初期就要做的一个重要决策。而为了调用库函数,程序员们必须将所有要调用的库函数的源代码包含到自己的程序代码中, 然后再进行整体编译。在那个年代,存储设备十分缓慢,而内存则非常昂贵,也非常有限,编译器在编译程序的过程中需要数次遍历整个源代码,由于内存非常有限,驻留所有的源代码是不现实的,编译器只能多次从缓慢的存储设备中读取源代码,而这样做是十分耗时的——库函数越多,编译就越慢。大型程序的编译过程经常需要几个小时。

  为了缩短编译时间,程序员们改将库函数的源代码单独编译,库函数的源代码单独编译后被加载到一个指定的内存位置,然后编译器会针对该库文件创建一个符号表(symbol table),并将其和应用程序代码编译在一起,当程序运行时,它会先加载二进制形式的库文件,再加载编译过的应用程序,其内存布局如下所示:

在这里插入图片描述
  当然,只要应用程序的代码能够完全存放在地址0000~1777(八进制)内,这种组织方式完全没有问题,但是,当应用程序的代码超过这个范围时,程序员则不得不将应用程序切分成两个不同的地址段,以跳过库函数存放的内存范围:
在这里插入图片描述
  很显然,这种方案也是不可持续的,因为随着函数库中函数的增加,它的大小也随之增加,我们同样也需要为函数库划分新的区域,这样一来,程序和函数库的碎片化程度会随着计算机内存的增加而不断增加。

  程序员们提出的解决方案是——生成可重定位的二进制文件,其原理是:程序员修改编译器输出文件的二进制格式,使其可以由一个智能加载器加载到任意内存位置。

  重定位技术要求我们在加载器启动时为这些文件指定要加载到的内存地址,而且可重定位的代码中还包含了一些记号,加载器将其加载到指定位置时会修改这些记号对应的地址值。一般来说,这个过程就是将二进制文件中包含的内存地址都按照其加载到的内存基础位置进行递增。

  这样一来,程序员们就可以用加载器来调整函数库及应用程序的位置了,事实上,这种加载器还可以接受多个二进制文件的输入,并顺序在内存中加载它们,再逐个进行重定位。这样,程序员们就可以只加载他们实际会用到的函数了。

  除此之外,程序员们还对编译器做了另外一个修改,就是在可重定位二进制文件中将函数名输出为元数据并存储起来,这样一来,如果一段程序调用了某个库函数,编译器就会将这个函数的名字输出为外部引用(external reference),而将库函数的定义输出为外部定义(external definition)。加载器在加载完程序后,会将外部引用和外部定义链接(link)起来。这就是链接加载器的由来。

  链接加载器让程序员们可以将程序切分成多个可被分别编译、加载的程序段。在程序规模较小、外部链接也较少的情况下,这个方案一直很好用。然而,在20世纪60年代末期到70年代初期,程序的规模突然有了大幅度的增长,导致链接加载器的处理过程变得很慢。

  于是,程序员们将加载过程和链接过程也进行了分离,将耗时较长的部分——链接部分——放到了一个单独的程序中去进行,这个程序就是所谓的链接器(linker)。链接器的输出是一个已经完成了外部链接的、可以重定位的二进制文件,这个文件可以由一个支持重定位的加载器迅速加载到内存中,这使得程序员可以用缓慢的链接器生产出可以很快进行多次加载的可执行文件。

  而随着时间的推移,到了20世纪80年代,程序的规模进一步扩大,链接器的生产效率也变得很慢,直到20世纪90年代,随着磁盘物理尺寸不断缩小、速度不断提高、内存造价的不断降低,以及计算机时钟频率的不断提升,链接器的生产速度也不断提升,并远远超过了程序规模的增长速度。

  与此同时,编译领域中还诞生了Active-X、共享库、jar文件等组件形式,由于计算与存储速度的大幅提升,我们又可以在加载过程中进行实时链接了,链接几个jar文件或是共享库文件通常只需要几秒种时间,由此,插件化架构也随之诞生了。

5.2 组件聚合

  组件聚合是一个非常重要的设计决策,通常我们会遵循三个与构建组件相关的基本原则,即复用/发布等同原则(REP)、共同闭包原则(CCP)和共同复用原则(CRP)。

5.2.1 复用/发布等同原则(Reuse/Release Equivalence Principle)

  核心定义:软件复用的最小粒度应等同于其发布的最小粒度。

  该原则强调了在软件设计中,一个组件或模块的修改只应该影响到那些需要同时被复用和发布的部分,这意味着当组件发生变化时,只有那些相关联且同样需要更新的组件才应当一起重新发布,这一原则有助于降低组件间的耦合度,并确保系统的稳定性和可维护性。

5.2.2 共同闭包原则(Common Closure Principle)

  核心定义:我们应该将那些会同时修改,并且为相同目的而修改的类放到同一个组件中,而将不会同时修改,并且不会为了相同目的而修改的那些类放到不同组件中。

  这其实是SRP(Single Responsibility Principle,单一职责原则)原则在组件层面的再度阐述。CCP的主要作用是提示我们将所有可能会被一起修改的类集中在一处,也就是说,如果两个类紧密相关,不管是源代码层面还是抽象理论层面,永远都会一起被修改,那么它们就应该归属为同一个组件。

  CCP和SRP的共同点是:将由于相同原因而修改,并且需要同时修改的东西放在一起。将由于不同原因而修改,并且不同时修改的东西分开。

5.2.3 共同复用原则(Common Reuse Principle)

  核心定义:不要强迫一个组件的用户依赖他们不需要的东西。

  CRP建议我们将经常共同复用的类和模块放在同一个组件中,更重要的是,不是紧密相连的在不应该被放在同一个组件中。

  当我们决定要依赖某个组件时,最好是实际需要依赖该组件中的每个类,而不是只依赖其中的某些类,但不依赖其他类的情况。

  CRP原则实际上是ISP(Interface Segregation Principle,接口隔离原则)原则的一个普适版,ISP原则建议我们不要依赖带有不需要的函数的类,而CRP原则则是建议我们不要依赖带有不需要的类的组件。它们的共同点是:不要依赖不需要用到的东西。

5.2.4 组件聚合张力图

  组件构建的这三个原则实际上是存在彼此竞争关系的,REP和CCP是黏合性原则,它们会上组件变更更大,而CRP原则则是排除性原则,它会尽量让组件变小。软件架构师的任务就是要在这三个原则中进行取舍。

  下图是组件聚合三大原则的张力图,图的边线所描述的是忽视对应原则的后果:
在这里插入图片描述
  只关注REP和CRP的软件架构师会发现,即使是简单的变更也会同时影响到许多组件,相反,如果架构师过于关注CCP和REP,则会导致很多不必要的发布。

  通常情况下,在项目早期,CCP原则会比REP原则更重要,因为在这一阶段研发速度比复用性更重要。

  一般来说,一个软件项目的重心会从该三角区域的右侧开始,先期主要牺牲的是复用性,然后随着项目逐渐成熟,其他项目会逐渐开始对其产生依赖,项目重心就会逐渐向该三角区域的左侧滑动。

  换句话说,一个项目在组件结构设计上的重心是根据该项目的开发时间和成熟度不断变动的,我们对组件结构的安排主要与项目开发的进度和它被使用的方式有关,与项目本身功能的关系其实很小。

5.3 组件耦合

  接下来要讨论的三条原则主要关注的是组件之间的关系,在这些原则中,我们同样会面临着研发能力和逻辑设计之间的冲突。这三条原则是无依赖环原则、稳定依赖原则和稳定抽象原则。

5.3.1 无依赖环原则(Acyclic Dependencies Principle)

  组件依赖关系图中不应该出现环

  如下是一个典型的应用程序的组件结构:
在这里插入图片描述
  在这个组件结构图中,不管我们从该图中的哪个节点开始,都不能沿着这些代表了依赖关系的边最终走回到起点。也就是说,这种结构中不存在环,这种结构被称为有向无环图(Directed Acyclic Graph,简称为DAG),这是符合无依赖环原则的。

  再看下面的组件结构图:
在这里插入图片描述
  可以明显看到,Interactors组件、Authorizer组件和Entities组件间形成了一个环,这就是循环依赖。

  在这种循环依赖的场景下,如果我们要修改Interactors组件,由于Authorizer组件依赖它,因此要考虑Interactors组件对Authorizer组件的影响而可能修改Authorizer组件,而由于Entities组件依赖Authorizer组件,因此要考虑Authorizer组件对Eneities组件的影响而可能修改Entities组件,由于Interactors组件依赖Entities组件,那么Entities组件的修改又可能影响Interactors组件导致其修改,出现了无限循环。

  而要打破这些组件中的循环依赖,将依赖图转化为DAG,则可以通过两种机制做到:

  (1) 应用依赖反转原则(DIP):在上图中,假设是Entities组件中的User类依赖了Authorizer中的Permissions类,那么我们可以在Entities中创建一个Permission接口,并在Authorizer组件中实现它,那么依赖关系就倒转过来了,如图5.3.1-1;

  (2) 创建一个新组件:在上图中,我们创建一个新组件Permissions,然后让Entities组件和Authorizer组件都依赖它,就可以打破循环依赖,如图5.3.1-2;

在这里插入图片描述

5.3.1-1 依赖反转

在这里插入图片描述

5.3.1-2 创建新组件

  当然,采用第二种解决方案也意味着在需求变更时,项目的组件结构也要随之变更,因此,我们必须要持续地监控项目中的循环依赖关系,当循环依赖出现时,必须以某种方式消除它们。为此,我们有时候不可避免地需要创建新的组件,而使整个组件结构变得更大。

  根据上述讨论,我们可以得出一个无法逃避的结论:组件结构图是不可能自上而下设计出来的。它必须随着软件系统的变化而变化和扩张,而不可能在系统构建的最初就被完美设计出来。

  这与我们通常了解的信息不一致,同样不一致的还有另一个概念:项目粗粒度的组件分组规则所产生的就是组件的依赖结构,也应该在某种程度上与项目的系统功能分解的结果相互对应,但是很明显,组件依赖关系图其实不具备这样的属性。

  事实上,组件依赖结构图并不是用来描述应用程序功能的,它更像是应用程序在构建性与维护性方面的一张地图。这就是组件的依赖结构图不能在项目的开始阶段被设计出来的原因——这时候该项目还没有任何被构建和维护的需要,自然也不需要一张地图来指引。然而,随着早期被设计并实现出来的模块越来越多,项目中就逐渐出现了要对组件依赖关系进行管理的需求,以此来预防“一觉醒来综合征”的爆发。除此之外,我们还希望将项目变更所影响的范围被限制得越小越好,因此需要应用单一职责原则(SRP)和共同闭包原则(CCP)来将经常同时被变更的类聚合在一起。

  组件结构图中的一个重要目标是指导如何隔离频繁的变更,因此软件架构师才有必要设计并且铸造出一套组件依赖关系图来,以便将稳定的高价值组件与常变的组件隔离开,从而起到保护作用。另外,随着应用程序的增长,创建可重用组件的需要也会逐渐重要起来,这时,CRP又会开始影响组件的组成。最后,当循环依赖出现时,随着无循环依赖原则(ADP)的应用,组件依赖关系会产生相应的抖动和扩张。组件依赖关系是必须要随着项目的逻辑设计一起扩张和演进的。

5.3.2 稳定依赖原则(Stable Dependencies Principle)

  依赖关系必须要指向更稳定的方向

  设计这件事不可能是完全静止的,如果我们要让一个设计是可维护的,那么其中某些部分就必须是可变的。通过遵守共同闭包原则(CCP),我们可以创造出对某些变更敏感,对其他变更不敏感的组件,这其中的一些组件在设计上就已经是考虑了易变性,预期它们会经常发生变更的。

  任何一个我们预期会经常变更的组件都不应该被一个难以修改的组件所依赖,否则这个多变的组件也将会变得非常难以被修改。

  下面是一个稳定的组件的例子,虽然非常理想化:
在这里插入图片描述
  X是一个稳定的组件,因为有三个组件依赖着X,所以X有三个不应该被修改的原因,即X需要对三个组件负责,另一方面,X不依赖于任何组件,所以不会有任何原因导致它需要被变更,我们称它为“独立组件”。

  而下图中的Y则是一个不稳定组件,它不被任何组件依赖,因此Y不需要对任何组件负责,但因为Y同时依赖于三个组件,所以它的变更就可能由三个不同的源产生:

在这里插入图片描述

  组件的稳定性是可以通过计算所有入和出的依赖关系来量化的,这里涉及到几个概念:

  (1) Fan-in:入向依赖,表示组件外部类依赖于组件内部类的数量;

  (2) Fan-out:出向依赖,表示组件内部类依赖于组件外部类的数量;

  (3) I:不稳定性, I = F a n − o u t F a n − i n + F a n − o u t I=\frac{Fan-out}{Fan-in + Fan-out} I=Fanin+FanoutFanout,该指标的范围是[0,1],I=0意味着组件是最稳定的,I=1意味着组件是最不稳定的;

  以下图为例:
在这里插入图片描述
  对于组件Cc来说,有三个组件外部类依赖于组件内部类,即Ca的q类依赖于Cc的t类、Ca的r类依赖于Cc的u类、Cb的s类依赖于Cc的u类,有一个组件内部类依赖于组件外部类,即Cc的u类依赖于Cd的v类,那么,Fan-in=3,Fan-out=1,于是 I = F a n − o u t F a n − i n + F a n − o u t = 1 3 + 1 = 0.25 I=\frac{Fan-out}{Fan-in + Fan-out}=\frac{1}{3 + 1}=0.25 I=Fanin+FanoutFanout=3+11=0.25

  稳定依赖原则(SDP)的要求是让每个组件的I指标都必须大于其所依赖的组件的I指标。也就是说,组件结构依赖图中各组件的I指标必须要按其依赖关系方向递减。

  由此可见,遵守稳定依赖原则(SDP)的组件结构是:可变更的组件位于顶层,而稳定组件位于底层。

5.3.3 稳定抽象原则(Stable Abstractions Principle)

  一个组件的抽象化程度应该与其稳定性保持一致。

  稳定抽象原则(SAP)为组件的稳定性与它的抽象化程度建立了一种关联:

  (1) 该原则要求稳定的组件同时应该是抽象的,这样它的稳定性就不会影响到扩展性;

  (2) 该原则要求一个不稳定的组件应该包含具体的实现代码,这样它的不稳定性就可以通过具体的代码被轻易修改;

  因此,如果一个组件想要成为稳定组件,那么它就应该由接口和抽象类组成,以便将来做扩展。如此,这些既稳定又便于扩展的组件可以被组合成既灵活又不会受到过度限制的架构。

  将SAP(稳定抽象原则)和SDP(稳定依赖原则)这两个原则结合起来,就等于组件层次上的DIP(依赖反转原则)。因为SDP(稳定依赖原则)要求的是让依赖关系指向更稳定的方向,而SAP(稳定抽象原则)则告诉我们稳定性本身就隐含了对抽象化的要求,即 依赖关系应该指向更抽象的方向

  抽象化程序同样可以量化:

  (1) Nc表示组件中类的数量(包括抽象类和接口);

  (2) Na表示组件中抽象类和接口的数量;

  (3) 抽象程度 A = N a N c A=\frac{Na}{Nc} A=NcNa

  A指标的取值范围从0到1,值为0代表组件不没有任何抽象类,值为1则意味着组件中只有抽象类。

  现在我们可以来定义组件的稳定性I与其抽象化程度A之间的关系了,如下图所示:
在这里插入图片描述

  我们无法要求所有组件都是最稳定的且包含了无限抽象类的(I=0,A=1)或者最不稳定的且无任何抽象的(I=1,A=0),因为组件通常都有各自的稳定程度和抽象化程度,因此我们会认为A/I图上存在着一个合理组件的区间,也存在不合理组件的区间,以下是两个不合理组件的区间,而这个区间外的区间,则可认为是合理组件区间:

  (1) 痛苦区:如上图所示,假设某个组件处于(0,0)的位置,那么它应该是一个非常稳定但也非常具体的组件,这样的组件在设计上是最不佳的,因为它很难被修改,这意味着该组件不能被扩展,当然,如果这个组件是不可变组件的话,它在这一区域是无害的,例如一些工具型类库,更具体的如String类;而如果一个多变的组件落在这个区域则会非常麻烦,因为它的任何修改都可能造成依赖它的组件的变更;

  (2) 无用区:如上图所示,当某个组件处于(1,1)的位置时,它是无限抽象的,但没有被任何组件所依赖,因此这个区域通常包含了大量的无用代码;

  很明显,最多变的组件应该离上述两个区域越远越好。

  距离两个区域最远的点连成的一条线,即从(0,1)连接到(1,0)的线,被称为 主序列线(main sequence) ,位于主序列线上的组件不会为了追求稳定性而被设计得“太过抽象”,也不会为了避免抽象化而被设计得“太过不稳定”,这样的组件既不会特别难以被修改,又可以实现足够多的功能,对于这些组件来说,通常会有足够多的组件依赖于它们,这使得它们会具有一定程度的抽象,同时它们也依赖了足够多的其他组件,这又使得它一定会包含很多具体实现。

  在整条主序列线上,组件所处的最优位置是线的两端,我们应该争取将大部分组件尽可能地推向这两个位置,但这很难,因此实践中,我们通常会将组件设计成在主序列线上,或者贴近主序列线。

  衡量一个组件距离最佳位置的距离也是可以量化衡量的:D指标,距离 D = ∣ A + I − 1 ∣ D=|A+I-1| D=A+I1∣,该指标的取值范围是[0,1],值为0意味着组件是直接位于主序列线上的,值为1则意味着组件在距离主序列最远的位置。

  通过计算每个组件的D指标,就可以量化一个系统设计与主序列的契合程度了,另外,我们也可以用D指标大于0多少来指导组件的重构与重新设计。

  对于一个良好的系统设计来说,D指标的平均值和方差都应该接近于0,其中,方差还可以被当作组件的“达标红线”来使用,我们可以通过它找出系统中那些不合常规的组件。

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

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

相关文章

IntelliJ创建一个springboot工程

安装jdk mac教程 windows教程 安装maven mac教程 windows教程 建议: 在本地磁盘新建一个文件夹叫maven,然后把下载的maven安装到这里。在后续的IntelliJ操作中,配置maven的settings.xml和repository地址为这个目录下的地址。 创建sprin…

stack和queue及优先级队列和适配器(包括deque)的介绍

stack stack的介绍 stack是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作。stack是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,并提供一组…

【INTEL(ALTERA)】为什么 F-tile Serial Lite IV FPGA IP 设计示例会失败

说明 由于Intel Agilex 7 FPGA I 系列收发器-SoC 开发套件的时钟控制器 GUI 存在问题,当您需要配置芯片 Si5332 的 OUT1 时钟频率时,您可能会发现 F-tile Serial Lite IV 英特尔 FPGA IP设计示例失败。这是因为此 Si5332 GUI 存在问题;无法准确配置 OUT…

【算法】约数之和(数论)

题目 给定 n 个正整数 ai,请你输出这些数的乘积的约数之和,答案对 1097 取模。 输入格式 第一行包含整数 n。 接下来 n 行,每行包含一个整数 ai。 输出格式 输出一个整数,表示所给正整数的乘积的约数之和,答案需…

CentOS 8最小安装和网络配置

文章目录 简介下载地址VMware 17创建虚拟机最小化安装拥有的外部命令yum源有问题网络配置开启SSH Server服务关闭防火墙(目前这个地方还是有问题-加上端口依然不能访问)设置host配置JDK环境完整参考 简介 CentOS 8的IOS如果下载DVD版本至少有10G 这里我们直接选择最小安装&…

当Meta转向AI并宣布为投资者分红时,其收入激增

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…

算法day9

算法day9 栈与队列基础232用栈实现队列225用队列实现栈 栈与队列理论基础 言简意赅:栈的原理就是后进先出。队列就是先进先出。 相关操作: 栈:入栈,出栈,判栈空,取栈顶元素。 队列:出队&#…

Linux第41步_移植ST公司uboot的第2步_修改网络驱动_USB OTG设备树_LCD驱动_以及编译和烧写测试

移植ST公司uboot的第1步,创建配置文件、设备树、修改电源管理和sdmmc节点后,还需要进一部修改,如:网络驱动、USB OTG设备树、LCD驱动,以及编译和烧写测试。 一、在虚拟机中,使用VSCode打开my_uboot工作区 …

1E,Jarvis March

四个问题: 一,Jarvis March算法借鉴了什么算法? 二,如何确定初始点 三,如何获取凸包的边? 四,Jarvis March算法的好处在哪里? 首先看第一个问题, 一,Jarvis …

Springboot 自定义参数配置化,密钥,密码,文件保存路径

application.properties 和 application.yml 都是一样的配置方法,只是格式不一样 定义配置文件 server.port8080 image.save.pathE:\ #自定义文件保存路径读取配置文件 Value("${image.save.path}")private String filePath;//E:\优化配置文件 如果我参…

算法——A/算法通识

目录 一、复杂度分析 A/时间复杂度 B/空间复杂度 C/分析技巧 二、枚举分析 A/枚举算法介绍 B/解空间的类型 C/循环枚举解空间 三、模拟算法 四、递归 A/递归介绍 递归的两个关键要素: B/递归如何实现 C/递归和循环的比较 一、复杂度分析 A/时间复杂度…

Unknown custom element:<xxx>-did you register the component correctly解决方案

如图所示控制台发现了爆红(大哭): 报错解释: 当我们看到报错时,我们需要看到一些关键词,比如显眼的“component”和“name”这两个单词, 因此我们就从此处切入,大概与组件有关系。…

Transition内置组件设置无效的原因

1. 包裹的组件是否有显示隐藏状态的切换&#xff0c;或者是绑定的key值是否发生改变 由 v-if 所触发的切换由 v-show 所触发的切换由特殊元素<component> 切换的动态组件改变特殊的key属性 2. 要放在发生变化的组件外层&#xff0c;如果中间有其他元素或组件会不生效 …

C++ copy()函数详细介绍

copy() 是一个标准库函数&#xff0c;位于 头文件中。它用于将一个容器中的元素复制到另一个容器中&#xff0c;或者将一个范围内的元素复制到另一个范围中。 函数参数介绍 copy( first, last, d_first );first 和 last&#xff1a;表示输入范围的迭代器。 first 指向要复制的…

Keil软件某些汉字输出乱码,0xFD问题,51单片机

1. 问题 keil软件输入某些汉字的时候会输出乱码&#xff0c;例如&#xff1a;升、 数 2. 原因 keil软件会忽略0xFD。 升的GB2312编码为 0xc9fd&#xff0c;keil解析为0xc9数的GB2312编码为 0xcafd&#xff0c;keil解析为0xca 关于Keil软件中0xFD问题的说明 3. 解决方案1 …

深入解剖指针篇(3)

个人主页&#xff08;找往期文章&#xff09; &#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 目录 二级指针 指针数组 指针数组模拟二维数组 字符指针变量 数组指针 数组指针初始化 二维数组传参的本质 函数指针 函数指针的使用 typedef关键字 函数指针数组 二级指针…

Linux内存管理:(十一)页面分配之慢速路径

文章说明&#xff1a; Linux内核版本&#xff1a;5.0 架构&#xff1a;ARM64 参考资料及图片来源&#xff1a;《奔跑吧Linux内核》 Linux 5.0内核源码注释仓库地址&#xff1a; zhangzihengya/LinuxSourceCode_v5.0_study (github.com) 1. 水位管理和分配优先级 页面分配…

0202-2-存储器管理

第四章:存储器管理 存储器的层次结构 多层结构的存储系统 存储器的多层结构 CPU寄存器主存辅存 可执行存储器 寄存器和主存的总称访问速度快&#xff0c;进程可以在很少的时钟周期内用一条load或store指令完成存取。 主存储器与寄存器 高速缓存和磁盘缓存 程序的装入和链…

《金融时报》:直面“雪球”风波 究竟影响几何?

“他们给我推荐的时候说是只要市场不大跌&#xff0c;我就能按照年化20%获得收益&#xff0c;当时我看大盘走势&#xff0c;也认为跌那么多的概率不大。”李先生告诉《金融时报》记者&#xff0c;他当初被银行客户经理推荐“雪球”产品并头脑一热买了的时候&#xff0c;以为按照…

springboot集成 mysql快速入门demo

一、mysql环境搭建 采用docker-compose搭建&#xff0c;配置如下&#xff1a; docker-compose.yml version: 3 services:mysql:image: registry.cn-hangzhou.aliyuncs.com/zhengqing/mysql:5.7 # 原镜像mysql:5.7container_name: mysql_3306 …