操作系统——内存管理(面试准备)

虚拟内存

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

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

操作系统如何解决

这里的关键问题是这两个程序都引用了绝对物理地址,这是我们最需要避免的。

我们可以把进程使用的地址隔离开来,让操作系统为每个进程分配一套独立的虚拟地址,人人都有,大家只操作自己的地址,互不干涉。
但是有个前提是每个进程都不能访问物理地址,至于虚拟地址最终怎么落到物理内存里,对进程来说是透明的,由操作系统安排。

在这里插入图片描述
操作系统会提供一种机制,将不同的虚拟地址和不同内存的物理地址映射起来。
如果程序要访问虚拟内存的时候,由操作系统转换成不同的物理地址,这样不同的进程运行的时候,写入的是不同的物理地址,这样就不会冲突了。

  • 我们程序使用的内存地址叫做虚拟内存地址
  • 存在硬件空间地址叫做物理内存地址

操作系统引入虚拟内存,进程将持有的虚拟地址通过CPU芯片中的内存管理单元MMU的映射关系,转换变成物理地址,再通过物理地址访问内存。

在这里插入图片描述
操作系统如何管理虚拟地址和物理地址之间的关系呢?

内存分段

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

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

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

内存分段的缺点:

  • 内存碎片的问题。
  • 内存交换的效率低的问题。

我们来看这样一个例子。我现在手头的这台电脑,有 1GB 的内存。我们先启动一个图形渲染程序,占用了 512MB 的内存,接着启动一个 Chrome 浏览器,占用了 128MB 内存,再启动一个 Python 程序,占用了 256MB 内存。这个时候,我们关掉 Chrome,于是空闲内存还有 1024 - 512 - 256 = 256MB。按理来说,我们有足够的空间再去装载一个 200MB 的程序。但是,这 256MB 的内存空间不是连续的,而是被分成了两段 128MB 的内存。因此,实际情况是,我们的程序没办法加载进来。

内存交换
我们可以把Python程序占用的256MB内存写到硬盘上,然后再从硬盘上读回来到内存里面。不过读回来的时候,我们不再把它加载到原来的位置,而是紧紧跟在那已经被占用了的 512MB 内存后面。这样,我们就有了连续的 256MB 内存空间,就可以去加载一个新的 200MB 的程序。

如果自己安装过Linux操作系统,应该遇到过分配一个swap硬盘分区的问题。这块分出来的磁盘空间,就是专门给Linux操作系统进行内存交换用的。

虚拟内存、分段,再加上内存交换,看起来似乎已经解决了计算机同时装载运行很多个程序的问题。不过,你千万不要大意,这三者的组合仍然会遇到一个性能瓶颈。硬盘的访问速度要比内存慢很多,而每一次内存交换,我们都需要把一大段连续的内存数据写到硬盘上。所以,如果内存交换的时候,交换的是一个很占内存空间的程序,这样整个机器都会显得卡顿。为了解决内存分段的内存碎片和内存交换效率低的问题,就出现了内存分页。

内存分页

分段的好处是能产生连续的内存空间,但是会出现内存碎片和内存交换的空间太大的问题。

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

分页是把整个虚拟和物理内存空间切成一段段固定尺寸的大小。这样一个连续并且尺寸固定的内存空间,称为页,Linux下,每一页为4KB。

虚拟地址与物理地址之间通过页表来映射。

页表实际上存储在CPU的内存管理单元(MMU)中,于是CPU可以直接通过MMU,找出要实际访问的物理内存地址。

当进程要访问的虚拟地址在页表中查不到时,系统会产生一个缺页异常,进入系统内核空间分配物理内存,更新进程页表,最后返回用户空间,恢复进程的运行。

分页怎么解决分段的内存碎片、内存交换效率低的问题?

由于内存空间都是预先划分好的,也就不会像分段会产生间隙非常小的内存,采用了分页,释放的内存都是以页为单位释放的,也就不会产生给进程使用的小内存。

如果内存空间不够,操作系统会把其它正在运行的进程中最近没使用的内存页面给释放掉,也就是暂时写作硬盘上,称为换出。一旦需要的时候,再加载进来,称为换入。

一次性写入磁盘的只有少数的一个页或者几个页,不会花太多时间,内存交换的效率也就相对较高。

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

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

对于一个内存地址转换,其实也就三个步骤:

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

简单分页

因为操作系统可以同时运行非常多的进程,也就意味着页表会非常庞大。

在32位的环境下,虚拟地址空间有4GB,假设一个页的大小是4KB,那么大概需要100万个页,每个页表项需要4个字节大小存储,那么共需要4MB的内存存储页表。

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

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

多级页表

要解决上面的问题,就需要采用一种叫做多级页表的解决方案。

对于单页表的实现方式,在32位和页大小4KB的环境下,一个进程的页表需要装下100多万个页表项,并且每个页表项占用4字节大小,于是每个页表占用4MB大小的空间。

我们把这个100多万个页表项的单级页表再分页,将页表分为1024个页表(二级页表),每个表(二级页表)中包含1024个页表项,形成二级分页。

为什么多级分页比普通分页更节省内存?

分了二级页表,映射4GB地址空间就需要4KB(一级页表)+4MB(二级页表)的内存,这样占用空间不是更大了吗?

当然如果4GB的虚拟地址全部映射到了物理内存上,二级分页占用空间确实更大了,但是,我们不会为一个进程分配那么多内存。

每个进程都有4GB的虚拟地址空间,而对于大多数程序来说,其使用到的空间远未达到4GB,因为会存在部分对应的页表项都是空的,根本没有分配,对于已分配的页表项,如果存在最近一定时间未访问的页表,在物理内存紧张情况下,操作系统会将页面换出到硬盘,也就是说不会占用物理内存。

如果使用了二级分页,一级页表就可以覆盖整个4GB的虚拟地址空间,如果某一个一级页表的页表项没有被用到,就不需要创建这个页表项对应的二级页表了,即可以在需要时创建二级页表。

程序局部性原理

程序在运行时,对数据的访问往往呈现出局部性特征,即在一段时间内,程序的大部分执行都集中在程序的某一部分,并且这段代码所引用的数据也大多位于相邻的内存区域。

程序局部性原理可以分为两种形式:

  • 时间局部性:在程序执行过程中,如果某个指令或数据已经被访问过,那么在不久之后,该指令或数据很可能再次被访问。例如,在循环体内,同一组指令和数据会被反复执行多次。
  • 空间局部性:某个存储单元被访问过,那么在不久之后,其相邻的存储单元也很可能被访问,例如,数组中的相邻元素通常会被连续访问。

根据程序局部性原理,计算机体系结构中设计了多级缓存、虚拟存储器等技术,操作系统中设计了页面调度、LRU缓存替换算法等机制。

页表缓存TLB

多级页表虽然解决了空间上的问题,但是虚拟地址到物理地址的转换就多了几道转换的工序,这显然就降低了这两个地址转换的速度,也就是带来了时间上的开销。

程序是有局部性的,即在一段时间内,整个程序的执行仅限于程序中某一部分。相应地,执行所访问的存储空间也局限于某个内存区域。

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

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

Linux内存管理

在Linux操作系统中,虚拟地址空间的内部又被分为内核空间和用户空间两部分。

在这里插入图片描述
32位系统的内核空间占用1G,位于最高处,剩下的3G是用户空间。

内核空间与用户空间的区别:

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

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

在这里插入图片描述

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

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

相关文章

(CVPR-2024)SwiftBrush:具有变分分数蒸馏的单步文本到图像扩散模型

SwiftBrush:具有变分分数蒸馏的单步文本到图像扩散模型 Paper Title:SwiftBrush: One-Step Text-to-Image Diffusion Model with Variational Score Distillation Paper 是 VinAI Research 发表在 CVPR 24 的工作 Paper地址 Code:地址 Abstract 尽管文本…

Flutter-实现物理小球碰撞效果

效果 引言 在Flutter应用中实现物理动画效果,可以大大提升用户体验。本文将详细介绍如何在Flutter中创建一个模拟物理碰撞的动画小球界面,主要代码实现基于集成sensors_plus插件来获取设备的加速度传感器数据。 准备工作 在开始之前,请确保…

一文详解DDL同步及其应用场景

目录 一、什么是DDL? 二、什么是DDL同步? 三、DDL同步的痛点 1、缺少自动DDL同步机制 2、缺少DDL变更监测预警 四、解决方案 五、应用场景及案例 案例一 案例二 案例三 在现代数据管理中,数据库的结构变更频繁且不可避免,特别是在…

Kubelet 认证

当我们执行kubectl exec -it pod [podName] sh命令时,apiserver会向kubelet发起API请求。也就是说,kubelet会提供HTTP服务,而为了安全,kubelet必须提供HTTPS服务,且还要提供一定的认证与授权机制,防止任何知…

C语言 | Leecode C语言题解之第229题多数元素II

题目: 题解: /*** Note: The returned array must be malloced, assume caller calls free().*//*假定 num1,num2 为出现次数大于 nums.length / 3 的两个数。(最多出现两个)遍历 nums, 若出现 num1、num2…

《C语言程序设计 第4版》笔记和代码 第十一章 指针和数组

第十一章 指针和数组 11.1 指针和一维数组间的关系 1 由于数组名代表数组元素的连续存储空间的首地址,因此,数组元素既可以用下标法也可以用指针来引用。 例11.1见文末 2 p1与p在本质上是两个不同的操作,前者不改变当前指针的指向&#xf…

无人机之遥控器保养

一、使用存放 1、避免让遥控器受到强烈的震动或从高处跌落,以免影响内部结构的精度; 2、遥控器在使用完后,需要将天线收拢,避免折断,养成定期检查天线的习惯; 3、定期检查遥控器按键有无裂纹、畸变、松旷…

ISO/OIS的七层模型②

OSI模型是一个分层的模型,每一个部分称为一层,每一层扮演固定的角色,互不干扰。OSI有7层,从上到下分别是: 一,每层功能 7.应用层(Application layer ):应用层功能&#x…

如何从gitlab删除仓库

嗨,我是兰若姐姐。今天发现gitlab上有些仓库的代码没有用,是个多余的仓库,想要删掉,经过一番操作之后,成功的删除了,git上没有 多余的仓库,看着干净舒服很多,现在把删除的过程分享出…

基于ssm的图书管理系统的设计与实现

摘 要 在当今信息技术日新月异的时代背景下,图书管理领域正经历着深刻的变革,传统的管理模式已难以适应现代社会的快节奏和高要求,逐渐向数字化、智能化的方向演进。本论文聚焦于这一转变趋势,致力于设计并成功实现一个基于 SSM&…

U-net和U²-Net网络详解

目录 U-Net: Convolutional Networks for Biomedical Image Segmentation摘要U-net网络结构pixel-wise loss weight U-Net: Going Deeper with Nested U-Structure for Salient Object Detection摘要网络结构详解整体结构RSU-n结构RSU-4F结构saliency map fusion module -- 显著…

JavaFx+MySql学生管理系统

前言: 上个月学习了javafx和mysql数据库,于是写了一个学生管理系统,因为上个月在复习并且有一些事情,比较忙,所以没有更新博客了,这个项目页面虽然看着有点简陋了,但是大致内容还是比较简单的,于是现在跟大家分享一下我的学生管理系统,希望对这方面有兴趣的同学提供一些帮助 &a…

Vue 3 中创建一个动态的组件实例

本文将介绍如何在 Vue 3 中实现一个动态 Toast 组件实例。我们将创建一个简单的 Toast 组件,并使用一个动态创建实例的脚本来显示 Toast 消息。在 Vue 3 中创建动态组件实例有许多好处,这些好处主要体现在灵活性、性能、可维护性和用户体验等方面。 创建…

【JavaScript 算法】快速排序:高效的排序算法

🔥 个人主页:空白诗 文章目录 一、算法原理二、算法实现三、应用场景四、优化与扩展五、总结 快速排序(Quick Sort)是一种高效的排序算法,通过分治法将数组分为较小的子数组,递归地排序子数组。快速排序通常…

分享一个 .NET 通过监听器拦截 EF 消息写日志的详细例子

前言 EF 开发效率确实很高也很便捷,但当它发生错误时,也挺让人头疼的,为什么?因为 EF 就像是一个黑盒子,一切全被封装起来,出错的时候很难定位原因,如果能够知道并打印 EF 生成的 SQL 语句&…

记录些Redis题集(1)

为什么Redis要有淘汰机制? 淘汰机制的存在是必要的,因为Redis是一种基于内存的数据库,所有数据都存储在内存中。然而,内存资源是有限的。在Redis的配置文件redis.conf中,有一个关键的配置项: # maxmemory…

Go语言入门之Map详解

Go语言入门之Map详解 1.基础定义 map是一个无序的,k-v键值对格式的集合 (1)特点 类型特点:map为引用类型,所以在函数中更新value值会永久改变顺序特点:map的遍历是无序的,因为底层是哈希表&am…

零基础学python(二)

1. 字典 字典的创建 最常见的创建方式: d {"k1": "v1", "k2": "v2"} 再向字典中添加一对键值: d["k3"] "v3" 字典还可以用dict()创建,这种方式中,键是不加引…

【Unity2D 2022:UI】制作主菜单

一、创建主菜单游戏场景 1. 在Scenes文件夹中新建一个游戏场景Main Menu 2. 为场景添加背景 (1)创建画布Canvas (2)在Canvas中创建新的空游戏物体Main Menu (3)在Main Menu中新建一个图像游戏物体Backgrou…

无人机之机身保养

一、外观检查 1、检查机器表面整洁无划痕、无针孔凹陷擦伤、畸变等损坏情况; 2、晃动机身,仔细听机身内部有无松动零件或者螺丝在机身内部。 二、桨叶检查 1、有无裂痕、磨损、变形等缺陷,如有明显缺陷建议更换; 2、卡扣、紧…