分段与分页,LDT与GDT,页目录表与页表简单的认识

综合四篇文章看下分段与分页机制:
分段:LDT与GDT
分页:页目录表与页表

首先明确两个结论:
1.cr3里保存页目录表的基址的地址类型为物理地址,页目录表里的每一项也是页表的物理地址。
2.gdtr的地址和gdt里的描述符里的地址类型一样,保护模式开启后都为线性地址,但在保护模式开启前放的确是物理地址。系统开机时,进入的实模式,这个时候如果想进入保护模式,必须先设置GDT表,所以首先是在实模式设置GDT表为进入保护模式做准备,这个时候当然用的都是物理地址。但进入保护模式后,分段启用,那些设置的物理地址就被当成线性地址使用了,所以GDT要设置成使得物理地址和线性地址等价。就linux来说,进入保护模式后利用实模式设置的那个临时GDT,开启分页,以后又再次重新设置了新的GDT,这个GDT就全用的线性地址了。

可以看出要动态地来看待寄存器中要放什么样的值,在CPU不同的工作模式下,有些寄存器的的值是需要重新设置。寄存器的值并不是一次性设置后就不会需要重新设置的。

《分段与分页,LDT与GDT》

原文链接:https://blog.csdn.net/yleek/article/details/8204393

一个进程的地址空间,从用户的角度看,是由若干的段(segment)组成的,这些段可以分为两种:私有段(private)、共享段(shared)。cpu也是按照用户的逻辑进行内存管理的(分段),Intel Pentium规定了每种段最多有8K个,每个segment最大4G。一个cpu对应有一个GDT(global descriptor table),该表详细描述了shared segment,这个表为所有进程共享的;一个process对应有一个LDT(local),该表详细描述了该process的private segment,这个表是进程私有的。

GDT/LDT的结构,每条记录有64bit:
在这里插入图片描述
硬件中有一个寄存器,叫做GDTR(registor),共48bit,如下:
在这里插入图片描述

注意:上面16bit规定了GDT的长度,该长度是指,从32bit的指针指向的GDT开始位置到GDT结束位置的绝对长度,这个长度不是表的长度,不是表的record数量。下面会说明这个绝对长度跟表的长度(表的record数量)的关系。

文章开头说了,Intel Pentium规定了每种段最多有8K个,也就是说每个GDT/LDT有8K(213)条record,GDTR中又规定了GDT的绝对长度是216,由此可以得出GDT中每个record长度为2^16 / 2^13 = 8Byte,共64bit,正好与上面GDT/LDT的结构中说明的相一致。

问题又来了,既然每个GDT/LDT可以有2^13条record,那么就需要一个至少13bit的Selector来确定是哪一条record了。事实也是这样的,一个逻辑地址包含两部分,如下:

由上可以发现,32bit的段内偏移量确实确定了每个段的大小最大为4G(如文章开头所述)。而Selector是16bit不是我们预测的13bit,事实上其结构如下:
Segment Selector的结构
在这里插入图片描述


由以上介绍可知一个逻辑地址如何转化成为线性地址(确实可以得到一个32bit的地址,先就叫他线性地址吧,其实这个32bit的地址还要分页的)。为什么一个逻辑地址是48bit而不是32bit呢(如表“逻辑地址结构”所示)?我们在程序中如果打印一个变量的地址,确实就是一个32bit的数字啊,类似0x12345678,它并不是48bit的啊?!别忘了,我们的程序是处在一个个的Segment中的(如文章开始所述),无论是指令还是数据。我们打印出的地址其实是段内的偏移地址,程序的16bit段号由操作系统分配管理,我们是看不见的,但是的确存在。

假设一个进程需要1M的内存(指令加数据),那么os先会从LDT中选择一个段号(16bit),分给这个进程(在这个段内,我们有4G的空间可以发挥,但是我们只需要1M _),如果进程有需要全局空间,os还会在GDT中为之分配的。因此…cpu寻址的时候,也需要根据段号加段内偏移地址来找到线性地址。

注意注意注意:以上的解释中,为简单,没有说明分页!
总之,SegmentSelector(16bit) + 段内Offset(32bit) = 一个32bit的地址值


下面我告诉你,我们要对刚刚得到的32bit地址值进行分页处理,下面所有的操作都是在一个Segment内的4G空间内进行的。分页是这样分的:

操作系统为每个Segment维护一张表,称作页表,结构如下:

在这里插入图片描述

一页大小有页内偏移量就可以看出,是4K,如此为了管理4G的空间,显然这个页表需要有220条record。一条记录32bit=4Byte,220条就得需要4MB的空间啊!怎么能接受!处理的方法就是在此对0-19bit进行分页,新的页表结构如下:
二次分页的页表结构
在这里插入图片描述
4G空间一共有1024x1024页,第一次分页把这1024x1024页分成1024组,每组1024个,这样就需要1024张表,每张表1024条record……接着第二次分组,我们用一张表来管理这1024张表,显然着这张表也有1024条记录,如此一共有1+1024帐表,os会把不用的表交换到外存,以节省内存空间,提高查找效率。


综上所述,在程序中新申请一块空间,大致过程是这样的:

---->os在LDT中分配一个段号,在这个段里有4G的空间;

---->把这4G空间先分成1024份,每份大小1024 x 4K

---->把每一分再分为1024份,每份大小4K

---->把其中的一份或几份分配给申请者,并记录。
————————————————

《两张图看懂GDT、GDTR、LDT、LDTR的关系》

原文链接:https://blog.csdn.net/weixin_46198176/article/details/120248319

段选择符

32位汇编中16位段寄存器(CS、DS、ES、SS、FS、GS)中不再存放段基址,而 是段描述符在段描述符表中的索引值,D3-D15位是索引值,D0-D1位是优先级(RPL)用于特权检查,D2位是描述符表引用指示位TI,TI=0指 示从全局描述表GDT中读取描述符,TI=1指示从局部描述符中LDT中读取描述符。这些信息总称段选择符
在这里插入图片描述

段描述符

8个 字节64位,每一个段都有一个对应的描述符。根据描述符描述符所描述的对象不同,描述符可分为三类:储存段描述符,系统段描述符,门描述符(控制描述 符)。在描述符中定义了段的基址,限长和访问内型等属性。其中基址给出该段的基础地址,用于形成线性地址;限长说明该段的长度,用于存储空间保护;段属性 说明该段的访问权限、该段当前在内存中的存在性,以及该段所在的特权级。
在这里插入图片描述

S: 描述符类型(0=系统段,1=数据段或者代码段)
TYPE: 段类型,包括数据段和代码段
整个段描述符包含三部分段基地址,三部分组合得到保护模式下完整的32位段基址。

段描述符表

IA-32处理器把所有段描述符按顺序组织成线性表 放在内存中,称为段描述符表。分为三类:全局描述符表GDT,局部描述符表LDT和中断描述符表IDT。GDT和IDT在整个系统中只有一张,而每个任务 都有自己私有的一张局部描述符表LDT,用于记录本任务中涉及的各个代码段、数据段和堆栈段以及本任务的使用的门描述符。GDT包含系统使用的代码段、数 据段、堆栈段和特殊数据段描述符,以及所有任务局部描述符表LDT的描述符。

GDTR全局描述符寄存器

48位,高32位存放GDT基址,低16为存放GDT限长。

LDTR局部描述符寄存器

16位,高13为存放LDT在GET中的索引值。

保护模式下的内存寻址
IA-32处理器仍然使用段选择器、偏移量逻辑方式表示一个线性地址,那么是怎么得到段的基址呢?在上面说明中我们知道,要得到段的基址首先通过段选择符段选择器中TI位指定的段描述符所在位置: 当 TI=0时表示段描述符在GDT中,如下图所示:
在这里插入图片描述
① 先从GDTR寄存器中获得GDT基址。

② 然后再GDT中以段选择符高13位位置索引值得到段描述符。

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

当TI=1时表示段描述符在LDT中,如下图所示:
在这里插入图片描述
① 还是先从GDTR寄存器中获得GDT基址。

② 从LDTR寄存器中获取LDT所在段的位置索引(LDTR高13位)。

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

④ 用段选择符高13位位置索引值从LDT段中得到段描述符。

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

④ 用段选择符高13位位置索引值从LDT段中得到段描述符。

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

《Linux系统文件页表目录和页表结构(图文详解)》

https://zhuanlan.zhihu.com/p/429914858

两级页表如何实现地址转换:

页表:是一种特殊的数据结构,记录着页面和页框的对应关系。(映射表)
页表的作用:是内存非连续分区分配的基础,实现从逻辑地址转化成物理地址。
在这里插入图片描述

(1) 按照地址结构将逻辑地址拆成三个部分。
(2) 从PCB中读取页目录起始地址,再根据一级页号查页目录表,找到下一级页表在内存中存放位置。
(3) 根据二级页号查表,找到最终想要访问的内存块号。
(4) 结合页内偏移量得到物理地址。

虚拟存储技术
再解决了页必须连续存放的问题后,再看如何第二个问题:没有必要让整个页表常驻内存,因为进程一段时间内可能只需要访问某几个特定的页面。
解决方案:可以在需要访问页面时才把页面调入内存——虚拟存储技术(后面再说)。可以在页表中增加一个标示位,用于表示该页表是否已经调入内存。

应用

若采用多级页表机制,则各级页表的大小不能超过一个页面。

举例说明,某系统按字节编址,采用40位逻辑地址,页面大小为4KB,页表项大小为4B,假设采用纯页式存储,则要采用()级页表,页内偏移量为()位?
页面大小 = 4KB,按字节编址,因此页内偏移量为12位。
页号 = 40 - 12 = 28位。
页面大小 = 4KB,页表项大小 = 4B,则每个页面可存放1024个页表项。因此各级页表最多包含1024个页表项,需要10个二进制位才能映射到1024个页表项,因此每级页表对应的页号应为10位二进制。共28位的页号至少要分为3级。

1、 进程的4G 线性空间被划分成三个部分:进程空间(0-3G)、内核直接映射空间(3G – high_memory)、内核动态映射空间(VMALLOC_START - VMALLOC_END)

2、 三个空间使用同一张页目录表,通过 CR3 可找到此页目录表。但不同的空间在页目录表中页对应不同的项,因此互相不冲突

3、 内核初始化以后,根据实际物理内存的大小,计算出 high_memory、VMALLOC_START、VMALLOC_END 的值。并为“内核直接映射”空间建立好映射关系,所有的物理内存都可以通过此空间进行访问。

4、 “进程空间”和“内核动态映射空间”的映射关系是动态建立的(通过缺页异常)

假设在有三个线性地址 addr1, addr2, addr3 ,分别属于三个线性空间不同部分(0-3G、3G-high_memory、vmalloc_start-vmalloc_end),但是最终都映射到物理页面1:

1、 三个地址对应不同的页表和页表项

2、 但是页表项的高 20bit 肯定是1,表示物理页面的索引号是1

3、 同时,根据高 20 bit,可以从 mem_map[] 中找到对应的 struct page 结构,struct page 用于管理实际的物理页面(就是实际物理页面的物理地址了,到这里就不绕弯子了,顺便想到高速缓冲的匹配命中操作是用哈希表,换算出的要访问的实际物理地址拿到哈希表的输入计算一下哈希值,看看有没命中)(红线)

4、 从线性地址最终的,根据页目录表,页表,可以找到物理地址

5、 Struct page 和物理地址之间很容易互相转换

6、 从物理地址,可以很容易的反推出在内核直接映射空间的线性地址(蓝线)。要想得到在进程空间或者内核动态映射空间的对应的线性地址,则需要遍历相应的“虚存区间”链表。

关于页目录表:
1、 每个进程有一个属于自己的页目录表,可通过 CR3 寄存器找到
2、 而内核也有一个独立于其它进程的页目录表,保存在 swapper_pg_dir[] 数组中

3、 当进程切换的时候,只需要将新进程的页目录把地址加载到 CR3 寄存器中即可

4、 创建一个新进程的时候,需要为它分配一个 page,作为页目录表,并将 swapper_pg_dir[] 的高 256 项拷贝过来,低 768 项则清0

linux0.11版本,所有进程共享同一个页目录而各自使用不同的页表,该共享的页目录就放在物理地址最前面的4k
在这里插入图片描述

《关于gdtr和cr3地址类型的理解》

https://blog.csdn.net/qq_33439820/article/details/79012896
结论:1.cr3里保存页目录表的基址的地址类型为物理地址,页目录表里的每一项也是页表的物理地址。

2.gdtr里保存的地址类型为线性地址。

原因:由于段表并不能保证页表存在或开启,所以它的机制,完全建立在无页表存在的情况。体现在gdtr上,就是gdtr的地址和gdt里的描述符里的地址类型一样,都为线性地址,当开启分页机制后有可能会和物理地址不同。而且,当想要更换段页式的时候,必须在现有地址转换情况下,构造段表和页表,但是当往gdtr里填段表基址是,却必须是在无段表转换的地址。页表和页目录表的基址是无段表和页表转换后的地址,也就是物理地址。

换句话说,就是换段表时,不依赖现有段表。换页表时,不依赖段表和页表。

比如下面这张图,好像是intel手册上的,但是根据我的实践,不知道是不是我的理解不对,我认为不仅cr3是物理地址,页目录表项和页表项都应该是物理地址。
在这里插入图片描述

题外话:最近,想实现一个简单的基于x86的操作系统内存管理功能,网上关于段页式内存管理介绍也挺多,但是,由于自己对计算机硬件不是很了解,所以,在算法之余,更多问题是硬件的细节问题。感觉如果硬件细节不清楚,总会遇到很多奇怪的问题,而且,很难排除,花了很多时间,心里也没底。所以也建议大家无论做什么,基础永远值得花更多时间。
————————————————

《linux 线性地址 物理地址,GDTR和LDTR中放置的是物理地址还是线性地址?》

https://blog.csdn.net/weixin_39945531/article/details/116878299
系统启动时候要设置GDTR和LDTR,它们使用的是线性地址还是物理地址呢?如果是物理地址,是不是说启动到这个时候,分页机制并没有开启,也就是说先转换到32位保护模式,不过是段式的?如果是线性地址的话,是不是说之前就要先设置好页目录和页表,然后装入它们,开启32位保护模式就是直接开启了分页机制?

|

系统开机时,进入的实模式,这个时候如果想进入保护模式,必须先设置GDT表,所以首先是在实模式设置GDT表

为进入保护模式做准备,这个时候当然用的都是物理地址,但进入保护模式后,分段启用,那些设置的物理地址

就被当成线性地址使用了,所以GDT要设置成使得物理地址和线性地址等价,就linux来说,进入保护模式后利用

实模式设置的那个临时GDT,开启分页,以后又再次重新设置了新的GDT,这个GDT就全用的线性地址了。

|

实模式何须要GDT/LDT等内存管理/分页分段的产物。

但是,GDT(boot_GDT)的建立是在实模式阶段进行的。

当然,GDT register里面的是线性地址了。

当real mode to protected mode时候,需要设定PDBR(cr3)的值,加载gdt(线性地址)。

每个process有一个gdt的copy,then they everyone can seperately modify their copy gdt.

ldt is build up after gdt.

the following codes are the codes what the real mode to protected mode.

real_to_prot:

.code16

cli

/* load the GDT register ///************************/

DATA32ADDR32lgdtgdtdesc //load line address

/* turn on protected mode */

movl%cr0, %eax

orl$GRUB_MEMORY_MACHINE_CR0_PE_ON, %eax

movl%eax, %cr0

/* jump to relocation, flush prefetch queue, and reload %cs */

DATA32ljmp$GRUB_MEMORY_MACHINE_PROT_MODE_CSEG, $protcseg

.code32

protcseg:

/* reload other segment registers */

movw$GRUB_MEMORY_MACHINE_PROT_MODE_DSEG, %ax

movw%ax, %ds

movw%ax, %es

movw%ax, %fs

movw%ax, %gs

movw%ax, %ss

/* put the return address in a known safe location */

movl(%esp), %eax

movl%eax, GRUB_MEMORY_MACHINE_REAL_STACK

/* get protected mode stack */

movlprotstack, %eax

movl%eax, %esp

movl%eax, %ebp

/* get return address onto the right stack */

movlGRUB_MEMORY_MACHINE_REAL_STACK, %eax

movl%eax, (%esp)

/* zero %eax */

xorl%eax, %eax

/* return on the old (or initialized) stack! */

ret

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

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

相关文章

MySQL:开始深入其数据(一)DML

在上一章初识MySQL了解了如何定义数据库和数据表(DDL),接下来我们开始开始深入其数据,对其数据进行访问(DAL)、查询DQL()和操作(DML)等。 通过DML语句操作管理数据库数据 DML (数据操作语言) …

Vue3列表触底请求(上手体验hooks新特性)

今天我们来聊一聊业务开发中的触底请求,其实就是分页的一种,只不过传统的分页感觉很丑而已,正好我的小博客最近在做触底分页,借此机会来说一说具体怎么实现的,以及来带领大家使用一下Vue3中的新特性hooks函数。 案例和…

uniapp小程序uView自定义tabbar

两年没接触小程序,又重新拾请来 前言 工具:HBuilder X 3.99版本 微信开发者工具 1.06 语言:vue2 uView 一、创建项目 先使用HBuilder X工具创建一个空白uni-app项目 uviewTest 二、安装和配置 HBuilder X找到工具-》插件安装-》插件市场 u…

Flutter中高级JSON处理:使用json_serializable进行深入定制

Flutter中高级JSON处理 使用json_serializable库进行深入定制 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at: https://jclee95.blog.csdn.netEmail: 291148484163.com. Shenzhen ChinaAddress of this article:https://blog.csdn.net/qq_28550263/article/details/1363…

vscode不能远程连接ubuntu18.04.6

目录 问题解决Portable Mode 安装vscode 补充说明学习资料 问题 vscode远程ssh连接ubuntu18.04.6时,出现如下提示框,单击Learn More后,定位到问题。Can I run VS Code Server on older Linux distributions? 原始是:需要glibc …

Mac 配置Clion Qt 调试显示变量值

背景 使用Clion开发Qt程序,在进行调试时,会看不到Qt类的变量值,只有指针形式,对于调试很不方便。 环境: Macbook ProCPU:M3Qt 5.15.13CLion 2023.3.4 解决方案 为了让Clion能显示Qt类的值,…

Ubuntu常用状态命令

目录 一、温度 1,查看CPU温度 2,查看硬盘温度 二、CPU状态 1,显示CPU的详细信息,包括型号、频率、缓存等 2,显示CPU架构、CPU核心数、线程数、频率等信息 三、登录状态 1,查看成功登录的用户 2&am…

4核8g服务器能支持多少人访问?

腾讯云4核8G服务器支持多少人在线访问?支持25人同时访问。实际上程序效率不同支持人数在线人数不同,公网带宽也是影响4核8G服务器并发数的一大因素,假设公网带宽太小,流量直接卡在入口,4核8G配置的CPU内存也会造成计算…

【代码解读】OpenCOOD框架之model模块(以PointPillarFCooper为例)

point_pillar_fcooper PointPillarFCooperPointPillarsPillarVFEPFNLayerPointPillarScatterBaseBEVBackboneDownsampleConvDoubleConv SpatialFusion检测头 (紧扣PointPillarFCooper的框架结构,一点一点看代码) PointPillarFCooper # -*- c…

在两台CentOS 7服务器上部署MinIO集群---准确

环境说明: 2台Centos7服务器 IP地址分别为172.16.1.9和172.16.1.10 1. 创建minio用户和目录 在两台服务器上执行以下命令: sudo useradd -m -d /app/minio minio sudo mkdir -p /app/minioData sudo mkdir -p /app/minio/logs sudo chown -R mini…

数据结构与算法(数组,栈,队列,链表,哈希表,搜索算法,排序算法,查找算法,策略算法,递归算法,二叉搜索树BST,动态规划算法)

文章目录 1 课程介绍1.1 前置知识1.2 为什么要学习算法1.3 大厂面试常见数据结构题目(基础)1.4 数据结构和算法的关系 2 数据结构2.1 数据结构概述2.1.1 数据结构是什么2.1.2 数据结构分类2.1.2.1 线性结构2.1.2.2 非线性结构2.1.2.3 小总结 2.1.3 数据结构范围 2.2 数组Array2…

leetcode 2.27

leetcode hot 100 哈希1.字母异位词分组2.最长连续序列 双指针1.盛最多水的容器2.和为 K 的子数组 数组1.除自身以外数组的乘积 哈希 1.字母异位词分组 49. 字母异位词分组 方法一:排序 由于互为字母异位词的两个字符串包含的字母相同,因此对两个字符…

Redis之一: 简介及环境安装搭建

什么是NoSQL? NoSQL,指的是非关系型的数据库。NoSQL有时也称作Not Only SQL的缩写,是对不同于传统的关系型数据库的数据库管理系统的统称。 NoSQL用于超大规模数据的存储。(例如谷歌或Facebook每天为他们的用户收集万亿比特的数据&#xf…

python:时间序列谐波拟合与残差分析(利用最小二乘法确定模型参数)

作者:CSDN _养乐多_ 在科学研究中,经常需要对实验数据进行拟合,以找出其中的规律。本文介绍了如何使用Python中的NumPy、Matplotlib和SciPy库进行谐波拟合,并对拟合结果进行残差分析。 从下图可以看出,谐波拟合曲线…

Python算法100例-2.6 分糖果

完整源代码项目地址,关注博主私信源代码后可获取 1.问题描述2.问题分析3.算法设计4.确定程序框架5.完整的程序6.运行结果 1.问题描述 10个小孩围成一圈分糖果,老师分给第1个小孩10块,第2个小孩2块,第3个小孩8块&…

C#,弗洛伊德-瑞文斯特(Floyd-Rivest)算法与源代码

Robert W. Floyd 1 Floyd-Rivest 算法 Floyd-Rivest 算法是一种选择算法,用于在不同元素的数组中找到第k个最小元素。它类似于快速选择算法,但在实际运行中有更好的运行时间。 和 QuickSelect 一样,该算法基于分区的思想工作。对数组进行分…

istio学习记录——VirtualService详解

上一篇使用VirtualService进行了简单的流量控制,并通过Gateway将流量导入到了集群内。这一篇将更加深入的介绍 VirtualService。 k8s中有service,service能够对流量进行负载均衡,那为什么istio又引入了VirtualService呢,因为serv…

最新IE跳转Edge浏览器解决办法(2024.2.26)

最新IE跳转Edge浏览器解决办法(2024.2.26) 1. IE跳转原因1.1. 原先解决办法1.2. 最新解决办法1.3. 最后 1. IE跳转原因 关于IE跳转问题是由于在2023年2月14日,微软正式告别IE浏览器,导致很多使用Windows10系统的电脑在打开IE浏览…

300分钟吃透分布式缓存-17讲:如何理解、选择并使用Redis的核心数据类型?

Redis 数据类型 首先,来看一下 Redis 的核心数据类型。Redis 有 8 种核心数据类型,分别是 : & string 字符串类型; & list 列表类型; & set 集合类型; & sorted set 有序集合类型&…

多线程与高并发(1)- 线程基础、并发特性、锁、JUC工具

文章目录 第一章、线程基础知识一、基础概念1、什么是程序?2、什么是进程?3、什么是线程?4、什么是线程的切换(Context Switch)?5、单核CPU 设定多线程是否有意义?6、工作线程数是不是设置的越大…