操作系统实战45讲笔记 -06 程序中的地址如何转换

每个应用程序的虚拟地址空间都是相同且独立的。

虚拟地址是由链接器产生的。开发软件经过编译步骤后,就需要链接成可执行文件才可以运行,而链接器的主要工作就是把多个代码模块组装在一起,并解决模块之间的引用,即处理程序代码间的地址引用,形成程序运行的静态内存空间视图。

这个地址是虚拟而统一的,而根据操作系统的不同,这个虚拟地址空间的定义也许不同,应用软件开发人员无需关心,由开发工具链给自动处理了。由于这虚拟地址是独立且统一的,所以各个公司开发的各个应用完全不用担心自己的内存空间被占用和改写。

虚拟地址只是逻辑上存在的地址,无法作用于硬件电路的,程序装进内存中想要执行,就需要和内存打交道,从内存中取得指令和数据。而内存只认一种地址,那就是物理地址。

物理地址在逻辑上也是一个数据,只不过这个数据会被地址译码器等电子器件变成电子信号,放在地址总线上,地址总线电子信号的各种组合就可以选择到内存的储存单元了。

地址总线上的信号(即物理地址),也可以选择到别的设备中的储存单元,如显卡中的显存、I/O 设备中的寄存器、网卡上的网络帧缓存器。不过如果不做特别说明,我们说的物理地址就是指选择内存单元的地址。

MMU 可以接受软件给出的地址对应关系数据,进行地址转换。

逻辑上的 MMU 工作原理框架图。如下图所示:

上图中展示了 MMU 通过地址关系转换表,将 0x80000~0x84000 的虚拟地址空间转换成 0x10000~0x14000 的物理地址空间,而地址关系转换表本身则是放物理内存中的。

把虚拟地址空间和物理地址空间都分成同等大小的块,也称为页,按照虚拟页和物理页进行转换。根据软件配置不同,这个页的大小可以设置为 4KB、2MB、4MB、1GB,这样就进入了现代内存管理模式——分页模型。

下面来看看分页模型框架,如下图所示:

结合图片可以看出,一个虚拟页可以对应到一个物理页,由于页大小一经配置就是固定的,所以在地址关系转换表中,只要存放虚拟页地址对应的物理页地址就行了。

MMU 即内存管理单元,是用硬件电路逻辑实现的一个地址转换器件,它负责接受虚拟地址和地址关系转换表,以及输出物理地址。根据实现方式的不同,MMU 可以是独立的芯片,也可以是集成在其它芯片内部的,比如集成在 CPU 内部,x86、ARM 系列的 CPU 就是将 MMU 集成在 CPU 核心中的。

x86 CPU 要想开启 MMU,就必须先开启保护模式或者长模式,实模式下是不能开启 MMU 的。由于保护模式的内存模型是分段模型,它并不适合于 MMU 的分页模型,所以我们要使用保护模式的平坦模式,这样就绕过了分段模型。这个平坦模型和长模式下忽略段基址和段长度是异曲同工的。地址产生的过程如下所示。

程序代码中的虚拟地址,经过 CPU 的分段机制产生了线性地址,平坦模式和长模式下线性地址和虚拟地址是相等的。

长模式下的分段弱化了地址空间的隔离,所以开启 MMU 是必须要做的,开启 MMU 才能访问内存地址空间。

页表,描述了虚拟地址到物理地址的转换关系,也可以说是虚拟页到物理页的映射关系。在这里又把线性地址叫做虚拟地址,虚拟地址和线性地址在很多时候没有严格的区分。在这里又把线性地址叫做虚拟地址,虚拟地址和线性地址在很多时候没有严格的区分。

为了增加灵活性和节约物理内存空间(因为页表是放在物理内存中的),所以页表中并不存放虚拟地址和物理地址的对应关系,只存放物理页面的地址,MMU 以虚拟地址为索引去查表返回物理页面地址,而且页表是分级的,总体分为三个部分:一个顶级页目录,多个中级页目录,最后才是页表,逻辑结构图如下。

从上面可以看出,一个虚拟地址被分成从左至右四个位段。第一个位段索引顶级页目录中一个项,该项指向一个中级页目录,然后用第二个位段去索引中级页目录中的一个项,该项指向一个页目录,再用第三个位段去索引页目录中的项,该项指向一个物理页地址,最后用第四个位段作该物理页内的偏移去访问物理内存。这就是 MMU 的工作流程

分页模式的灵活性、通用性、安全性,是现代操作系统内存管理的基石,更是事实上的标准内存管理模型,

以x86 CPU 上的分页模式为例,保护模式下只有 32 位地址空间,最多 4GB-1 大小的空间。根据前面得知 32 位虚拟地址经过分段机制之后得到线性地址,又因为通常使用平坦模式,所以线性地址和虚拟地址是相同的。

保护模式下的分页大小通常有两种,一种是 4KB 大小的页,一种是 4MB 大小的页。分页大小的不同,会导致虚拟地址位段的分隔和页目录的层级不同,但虚拟页和物理页的大小始终是等同的。

该分页方式下,32 位虚拟地址被分为三个位段:页目录索引、页表索引、页内偏移,只有一级页目录,其中包含 1024 个条目 ,每个条目指向一个页表,每个页表中有 1024 个条目。其中一个条目就指向一个物理页,每个物理页 4KB。这正好是 4GB 地址空间。如下图所示。(注:页目录索引:占10bit,所以只能包含1024个条目 页表索引:同样占10bit,所以也只能包含1024个条目 页内偏移:占12bit,2的12次方对应4kb,代表每个物理页占4kb)

上图中 CR3 就是 CPU 的一个 32 位的寄存器,MMU 就是根据这个寄存器找到页目录的。下图是分页模式下的 CR3、页目录项、页表项的格式。

可以看到,页目录项、页表项都是 4 字节 32 位,1024 个项正好是 4KB(一个页),因此它们的地址始终是 4KB 对齐的,所以低 12 位才可以另作它用,形成了页面的相关属性,如是否存在、是否可读可写、是用户页还是内核页、是否已写入、是否已访问等。

32 位虚拟地址被分为两个位段:页表索引、页内偏移,只有一级页目录,其中包含 1024 个条目。其中一个条目指向一个物理页,每个物理页 4MB,正好为 4GB 地址空间(注: 1024*4MB=4096MB),如下图所示。

CR3和页表项的格式如下图:

页表项依然是4字节32位,只需高10位来保存物理页面的基地址即可(2^10=1024)。为了兼容 4MB 页表项,低 8 位和 4KB 页表项一样,第 7 位变成了 PS 位,且必须为 1,而 PAT 位移到了 12 位。

开启长模式,必须同时开启分页模式。长模式弱化了分段模式,分段模式不适应在现代OS和app的发展。

长模式下的虚拟地址必须等于线性地址且为 64 位。长模式下通常有4KB和2MB大小的页。

4KB 64位虚拟地址由6个分段组成,保留位段、顶级页目录索引、页目录指针索引、页目录索引、页表索引、页内偏移。

顶级页目录项、页目录指针、页目录、页表各占4KB大小,各有512个条目,每个条目8B(64bit)。(4KB/512=8B)

CR3是 64 位的 CPU 的寄存器,指向一个顶级页目录,里面的顶级页目项指向页目录指针,依次类推。虚拟地址 48 到 63 这 16 位是根据第 47 位来决定的,47 位为 1,它们就为 1,反之为 0,这是因为 x86 CPU 并没有实现全 64 位的地址总线,而是只实现了 48 位,但是 CPU 的寄存器却是 64 位。

当前分页模式下的 CR3、顶级页目录项、页目录指针项、页目录项、页表项的格式,如图:

顶级页目录项–>页目录指针页–>页目录页–>页表页–> 4KB 大小的物理页。其中的 XD 位,可以控制代码页面是否能够运行。

该模式下,64位虚拟地址被分为5个位段:保留位段、顶级页目录索引、页目录指针索引、页目录索引、页内偏移。

顶级页目录、页目录指针、页目录项各占有4KB大小,各有512个条目,每条目8B 64bit

2MB 分页下是页目录项直接指向了 2MB 大小的物理页面,放弃了页表项,然后把虚拟地址的低 21 位作为页内偏移,21 位正好索引 2MB 大小的地址空间。

2MB 分页模式下的 CR3、顶级页目录项、页目录指针项、页目录项的格式,格式如下图:

CPU 进入保护模式或者长模式 –> 开启MMU –> 使用分页模式。

开启MMU步骤:

2.准备好页表数据,这包含顶级页目录,中间层页目录,页表,假定我们已经编写了代码,在物理内存中生成了这些数据。

mov eax, PAGE_TLB_BADR ;页表物理地址
mov cr3, eax

4.设置 CPU 的 CR0 的 PE 位为 1,这样就开启了 MMU

开启 保护模式和分页模式

mov eax, cr0
bts eax, 0    #CR0.PE =1
bts eax, 31   #CR0.P = 1
mov cr0, eax

MMU地址存在转换失败的可能,例如:页表项中的数据为空,用户程序访问了超级管理者的页面,向只读页面中写入数据。这些都会导致 MMU 地址转换失败。

失败后,MMU执行的操作如下:

a. 多道程序同时运行有很多问题,内存需要隔离和保护。提出了虚拟地址与物理地址分离,让应用程序从实际的物理内存中解耦出来。

b. 虚拟地址必须转换成物理地址,才能在硬件上执行。为了执行这个转换过程,才开发出了 MMU(内存管理单元),MMU增加了转换的灵活性,它的实现方式是硬件执行转换过程,但又依赖于软件提供的地址转换表。

c. x86 CPU 上的 MMU 在其保护模式和长模式下提供 4KB、2MB、4MB 等页面转换方案

思考题:操作系统是如何对应用程序的地址空间进行隔离的?

参考答案:

使用虚拟地址。在保护模式下,每个进程独占4GB的内存空间,可以在权限允许的情况下进行任意内存地址的访问,但是这4GB内存空间只是逻辑上的,最终虚拟地址通过MMU和页表转换为物理地址,完成了地址空间上的隔离,保证最后不会存在地址上的冲突。,但是多个进程也是可以共享某个页表,这也是进程通信(IPC)的根本手段。

Original: https://www.cnblogs.com/xyjk1002-rejuvenation/p/16712183.html
Author: miyan
Title: 操作系统实战45讲笔记 -06 程序中的地址如何转换

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/581733/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Golang 实现 Redis(5): 使用跳表实现 SortedSet

    本文是使用 golang 实现 redis 系列的第五篇, 将介绍如何使用跳表实现有序集合(SortedSet)的相关功能。 跳表(skiplist) 是 Redis 中 Sort…

    Linux 2023年5月28日
    0101
  • c++ 跨平台线程同步对象那些事儿——基于 ace

    前言 ACE (Adaptive Communication Environment) 是早年间很火的一个 c++ 开源通讯框架,当时 c++ 的库比较少,以至于谈 c++ 网络通…

    Linux 2023年6月6日
    0108
  • 实验

    编写程序实现以下功能 编写程序,打印99乘法表 将一面额为10元倍数的整钱( 输入一行字符,统计其中单词的个数。各单词之间用空格分隔,空格数可以是多个。 输入输出示例 Input …

    Linux 2023年6月7日
    099
  • 【设计模式】Java设计模式-原型模式

    【设计模式】Java设计模式 – 原型模式 😄 不断学习才是王道🔥 继续踏上学习之路,学之分享笔记👊 总有一天我也能像各位大佬一样🏆原创作品,更多关注我CSDN: 一个…

    Linux 2023年6月6日
    0128
  • Centos7安装Docker

    一、docker运行流程 举个例子你想使用MySQL镜像,那么执行docker pull 下载镜像的时候 首先它会在本地仓库进行运行,如果本地仓库有你想要的MySQL镜像 那么它会…

    Linux 2023年6月6日
    089
  • UWP Add transport control button to taskbar preview

    I want to add transport control button to taskbar preview, like Netease Music. This is rea…

    Linux 2023年6月13日
    0115
  • django解析POST过来的json时,Unterminated string starting

    结论:我遇到的问题是与号( &), 分号( ; ), 等号( = ) 都会成为字符串分割符。导致后端解析json参数失败 1.bug产生背景 handsontable插件实…

    Linux 2023年6月8日
    0101
  • redis八种基本数据类型及其应用

    NoSQL 开发中或多或少都会用到,也是面试必问知识点。最近这几天的面试每一场都问到了。但是感觉回答的并不好,还有很多需要梳理的知识点。这里通过几篇 Redis 笔记整个梳理一遍,…

    Linux 2023年5月28日
    091
  • 了解GFS

    参考: https://wenku.baidu.com/view/4392293517791711cc7931b765ce0508763275f2.html 论文翻译 https:…

    Linux 2023年6月7日
    0112
  • 如何在shell脚本中传变量的值传给curl

    随着即时通讯的发展,大量的报警媒介已经从以往的邮件转为钉钉,企业微信等聊天工具。当我使用shell脚本来监控 Keepalived的时候,在给curl传递变量的时候无法生效,经过查…

    Linux 2023年6月8日
    099
  • grafana+prometheus如何查看tcp连接数量

    最后解决方案 经过和负责监控的大佬了解,获得了一个可行的方案:在每个pod中新增一个sidecar容器,在容器中部署node_exporter,或者在容器中放个自动查看端口连接数并…

    Linux 2023年6月13日
    0108
  • 使用ssh连接到centos7中docker容器

    任务: 使用ssh连接到centos7中docker容器 实验步骤: 如图,首先用真机ping容器(容器事先安装了常用的软件,具体步骤请看上一篇) 然后用容器ping真机以及外网,…

    Linux 2023年5月27日
    091
  • STP 指定端口 根端口 区别和理解

    不多说,先上图,A为指定端口,B为非指定端口。 看本文的网友应该知道根端口和指定端口的选举,但是对指定端口和根端口的理解不清楚。这里我就略过选举过程,直接描述这两者的区别和存在的意…

    Linux 2023年6月6日
    0145
  • JavaScript快速入门-03-数据类型

    3 数据类型 3.1 简介 JavaScript中的每个值都是属于一种特定的数据类型。JavaScript中一共有以下几种数据类型,详细如下所示: 原始类型: Undefined、…

    Linux 2023年6月7日
    0164
  • 日常开发方案设计指北

    互联网公司管理研发流程,常常使用TAPD一类的敏捷工具。一个需求从提出到上线要经历至少七个流程: 1)需求评审:产品经理给出需求文档,邀请技术参与需求评审,目的是扫清需求疑点,排除…

    Linux 2023年6月6日
    0114
  • 在Ubuntu20.04上安装Kubernetes-Kubeadm和Minikube

    镜像下载、域名解析、时间同步请点击阿里云开源镜像站 在本文中,我们将了解如何在 Ubuntu 20.04 上安装 Kubernetes。在过去的几年里,容器化为开发人员提供了很大的…

    Linux 2023年5月27日
    0113
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球