x86的内存寻址方式

文章目录

    • 一、实模式寻址
    • 二、保护模式寻址
    • 三、段页式内存管理
    • 四、Linux的内存寻址
    • 五、进程与内存
      • 1、内核空间和用户空间
      • 2、内存映射
      • 3、进程内存分配与回收

一、实模式寻址

在16位的8086时代,CPU为了能寻址超过16位地址能表示的最大空间(因为 8086 的地址线 20 位而数据线 16 位),引入了段寄存器。通过将内存空间划分为若干个段(段寄存器像 ds、cs、ss 这些寄存器用于存放段基址),然后采用段基地址+段内偏移的方式访问内存,这样能访问1MB的内存空间了。

使用这样的寻址方式的好处是所见即所得,程序员指定的地址就是物理地址,物理地址对程序员是可见的。但是,由此也带来两个问题:

  1. 无法支持多任务
  2. 程序的安全性无法得到保证(用户程序可以改写系统空间或者其他用户的程序内容)。

实模式将整个物理内存看成分段的区域,程序代码和数据位于不同区域,系统程序和用户程序没有区别对待,而且每一个指针都是指向"实在"的物理地址。

这样一来,用户程序的一个指针如果指向了系统程序区域或其他用户程序区域,并改变了值,那么对于这个被修改的系统程序或用户程序,其后果就很可能是灾难性的。

为了克服这种低劣的内存管理方式,处理器厂商开发出保护模式:物理内存地址不能直接被程序访问,程序内部的地址(虚拟地址)要由操作系统转化为物理地址去访问,从而保护进程地址空间,程序对此一无所知。

二、保护模式寻址

从IA-32开始,cpu有三种工作方式:实模式,保护模式和虚拟8086模式。只有在刚刚启动的时候是实模式,等操作系统运行起来以后就运行在保护模式。虚拟8086模式是运行在保护模式中的实模式,为了在32位保护模式下执行纯16位程序,它不是一个真正的CPU模式,还属于保护模式。在保护模式下,CPU 有更强的寻址能力。

两者的区别主要体现在段寄存器如CS,DS,ES,SS的解释方式不同——实模式解释为段寄存器,保护模式解释为段选择子。

在实模式(也就是16位模式)情况下,一个地址由段和偏移两部分组成,计算公式为:Segment << 4 + Offset(所以实模式下只能访问1M的内存空间)。CS (代码段寄存器) 和 DS (数据段寄存器) 主要用于存储代码段和数据段的起始地址。

在保护模式下寻址方式还是段基址+偏移地址。但是此时寄存器不再直接存储段的基地址,而是存储段描述符表中的索引即段选择子,如下:

|-----------------------------------|-----|--------|
|              索引号(13)            |TI(1)| RPL(2) |
|-----------------------------------|-----|--------|
TI:表指示器,表示使用的是哪个段描述符表(GDT或LDT)
RPL:请求者特权级

段基址不直接放在段寄存器了,而是在 GDT即全局描述符表(或LDT即局部描述符表)中,如下:

此外CPU中单独添置了两个寄存器,用来指向这两个表,分别是gdtr和ldtr。在寻址的时候,CPU首先根据段寄存器的TI位和gdtr或ldtr找到描述符表,之后根据段寄存器的索引号到GDT/LDT中找出对应的段描述符,然后再取出这个段的基地址,最后再结合段内的偏移完成内存寻址。

三、段页式内存管理

x86架构的CPU(保护模式下)采用的是分段+分页的内存管理方式,上述根据段基址和偏移地址其实只是段寻址的过程,用于将逻辑地址转换为线性地址,所以还需要进行页寻址,将线性地址转换为物理地址。

要将线性地址转换为物理地址,那就得有地方记录它们之间的映射关系,这是通过页表的实现的。页表是用来记录虚拟内存页面和物理内存页面之间的映射关系的,每一个页表项记录一个页面的映射关系。但进程的地址空间很大,这样算下来需要的页表项的数量也会非常多。而实际上进程地址空间中很多页面都没有真正使用,也就没有映射关系,这样是一种浪费。为了解决这个问题,CPU引入了多级页表的机制,在32位下一般是2级页表,像下面这样:

在这里插入图片描述

线性地址被分为三段,页目录索引、页表索引和页内偏移:

  1. 页目录索引(Page Directory Index):用于在页目录中查找对应的页表入口。
  2. 页表索引(Page Table Index):用于在找到的页表中查找对应的页框入口。
  3. 页内偏移(Offset):在找到的页框内的具体地址。

用页目录索引去页目录(PGD)中拿到页表入口,接着用页表索引去页表(Page Table)中拿到页表项(Page Table Entry)。

页表项(Page Table Entry,简称PTE)是页表的组成部分,其主要目标是存储虚拟地址到物理地址的映射信息。每一个页表项对应一个虚拟页面到物理页面的映射。找到页表项后,就可以找到里面存储的物理内存块的起始地址(其实就是是物理内存编号),把它加上页内偏移就得到了最终的物理地址。我们把这个过程称作页式内存管理。

上面这些地址转换的实现,就是由 MMU 来完成的。

虚拟地址和线性地址:

其实在 Intel IA-32 手册里并没有提到虚拟地址这个术语,但是在内核的确是用到了这个概念,比如__va和__pa这两个宏定义。经过我的考证,virtual address就是linear address的别名,俩词汇是一个意思,内核代码和我们编程中喜欢用virtual address这个术语,而Intel手册里只用linear address这个术语。

引用自Linux 线性地址,逻辑地址和虚拟地址的关系?

四、Linux的内存寻址

按照 Intel 的设计,段式内存管理中的段类型分为代码段、数据段、栈段、扩展段四个段(即对应CPU中的cs、ds、ss、es四个段寄存器),实在是太麻烦了。我们只靠页式内存管理就已经可以完成Linux内核需要的所有功能,根本不需要段映射。

因此Linux为了简化处理,采用了平坦内存模型。在这个模型中,所有的段的基址都被设置为0,限长被设置为最大的地址,也就是4GB(对于32位系统)或更大(对于64位系统)。这样,每一个段都覆盖了整个线性地址空间,即从0到最大的地址。也就是说在Linux系统中虽然保留了段机制,但是进程的代码段、数据段、栈段、扩展段这四个段全部重合了,而且是整个进程地址空间共计4GB成为了一个段。虽然仍然有代码段(CS)和数据段(DS)这样的名词,但它们实际上都指向同一个,覆盖了整个地址空间的段。

所以说起来是分段,实际上等于没分了,再加上段的基地址全部是0,那进行地址翻译的时候,对于任何一个给定的逻辑地址,只需要看它的偏移量就可以知道它在内存中的位置,不需要去查找段描述符表和进行基址加偏移的运算。这大大简化了内存管理,尤其是在进行上下文切换时,因为不需要去加载不同的段表。即虽然逻辑地址和线性地址是两种不同的地址空间,但在Linux中逻辑地址就等于线性地址。

GDT、LDT是供分段式内存使用的设施,Intel/AMD的64位模式下内存并不分段(硬件保证所有段的基址都是0),所以它们就用不上了。32位模式下还是需要的,虽然当今主流32位操作系统也是平坦(flat)内存模型,但是操作系统是以软件将每个段都设置为0基址,也就是通过正确初始化GDT、LDT来实现的。

Linux的段式管理事实上只是“哄骗”了一下硬件而已,按照Intel的本意是需要去通过段描述符来拿到段基址,之后再与偏移地址相加来拿到线性地址的,但是Linux对所有的进程都使用了相同的段来对指令和数据寻址。即所有的段的基地址都是0,段长4G。所以也就是说进程使用的地址可以直接理解为是线性地址,因为段基址都是0,只需要进行页式转换即可。但这并不意味着段机制彻底没用到,CPU的任务管理TSS还是需要用到的。

五、进程与内存

1、内核空间和用户空间

Linux操作系统采用虚拟内存管理技术,使得每个进程都有各自互不干涉的进程地址空间。该空间是块大小为4G(对32位而言)的线性虚拟空间,其中内核空间占1GB,用户空间占3GB。

  1. 用户空间与内核空间是人为划分的,用户空间从0到3G(0xC0000000),内核空间占据3G到4G。用户进程通常情况下只能访问用户空间的虚拟地址,不能访问内核空间虚拟地址。只有用户进程进行系统调用(代表用户进程在内核态执行)等时刻可以访问到内核空间。
  2. 用户空间对应进程,所以每当进程切换,用户空间就会跟着变化;而内核空间是由内核负责映射,它并不会跟着进程改变,是固定的。内核空间地址有自己对应的页表(init_mm.pgd),用户进程各自有不同的页表。
  3. 每个进程的用户空间都是完全独立、互不相干的。

在这里插入图片描述

内核空间包括内核镜像、物理页面表、驱动程序等

用户空间分为五个不同的区域:

  • 代码段:只读,存放可执行文件的操作指令;镜像;
  • 数据段:存放可执行文件中已初始化全局变量;存放静态变量和全局变量;
  • BSS段:未初始化全局变量;
  • 堆:存放被动态分配的内存段;
  • 栈:存放临时创建的局部变量;

在这里插入图片描述

这里的段和前面提到的是不同层次上的概念,可以理解为前面是操作系统的段式管理为每个进程都划分出了一个大的0-4G的段。段式管理中划分为了数据段、代码段、栈段和扩展段,但是在Linux里都不管了,全部都划到这个4G的段里。然后在这个段中再进行一个划分,按照不同属性抽象出了这五个不同的区域,将相同属性的数据集中放在一起。

2、内存映射

物理地址空间是有限的(取决于实际物理设备),虚拟地址空间可以是任意大小(受限于CPU位数),对于32位的CPU,虚拟地址空间可以为4G,其中内核空间占1GB,用户空间占3GB。如果物理内存也是4GB的大小,那么他们之间的映射关系如下图:
在这里插入图片描述
因为内核的虚拟地址空间只有1GB,但它需要访问整个4GB的物理空间,因此从物理地址0~896MB的部分(ZONE_DMA+ZONE_NORMAL),直接加上3GB的偏移(在Linux中用PAGE_OFFSET表示),就得到了对应的虚拟地址,这种映射方式被称为线性/直接映射(Direct Map)。

而896M-4GB的物理地址部分(ZONE_HIGHMEM)需要映射到(3G+896M)-4GB这128MB的虚拟地址空间,显然也按线性映射是不行的。采用的是做法是,ZONE_HIGHMEM中的某段物理内存和这128M中的某段虚拟空间建立映射,完成所需操作后需要断开与这部分虚拟空间的映射关系,以便ZONE_HIGHMEM中其他的物理内存可以继续往这个区域映射,即动态映射的方式。

在64位系统中,内核空间的映射变的简单了,因为这时内核的虚拟地址空间已经足够大了,即便它要访问所有的物理内存,直接映射就是,不再需要ZONE_HIGHMEM那种动态映射机制了。

3、进程内存分配与回收

进程所能直接操作的地址都为虚拟地址。当进程需要内存时,从内核获得的仅仅是虚拟的内存区域,而不是实际的物理地址,进程并没有获得物理内存(物理页面),获得的仅仅是对一个新的线性地址区间的使用权。实际的物理内存只有当进程真的去访问新获取的虚拟地址时,才会由“请求页机制”产生“缺页”异常,从而进入分配实际页面的例程。

该异常是虚拟内存机制赖以存在的基本保证——它会告诉内核去真正为进程分配物理页,并建立对应的页表,这之后虚拟地址才实实在在地映射到了系统的物理内存上(当然,如果页被换出到磁盘,也会产生缺页异常,不过这时不用再建立页表了)。这种请求页机制把页面的分配推迟到不能再推迟为止,节约了空闲内存。

当程序试图访问的内存页面不在物理内存中时(也就是说,这个页面被换出到磁盘(页表项有效位为0),或者还未被分配(没有对应页表项)),处理器会触发一个缺页异常。这个异常会导致当前的程序暂停,并且切换到操作系统的内核模式。

操作系统的缺页异常处理程序会首先检查这个访问是否有效(也就是说,程序是否有权访问这个地址)。如果这个访问是无效的(例如,程序试图访问它没有权限访问的内存),操作系统会终止这个程序。

如果访问是有效的,缺页异常处理程序会试图修复这个异常。它可能会从磁盘的交换空间中读取所需的页面,或者分配一个新的页面。然后,它会更新页表,把虚拟地址映射到新加载或新分配的物理页面上。

页表项(Page Table Entry,PTE)与物理内存基址的对应关系由内存管理单元(MMU)和操作系统的内存管理子系统共同决定。以下是这个过程的大致步骤:

  1. 内存分配:当一个进程需要更多的内存时(例如,因为程序的执行或者动态内存分配请求),操作系统会分配一个或多个物理内存页给这个进程。操作系统通常会选择一些空闲的、未被其他进程使用的内存页进行分配。
  2. 虚拟地址选择:操作系统为这些新分配的内存页选择一些虚拟地址。这些虚拟地址通常会在进程的虚拟地址空间中找到。
  3. 页表更新:操作系统在页表中创建或更新一些页表项,将新选择的虚拟地址映射到新分配的物理内存页。具体来说,操作系统会将每个页表项的物理地址部分设置为对应的物理内存页的基址。
  4. 内存访问:之后,当CPU执行该进程的代码时,如果遇到对这些虚拟地址的访问,MMU会通过查找页表,将虚拟地址转换为对应的物理地址,然后访问对应的物理内存。

这样,页表项和物理内存基址的对应关系就是由操作系统在分配内存和更新页表时确定的。这个对应关系是动态的,可以随着内存的分配和释放,进程的创建和销毁,以及内存管理的其他活动而改变。

我们知道不同的进程之间看到的虚拟地址范围是一样的,所以多个进程下,不同进程的相同的虚拟地址可以映射不同的物理地址。这就会造成歧义问题。例如,进程A将地址0x2000映射物理地址0x4000。进程B将地址0x2000映射物理地址0x5000。当进程A执行的时候将0x2000对应0x4000的映射关系缓存到TLB中。当切换B进程的时候,B进程访问0x2000的数据,会由于命中TLB从物理地址0x4000取数据。这就造成了歧义。

如何消除这种歧义,我们可以借鉴VIVT数据cache的处理方式,在进程切换时将整个TLB无效。切换后的进程都不会命中TLB,但是会导致性能损失。

参考文献:

现代操作系统内存管理到底是分段还是分页,段寄存器还有用吗? - 知乎 (zhihu.com)

逻辑地址、物理地址、虚拟地址_虚拟地址是逻辑地址吗_闫晟的博客-CSDN博客

linux内核中 逻辑地址、虚拟地址、线性地址和物理地址大扫盲

Linux的进程地址空间[一] - 知乎 (zhihu.com)

【转】Linux内存管理(最透彻的一篇) - ralap7 - 博客园 (cnblogs.com)

操作系统中的多级页表到底是为了解决什么问题?

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用Python写入数据到Excel:实战指南

在数据科学领域&#xff0c;Excel是一种广泛使用的电子表格工具&#xff0c;可以方便地进行数据管理和分析。然而&#xff0c;当数据规模较大或需要自动化处理时&#xff0c;手动操作Excel可能会变得繁琐。此时&#xff0c;使用Python编写程序将数据写入Excel文件是一个高效且便…

【SpringBoot】第二篇:RocketMq使用

背景&#xff1a; 本文会介绍多种案例&#xff0c;教大家如何使用rocketmq。 一般rocketmq使用在微服务项目中&#xff0c;属于分模块使用。这里使用springboot单体项目来模拟使用。 本文以windows系统来做案例。 下载rocketmq和启动&#xff1a; RocketMQ 在 windows 上运行…

微信小程序开发教学系列(4)- 数据绑定与事件处理

4. 数据绑定与事件处理 在微信小程序中&#xff0c;数据绑定和事件处理是非常重要的部分。数据绑定可以将数据和页面元素进行关联&#xff0c;实现数据的动态渲染&#xff1b;事件处理则是响应用户的操作&#xff0c;实现交互功能。本章节将详细介绍数据绑定和事件处理的基本原…

R-Meta分析核心技术教程

详情点击链接&#xff1a;全流程R-Meta分析核心技术教程 一&#xff0c;Meta分析的选题与检索 1、Meta分析的选题与文献检索 1)什么是Meta分析 2)Meta分析的选题策略 3)精确检索策略&#xff0c;如何检索全、检索准 4)文献的管理与清洗&#xff0c;如何制定文献纳入排除标准 …

腾讯云服务器地域有什么区别?怎么选择合适?

腾讯云服务器地域有什么区别&#xff1f;怎么选择比较好&#xff1f;地域选择就近原则&#xff0c;距离地域越近网络延迟越低&#xff0c;速度越快。关于地域的选择还有很多因素&#xff0c;地域节点选择还要考虑到网络延迟速度方面、内网连接、是否需要备案、不同地域价格因素…

【PHP】echo 输出数组报Array to string conversion解决办法

代码&#xff1a; <?PHP echo "Hello World!";$demoName array("kexuexiong","xiong");echo "<pre>";var_dump($demoName);echo $demoName; print_r($demoName);echo "</pre>"; ?>输出结果&#xff1…

Docker的革命:容器技术如何重塑软件部署之路

引言 在过去的几年中&#xff0c;容器技术已经从一个小众的概念发展成为软件开发和部署的主流方法。Docker&#xff0c;作为这一变革的先驱&#xff0c;已经深深地影响了我们如何构建、部署和运行应用程序。本文将探讨容器技术的起源&#xff0c;Docker如何崛起并改变了软件部…

Dreamweaver软件安装包分享(附安装教程)

目录 一、软件简介 二、软件下载 一、软件简介 Dreamweaver软件是一款专业的网页开发工具&#xff0c;由Adobe公司开发并广泛应用于Web开发领域。它提供了一站式的网页开发解决方案&#xff0c;包括网页设计、网页编程、网站管理和移动应用开发等功能。 Dreamweaver软件具有…

【Linux-Day8- 进程替换和信号】

进程替换和信号 问题引入 我们发现 终端输入的任意命令的父进程都是bash,这是因为Linux系统是用fork()复制出子进程&#xff0c;然后在子进程中调用替换函数进行进程替换&#xff0c;实现相关命令。 &#xff08;1&#xff09; exec 系列替换过程&#xff1a;pcb 使用以前的只…

响应式web-PC端web与移动端web(H5)兼容适配 选型方案

背景 项目需要&#xff0c;公司已经有一套PC端web&#xff0c;需要做一套手机端浏览器可用的&#xff0c;但是又想兼容pc端&#xff0c;适配的web项目。 以下是查阅到响应布局现成的开源模版。根据自己技术栈&#xff0c;vue2,js来搜索相关的开源项目。 RuoYi 使用若依快速…

网络编程套接字(2): 简单的UDP网络程序

文章目录 网络编程套接字(2): 简单的UDP网络程序3. 简单的UDP网络程序3.1 服务端创建(1) 创建套接字(2) 绑定端口号(3) sockaddr_in结构体(4) 数据的接收与发送接收发送 3.2 客户端创建3.3 代码编写(1) v1_简单发送消息(2) v2_小写转大写(3) v3_模拟命令行解释器(4) v4_多线程版…

「MySQL-01」MySQL基础

目录 一、数据库概念 1. 什么是数据库 2. 为什么要有数据库&#xff1f; 3. 数据库将数据存在哪里&#xff1f; 二、知名数据库介绍 1.知名数据库介绍 2.为什么要学习MySQL 三、MySQL的基本使用 0. 安装MySQL 1. 数据库客户端链接服务端 2. Windows下的MySQL服务端管理 3. 数据…

TMS FlexCel Studio for VCL and FireMonkey Crack

TMS FlexCel Studio for VCL and FireMonkey Crack FlexCel for VCL/FireMonkey是一套允许操作Excel文件的Delphi组件。它包括一个广泛的API&#xff0c;允许本机读取/写入Excel文件。如果您需要在没有安装Excel的Windows或macOS机器上阅读或创建复杂的电子表格&#xff0c;Fle…

基于JSP+Servlet+Mysql员工信息管理系统

基于JSPServletMysql员工信息管理系统 一、系统介绍二、功能展示三.其他系统实现五.获取源码 一、系统介绍 项目类型&#xff1a;Java web项目 项目名称&#xff1a;基于JSPServlet的员工/客户/人员信息管理系统 项目架构&#xff1a;B/S架构 开发语言&#xff1a;Java语言…

行业追踪,2023-08-23

自动复盘 2023-08-23 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…

Python3 元组

Python3 元组 Python 的元组与列表类似&#xff0c;不同之处在于元组的元素不能修改。 元组使用小括号 ( )&#xff0c;列表使用方括号 [ ]。 元组创建很简单&#xff0c;只需要在括号中添加元素&#xff0c;并使用逗号隔开即可。 实例(Python 3.0) >>> tup1 (Go…

pywebview 通过 JSBridge 调用 TTS

pip install pywin32 ; pip install pywebview ; 通过 JSBridge 调用本机 TTS pip install cefpython3 cefpython3-66.1-py2.py3-none-win_amd64.whl (69.0 MB) Successfully installed cefpython3-66.1 编写 pywebview_tts.py 如下 # -*- coding: utf-8 -*- ""&…

记录一次Modbus通信的置位错误

老套路&#xff0c;一图胜千言&#xff0c;框图可能有点随意&#xff0c;后面我会解释 先描述下背景&#xff0c;在Modbus线程内有一个死循环&#xff0c;一直在读8个线圈的状态&#xff0c;该线程内读到的消息会直接发送给UI线程&#xff0c;UI线程会解析Modbus数据帧&#xf…

SpringBoot实现文件上传和下载笔记分享(提供Gitee源码)

前言&#xff1a;这边汇总了一下目前SpringBoot项目当中常见文件上传和下载的功能&#xff0c;一共三种常见的下载方式和一种上传方式&#xff0c;特此做一个笔记分享。 目录 一、pom依赖 二、yml配置文件 三、文件下载 3.1、使用Spring框架提供的下载方式 3.2、通过IOUti…

【C++】priority_queue优先级队列

&#x1f3d6;️作者&#xff1a;malloc不出对象 ⛺专栏&#xff1a;C的学习之路 &#x1f466;个人简介&#xff1a;一名双非本科院校大二在读的科班编程菜鸟&#xff0c;努力编程只为赶上各位大佬的步伐&#x1f648;&#x1f648; 目录 前言一、priority_queue的介绍二、pr…
最新文章