开始13180 操作系统的学习过程,献给每一位拥有梦想的"带专人",
ps:有不正确的地方麻烦更新在评论区,我会一一修复 😅
第五章 存储管理
计算机系统中的存储器可以分为两类:内存储器(简称内存)和外存储器(简称外存)。
处理器可以直接访问内存,但不能直接访问外存
处理器只有通过相应的输入/输出设备才可以将可以将外存装入内存或内存存存放到外存
1. 概述
存储体系
-
寄存器:
通常是 MB 数量级
特点:少量的、速度非常快、昂贵的、内容易变
-
内存:
通常是 GB 数量级
特点:中等速度、中等价格、内容易变
-
外存:
通常是 TB 数量级
特点:低速的、廉价的,内容不易变
快速存储设备和大容量存储设备必须成为统一的整体,由操作系统协调这些存储器的使用
对于内存速度和容量的要求是:
- 尽量快到与处理器存取速度相匹配
- 能装下当前运行的程序与数据
各种速度和容量的存储器硬件,在操作系统协调之下形成了一种存储器层次结构,或称存储体系
存储管理的任务
内存空间:由存储单元(字或字节)组成的一维连续地址空间,用来存放当前正在运行程序的代码及数据
分为两部分:
-
系统区:
存放操作系统常驻内存部分。
包含操作系统的公共代码和数据,不可被用户程序占用。
-
用户区:
分配给用户,存放用户程序和数据。
内容随运行程序的变化而更新。
内存的分配和回收
一个有效的内存分配机制,应对用户提出的需求予以快速响应,为之分配相应的内存空间;在用户程序不需要它时,及时回收,以供其他用户使用
功能:
-
记住每个内存区域的状态
记录内存空间的分配与空闲状态。
-
实施分配
按需分配,方式有静态和动态两种。
-
回收
接收并释放用户不用的内存区域。
内存分配表:
-
位图表示法:
用 1 位表示一个内存块状态,0 表示空闲,1 表示占用。
-
链表法:
已分配链表:记录已用内存的首地址和长度。
空闲链表:记录未分配内存并合并相邻空闲区域。
内存分配的两种方式:
-
静态分配:
特点:内存空间在程序加载前分配,运行中不可申请额外空间或移动内存。
场景:适用于资源需求固定的程序。
-
动态分配:
特点:内存空间可在运行时按需分配或调整,提高利用率。
优势:灵活性高,适应程序动态需求。
内存共享
指两个或多个进程共用内存中相同区域
- 能使多道程序动态地共享内存,提高内存率用率
- 能共享内存中某个区域的信息
共享的内容包括代码共享和数据共享,特别是代码共享要求代码必须是纯代码
目的:
- 通过代码共享节省内存空间,提高内存率用率
- 通过数据共享实现进程通信
存储保护
内存保护的内容:
- 保护系统程序区不被用户有意或无意侵犯
- 不允许用户程序读写不属于自己程序的内存空间,如系统区地址空间、其他用户程序的地址空间
保护分类:
-
地址越界保护:
作用:防止进程访问超出自己地址空间范围的数据,避免影响其他进程或操作系统。
机制:运行时检查地址范围,越界则触发中断,由操作系统处理。
-
权限保护:
规则:
- 自己的数据:可读写。
- 公共区域(获授权):可读不可写。
- 未授权数据:不可读写。
“扩充”内存容量
在硬件支持下,软件硬件相互协作,将内存和外存结合起来使用
借助虚拟存储技术或其他交换技术,达到在逻辑上扩充内存容量的目的
地址转换
存储器以字节为编址单位,每个字节都有一个地址与其对应
假定存储器的容量为 n 个字节,其地址编号顺序为 0、1、2、……、n-1
这些地址称为内存的绝对地址,由绝对地址对应的内存空间称为物理地址空间
用户程序中使用的地址称为逻辑地址,由逻辑地址对应的存储空间称为逻辑地址空间
地址重定位
把逻辑地址转换为绝对地址
-
静态重定位:
地址转换工作是在程序开始执行前集中完成的,所以程序在执行过程中就无须再进行地址转换工作
-
动态重定位:
再程序执行过程中,每当执行一条指令时都由硬件的
地址转换机构将程序中的逻辑地址转换成绝对地址。这种方式的地址转换是在程序执行时动态完成的是由
硬件和软件相互配合来实现。硬件要有一个地址转换机构,该机构可由一个基址寄存器和一个地址加法器组成
若程序执行时,被改变了存放区域仍能正确执行,则称程序是可浮动的
采用动态重定位的系统支持程序浮动
采用静态重定位的系统不支持程序浮动
2. 分区管理方案
基本思想:
把内存划分成若干个连续区域,称为分区,每个分区装入一个运行程序
固定分区
基本思想:
系统先把内存划分成若干个大小固定的分区,一旦划分好,在系统运行期间便不再重新划分。
用于固定分区管理的内存分配表/分区说明表
表头内容包括序号、大小、起始地址以及状态
缺点:
不能充分利用内存。因为一个程序的大小,不可能刚好等于某个分区的大小- 固定分区方案
灵活性差,可接纳程序的大小受到了分区大小的限制
可变分区
基本思想:
是指系统不预先划分固定分区,而是在装入程序时划分内存分区,使为程序分配的分区的大小正好等于该程序的需求量,且分区的个数时可变的
优点:
- 可变分区有较大的
灵活性 - 较之固定分区能获得较好的
内存率用率
紧锁技术
基本思想:
内存经过一段时间的分配回收后,会存在很多很小的空闲块,折现空闲块称为碎片
优点:
- 集中分散的空闲区
- 提高内存利用率
- 便于进程动态扩充内存
注意问题:
- 紧缩技术会增加系统的开销
- 移动是有条件的
在采用紧缩技术时,应该尽可能减少需要移动的进程数和信息量
可变分区的实现
当逻辑地址小于限长寄存器中的限长值时,则逻辑地址加基址寄存器值就可得到绝对地址。当逻辑地址大于限长寄存器中的限长值时,表示欲访问的内存地址超出了所分配的分区范围。这时就不允许访问,形成一个地址越界程序性中断
在采用可变分区方式管理时,要有硬件的地址转换机构支持
限长寄存器用来存储程序所占分区的长度
基址寄存器用来存储程序所占分区的起始地址
内存分配表:
-
已分配区表:
记录已装入的程序在内存中占用分区的起始地址和长度,用标志位指出占用分区的程序名
-
空闲区表:
记录内存中可供分配的空闲区的起始地址和长度,用标志位指出该分区是未分配的空闲区
空闲区的分配策略
-
最先适应算法:
当接到内存申请时,顺序查找分区说明表,找到
第一个满足申请长度的空闲区此算法简单,可以快速做出分配决定
-
最优适应算法:
当接到内存申请时,查找分区说明表,找到
第一个能满足申请长度的最小空闲区优点:最节约空间
缺点:可能会形成碎片
-
最坏适应算法:
当接到内存申请时,查找分区说明表,找到能满足申请要求的最大空闲区
优点:可以避免形成碎片
缺点:分割了大的空闲区后,如果再遇到较大的程序申请内存时,无法满足要求的可能性较大
分区的回收
当用户程序执行结束后,系统要回收已使用完毕的分区,将其记录在空闲区表中
假定进程归还的分区起始地址为 S,长度为 L
应考虑以下四种可能:
- 回收分区的
上邻分区是空闲的
需要将两个空闲区合并成一个更大的空闲区,然后修改空闲区表
公式:
起始地址不变
长度 = 原长度 + L
空闲区第 i 个登记栏中的起始地址为 1000,长度为 1000,而回收的分区起始地址 S 为 2000,长度 L 为 1000。空闲区表中第 i 个登记栏中 起始地址 + 长度 = 2000,正好等于 S。于是要修改第 i 栏登记项的内容,即起始地址不变,长度为原长度加上 L,即 1000 + 1000 = 2000。实现了回收的分区与上邻空闲区的合并
- 回收分区的
下邻分区是空闲的
需要将两个空闲区合并成一个更大的空闲区,然后修改空闲区表
公式:
起始地址 = S
长度 = 原长度 + L
之前 3000 ~ 4000 是空闲的,现在 2000~3000 需要回收,回收时发现 3000 ~ 4000 也是空闲的所以进行合并合并后 2000 ~ 4000
- 回收分区的
上邻和下邻分区都是空闲的
需要将三个空闲区合并成一个更大的空闲区,然后修改空闲区表
公式:
起始地址 = 上邻的起始地址
长度 = 上邻的长度 + L + 下邻的长度
之前1000 ~ 2000 是空闲的,3000 ~ 4000 也是空闲的,现在 2000 ~ 3000 需要回收,发现上邻和下邻都是空闲的1000 ~ 4000
下邻被合并后要改为空状态
-
回收分区的
上邻和下邻分区都不是空闲的直接将空闲分区记录在空闲区表中
找一个标志位
空的登记栏,把回收的分区的起始地址和长度登记入表,把该栏目中的标志位修改成未分配,表示该登记栏中指示了一个空闲区
分区的保护
两种保护方法:
-
设置界限寄存器
系统会设置一对界限寄存器。界限寄存器可以是上、下界限寄存器或基址、限长寄存器。
通常系统会设置一对界限寄存器,用来存放先行进程的内存界限。在进程的 PCB 中保存界限值,当轮到该进程执行时,将界限值作为进程现场的一部分恢复。对于进程执行过程中产生的每一个访问内存的地址,硬件都将自动将其与界限寄存器的值进行比较,若发生地址越界,便产生保护性地址越界中断
-
保护键方法
为每个分区分配一个保护键,相当于一把锁。同时为每个进程分配一个相应的保护键,相当于一把钥匙,存放在程序状态字中。每当访问内存时,都需要检查钥匙和锁是否匹配,若不匹配,将产生保护性中断
分区管理方案的优缺点
分区管理是实现多道程序设计的一种简单易行的存储管理技术
优点:
- 算法简单
- 实现容易
- 内存开销少
- 存储保护措施简单
- 内存利用率方面,可变分区的内存利用率比固定分区高
缺点:
- 内存使用不充分,存在较为严重的碎片问题
- 浪费了处理器时间
- 不能为用户提供虚存,不能实现内存的扩容,受到物理存储器实际存储容量的限制
- 内存中可能包含一些实际不使用的信息
3. 覆盖与交换技术
主要区别是控制交换方式不同,覆盖技术主要用在早期系统中,交换技术主要用于小型分时系统
覆盖技术
指的是一个程序的若干程序段,或几个程序的某些部分共享一个存储空间
覆盖技术不需要操作系统的特殊支持,可以完全由用户实现,覆盖技术是用户程序自己附加的控制
覆盖技术可以由编译程序提供支持。覆盖可以从用户级解决内存小装不下程序的问题
交换技术
进程从内存移到磁盘,并再移回内存称为交换
交换技术是进程在内存与外存之间的动态调度,是由操作系统控制的
主要特点:打破了一个程序一旦进入内存便一直运行到结束的限制
减少信息交换量是交换技术的关键问题
与覆盖技术相比:
- 交换技术对用户而言是
透明的 - 交换可以发生在不同的进程或程序之间,而覆盖发生在同一进程或程序内部,而且只能覆盖哪些与覆盖段无关的程序段
因此,交换技术比覆盖技术更加广泛地用于现代操作系统
4. 虚拟页式存储管理方案
虚拟存储技术
把一个逻辑地址连续的程序分散存储到几个不连续的内存区域中,并且保证程序的正确执行,既可充分利用内存空间,又可减少移动所花费的内存开销
页式存储管理就是这样一种有效的管理方式
将虚拟存储技术与页式存储管理方案结合后的一种典型的、大多数操作系统采用的虚拟页式存储管理方案
基本思想:
利用大容量的外存来扩充内存,产生一个比有限的实际内存空间大的多的、逻辑的虚拟内存空间,简称虚存,以便能够有效地支持多道程序系统的实现和大型程序运行的需要,从而增强系统的处理能力
实现虚拟存储器需要以下硬件支持
- 系统有容量足够大的外存
- 系统有一定容量的内存
- 最主要的是:硬件提供实现虚-实地址映射的机制
虚拟存储器的工作原理:
- 先加载部分程序到内存,剩余部分保留在外存。
- 若执行指令不在内存,系统自动从外存调入。
- 内存不足时,系统将部分内容交换到磁盘,释放空间供其他进程使用。
虚拟页式存储管理
支持页式存储管理的硬件部件通常称为存储管理部件。首先把内存分成大小相等的许多区,把每个区称为物理页面,物理页面是进行内存空间分配的物理单位。同时,要求程序中的逻辑地址也进行分页,也的大小与物理页面的大小一致
逻辑地址也可称为虚拟地址
由两部分组成:虚拟页号和页内地址
页表
- 多级页表
- 散列页表
- 反置页表
大多数操作系统中采用二级页表,即页表页和页目录一起构成进程页表
第一级表示页目录,保存页表页的地址
第二级表示页表页,保存物理页面号
转换检查缓冲区(TLB)快表
按给定的虚拟地址进行读写时,必须访问两次内存
第一次按页号读出页表中对应的块号
第二次按计算出来的绝对地址进行读写
两次访问内存显然延长了指令的执行周期,降低了执行速度
为了提高存取速度,有两种方法
- 在地址映射机制中增加一组
高速寄存器保存页表 - 在地址映射机制中增加一个小容量的联想寄存器(相联存储器),它由
高速缓冲存储器组成
增加 TLB 后,命中时,访问一次 TBL,访问一次内存;不命中时,访问两次内存
TLB的更新原理:
-
查找 TLB(快表):
先在 TLB(一个缓存)中查找对应的逻辑页号。如果找到了页号,就直接拼接页内地址形成物理地址,无需进一步操作。
-
TLB 不命中(缺页):
如果在 TLB 中找不到该逻辑页号,就需要访问内存中的页表来找到对应的页框号(逻辑页与物理页的对应关系)。
找到页框号后,将它添加到 TLB 中以提高后续访问效率
-
TLB 空间管理:
如果 TLB 中有空闲单元,就直接将新页号填入。
如果 TLB 已满,使用淘汰算法(如最近最少使用 LRU)替换掉一个旧页号。
-
并行优化:
系统会同时查找 TLB 和内存页表。如果在查找过程中,发现 TLB 已经命中,就立即停止访问页表,直接使用 TLB 中的结果,提升效率。
例:假定访问内存的时间为 200 飞秒,访问高速缓冲存储器的时间为 40 飞秒,查 TLB 的命中率为 90%
平均访问时间为:
缺页异常及缺页率
若在页表中发现所要访问的页面不在内存,则产生缺页异常
如果程序执行中访问页面的总次数为 A,其中有F次访问的页面尚未装入内存,故产生了 F 次缺页异常
定义:f = F / A,把 f 称为缺页率
显然,缺页率与缺页异常的次数有关
影响缺页率的因素:
-
分配给程序的物理页面数:
分配给程序的物理页面多,则同时装入内存的页面数就多,故减少了缺页异常的次数,也就降低了缺页率。反之,缺页率就高
-
页面的大小:
虚拟页面的大小取决于物理页面的大小,物理页面大则虚拟页面也大,每个页面大了,则程序的页面数就少。装入程序时是按页存放在内存中的,因此,装入一页的信息量就大,就减少了缺页异常的次数,降低了缺页率。反之,若页面小,则缺页率就高
-
程序的编制方法:
程序的编写方法不同,对缺页异常的次数也会有很大的影响
-
页面置换算法的性能:
页面置换算法对缺页率的影响也很大,置换不好就会出现
颠簸。理想页面置换算法(OPT)能使缺页率最低采用 FIFO 页面置换算法产生的缺页率约为 OPT 置换算法的三倍
页面置换算法 重点
理想页面置换算法(OPT)最佳
淘汰以后不需要的或者在最长时间以后才会用到的页面。这一算法一般不可能实现,但它可以作为衡量其他页面淘汰算法优劣的一个标准。如果遇到多个页面要淘汰,选择时间短的页面
核心:命中时,位置不变,淘汰后置换成新的页面,看页面时间可能调位置
例:某程序在内存中分配到了三个页面,初始为空,所需页面的走向为 4、3、2、1、4、3、5、4、3、2、1、5,采用 OPT 算法,计算缺页次数
| 页面走向 | 4 | 3 | 2 | 1 | 4 | 3 | 5 | 4 | 3 | 2 | 1 | 5 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 时间短-页 | 4 | 3(后进来的占据时间短的) | 2 | 1(2、3、4 在后面需要使用,2 在倒数第三个使用属于最长时间) | 1 | 1 | 5(1 后面只用到一次并且在倒数第二次) | 5 | 5 | 2(3 和 4 后面都不在使用,所以选择时间短的淘汰,后进来的占据时间短的) | 1(2 和 4 都可以淘汰,选择时间短的) | 1 |
| 时间中-页 | 4 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 5 | 5 | 5 | |
| 时间长-页 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | ||
| 缺页 | ❌(首次进入没有页面 4) | ❌(首次进入没有页面 3) | ❌(首次进入没有页面 2) | ❌(首次进入没有页面 1) | ✅(命中以后位置不变) | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ |
| 缺页次数 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
先进先出页面置换算法(FIFO)
优点:FIFO 算法简单,容易实现
缺点:最先装入内存的一页,不等于说这一页是不常使用的。很少使用纯 FIFO 算法
核心:命中是,位置不变,淘汰时间长-页
例:某程序在内存中分配到了三个页面,初始为空,所需页面的走向为 4、3、2、1、4、3、5、4、3、2、1、5,采用先进先出页面置换算法,计算缺页次数
| 页面走向 | 4 | 3 | 2 | 1 | 4 | 3 | 5 | 4 | 3 | 2 | 1 | 5 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 时间短-页 | 4 | 3(后进来的占据时间短的) | 2 | 1 | 4 | 3 | 5 | 5 | 5 | 2 | 1 | 1 |
| 时间中-页 | 4 | 3 | 2 | 1 | 4 | 3 | 3 | 3 | 5 | 2 | 2 | |
| 时间长-页 | 4 | 3 | 2 | 1 | 4 | 4 | 4 | 3 | 5 | 5 | ||
| 缺页 | ❌(首次进入没有页面 4) | ❌(首次进入没有页面 3) | ❌(首次进入没有页面 2) | ❌(首次进入没有页面 1,4 是最先进入的所以淘汰) | ❌(3 是最先进入的所以淘汰) | ❌(2 是最先进入的所以淘汰) | ❌(1 是最先进入的所以淘汰) | ✅ | ✅ | ❌(4 是最先进入的淘汰) | ❌(3 是最先进入的所以淘汰) | ✅ |
| 缺页次数 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
第二次机会页面置换算法
检查进入内存时间最久页面的 R 位
如果是 0,那么这个页面既老又没有被使用,可以立即置换掉
如果是 1,就将 R 位清 0,并把该页面放到链表的尾端,修改其进入时间,然后继续搜索
时钟页面置换算法(Clock)
尽管第二次机会算法是一个比较合理的算法,但它经常要在链表中移动页面,既降低了效率又不是很有必要。一个更好的办法是:环形链表(俗称:循环链表)
最近很少使用页面置换算法(LRU)
在缺页发生时,首先淘汰掉最长时间未被使用过的页面,这种实现方法必须对每一页的访问情况时时刻刻地加以记录和更新,实现起来开销比较大,在效果上最接近 OPT 算法的算法
核心:命中时,要移动位置到时间短-页,淘汰时间长-页
例:某程序在内存中分配到了三个页面,初始为空,所需页面的走向为 4、3、2、1、4、3、5、4、3、2、1、5,采用LRU 页面置换算法,计算缺页次数
| 页面走向 | 4 | 3 | 2 | 1 | 4 | 3 | 5 | 4 | 3 | 2 | 1 | 5 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 时间短-页 | 4 | 3(后进来的占据时间短的) | 2 | 1 | 4 | 3 | 5 | 4 | 3 | 2 | 1 | 5 |
| 时间中-页 | 4 | 3 | 2 | 1 | 4 | 3 | 5 | 4 | 3 | 2 | 1 | |
| 时间长-页 | 4 | 3 | 2 | 1 | 4 | 3 | 5 | 4 | 3 | 2 | ||
| 缺页 | ❌(首次进入没有页面 4) | ❌(首次进入没有页面 3) | ❌(首次进入没有页面 2) | ❌(1 占据时间短的页,淘汰 4 时间长的页) | ❌(4 占据时间短的页,淘汰 3 时间长的页) | ❌(3 占据时间短的页,淘汰 4 时间长的页) | ❌(5 占据时间短的页,淘汰 1 时间长的页) | ✅(4 命中,移动位置到时间短的页,其他按照之前的顺序排列) | ✅(3 命中,移动位置到时间短的页,其他按照之前的顺序排列) | ❌(2 占据时间短的页,淘汰 5 时间长的页) | ❌(1 占据时间短的页,淘汰 4 时间长的页) | ❌(5 占据时间短的页,淘汰 3 时间长的页) |
| 缺页次数 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
虚拟页式存储管理的优缺点
优点:
- 有效地解决了碎片问题
- 提高了内存的利用率
- 利于组织多道程序执行
缺点:
- 存在页面空间浪费问题
虚拟存储管理的性能问题
引入虚拟存储管理,把内外存统一管理,将哪些访问概率非常高的页放入内存,减少内外存交换次数
在虚存中,页面可能在内存与外存之间频繁地调度,有可能出现抖动或颠簸
原因:
- 颠簸是由于
缺页率高引起的 - 分配给一个进程的
内存物理页面数太少
练习
- 计算机系统中的存储器可以分为内存储器和
- 外存储器
- 虚拟存储器
- 转换存储器
- 程序存储器
- 若干兆字节、中等速度、中等价格、内容易变的内存 RAM 通常属于哪一数量级
- GB 的数量级
- TB 的数量级
- MB 的数量级
- FB 的数量级
- 对于存储保护,下列说法错误的是:
- 存储保护一般以硬件保护机制为主,软件为辅
- 其目的在于为多个程序共享内存提供保障,使用在内存中的各道程序,只能访问它自己的区域,避免各道程序间互相干扰
- 存储保护中,保护系统程序区可以被用户有意或无意的侵犯
- 存储保护中,不允许用户读写不属于自己地址空间的数据
- 动态重定位中,若程序执行时,被改变了存储区域扔能正确执行,则称程序是
- 可浮动的
- 可转换的
- 可更改的
- 可分配的
- 把逻辑地址转换成绝对地址的工作称为
- 地址重定位
- 静态重定位
- 动态重定位
- 绝对地址重定位
- 下列存储设备中,访问速度最快的是
- 硬盘
- 内存
- 远程存储
- 高速缓存
- 为了提高内存利用率,往往使得多个进程共用内存中相同的区域,即
- 存储保护
- 存储共享
- 内存扩充
- 内存分配与回收
- 存储器以字节为编址单位,每个字节都有一个地址,这些地址称为内存的
- 虚拟地址
- 绝对地址
- 逻辑地址
- 线性地址
- 下列容量最小的存储设备是
- 内存
- 硬盘
- 远程存储
- 高速缓冲存储
- 操作系统通常会为用户提供比内存物理空间大得多的地址空间,使得用户感觉他的程序是在一个大的存储器中运行。这一功能即
- 把指令中的页内地址转换成逻辑地址
- 把指令中的物理地址转换成逻辑地址
- 把指令中的逻辑地址转换成页内地址
- 把指令中的逻辑地址转换成物理地址
- 为了扩充内存,可以让用户把程序分成相互独立的程序段,那些不会同时执行的程序段共享同一块内存区域,这一方法叫覆盖。在分时系统中所采用的交换技术则是由操作系统控制的,将那些内存放不下、暂时不运行的进程保存在磁盘上,在需要运行时,再将它们装入内存。
- 采用可变分区方式进行存储管理时,需要有硬件的地址转换机构进行支持,其中基址寄存器用来存储程序所占分区的起始地址,限长寄存器用来存储程序所占分区的长度
捏捏捏捏捏捏捏捏捏捏捏
笔者观看的课程是 B 站博主小飞学长Pro的 课程,前四章是免费的如果需要看后面的建议大家去购买正版课程 😊😊
🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉