北邮 操作系统(六)

文章详细阐述了操作系统中的内存管理,包括重定位确保指令正确执行,分段和分页机制以适应不同需求,以及Cache和TLB在提高内存访问效率中的作用。分段侧重逻辑结构,分页关注物理内存分配,而虚拟内存结合两者优点,实现了大于物理内存的地址空间,并通过页面换入换出来管理内存资源。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第六章 内存管理

“进程管理和内存管理两部分构成操作系统的真正内核”

内存管理相对复杂,涉及到硬件和软件,从微机原理到应用程序到内核。

比如,硬件上的cache,CPU如何去寻址内存,页表, DMA,IOMMU; 软件上,要知道底层怎么分配内存,怎么管理内存,应用程序怎么申请内存。

本章的主线就是程序正确载入内存和指令正确读写内存:

  • 程序载入内存旨在让程序执行;
  • 指令正确读写内存旨在让某些读写指令能够正确执行;

1.重定位

重定位保证了指令正确读写内存,以致读写指令能够正确执行;重定位是由操作系统安排执行的;

我们知道,计算机工作的基本过程是:CPU不断重复“取指-执行”的过程,而取指和执行指令这两个过程都涉及内存;

源文件编译形成可执行文件的时候(准确来说应该是链接后形成可执行文件),使用的地址都是从0开始的相对地址,而当程序真正被载入物理内存的时候可能使用任意一段空闲物理地址,此时如果想要保证程序的正确执行就需要做重定位(也就是将程序中的逻辑地址对应到实际使用的物理内存地址)

重定位主要分为如下几种:

(1)编译时重定位:这样产生的可执行文件的地址就是实际地址而不是相对地址,显然这种方法不能用于任务不断“启动-退出”的通用计算机系统中;

(2)载入时重定位:在程序载入的过程中根据载入的物理内存地址区域来修改程序中的逻辑地址;

但是载入时重定位不是最优的方法,因为假如进程1因为阻塞所以被换出到磁盘上,过一段时间被重新载入的时候很可能已经不是第一次载入时的内存地址了

因此诞生了另一种重定位的方法

(3)运行时重定位:在内存中存放的指令一直都是“call 40”,只有在指令执行的时候才将指令的逻辑地址转换为物理地址;

具体实现:程序在载入内存的时候,记录下这段内存区域的基址(将其放在某个约定的寄存器中),执行每一条指令都会将指令中的逻辑地址加上基址以后才会放在地址总线上,计算机中有这样专门的硬件MMU,每条指令运行时进行的从逻辑地址到物理地址换算的过程被称为地址转换

现在回到多进程的视图下,MMU进行重定位的CPU寄存器只能有一个,所以每个进程的重定位基址都需要放在PCB中供需要时使用;

进程切换的两个部分:指令执行序列的切换(实际就是PC指针的切换)和地址空间的切换(实际上就是重定位基址寄存器的切换)到此阐述完毕;

2.分段

分段给我的感觉不像是操作系统做的事,而是CPU自己完成了分段,操作系统不得不接受这个事实,也就是与硬件上的内存分段机制对应(毕竟操作系统是运行在硬件之上的,这就类似于在操作系统之下写的汇编程序也得遵守CPU的寻址方式);

(上面那个理解是有错误的),实际上在硬件层面根本就没有分段这个概念,这个概念是将程序载入内存区域的一种方式,分段是程序层面的概念;(虽然这门课叫做操作系统,但这并不意味着书中所有的概念都是操作系统的子集)

上面我们已经清楚了指令如何正确读写内存,现在我们来介绍程序载入内存的方式;

关于段的概念,我们在汇编语言中有简单的介绍汇编语言 - Tintoki_blog (gintoki-jpg.github.io),程序由若干段组成(这里的程序一般指的都是汇编程序,在编程中可以根据需要将若干连续内存单元看作一个段):

  • 代码段:程序指令形成的段(代码段只读);
  • 数据段:存放程序使用的数据(数据段可读可写);
  • 栈段:实现函数调用,栈通常只能向下增长;

实际上段这个概念源自于CPU管理内存(分段是CPU管理内存的一种方式),这也是我们接下来会介绍的;

既然程序已经被分段,那么在载入内存的时候也不应该作为一个整体载入 —— 程序中多个段分别载入内存,前面我们提高如果程序整体载入内存需要记录基址,那么这里需要记录每个段的基址,多个基址形成段表

使用了分段机制以后的,程序中的逻辑地址会变成“段号:段内偏移”这种基本格式(注意是逻辑地址不是物理地址!!物理地址还是一个整型数值);

分段机制下的地址转换核心就是查找段表,我们给出下面一个地址转换过程图

3.分页

在介绍分页之前先介绍一下内存分区,前面介绍了程序以分段的方式载入内存,但能够载入的前提是内存中拥有这样一段空闲内存,因此我们需要分割内存;

从空闲内存区域中分割出一个分区来满足段请求,关键是记录和维护“内存空闲区域”信息,具体的内存分区的算法我们这里就不介绍,感兴趣自行Google;

因为不合理的内存分区,将会导致尽管总空闲的内存很大,但是无法满足某个大小的内存请求,这就是内存碎片导致的,解决内存碎片的方法主要有:

  • 内存紧缩:通过移动将零碎的空闲区域合并成一整块空闲区域;
  • 内存离散化:将内存主动分为固定大小的片,内存请求到达时,根据请求尺寸计算出总共需要的小片个数,然后在内存中(任意位置)找出同样数量的小片分配给内存请求;

这就是分页机制的基本思想,上述片就是内存页;


Q:刚才讲的分段机制难道不算内存分区?

A:严格意义上来说分段机制甚至都算不上内存分区,它只是方便程序员编程使用而提出的概念(这么说吧,分页是系统管理的需要,对用户透明,分段是为了满足用户编程需要);

分段和分页根本就不是一个层面上的概念,一个是内存硬件的层面,一个是程序员编程的层面,不要将两者混为一谈;

Q:分页机制和分段机制有什么不同?为什么虚拟内存中又把页式虚拟存储器和段式虚拟存储器以及段页式虚拟存储器相提并论?

A:分段和分页相似之处在于两者都不要求作业连续存放,但两者在概念上是完全不同的:

分页分段
页是信息的物理单位,分页是为了实现非连续分配以便解决内存碎片的问题;段是信息的逻辑单位,含有一组意义相对完整的信息,分段是为了更好的实现共享;
分页是系统管理的需要;分页是用户的需要;
页的大小固定,由系统确定,将地址划分为页号和页内地址由机器硬件实现;段的长度不固定,取决于用户编写的程序,通常由编译程序在对源程序编译的时候根据信息的性质划分;
分页的作业地址空间是一维的;分段的地址空间是二维的;

分页能有效提高内存的利用率,分段能反映程序的逻辑结构,便于段的共享和保护,因此诞生了一种新的思想 —— 将分页和分段存储方式结合,形成段页式存储管理方式;

至于第二个问题,虚拟存储器为什么将分页和分段放在一个高度谈,这就是我们之后才讨论的问题了;


分页机制首先将物理内存分割成大小相等的页框,然后再将请求放入物理内存的数据(比如代码段)也分割成同样大小的页,最后将所有页都映射到页框上,完成物理内存页框的使用;

上述过程是内存使用的第一步——程序载入,接下来解决重定位的问题;

分页机制下的地址转换过程如下所示

因为页是信息的最小的物理单位,所以为了避免内存空间的浪费,通常需要将页的尺寸设置的合理,而页的尺寸越小,页表就越大,这将引出多级页表和快表;

页表由页表项组成,每个页表项记录逻辑页放在了哪个物理页框;

根据程序执行的局部性原理(一段时间内执行的指令地址总是在一个局部变化),这就意味着即使是很大的程序,当前访问的逻辑页也不多;

根据上述理论,提出这样的想法:对于没有被存放到物理页框中的逻辑页(因为物理页框是有数量限制的),将其页表项从页表中删除(这样就减小了页表占用内存的大小);但是因为删除了无效页表项,导致页表中的逻辑页号不再连续,这将增大查找的时间代价,并且这种代价我们是不能接受的(具体论证见书P167)

总结一下现在的问题:设计页表,使其既不存储无效页表项,又能保证页表中的逻辑页号连续;

3.1 多级页表

多级页表的思想与书本的主目录非常相似

上图中的节目录对应页表(每一小节对应一个页表项),多级页表是在页表的基础上建立一个高层结构,通常称为页目录(多级页表的基本思想就是构造一个页表的页表,每一章对应一个页表);

两级页表结构及其地址转换过程如下所示

Q:多级页表究竟怎么减小进程对内存的占用?

A:单级页表存在的最大问题就是操作系统为每个进程分配了固定大小的空间作为页表,并且这个页表必须包括所有的页表项(因为OS不知道进程到底会访问哪些页表项),这就意味着操作系统不能实现按需分配页表空间;而多级页表可以在使用时根据内存的占用为进程分配页表空间,实现动态按需分配而不是预先全部分配;


3.2 Cache

尽管引入多级页表的页目录后,降低了存储页表造成的空间代价,但取而代之的是加长了地址的转换时间(简单来看地址的转换效率降低了50%);这里我们自然而然的想到将一些常用的逻辑页映射关系记录下来,具体是记录在Cache寄存器中,这样能够一定程度上加快地址转换;

计组中已经介绍过Cache,现在我们再次复习一遍;

3.2.1 基本概念

Cache出现的前提是因为CPU和内存之间的速度差异,在Cache之前有诸如双端口RAM、多模块存储器这些特殊结构的存储器,但是采用存储体结构上的优化速度仍然匹配不上CPU,所以考虑直接优化存储元,引入了局部性原理的Cache层次化设计;

CPU与Cache/主存之间交换数据以字为单位,Cache与主存之间交换数据以块(多个字)为单位(一定程度上我们可以把内存的基本单位 “页”也称为“块”,之后的文件系统章节我们直接认为无论是物理还是逻辑,内存都以“块”为单位),Cache基于局部性原理:

  • 空间局部性:访问顺序与存放顺序是一致的;

  • 时间局部性:访问过的元素再次访问则时间局部性好;

下面我们介绍几个重要的知识点:

命中率:设一个程序执行期间Cache的总命中次数为Nc,访问主存的次数为Nm,则命中率H为

H = N c / ( N c + N m ) H=Nc/(Nc+Nm) H=Nc/(Nc+Nm)

缺失率=1-命中率

Cache-主存系统平均访问时间Ta:假设命中率为H,tc为命中时Cache的访问时间,tm为未命中时的访问时间

——访问时间指的是CPU访问存储单元的时间(一般认为存取周期等于访问时间)
——Cache和主存同时被访问/先访问Cache未命中时再访问主存这两种方式,tc相同而tm不同
T a = H ∗ t c + ( 1 − H ) ∗ t m Ta=H*tc+(1-H)*tm Ta=Htc+(1H)tm

假设已知命中时间tc,未命中时间tm,平均访问时间Ta,则
系 统 效 率 = t c / T a 系统效率=tc/Ta =tc/Ta

不 使 用 C a c h e 时 间 / 使 用 C a c h e 时 间 = 性 能 提 升 = t m / T a 不使用Cache时间/使用Cache时间=性能提升=tm/Ta 使Cache/使Cache==tm/Ta

3.2.2 地址映射
  • 地址映射:把主存地址空间映射到Cache地址空间,也就是将主存中的信息按照某种映射关系装入Cache中;

  • 地址变换:在信息按上述映射关系装入Cache后,CPU执行程序时,会将程序中的主存地址变换成Cache地址;

(1)直接映射

地址映射规则:将主存分区,每个区域内的主存块数与Cache内的块数相同;

  1. 主存中的每一块只能映射到Cache内的固定行,规则为
    i = j m o d m i=j mod m i=jmodm
    其中i为Cache的行号,j为主存的块号,m为Cache的总块数

  2. 不使用替换算法,产生冲突直接替换原有内容;

  3. 为了在Cache中记住自己存储的数据块属于主存中的哪一个区,Cache需要额外在Cache行中设置标记字段,假设主存有256块,Cache有8块,则主存要划分32个区,Cache需要5位标记字段来标记区号,3位标记Cache行号;

  4. 使用直接映射,则CPU使用的内存地址结构为:

  5. 地址变换过程为:先按照Cache行号找到Cache中的块,接着用标记字段与Cache中的区号/标记进行比较,如果相同则命中,使用块内地址在Cache中取出需要的数据;

(2)全相联映射

地址映射规则:主存的任意一块可以映射到Cache中的任意一块

  1. 主存与缓存分成相同大小的数据块。

  2. 主存的某一数据块可以装入缓存的任意一块空间中。

  3. 为了在Cache中记住自己存储的数据块来自主存中的哪一块,Cache需要额外在Cache行中设置标记字段,假设主存有2048个地址块,则Cache标记字段的位数为11位

  4. 假设使用全相联映射,则CPU使用的内存地址结构为:

  5. 地址变换过程为:先按照标记找到存储主存块数据的Cache块,若找到则代表命中,接着使用块内地址在Cache块中取出需要的数据;

(3)组相联映射

地址映射规则:将主存分区,将Cache分组,要求主存的每个区的块数与Cache的组数相同(主存中一个区分为4块所以Cache中需要4组)

为了在Cache中记住自己存储的数据块来自主存中的哪一个区,Cache额外增加了标记字段;

假设每组有r个Cache块,则称为r路组相联;

  1. Cache组间采用直接映射(主存块存放到哪个组是固定的),组内采用全相联映射(主存块存放到组内的哪一行是灵活的)

组间直接映射规则:
C a c h e 组 号 = 主 存 块 号 m o d c a c h e 组 数 Cache组号=主存块号modcache组数 Cache=modcache
2.使用组相联映射,CPU的内存地址结构为:

​ 标记:主存区号的标记 组号:Cache组的地址 块内地址:Cache中的字块内的地址

3.地址变换过程:首先使用组号找到Cache中的组,然后将标记与该组所有Cache块中的区号比较,如果相同则命中,使用块内地址取出需要的数据;

3.2.3 Cache替换算法

采用全相联或组相联映射方式的时候,从主存向Cache映射一个块,当Cache或Cache组中的空间被占满时候,需要使用替换算法(直接映射没有选择直接替换,故不考虑替换算法);

  • 随机算法:随机确定要替换的Cache块;
  • FIFO算法:选择最早调入的Cache进行替换;
  • LRU算法:

3.2.4 Cache写策略

Cache中的内容是主存的副本,当Cache中的内容更新的时候,需要选择写策略使得Cache与主存内容保持一致;

(1)全写法

也称为写直通法、write-throught:

  • 当CPU对Cache写命中时,将数据同时写入Cache和主存;
  • 当Cache某块需要替换时,不必将这块写回主存(因为Cache和主存随时同步),直接用新调入的Cache块覆盖即可;

优点是简单,随时保持主存数据的正确性;

缺点是增加了访存次数,降低了Cache效率,我们可以适当在Cache和主存之间增加一个写缓冲 —— CPU同时将数据写入写缓冲和Cache,写缓冲再将内容写入主存,由此解决速度不匹配的问题;

(2)写回法

write-back:

  • 当CPU对Cache写命中时,只修改Cache的内容,不会立刻写入主存,只有当该Cache块被替换出的时候才写回主存;

这种方法减少了访存次数,但是增加了不一致的风险 —— 采用这种策略时每个Cache块必须设置一个标志位脏位以标志此块是否被CPU修改过;

上述两种方法都应对的是Cache写命中(也就是要修改的单元在Cache中),对于Cache不命中也有两种处理方法:

(3)写分配法

加载主存中的块到Cache中,接着再更新这个块(这个方法需要搭配写回法,在该Cache块被替换的时候更新主存对应块);

优点是利用了程序的空间局部性,缺点是每次不命中都需要从主存中读取一块,效率过低;

(4)非写分配法

这种方法只写入主存,不进行调块,通常与全写法合用;

3.3 快表TLB

(快表的知识点网上是真难找啊!!!参考文章快表原理_csjinzhao的博客-优快云博客深入理解TLB原理_极客重生的博客-优快云博客

上面我们介绍了Cache,但是实际上常用的逻辑页不会是连续的,且常用的逻辑页不会很少(这意味着Cache中往往缓存了几百个映射关系),使用基于Cache的传统比较查找方法效率将会很低,无法起到提高效率的作用,因此我们引出了借助硬件电路设计来实现对Cache的查找,我们将这种硬件电路称为快表(简单理解的话就是快表是专门针对页表的Cache,所以其效率比普通Cache要高(具体原因我这里猜测是因为不同的组织方式有关系));

注意虽然快表和页表都是表,但是两个根本不是一个层次的概念(快表是个硬件!);快表是一种特殊的Cache,内容是页表中的一部分或全部内容,在OS中引入快表是为了加快地址映射的速度;快表就是将页表存在Cache中,慢表表示将页表存在内存上;

快表会缓存常用的逻辑页映射(虚拟地址<->逻辑地址),引入快表的地址转换过程为:先查快表,若命中hit则能够快速的获得物理页框号;如果未命中则查找页目录表、页表、找到物理页框号并更新快表;

快表、CPU以及Cache的速度比较大致如下

TLB表项映射

TLB中存放的基本单位是页表条目(虚拟页表项),对应RAM中存放的页表条目(物理页框);页表条目大小不变,但是RAM不可能与TLB一一对应(因为TLB始终容量有限);

CPU收到一个线性地址需要做如下判断:

  • 所需的页表条目否已经缓存在TLB内部(TLB miss或者TLB hit);
  • 所需的页表条目对应TLB的哪个条目;

为了减少CPU做出上述判断的时间,所以必须在TLB页表条目和内存页表条目之间的对应方式下功夫,而实际上对应方式也就是Cache和主存之间的映射方式(因为快表就是一种特殊的Cache,本质上也是Cache):

  • 全相联映射;
  • 直接映射;
  • 组相联;

TLB表项更新

TLB表项更新可以由TLB硬件自动发起,也可以由软件主动更新

  1. TLB miss发生后,CPU从RAM获取页表项,会自动更新TLB表项

  2. TLB中的表项在某些情况下是无效的,比如进程切换,更改内核页表等,此时CPU硬件不知道哪些TLB表项是无效的,只能由软件在这些场景下,刷新TLB。在linux kernel软件层,提供了丰富的TLB表项刷新方法,但是不同的体系结构提供的硬件接口不同。比如x86_32仅提供了两种硬件接口来刷新TLB表项:

    • 向cr3寄存器写入值时,会导致处理器自动刷新非全局页的TLB表项;

    • 在Pentium Pro以后,invlpg汇编指令用来无效指定线性地址的单个TLB表项无效;

TLB Shootdown

文章参考linux内存管理笔记(三)----TLB_奇小葩的博客-优快云博客以及Stack Overflowcaching - What is TLB shootdown? - Stack Overflow

TLB 条目需要始终与其各自的页表条目同步,现在,TLB是每核缓存,即每个核心都有自己的 TLB,每当页表条目被任何内核修改时,该特定 TLB 条目在所有内核中都会失效,此过程称为 TLB 击落;

简单来说,一个处理器导致 TLB 在其他处理器上刷新(当处理器更改地址的虚拟到物理映射时,它需要告诉其他处理器在其缓存中使该映射失效)的操作就是所谓的 TLB 击落;

TLB刷新可以由各种虚拟内存操作触发,这些操作会更改页表条目,如页面迁移,释放页面等;

4.虚拟内存

现在我们终于到了这小节了,前面分段和分页的概念极其烧脑,以至于我们花费大量时间来整介绍、辨析这两个概念以及相关知识点,这个小节我们正式将两者作为主角同时讨论;

不知道在学习分页和分段的时候大家脑子里有没有这样一个问题“这两概念看起来和操作系统没啥关系啊?”,而事实的确是这样,程序用户想要分段,物理内存想要分页;那么能不能将分段和分页结合在一起?这就是操作系统大显身手的时候了 —— 操作系统通过在用户和硬件之间加一个中间结构(中间层这个概念真的是计算机界的万精油),我们将这个数据结构称为虚拟内存,正是虚拟内存这个抽象将分段机制和分页机制有机地结合;

现在结合虚拟内存来回答之前的两个问题:

  • 程序如何放在内存中?
    1. 在虚拟内存中分区,将程序各个段载入;
    2. 建立段表记录程序各个段和虚拟内存之间的映射关系;
    3. 将虚拟内存分割成页,选择物理内存中的空闲页框,将虚拟内存中的页放在物理页框中;
    4. 建立页表记录虚拟内存页和物理页框之间的映射关系;
  • 放在内存中的指令如何正确执行?
    1. 利用 段地址:段内偏移 算出在虚拟内存中的位置(即虚拟地址);
    2. 根据虚拟地址查找页表找到物理地址;
    3. 只需要将LDTR和CR3的值设置为正确段表初始地址和页表初始地址,则执行每条指令时MMU都会自动地完成上述地址转换过程;

4.1 页面换入

接着我们来聊聊虚拟内存真正厉害的地方,假设虚拟内存大小为4GB而实际物理内存只有1GB,是否意味着我们4GB的程序无法运行了呢?

换入/换出行为使得可以使用一个小的物理内存来制造一个大且规整的虚拟内存用户视图 —— 实际上就算物理内存是4GB也需要换入/换出,因为每个进程都需要有一个4GB的虚拟内存


当然想要在系统中实现换入/换出只是一个概念,真正要实现需要从段页式内存管理机制入手分析,下图给出了段页式内存机制下地址转换过程遇到的问题:

地址转换的第一步能够正确得到虚拟地址,但是根据虚拟地址和页表获得物理地址时发现页表中没有该虚拟页对应的物理页 —— 这是因为这一页的虚拟内存还没有和物理内存建立关联;

在具体实现时,MMU检查页表项的有效位,如果为0表示该虚拟页还没有映射到物理页框,此时就需要内存换入 —— 操作系统实现请求调页:

  1. MMU向CPU发出缺页中断;
  2. 执行缺页中断处理程序,操作系统去磁盘找到未驻留虚拟页对应的程序代码,将其读入物理内存(当然此时的物理内存应该是有空位的)并更新页表;
  3. 中断处理返回以后重新执行被中断的指令,此时指令就能正确找到物理地址;

常识:一个程序在被加载进入内存的时候只会加载很少一部分进入内存(这部分标识为“驻留”),另一部分会在需要的时候才调入内存(这部分标识为未驻留),也就对应了有效位为1和0的情况;

4.2 页面换出

页面换出要解决的基本问题就是选择哪个页面进行淘汰,我们定义一个指标来评价页面换出算法的优劣:缺页次数;

我们要介绍的页面换出算法曾经在计算机组成原理课程中也有介绍 —— LRU最近最少使用算法,LRU也有多种选择,这里我们选择使用基于页面栈的LRU算法

然而直接在计算机中实现精准的LRU算法代价过大,所以出现了类LRU的算法 —— clock算法:

  • 将分配给进程的所有虚拟页框组织成环形线性表,产生缺页时,,从当前的线性表指针开始进行环形扫描;
  • 如果扫描的虚拟页框的访问位R为1则修改为0,如果访问的虚拟页框的访问位为0则将该页面淘汰换出;

当然还可以引入一个扫描指针,该指针定期扫描所有页面,并将所有页面为1的清零;当出现缺页时调用换出指针来扫描页面判断页面的R位

4.3 页框个数

只有当分配给进程的所有物理页框都被用完之后,发生缺页才会调用clock算法进行淘汰,所以操作系统应该决定给进程分配多少个物理页框:

  • 从进程自身的角度来说分配的页框越多越好,但是物理内存总量有限,页框越多能够支持的并发进程就越少;
  • 如果给进程分配的页框太少会出现系统颠簸的情况,也就是进程在内存页和磁盘之间频繁换入/换出,导致CPU利用率急剧下降(因为换入/换出的时候CPU只能空闲等待)

5.Page Coloring

“页着色是一种通过选择性物理页分配来实现把虚存映射到特定cache位置的软件方法”(文章参考(2条消息) page coloring小结_朱乐乐在路上的博客-优快云博客

在学完上述内容后可能很多人认为自己已经懂了个大概,现在我们抛出一个问题 —— Cache使用的是虚拟地址还是物理地址?我相信很多人也是懵的,确实从来没有认真思考过这个问题,而实际上这个问题现在仍然在讨论,比较好的总结可以看这篇文章CPU架构:缓存(1)—— 虚拟地址 - 知乎 (zhihu.com)

当Cache使用的是虚拟地址的时候会存在别名问题,原因是操作系统和用户程序可能对同一个物理地址使用两种以上不同形式的虚拟地址来访问,这些地址被称作别名,他们会导致同一个数据在使用虚拟地址的cache中存在两个副本,如果其中一个数据被修改,那么另外一个就是错误的;

解决别名问题的方法之一就是使用页着色:

  • 如果强行要求别名的某些地址位相同,就可以用软件很容易地解决这一问题(如SUN公司的UNIX要求所有使用别名的地址最后18位都相同,这种限制被称为页着色);
  • 上述页着色的限制使得容量不超过2^18字节(256KB)的直接映射Cache中不可能出现Cache块有重复物理地址的情况,所有别名将被映射到同一Cache块位置;

Cache中存储同一个颜色的连续多个set(组,直接映射的Cache只有一个set)被称为bin,上图中两个黄色的页因为具有相同的colorbits,于是被映射到L2 Cache中的bin中;

总结:

  • 相同的color在内存中离散存在;
  • 相同的color在Cache中连续存在;
  • 操作系统需要做的就是把一个进程的虚拟地址空间映射到不同的物理地址中,进而映射到特定的Cache位置。在上图中,操作系统将A进程的虚拟地址空间映射到黄色的物理页地址空间,从而A进程的页都放置在cache中的黄色bin中;

### 下载 Popper.min.js 文件的方法 对于希望获取 `popper.min.js` 的开发者来说,可以通过多种方式来实现这一目标。通常情况下,推荐通过官方渠道或可靠的分发网络 (CDN) 来获得最新的稳定版文件。 #### 使用 CDN 获取 Popper.min.js 最简单的方式之一是从流行的 CDN 中加载所需的 JavaScript 库。这不仅简化了集成过程,还可能提高性能,因为许多用户已经缓存了来自这些服务提供商的内容。例如: ```html <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2/dist/umd/popper.min.js"></script> ``` 这种方式不需要手动下载文件到本地服务器;只需将上述 `<script>` 标签添加至 HTML 文档中的适当位置即可立即使用 Popper 功能[^1]。 #### 从 npm 或 yarn 安装 如果项目采用模块化构建工具链,则可以直接利用包管理器如 npm 或 Yarn 进行安装。命令如下所示: ```bash npm install @popperjs/core # 或者 yarn add @popperjs/core ``` 之后可以根据具体需求引入特定功能模块,而不是整个库,从而减少打包后的体积并优化加载速度[^2]。 #### 访问 GitHub 发布页面下载压缩包 另一种方法是访问 Popper.js 的 [GitHub Releases](https://github.com/popperjs/popper-core/releases) 页面,在这里可以选择不同版本的 tarball 或 zip 归档进行下载解压操作。这种方法适合那些偏好离线工作环境或是想要定制编译选项的人群[^3]。 #### 手动克隆仓库 最后一种较为少见但也可行的办法便是直接克隆完整的 Git 存储库副本。这样可以获得开发分支以及历史记录等更多信息,适用于贡献代码或者深入学习内部机制的情况。 ```bash git clone https://github.com/popperjs/popper-core.git cd popper-core ``` 完成以上任一途径后便能成功取得所需版本的 Popper.min.js 文件,并将其应用于个人项目之中[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

坂.y

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值