实模式和保护模式

前言

大家好,我是jiantaoyab,内存中的每一个字节都有一个唯一的地址,通过这个地址我们能去取出一个个比特,我们可以称这个过程为寻址。在现实生活中,我们去菜鸟拿快递的时候,是通过取件码上的编号到架子上找的。取件码上面的信息就包含物品在哪个区域偏移多少。

同样的程序编译好之后放到磁盘上,运行的时候加载到内存中,同样的存在寻址问题,这里有2种寻址方式。

一是绝对地址,必须把某个指令加载到内存中的某个地址上面,否则不能运行。这种方式在运行多个程序的时候是行不通的,除非我们事前确定每个程序放在哪个内存上。

二是相对地址,把程序加载到内存的起始地址,再加上偏移地址得出真实的物理地址,解决了重定位的问题。

逻辑地址和物理地址

逻辑地址是指与当前数据在内存中的物理分配地址无关的访问地址,在执行内存访问之前必须转为物理地址。相对地址是逻辑地址的一个特例,通常是相对程序的开始处的存储单元。

物理地址或绝对地址是数据在内存中的实际位置。


分段式内存管理

程序的内存从内容上可以分为存放机器指令的代码区域、存放全局变量的数据区域、保存函数运行时信息的栈区等,显然我们可以将程序按照这种划分进行分段管理,段内使用相对地址,这样无论这些段被加载到内存的哪个区域我们都能方便的计算出正确的物理内存地址。

我们将各个段在内存中的起始地址放到专用的寄存器中,X86 CPU中有这样几个段寄存器,CS、DS、SS以及ES。

  • 保存机器指令的区域,这个区域就是代码段(Code Segment),因此我们可以使用一个寄存器来专门指向代码段,这就是CS寄存器的作用,CS也是Code Segment的缩写。
  • 程序运行起来后还有专门的区域用来保存数据,因此必须要专门的寄存器指向数据段(Data Segment),这就是DS寄存器的作用,DS是Data Segment的缩写。
  • 程序运行起来后还有运行时栈(Stack Segment),因此可以使用SS寄存器来指向程序员运行时栈,SS是Stack Segment的缩写。
  • 还有ES寄存器,Extra Segment,其用作临时段寄存器。

实模式

实模式(real mode),实模式的特点是20 bit分段内存(segmented memory)地址空间(精确到1 MB的可寻址内存)以及 对所有可寻址内存,I/O地址和外设硬件的无限制直接软件访问。

1MB的内存空间划分

image-20240407234015630

最早期的8086 CPU只有一种工作方式,那就是实模式,而且数据总线为16位,地址总线为20位,实模式下所有寄存器都是16位。寄存器只有16位,16位寄存器是没有办法访问整个1MB内存的,16位寄存器最多能访问64K大小的内存,要想访问1MB内存那么内存地址就需要20位,而寄存器本身就16位,因此根本装不下,怎么办呢?

使用2个寄存器,第一个寄存器被叫做selector,也叫做段寄存器, segment register。第二个寄存器被叫做offset,又叫做区域内的偏移,此时内存地址的计算方式是这样的:16 ∗ selector + offset,这里计算出来的内存地址就是物理内存地址。

此外,在实模式下CPU不提供内存保护机制,程序可以随意读写任何内存区域,哪怕是操作系统所在的区域其它程序也可以读写,实模式也不支持多任务处理和代码权限级别。

随着发展在80286开始就有了保护模式,从80386开始CPU数据总线和地址总线均为32位,而且寄存器都是32位。设计师向前兼容之前使用了实模式的x86系列处理器在重置时会首先进入实模式,对于不使用实模式的现代操作系统来说简单的初始化工作后会跳转到保护模式。


保护

每个进程都受到保护,该进程以外的其他进程的程序不能未经授权进行读操作或写操作该进程的内存单元。

通常,用户进程不能访问操作系统的任何部分,无论是程序还是数据,并且处理器能够终止一个进程中的程序想要访问另一个进程中数据区。内存保护由处理器来完成,因为操作系统不能预测程序可能产生的所以内存访问,只能在指令访问内存的时来判断内存访问是不是合法。

保护模式

保护模式是相对于实模式而言的,它首次出现是在Intel 80286 CPU中首次出现的,紧跟着8086,为了克服在实模式下的种种问题,处理器厂商开发出保护模式。

32 位 CPU 具有保护模式和实模式两种运行模式,可以兼容实模式下的程序。假设我们使用的是32位的CPU,当开机时,32位的CPU是处于先处于实模式之后再进入保护模式的,在保护模式下每运行一个实模式下的程序,就要为其虚拟一个实模式环境,故称虚拟模式。

保护模式和实模式的寻址有很大的不同,保护模式下利用段选择子从段描述符表(GDT,LDT)中找到对应的段描述符,段描述符中含有需要寻址的段基地址,段长度,段属性等,利用段选择子找到的段描述符和偏移量就能寻址到对应的内存地址。可以先简单的理解,段描述符表是一个数组,数组中的每一项都是一个段描述符,段选择子是数组的下标。

段选择子

原先实模式下的各个段寄存器作为保护模式下的段选择子(CS,SS,DS,ES,FS,GS)16位寄存器。段选择子也不全是寄存器,可以是常数。

段选择子的大小为16bit,包含符索引,TI,请求特权级(RPL)

  • 0 - 1 位用来存RPL,请求特权级可以表示 0、 1、 2、 3 四种特权级。
  • 第2位是TL位, Table Indicator,用来指示选择子是在 GDT 中,还是 LDT 中索引描述符。 TI为 0 表示在 GDT 中索引描述符, TI 为 1 表示在 LDT 中索引描述符。
  • 选择子的高 13 位,即第 3~15 位是描述符的索引值,由于选择子的索引值部分是 13 位,即 2 的 13 次方是 8192,故最多可以索引 8192 个段。

通过段选择子找到对应的GDT或LDT表中的表项

image-20240408221527185

关于特权级的说明

任务中的每一个段都有一个特定的级别。每当程序试图访问某一个段,就将该进程拥有的特权级和要访问的特权级相比较来决定是否能访问该段。CPU只能访问同一特权或者低特权的段。

Linux0.11中只用了0级和3级,操作系统的内核区为0级,用户区为3级,特权级别有CPL、DPL、RPL。

  • CPL表示当前特权级目标是当前正在执行的程序特权级,存放在cs和ss寄存器中,int指令可以将CPL改为0
  • DPL:为描述符特权级,表示要访问的段(目标特权级)。DPL存在段描述符号或者门描述符中。
  • RPL为请求特权级,存放在段选择子(ds、cs等寄存器中)

当一个程序访问一个段时候,处理器会检测CPL、DPL、RPL,只有当DPL >= CPL 且 DPL >= RPL 处理器才能允许进程访问该段。

值小权限越高。

段描述符

image-20240408221356706

image-20240408234834421

段描述符共64位,8个字节大小,用于描述内存段拥有的属性。

image-20240408220038095

  • 段描述符第31~24位:段基址高8位

  • 段描述符第23位G:1代表段界限单位为4kB;0代表段界限单位为1B。

  • 段描述符22位D/B:如果是代码段则这位称为D位:1代表有效地址操作数是32位,使用32位寄存器eip;0代表16位使用eip低16位ip。如果是栈段则这位称为B位:1代表使用32位寄存器esp,操作数大小32位;0代表使用esp低16位sp,操作数大小16位。

  • 段描述符第21位L:用来设置是否是64位代码段,1代表代码段是64位;0代表是32位。

  • 段描述符第20位AVL:保留暂无意义。

  • 段描述符第19~16位:段界限高四位。

  • 段描述符第15位P:段是否存在,1代表段存在内存中;0代表不在,cpu进行检查,如果为0则抛出异常。操作系统再将这个段从硬盘加载到内存

    把p再设置为1

  • 段描述符14~13位DPL:代表权级别,数字越小级别越高,os级别为0,应用程序为3。

  • 段描述符第12位S:1代表该内存段是非系统段;0代表该内存段是系统段,配合type使用。

  • 段描述符第11~8位:用于指定内存段的具体类型。

  • 段描述符第7~0位:段基址中间8位。

image-20240408220050621

  • 段基址低16位
  • 段界限低16位

GDT

当系统在保护模式下运行时,程序的所有内存访问操作都需要经过全局描述符表和局部描述符表。

GTD(Global Describe Table)全局描述符表是系统用的,存放在内存中,只有一张全局可见,GDT中有用户的CS和DS段描述符还有内核的CS和DS段描述符等等。GDT是一个段描述符类型的数组。

全局描述符表位于内存中,需要用专门的寄存器指向它后, CPU 才知道它在哪里。这个专门的寄存器便是 GDTR,即 GDT Register,专门用来存储 GDT 的内存地址及大小。

image-20240408221015205

GDT界限表示它能存储的段描述符的多少,由于 GDT 的大小是 16 位,其表示的范围是2的 16次方等于 65536字节。每个描述符大小是 8字节,故,GDT中最多可容纳的描述符数量是 65536/8=8192个,即 GDT 中可容纳 8192 个段或门。

CPU处理一个逻辑地址“cs:offset”时:

  • 将GDTR中的基址加上cs中的下标值,得到段描述符
  • 从段描述符取出段基址
  • 最后将段基址与偏移值相加,就得到线性地址(虚拟地址)
  • 得到线性地址后,由CPU的MMU,将线性地址映射为物理地址,然后就可交给地址总线进行读写

访问GDT

image-20240408222739568

  1. 当TI=0时表示段描述符在GDT中,如上图所示:

  2. 先从GDTR寄存器中获得GDT基址。

  3. 然后再根据段选择子高13位位置索引值得到段描述符。

  4. 段描述符符包含段的基址、限长、优先级等各种属性,这就得到了段的起始地址(基址),再以基址加上偏移地址才得到地址。

LDT

一个处理器只有一个GDT表,是内核权限访问的。在多个任务的系统中,所有任务的段描述符(一个任务的数据段描述符、代码段描述符、堆栈描述符等等)都放在GDT表中这对操作系统来说,是个负担。

而且每次访问GDT表,还要陷入内核级才能访问,这比较费时间。这就有了LDT,操作系统在加载每一个任务的时候,都在给这个任务创建一个LDT表,存储着该任务的信息。

LDT的局部段描述表,和GDT不同的是,LDT可以存在多个,只对引用他们的任务可见,每个任务最多可以拥有一个LDT,另外每一个LDT自身作为一个段存在,它们的段描述符被放在GDT中。

image-20240408222540754

访问LDT

image-20240408223004389

  1. 当TI=1时表示段描述符在LDT中,如上图所示:

  2. 还是先从GDTR寄存器中获得GDT基址。

  3. 从LDTR寄存器中获取LDT所在段的位置索引(LDTR高13位),此时的LDTR可以看成一个段选择子;

  4. 以这个位置索引在GDT中得到LDT段描述符从而得到LDT段基址。

  5. 用段选择器高13位位置索引值从LDT段中得到需要访问内存段描述符。

  6. 段描述符符包含段的基址、限长、优先级等各种属性,这就得到了段的起始地址(基址),再以基址加上偏移地址才得到最后的线性地址。

80286访问内存的全过程

mov ax, 0x08
mov ds, as //1.给段寄存器一个16位的值(包含段选择子,类型,优先级)
xor si, si
mov [si], 0x07 //2.通过段地址+偏移地址访问某个内存

3.CPU通过TI位置的值决定去GDT还是LDT查看具体的段描述符,

4.CPU通过段寄存器的高13位来获取段选择子的值确定要访问的段

5.根据段选择子,找到段描述符,并比较PRL与DPL的值,当RPL的值小于等于DPL的值,说明本次请求被允许

6.如果能访问段描述符,就取出24位段地址

7.把段地址与偏移地址相加,得到真实的物理地址

总结

GDT只是一个段描述类型的数组,用于内存的寻找,为了对内存区域进行保护,将段描述符号设计成三部分。LDT和GDT类似,可以理解成GDT为1级描述符表,LDT为2级描述符表。

LDT不在GDT中,GDT包含LDT描述符一个指向LDT起始地址的指针。

image-20240409235631273

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

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

相关文章

CLion 解决中文输出乱码的问题

问题介绍 在 Clion 的默认设置下&#xff0c;输出中文会出现乱码&#xff0c;如下 #include <iostream> using namespace std;int main() {cout << "你好" << endl;return 0; }输出 浣犲ソProcess finished with exit code 0解决方案 编码问题…

LangChain - Chain

文章目录 1、概览为什么我们需要链? 2、快速入门 (Get started) - Using LLMChain多个变量 使用字典输入在 LLMChain 中使用聊天模型&#xff1a; 3、异步 API4、不同的调用方法__call__调用仅返回输出键值 return_only_outputs只有一个输出键 run只有一个输入键 5、自定义cha…

git submodule---同步最新的内容

0 Preface/Foreword 1 同步最新submodule内容到repo中 项目的repo包含了一个子模块&#xff0c;在开发过程中&#xff0c;经常需要同步子模块最新的commit到repo中。该如何操作呢&#xff1f; 本地在克隆时候&#xff0c;已经同步把子模块中的内容克隆下来了&#xff0c;但是…

Spring 之 IoC概述

目录 1. IoC概述 1.1 控制反转 1.2 依赖注入 2. IoC容器在Spring中的实现 2.1 BeanFactory 2.2 ApplicationContext 2.2.1 ApplicationContext的主要实现类 1. IoC概述 全称&#xff1a;Inversion of Control&#xff0c;译为 “控制反转” Spring通过IoC容器来管理所有…

【LAMMPS学习】八、基础知识(1.6) LAMMPS 与其他代码耦合

8. 基础知识 此部分描述了如何使用 LAMMPS 为用户和开发人员执行各种任务。术语表页面还列出了 MD 术语&#xff0c;以及相应 LAMMPS 手册页的链接。 LAMMPS 源代码分发的 examples 目录中包含的示例输入脚本以及示例脚本页面上突出显示的示例输入脚本还展示了如何设置和运行各…

基于拉格朗日分布算法的电动汽车充放电调度MATLAB程序

微❤关注“电气仔推送”获得资料&#xff08;专享优惠&#xff09; 程序简介 该模型主要做的是基于拉格朗日分布算法的电动汽车充放电调度模型。利用蒙特卡洛模拟法模拟出电动汽车负荷曲线&#xff0c;并求解出无序充电功率曲线和有序充电曲线&#xff0c;该模型在电动汽车个…

标准C库文件操作

open 系列API 和 fopen系列API的区别 1.来源: -open 是UNIX系统调用函数(包括LINUX系统)&#xff0c;返回的是文件描述符 -fopen是ANSIC标准的C语言库函数&#xff0c;在不同系统重调用不同内核的API 2.移植性: fopen 是C标准函数&#xff0c;具有良好的移植性&#xff1b; 而…

JUC-线程的创建、运行与查看

创建和运行线程 Thread创建线程 Thread 创建线程方式&#xff1a;创建线程类&#xff0c;匿名内部类方式 start() 方法底层其实是给 CPU 注册当前线程&#xff0c;并且触发 run() 方法执行线程的启动必须调用 start() 方法&#xff0c;如果线程直接调用 run() 方法&#xff…

【鸿蒙开发】组件状态管理@Prop,@Link,@Provide,@Consume,@Observed,@ObjectLink

1. Prop 父子单向同步 概述 Prop装饰的变量和父组件建立单向的同步关系&#xff1a; Prop变量允许在本地修改&#xff0c;但修改后的变化不会同步回父组件。当父组件中的数据源更改时&#xff0c;与之相关的Prop装饰的变量都会自动更新。如果子组件已经在本地修改了Prop装饰…

01-Git 快速入门

https://learngitbranching.js.org/?localezh_CN在线练习git 1. Git 安装好Git以后, 先检查是否已经绑定了用户名和邮箱 git config --list再检查C:\Users\xxx.ssh 下是否存在 id_rsa.pub , 存在的话复制其内容到 GitHub 的 SSH KEY 中 没有这一步, PUSH操作的时候会报错:…

Altair® (澳汰尔)Inspire™ Render —— 强大的 3D 渲染和动画工具

Inspire Render 是一种全新 3D 渲染和动画工具&#xff0c;可供创新设计师、建筑师和数字艺术家以前所未有的速度快速制作精美的产品演示。 借助基于物理特性的内置高品质全局照明渲染引擎 Thea Render&#xff0c;可以快速创建、修改和拖放各种材质并添加照明环境&#xff0c…

【JavaWeb】Day34.MySQL概述——数据库设计-DDL(一)

项目开发流程 需求文档&#xff1a; 在我们开发一个项目或者项目当中的某个模块之前&#xff0c;会先会拿到产品经理给我们提供的页面原型及需求文档。 设计&#xff1a; 拿到产品原型和需求文档之后&#xff0c;我们首先要做的不是编码&#xff0c;而是要先进行项目的设计&am…

Leetcode 538. 把二叉搜索树转换为累加树

心路历程&#xff1a; 二分搜索树 中序遍历&#xff0c;记住这一点即可&#xff1b; 两次遍历&#xff0c;第一次求和&#xff0c;第二次赋值即可 注意的点&#xff1a; 1、注意赋值的时候需要包含node.val&#xff0c;相当于包含i的后缀和 解法&#xff1a;DFS # Defini…

视帝餐厅生意亮红灯?50岁前TVB「电波少女」帮衬撑留港消费

现年50岁的前「电波少女」成员姚乐怡&#xff0c;2012年与商人吴俊匡结婚&#xff0c;2015年诞下一女吴芊憧&#xff08;Gaibe、乳名小鸡髀&#xff09;后淡出幕前&#xff0c;今积极拍片转型做「美食网红」。近年不时介绍香港地道美食&#xff0c;近日更以行动支持留港消费&am…

MySQL:关于数据库的一些练习题

文章目录 前面的内容已经把数据库的一些必要知识已经储备好了&#xff0c;因此下面就对于这些语句进行一些练习&#xff1a; 批量插入数据 insert into actor values (1, PENELOPE, GUINESS, 2006-02-15 12:34:33), (2, NICK, WAHLBERG, 2006-02-15 12:34:33);SQL202 找出所有…

【linux基础】bash脚本的学习:定义变量及引用变量、统计目标目录下所有文件行数、列数

假设目的&#xff1a;统计并输出指定文件夹下所有文件行数 单个文件可以用 wc -l &#xff1b;多个文件&#xff0c;可以用通配符 / 借助bash脚本 1.定义变量名&#xff0c;使用引号 a"bestqc.com.map" b"Anno.variant_function" c"enrichment/GOe…

代码签名证书是什么?软件签名证书功能和分类

代码签名证书是什么&#xff1f;代码签名证书&#xff08;Code Signing Certificate&#xff09;是用于对可执行文件或脚本&#xff0c;软件代码等进行数字签名&#xff0c;可验证软件发布者身份、保证软件签名后未被篡改&#xff0c;以此验证开发者身份的真实性和保护代码的完…

PostgreSQL入门到实战-第十弹

PostgreSQL入门到实战 PostgreSQL数据过滤(三)官网地址PostgreSQL概述PostgreSQL中OR操作理论PostgreSQL中OR实操更新计划 PostgreSQL数据过滤(三) 了解PostgreSQL OR逻辑运算符以及如何使用它来组合多个布尔表达式。 官网地址 声明: 由于操作系统, 版本更新等原因, 文章所列…

Node.js cnpm的安装

百度搜索 cnpm,进入npmmirror 镜像站https://npmmirror.com/ cmd窗口输入 npm install -g cnpm --registryhttps://registry.npmmirror.com

Vue.js前端开发零基础教学(五)

目录 4.1 动态组件 4.1.1 定义动态组件 4.1.2 利用KeepAlive组件实现组件缓存 4.1.3 组件缓存相关的生命周期函数 4.1.4 KeepAlive组件的常用属性 4.2 插槽 4.2.1 什么是插槽 ​编辑 4.2.2 具名插槽 4.2.3 作用域插槽 4.3 自定义指令 4.3.1 什么是自定义指令…