当前位置: 首页 > news >正文

深度剖析操作系统核心(第一节):从X86/ARM/MIPS处理器架构到虚拟内存、分段分页、Linux内存管理,再揭秘进程线程限制与优化秘籍,助你成为OS高手!

文章目录

  • OS
    • 处理器
      • X86
      • ARM
      • MIPS
      • PowerPC
    • 内存管理
      • 虚拟内存
      • 内存分段
      • 内存分页
      • 段页式内存管理
      • Linux 内存管理

OS

处理器

常见处理器有X86、ARM、MIPS、PowerPC四种。

X86

X86架构是芯片巨头Intel设计制造的一种微处理器体系结构的统称。如果这样说你不理解,那么当我说出8086,80286等这样的词汇时,相信你肯定马上就理解了,正是基于此,X86架构这个名称被广为人知。

如今,我们所用的PC绝大部分都是X86架构。可见X86架构普及程度,这也和Intel的霸主地位密切相关。

x86采用CISC(Complex Instruction Set Computer,复杂指令集计算机)架构。与采用RISC不同的是,在CISC处理器中,程序的各条指令是按顺序串行执行的,每条指令中的各个操作也是按顺序串行执行的。顺序执行的优点是控制简单,但计算机各部分的利用率不高,执行速度慢。

  • 优势
    • 速度快:单挑指令功能强大,指令数相对较少
    • 带宽要求低:还是因为指令数相对少,即使高频率运行也不需要很大的带宽往CPU传输指令
    • 由于X86采用CISC,因此指令均是按顺序串行执行的,每条指令中的各个操作也是按顺序串行执行的,而顺序执行的优点就是控制简单
    • 个人认为其最大的优势就是:产业化规模更大。目前觉大数据的CPU厂商(如AMD,Intel)生产的就是这种处理器
  • 劣势
    • 通用寄存器组——X86指令集只有8个通用寄存器。所以,CISC的CPU执行是大多数时间是在访问存储器中的数据,而不是寄存器中的。这就是拖慢了整个系统的速度。而RISC系统往往具有非常多的通用寄存器,并采用了重叠寄存器窗口和寄存器堆等技术使寄存器资源得到充分的利用。
    • 解码——这是X86 CPU才有的东西。其作用是把长度不定的X86指令转换为长度固定的类似于RISC的指令,并交给RISC内核。解码分为硬件解码和微解码,对于简单的X86指令只要硬件解码即可,速度较快,而遇到复杂的X86指令则需要进行微解码,并把它分为若干条简单指令,速度较慢且很复杂
    • 寻址范围小——约束了用户需要
    • 计算机各部分的利用率不高,执行速度慢

ARM

ARM是高级精简指令集的简称(Advanced RISC Machine),它是一个32位的精简指令集架构,但也配备16位指令集,一般来讲比等价32位代码节省达35%,却能保留32位系统的所有优势。

  • 优势:
    • 体积小、低功耗、低成本、高性能——ARM被广泛应用在嵌入式系统中的最重要的原因
    • 支持Thumb(16位)/ARM(32位)双指令集,能很好的兼容8位/16位器件
    • 大量使用寄存器,指令执行速度更快
    • 大多数数据操作都在寄存器中完成
    • 寻址方式灵活简单,执行效率高
    • 流水线处理方式
    • 指令长度固定,大多数是简单指定且都能在一个时钟周期内完成,易于设计超标量与流水线
  • 劣势
    • 性能差距稍大。ARM要在性能上接近X86,频率必须比X86处理器高很多,但是频率一高能耗就疯涨,抵消了ARM的优点

MIPS

MIPS架构(英语:MIPS architecture,为Microprocessor without interlocked piped stages architecture的缩写,亦为Millions of Instructions Per Second的相关语),是一种采取精简指令集(RISC)的处理器架构,1981年出现,由MIPS科技公司开发并授权,广泛被使用在许多电子产品、网络设备、个人娱乐装置与商业装置上。最早的MIPS架构是32位,最新的版本已经变成64位。 它的基本特点是:

  • 包含大量的寄存器、指令数和字符
  • 可视的管道延时时隙

这些特性使MIPS架构能够提供最高的每平方毫米性能和当今SoC设计中最低的能耗。

  • 优势
    • MIPS支持64bit指令和操作,ARM目前只到32bit
    • MIPS有专门的除法器,可以执行除法指令
    • MIPS的内核寄存器比ARM多一倍,所以同样的性能下MIPS的功耗会比ARM更低,同样功耗下性能比ARM更高
    • MIPS指令比ARM稍微多一点,稍微灵活一点
    • MIPS开放
  • 劣势
    • MIPS的内存地址起始有问题,这导致了MIPS在内存和Cache的支持方面都有限制,现在的MIPS处理器单内核面对高容量内存时有问题
    • MIPS今后的发展方向时并行线程,类似INTEL的超线程,而ARM未来的发展方向时物理多核,目前看来物理多核占优
    • MIPS虽然结构更加简单,但是到现在还是顺序单发射,ARM已经进化到了乱序双发射

PowerPC

内存管理

虚拟内存

单片机是没有操作系统的,所以每次写完代码,都需要借助工具把程序烧录进去,这样程序才能跑起来。另外,单片机的 CPU 是直接操作内存的「物理地址」。

在这里插入图片描述

在这种情况下,要想在内存中同时运行两个程序是不可能的。如果第一个程序在 2000 的位置写入一个新的值,将会擦掉第二个程序存放在相同位置上的所有内容,所以同时运行两个程序是根本行不通的,这两个程序会立刻崩溃。

操作系统的解决方案

在这里插入图片描述

**操作系统会提供一种机制,将不同进程的虚拟地址和不同内存的物理地址映射起来,然后为每个进程分配独立的一套虚拟地址。**如果程序要访问虚拟地址的时候,由操作系统转换成不同的物理地址,不同的进程运行的时候,写入的是不同的物理地址,这样就不会冲突了。于是,这里就引出了两种地址的概念:

  • 我们程序所使用的内存地址叫做:虚拟内存地址(Virtual Memory Address)
  • 实际存在硬件里面的空间地址叫:物理内存地址(Physical Memory Address)

操作系统引入了虚拟内存,进程持有的虚拟地址会通过 CPU 芯片中的内存管理单元(MMU)的映射关系,来转换变成物理地址,然后再通过物理地址访问内存,如下图所示:

在这里插入图片描述

操作系统管理虚拟地址与物理地址之间关系的方式:

  • 内存分段(比较早提出)
  • 内存分页

内存分段

程序是由若干个逻辑分段组成的,如可由代码分段、数据分段、栈段、堆段组成。不同的段是有不同的属性的,所以就用分段(Segmentation)的形式把这些段分离出来。

① 分段机制中的虚拟地址和物理地址映射

分段机制下的虚拟地址由两部分组成,段选择因子段内偏移量

在这里插入图片描述

  • 段选择因子就保存在段寄存器里面。段选择因子里面最重要的是段号,用作段表的索引。段表里面保存的是这个段的基地址、段的界限和特权等级
  • 虚拟地址中的段内偏移量应该位于 0 和段界限之间,如果段内偏移量是合法的,就将段基地址加上段内偏移量得到物理内存地址

② 程序虚拟地址映射

在上面,知道了虚拟地址是通过段表与物理地址进行映射的,分段机制会把程序的虚拟地址分成 4 个段,每个段在段表中有一个项,在这一项找到段的基地址,再加上偏移量,于是就能找到物理内存中的地址,如下图:

在这里插入图片描述

如果要访问段 3 中偏移量 500 的虚拟地址,我们可以计算出物理地址为,段 3 基地址 7000 + 偏移量 500 = 7500。分段的办法很好,解决了程序本身不需要关心具体的物理内存地址的问题,但它也有一些不足之处:

  • 产生内存碎片
  • 内存交换效率低

③ 内存碎片

内存碎片主要有两类问题:

  • 外部内存碎片:产生了多个不连续的小物理内存,导致新的程序无法被装载。
  • 内部内存碎片:程序所有内存都被装载到物理内存,但程序有部分的内存可能并不是很常使用,这也会导致内存浪费

解决方案

  • 解决外部内存碎片的问题就是:内存交换。先把占用内存的程序写到硬盘中,然后再从硬盘读到内存中(装在位置已变)

④ 内存交换效率低

对于多进程的系统来说,用分段的方式,内存碎片是很容易产生的,产生了内存碎片,那不得不重新 Swap 内存区域,这个过程会产生性能瓶颈。因为硬盘的访问速度要比内存慢太多了,每一次内存交换,我们都需要把一大段连续的内存数据写到硬盘上。所以,如果内存交换的时候,交换的是一个占内存空间很大的程序,这样整个机器都会显得卡顿。

为了解决内存分段内存碎片内存交换效率低的问题,就出现了内存分页

内存分页

分段的好处就是能产生连续的内存空间,但是会出现内存碎片和内存交换的空间太大的问题。要解决这些问题,那么就要想出能少出现一些内存碎片的办法。另外,当需要进行内存交换的时候,让需要交换写入或者从磁盘装载的数据更少一点,这样就可以解决问题了。这个办法,也就是内存分页(Paging)。

① 内存分页(Paging)

分页是把整个虚拟和物理内存空间切成一段段固定尺寸的大小。这样一个连续并且尺寸固定的内存空间,我们叫Page)。在 Linux 下,每一页的大小为 4KB。虚拟地址与物理地址之间通过页表来映射,如下图:

在这里插入图片描述

页表实际上存储在 CPU 的内存管理单元 (MMU) 中,于是 CPU 就可以直接通过 MMU,找出要实际要访问的物理内存地址。而当进程访问的虚拟地址在页表中查不到时,系统会产生一个缺页异常,进入系统内核空间分配物理内存、更新进程页表,最后再返回用户空间,恢复进程的运行。

② 分页解决分段问题

由于内存空间都是预先划分好的,也就不会像分段会产生间隙非常小的内存,这正是分段会产生内存碎片的原因。而采用了分页,那么释放的内存都是以页为单位释放的,也就不会产生无法给进程使用的小内存。

如果内存空间不够,操作系统会把其他正在运行的进程中的「最近没被使用」的内存页面给释放掉,也就是暂时写在硬盘上,称为换出(Swap Out)。一旦需要的时候,再加载进来,称为换入(Swap In)。所以,一次性写入磁盘的也只有少数的一个页或者几个页,不会花太多时间,内存交换的效率就相对比较高。

在这里插入图片描述

更进一步地,分页的方式使得我们在加载程序的时候,不再需要一次性都把程序加载到物理内存中。我们完全可以在进行虚拟内存和物理内存的页之间的映射之后,并不真的把页加载到物理内存里,而是只有在程序运行中,需要用到对应虚拟内存页里面的指令和数据时,再加载到物理内存里面去。

③ 分页机制中虚拟地址和物理地址的映射

在分页机制下,虚拟地址分为两部分,页号页内偏移。页号作为页表的索引,页表包含物理页每页所在物理内存的基地址,这个基地址与页内偏移的组合就形成了物理内存地址,见下图:

在这里插入图片描述

总结一下,对于一个内存地址转换,其实就是这样三个步骤:

  • 把虚拟内存地址,切分成页号和偏移量
  • 根据页号,从页表里面,查询对应的物理页号
  • 直接拿物理页号,加上前面的偏移量,就得到了物理内存地址

④ 简单分页缺陷

有空间上的缺陷。因为操作系统是可以同时运行非常多的进程的,那这不就意味着页表会非常的庞大。

在 32 位的环境下,每个进程的虚拟地址空间共有 4GB,假设一个页的大小是 4KB(2^12),那么就需要大约 100 万 (2^20) 个页,每个「页表项」需要 4 个字节大小来存储,那么整个 4GB 空间的映射就需要有 4MB 的内存来存储页表。

这 4MB 大小的页表,看起来也不是很大。但是要知道每个进程都是有自己的虚拟地址空间的,也就说都有自己的页表。

那么,100 个进程的话,就需要 400MB 的内存来存储页表,这是非常大的内存了,更别说 64 位的环境了。

⑤ 多级页表

要解决上面的问题,就需要采用的是一种叫作多级页表(Multi-Level Page Table)的解决方案。

把这个 100 多万个「页表项」的单级页表再分页,将页表(一级页表)分为 1024 个页表(二级页表),每个表(二级页表)中包含 1024 个「页表项」,形成二级分页。如下图所示:

在这里插入图片描述

如果使用了二级分页,一级页表就可以覆盖整个 4GB 虚拟地址空间,但如果某个一级页表的页表项没有被用到,也就不需要创建这个页表项对应的二级页表了,即可以在需要时才创建二级页表。做个简单的计算,假设只有 20% 的一级页表项被用到了,那么页表占用的内存空间就只有 4KB(一级页表) + 20% * 4MB(二级页表)= 0.804MB,这对比单级页表的 4MB 是不是一个巨大的节约?

那么为什么不分级的页表就做不到这样节约内存呢?我们从页表的性质来看,保存在内存中的页表承担的职责是将虚拟地址翻译成物理地址。假如虚拟地址在页表中找不到对应的页表项,计算机系统就不能工作了。所以页表一定要覆盖全部虚拟地址空间,不分级的页表就需要有 100 多万个页表项来映射,而二级分页则只需要 1024 个页表项(此时一级页表覆盖到了全部虚拟地址空间,二级页表在需要时创建)。我们把二级分页再推广到多级页表,就会发现页表占用的内存空间更少了,这一切都要归功于对局部性原理的充分应用。

对于 64 位的系统,两级分页肯定不够了,就变成了四级目录,分别是:

  • 全局页目录项 PGD(Page Global Directory)
  • 上层页目录项 PUD(Page Upper Directory)
  • 中间页目录项 PMD(Page Middle Directory)
  • 页表项 PTE(Page Table Entry)

在这里插入图片描述

⑥ TLB

多级页表虽然解决了空间上的问题,但是虚拟地址到物理地址的转换就多了几道转换的工序,这显然就降低了这俩地址转换的速度,也就是带来了时间上的开销。程序是有局部性的,即在一段时间内,整个程序的执行仅限于程序中的某一部分。相应地,执行所访问的存储空间也局限于某个内存区域。

我们就可以利用这一特性,把最常访问的几个页表项存储到访问速度更快的硬件,于是计算机科学家们,就在 CPU 芯片中,加入了一个专门存放程序最常访问的页表项的 Cache,这个 Cache 就是 TLB(Translation Lookaside Buffer) ,通常称为页表缓存、转址旁路缓存、快表等。

在这里插入图片描述

在 CPU 芯片里面,封装了内存管理单元(Memory Management Uni)芯片,它用来完成地址转换和 TLB 的访问与交互。有了 TLB 后,那么 CPU 在寻址时,会先查 TLB,如果没找到,才会继续查常规的页表。TLB 的命中率其实是很高的,因为程序最常访问的页就那么几个。

段页式内存管理

内存分段和内存分页并不是对立的,是可以组合起来在同一个系统中使用的,那么组合起来后,通常称为段页式内存管理

在这里插入图片描述

段页式内存管理实现的方式:

  • 先将程序划分为多个有逻辑意义的段,也就是前面提到的分段机制
  • 接着再把每个段划分为多个页,也就是对分段划分出来的连续空间,再划分固定大小的页

这样地址结构就由段号、段内页号和页内位移三部分组成。用于段页式地址变换的数据结构是每一个程序一张段表,每个段又建立一张页表,段表中的地址是页表的起始地址,而页表中的地址则为某页的物理页号,如图所示:

在这里插入图片描述

段页式地址变换中要得到物理地址须经过三次内存访问:

  • 第一次访问段表,得到页表起始地址
  • 第二次访问页表,得到物理页号
  • 第三次将物理页号与页内位移组合,得到物理地址

可用软、硬件相结合的方法实现段页式地址变换,这样虽然增加了硬件成本和系统开销,但提高了内存的利用率。

Linux 内存管理

① Intel处理器的发展历史

早期 Intel 的处理器从 80286 开始使用的是段式内存管理。但是很快发现,光有段式内存管理而没有页式内存管理是不够的,这会使它的 X86 系列会失去市场的竞争力。因此,在不久以后的 80386 中就实现了对页式内存管理。也就是说,80386 除了完成并完善从 80286 开始的段式内存管理的同时,还实现了页式内存管理。

但是这个 80386 的页式内存管理设计时,没有绕开段式内存管理,而是建立在段式内存管理的基础上,这就意味着,页式内存管理的作用是在由段式内存管理所映射而成的地址上再加上一层地址映射。

由于此时由段式内存管理映射而成的地址不再是“物理地址”了,Intel 就称之为“线性地址”(也称虚拟地址)。于是,段式内存管理先将逻辑地址映射成线性地址,然后再由页式内存管理将线性地址映射成物理地址。

  • 逻辑地址:程序所使用的地址,通常是没被段式内存管理映射的地址。即段式内存管理转换前的地址
  • 线性地址:也叫虚拟地址,通过段式内存管理映射的地址。页式内存管理转换前的地址

② Linux管理内存

Linux 系统主要采用了分页管理,但是由于 Intel 处理器的发展史,Linux 系统无法避免分段管理。于是 Linux 就把所有段的基地址设为 0,每个段都是从 0 地址开始的整个 4GB 虚拟空间(32 位环境下),也就意味着所有程序的地址空间都是线性地址空间(虚拟地址),相当于屏蔽了 CPU 逻辑地址的概念,所以段只被用于访问控制和内存保护。

③ Linux的虚拟地址空间分布

在 Linux 操作系统中,虚拟地址空间的内部又被分为内核空间和用户空间两部分,不同位数的系统,地址空间的范围也不同。比如最常见的 32 位和 64 位系统,如下所示:

在这里插入图片描述

  • 32 位系统的内核空间占用 1G,位于最高处,剩下的 3G 是用户空间
  • 64 位系统的内核空间和用户空间都是 128T,分别占据整个内存空间的最高和最低处,剩下的中间部分是未定义的

④ 内核空间与用户空间

  • 进程在用户态时,只能访问用户空间内存
  • 只有进入内核态后,才可以访问内核空间的内存;

虽然每个进程都各自有独立的虚拟内存,但是每个虚拟内存中的内核地址,其实关联的都是相同的物理内存。这样,进程切换到内核态后,就可以很方便地访问内核空间内存。

在这里插入图片描述

⑤ 32位操作系统中的用户空间分布

用户空间内存,从低到高分别是 7 种不同的内存段:

  • 程序文件段,包括二进制可执行代码
  • 已初始化数据段,包括静态常量
  • 未初始化数据段,包括未初始化的静态变量
  • 堆段,包括动态分配的内存,从低地址开始向上增长
  • 文件映射段,包括动态库、共享内存等,从低地址开始向上增长(跟硬件和内核版本有关)
  • 栈段,包括局部变量和函数调用的上下文等。栈的大小是固定的,一般是 8 MB。当然系统也提供了参数,以便我们自定义大小

在这里插入图片描述

在这 7 个内存段中,堆和文件映射段的内存是动态分配的。比如说,使用 C 标准库的 malloc() 或者 mmap() ,就可以分别在堆和文件映射段动态分配内存。

相关文章:

  • Ubuntu 一站式部署 RabbitMQ 4 并“彻底”迁移数据目录的终极实践
  • 【回眸】Aurix TC397 IST 以太网 UDP 相关开发
  • 观成科技:摩诃草组织Spyder下载器流量特征分析
  • SpringCloud——负载均衡
  • Mediamtx与FFmpeg远程与本地推拉流使用
  • 信息系统项目管理工程师备考计算类真题讲解七
  • 【晶振】晶振的工作原理及其与单片机关系
  • 【C语言】C语言中的联合体与枚举类型
  • 网站高可用架构设计基础——高可用策略和架构原则
  • 关于nginx,负载均衡是什么?它能给我们的业务带来什么?怎么去配置它?
  • Spring Boot + MyBatis-Plus 的现代开发模式
  • uniapp跨平台开发---switchTab:fail page `/undefined` is not found
  • HTML邮件背景图兼容 Outlook
  • HTML响应式网页设计与跨平台适配
  • 基于RFID的智能家居系统设计与实现
  • C++:函数重载
  • 【Python Web开发】02-Socket网络编程02
  • 从零到精通:全面解析人工智能的核心技术与发展趋势
  • 矩阵运营的限流问题本质上是平台与创作者之间的流量博弈
  • 高德MCP制作旅游攻略
  • 84%白化!全球珊瑚正经历最严重最大范围白化现象
  • 詹妮弗·劳伦斯、罗伯特·帕丁森新片入围戛纳主竞赛单元
  • 吉林建筑大学党委原书记崔征接受纪律审查和监察调查
  • 苗旋已任民航局空管局局长、党委副书记
  • 郑庆华任同济大学党委书记
  • 搜索市场战火延伸:OpenAI虎视眈眈,欲收购谷歌浏览器