新书推荐:1.3 内存管理模式

本节必须掌握的知识点:

        4GB虚拟空间

        虚拟内存

        多任务切换

1.3.1 4GB虚拟空间

       ■Win16操作系统

Windows1.0版本为16位操作系统,支持16位处理器实模式,最大寻址空间为1MB。Win16操作系统的内存管理非常简单,采用分段内存管理模式,最小段为16个字节,最大段为64KB,整个1MB内存空间都是透明的,没有任何访问限制。逻辑地址为“段值:偏移”,段值和偏移都是16位。将段值左移4位+偏移得到物理地址。在这种模式下,CPU的寄存器的宽度为16位,因此C语言的int数据类型的宽度也是16位。

Win32操作系统

当体系结构从16位到32位的时候,Windows API及其语法发生了最大改变。Windows1.0到3.1版本一直采用16位的分段内存管理模式。为了保证兼容性,INTEL的386之后的32位处理器也都支持这一模式。从Windows NT及Windows 95开始,利用32位intel 386、486以及奔腾处理器,Windows开始支持32位的flat平坦内存管理模式。C语言的int数据类型也因此扩展成32位宽。

Windows32位应用程序按照32位最大寻址范围4GB虚拟空间编写源程序。由于Win32操作系统为保护模式,普通应用程序只具有R3权限,只可以访问低2GB虚拟内存空间(高2GB空间为操作系统内核),在源程序中只需要使用32位偏移地址。

在32位保护模式中,仍然采用分段和分页模式管理内存空间。32位段基址为起始地址,源程序中的32位偏移地址为段内偏移。想要将源程序中的32位偏移地址转换为物理地址需要经过两个步骤:

●步骤一:将32位源程序中的逻辑地址转换为32位线性地址。

转换方法为:32位段基址+32位偏移地址=32位线性地址。

新的问题来了,在32位处理器中的段寄存器只有16位,且段寄存器数量只有6个。由于段寄存器无法存放32位段基址。为此,Windows操作系统设计了段选择子和段描述符表,段选择子为16位,作为段描述符表的索引存入段寄存器。段描述符表的每个表项为一个64位值,其中32位为段基址,20位为段界限域(定义段大小),12位段属性(定义段权限)。描述符表项的索引为13位,作为段选择子的高13位。段选择子的第0位和第1位存放RPL(段权限请求R0~R3)。段选择子第2位为0时,表示访问全局段描述符表并查找段基址,第2位为1时,表示访问局部段描述符表并查找段基址。找到对应的段基址后,再加上32位偏移地址就得到了32位线性地址。

那么如何找到段描述符表呢?为此,32位处理器增加了GDTR全局段描述符表寄存器、LDTR局部段描述表寄存器、中断描述符表寄存器IDTR和任务TR寄存器。GDTR寄存器为48位寄存器,存储全局段描述符表的32位地址和16位段界限。指令LGDT和SGDT分别用于加载和保存GDTR寄存器的内容。操作系统中只有一个GDT全局段描述符表。局部描述符表寄存器LDTR和任务寄存器TR为16位寄存器,存储局部描述符表段和任务段选择子,这是因为局部描述符表和任务段本身是作为全局描述符表的表项存在的,Windows操作系统可以同时存在多个局部段描述表和TSS任务段。如果需要访问局部段描述符表或TSS任务段可以使用LDT指令或LTR指令将段选择子存入LDTR和TR寄存器。

看到这里,读者可能会产生一些畏惧的情绪,Win32位系统寻址尽然如此复杂。如果你仅是一名Windows应用程序员,完全没必要担心。在应用程序的源程序中只需要使用32位的偏移地址就可以了。至于段基址和段权限这些内容都是使用操作系统默认值,应用程序的段基址通常为0,而段的大小是以页(4KB)为单位分配,在编译时由编译器更具源代码中分段的大小确定。至于段属性只能是R3特权级。接下来我们做一个实验。

实验一:查看32位应用程序的内存分配

打开DtDebug调试器,随便拖入一个32位应用程序,如图1-1所示。

【注】本机环境为X64处理器,Windows 10操作系统。

图1-1 观察段寄存器的值

●观察段寄存器的值

图中ES附加段寄存器的值为002BH,CS段寄存器的值为0023H,SS段寄存器的值为002BH,DS寄存器的值为002BH。这4个段寄存器的段选择子中的权限请求都是R3(低两位的值为3),表明当前进程的特权级为R3。FS段寄存器指向线程环境块,在32位程序中GS段寄存器通常用来指向TLS本地线程存储。

【注意】不同版本的Windows操作系统中默认的段寄存器的值可能会不同,但是其特权级一定是相同的。

●观察内存分配

图1-2 查看内存窗口

如图1-2所示,点击工具栏内存窗口“M”,在内存中可以清晰的看到当前进程HelloWord.exe的内存分配情况。在调试器中只显示低2GB空间的内存分配。Address一栏显示32位线性地址(每个段的基址),Size一栏显示内存分配大小(以4KB页为单位)。Owner一栏显示进程名称,Section一栏显示节区名(段名)。在低2GB空间的高地址处加载dll系统动态链接库。0x7FFF0000~0x7FFFFFFF和0~0xFFFF为禁止访问区域,其余空白空间为进程环境块、线程环境块、堆区和栈区。

如果读者想要更深入的学习Windows系统内核,请留意我们关于操作系统内核的课程。

●步骤二:将32位线性地址转换为物理地址

在转换之前首先需要考虑的是物理地址是多少位。如果物理内存小于4GB,那么物理地址使用32位就可以了。如果物理内存大于4GB,可以考虑使用36位,可以寻址的范围就是64GB了。剩下的问题就是如何将32位线性地址转换为32位或36位或者更多位的物理地址了。我们以小于等于4GB物理内存为例,对应的物理地址是32位,以4KB页为单位分配内存。我们可以按照10-10-12分页机制创建一个PageTable地址映射表。如图1-3所示,32位线性地址中的高10位为页目录表索引,32位线性地址中间10位为页表索引,32位线性地址低12位为偏移地址。创建进程初始化时创建了地址映射表,页目录表的物理基址存储在CR3寄存器中。

图1-3 10-10-12分页机制

转换公式:页目录表基址+PDI*4+PTI*4+12位偏移。

每个进程在初始化时都会创建一个CR3(准确的说是一个CR3的值,CR3本身是个寄存器,一个核,只有一套寄存器), CR3指向一个物理页就是页目录表,一共1024个表项(也就是1024个页表),每一项32位,共4096字节(4KB)。每一个页表中也有1024个表项,每一项都是一个32位的物理页基址。每一个页表是4KB大小,一共1024个页表(页目录表本身为其中一个页表),因此地址映射表一共占用4MB大小的空间,并且存储在高2GB空间中。我们将高2GB空间虚拟地址0xC0300000和CR3中存储的页目录表的物理页地址对应,将虚拟地址0xC0000000与页表的第0页对应,因此只需要使用虚拟地址0xC0300000或0xC0000000就可以直接访问地址映射表了。通过查表的方法将32位线性地址转换为32位物理地址。

以此类推,如果物理内存大于4GB,可以使用2-9-9-12分页机制建立地址映射表。也可以是其它类似的分页机制,只要可以正确访问物理地址就可以了。

 注意

       只有真正发生读写操作的时候,才需要查找地址映射表,访问物理内存。

       ■Win64操作系统:

       Windows 64位操作系统理论上可以访问的最大内存为264,但实际只使用了低48位,可以访问的248TB内存空间。Windows 64位操作系统寻址变得简单了,DS、CS、SS段寄存器不再需要,没有了分段机制,只保留了分页机制。Windows 64位系统采用9-9-9-9-12分页机制,如图1-4所示:

       Win64操作系统分页机制原理和Win32操作系统分页机制原理相同,此处不再赘述。

                                                        图1-4 9-9-9-9-12分页机制

1.3.2 虚拟内存

       上一小节我们讲述了4GB虚拟空间,这一小节我们讲解虚拟内存。很多读者无法正确区分虚拟内存和虚拟空间。

       ■虚拟空间

4GB虚拟空间是我们编写32位Windows应用程序的模板,我们在源程序中按照32位逻辑地址编写应用程序,程序可以访问的虚拟地址空间的大小为4GB。

虚拟内存

●分页机制

虚拟内存是和页交换机制密切相关的。早期的32位计算机的物理内存很小,可能只有512KB,后来随之集成电路的发展,物理内存逐渐扩大到1GB、2GB、4GB、8GB,今天32GB的物理内存已经是比较常用的了。32位应用程序按照4GB模板编写,随着应用程序的功能越来越复杂,程序的体积必然越来越大。而且Win32系统支持多任务,可以同时加载多个进程,这必然涉及到一个非常严峻的问题,物理内存不足。为了解决这个问题,Windows操作系统采用了分页机制和页交换机制。由于地址映射表的存在,我们可以通过映射机制(映射就是查表的意思)将虚拟地址转换为物理地址。Windows操作系统以4KB页为单位分配虚拟4GB空间,如图1-2所示,HelloWord.exe进程初始化时一共分配了4个页,其中PE头分配了一页,代码段.text分配了一页,.rdata节区分配了一页,数据段.data分配了一页。与此对应,物理内存足够分配4个对等的物理页,并将其填写到地址映射表中,与虚拟线性地址想对应。

●页交换机制

假设HelloWord.exe进程体积大小为4GB,再加上系统DLL、堆栈、进程环境块和线程环境块占用的内存空间,在低2GB虚拟空间中无法正常加载整个进程,只能先加载部分页。再假设物理内存只有512MB大小,在地址映射表中实际映射的物理页必然小于实际需要的物理页。为了保证已加载的页通过地址映射表可以正常访问物理内存,当前已加载的部分页(必定小于512MB)。当前已加载页执行完之后,后续的页由于未加载,引发缺页异常,Windows操作系统的异常处理程序将已执行过的页存储到物理磁盘上的页交换文件中,然后在装载将要访问的页面,并重新建立物理地址映射后继续执行。我们把这个过程称之为页交换机制。我们将磁盘上的交换文件和物理内存一并称之为虚拟内存,虚拟内存的大小=页交换文件的大小+实际物理内存的大小。实际情况是,操作系统同时加载了多个类型HelloWord.exe进程,实际需要的内存要大的多。接下来我们动手做一个实验为证。

实验二:查看虚拟内存和物理内存

打开任务管理器,如图1-5所示:

图1-5 任务管理器查看内存

图中显示当前机器物理内存为16GB,正在使用中的物理内存为5.8GB,剩余可以使用的物理内存为10GB。注意下方,已经提交的内存为17.6GB,已申请使用的内存为22.9GB,这里的内存已经超出物理内存16GB的大小,必定是指虚拟内存。

此外,我们还可以通过另外一种方式查看当前Windows系统的虚拟内存大小。在控制台窗口输入命令:systeminfo,查看当前机器信息如下所示:

C:\Users\16400>systeminfo

主机名:           DESKTOP-ENIJKEB

OS 名称:          Microsoft Windows 10 教育版

OS 版本:          10.0.16299 暂缺 Build 16299

OS 制造商:        Microsoft Corporation

OS 配置:          独立工作站

OS 构件类型:      Multiprocessor Free

注册的所有人:     暂缺

注册的组织:       暂缺

产品 ID:          00328-10000-00001-AA821

初始安装日期:     2018/12/22, 22:50:35

系统启动时间:     2023/11/21, 7:01:29

系统制造商:       MSI

系统型号:         MS-7A59

系统类型:         x64-based PC

处理器:           安装了 1 个处理器。

                 [01]: Intel64 Family 6 Model 158 Stepping 9 GenuineIntel ~4200 Mhz

BIOS 版本:        American Megatrends Inc. A.50, 2017/3/31

Windows 目录:     C:\Windows

系统目录:         C:\Windows\system32

启动设备:         \Device\HarddiskVolume1

系统区域设置:     zh-cn;中文(中国)

输入法区域设置:   zh-cn;中文(中国)

时区:             (UTC+08:00) 北京,重庆,香港特别行政区,乌鲁木齐

物理内存总量:     16,271 MB

可用的物理内存:   9,627 MB

虚拟内存: 最大值: 23,393 MB

虚拟内存: 可用:   4,847 MB

虚拟内存: 使用中: 18,546 MB

页面文件位置:     C:\pagefile.sys

域:               WORKGROUP

登录服务器:       \\DESKTOP-ENIJKEB

修补程序:         安装了 13 个修补程序。

                  [01]: KB4534129

                  [02]: KB4134661

                  [03]: KB4295110

                  [04]: KB4462930

                  [05]: KB4471331

                  [06]: KB4477136

                  [07]: KB4480979

                  [08]: KB4486153

                  [09]: KB4486155

                  [10]: KB4541731

                  [11]: KB4561600

                  [12]: KB4562560

                  [13]: KB4561602

网卡: 安装了 3 个 NIC。

                  [01]: VMware Virtual Ethernet Adapter for VMnet1

                      连接名:      VMware Network Adapter VMnet1

                      启用 DHCP:   是

                      DHCP 服务器: 192.168.194.254

                      IP 地址

                        [01]: 192.168.194.1

                        [02]: fe80::6c0e:7577:e534:10d6

                  [02]: VMware Virtual Ethernet Adapter for VMnet8

                      连接名:      VMware Network Adapter VMnet8

                      启用 DHCP:   是

                      DHCP 服务器: 192.168.76.254

                      IP 地址

                        [01]: 192.168.76.1

                        [02]: fe80::5d7b:2e73:7d56:c7f6

                  [03]: Intel(R) Ethernet Connection (2) I219-V

                      连接名:      以太网

                      启用 DHCP:   否

                      IP 地址

                        [01]: 192.168.2.105

                        [02]: fe80::4d62:17e6:4fc3:95a4

Hyper-V 要求:     虚拟机监视器模式扩展: 是

                  固件中已启用虚拟化: 是

                  二级地址转换: 是

                  数据执行保护可用: 是

【注意】页交换文件位置: C:\pagefile.sys

1.3.3 多任务切换

       ■DOS系统任务切换

回顾一下简单直白的DOS操作系统。DOS操作系统为单任务系统,处理器和操作系统本身并不支持多任务切换。聪明的程序员自己想办法实现了DOS系统的任务切换。当一个任务(进程)退出时,将其一部分程序驻留在内存中,然后通过热键激活驻留程序实现任务切换。还可以通过另外一种方式,设置一个时钟,到了指定时间执行特定的任务。这两种方式从本质上都是通过中断(键盘中断和时钟中断)实现任务切换。

       ■Windows系统任务切换

我们再看Windows操作系统是如何实现任务切换的。先明确一下什么是任务。在单核处理器时代,一个进程只有一个线程,任务等同于进程或线程。到了多核处理器时代,一个进程可以同时拥有多个线程,此时,任务特指线程,任务切换就是线程切换。

●Windows任务切换可以分为几种情形:

情形一:线程时间片到期,切换线程就绪队列中的新线程。

情形二:有了更高优先级的线程进入就绪队列,终止当前线程,切换到优先级高线程。

情形三:线程结束,自然退出。

情形四:强制结束当前线程。

线程切换时,需要将线程信息保存到线程环境块中,以备再次轮循执行。

●在应用程序中如何实现任务切换呢?32位处理器提供了几种任务切换的方法,包括调用门、中断门、陷阱门和中断门。而实际情况是,Windows操作系统只使用了陷阱门和中断门,部分使用了任务门,完全未使用调用门。不过作为程序员,可以利用处理器提供的所有方法实现任务切换。

作为Windows应用程序的开发者不必担心,任务切换的实现都是隐藏在现有的API函数调用中,只需掌握其调用方法就可以了。如果你是一个有追求的开发者,可以进一步深入学习,这里作者推荐《Windows核心编程》这本书经典的教科书,也可以参考编程达人关于保护模式的Windows内核相关的系列教材。 

本文摘自编程达人系列教材《Winwows API每日一练》。

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

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

相关文章

I.MX RT1170之MIPI DSI初始化和显示流程详解

MIPI DSI(Mobile Industry Processor Interface Display Serial Interface)是一种广泛应用于移动设备显示屏的接口标准。由MIPI联盟制定,DSI接口旨在提供高效、低功耗的显示屏数据传输解决方案。 本节来就通过学习I.MX RT1170单片机中的MIPI…

【Linux】Linux项目自动化构建工具——make/Makefile

1.背景 会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的 规则来指定,哪些文件需要先编译,哪些文件需…

Java集合思维导图

详细内容请看链接内容 Java集合面试题集——2024最新大厂面试

【图像识别系统】表情识别Python+人工智能深度学习+TensorFlow+卷积算法网络模型+图像识别

表情识别系统,本系统使用Python作为主要编程语言,通过TensorFlow搭建ResNet50卷积神经算法网络模型,通过对7种表情图片数据集(‘Neutral’, ‘Anger’, ‘Disgust’, ‘Fear’, ‘Happy’, ‘Sad’, ‘Surprise’)进行…

RabbitMQ学习笔记(一)RabbitMQ部署、5种队列模型

文章目录 1 认识MQ1.1 同步和异步通讯1.1.1 同步通讯1.1.2 异步通讯 1.2 技术对比 2 RabbitMQ入门2.1 RabbitMQ单机部署2.2 RabbitMQ基本结构2.3 RabbitMQ队列模型2.3.1 简单队列模型(Simple Queue Model)2.3.2 工作队列模型(Work Queue Mode…

visual studio打包qt算子时,只生成dll没有生成lib等文件

问题:在visual studio配置了qt项目,并打包成dll,原则上会生成一堆文件,包括dll,lib等文件。 解决办法: 挨个右击源代码的所有头文件-》属性-》项类型。改成qt头文件形式,如下。

事务详讲(本地及分布式)

本地事务在分布式的问题: 因为在分布式服务中,难免一个接口中会有很多调用远程服务的情况,这个就非常容易出现问题,以下是一个详细的例子: 例如,你为了保证事物的一致性等要求,所以,你方法上只写了Transactional,但你的业务中又需要调用其他微服务的方法(Feign),这时就容易出现…

【机器学习】Qwen1.5-14B-Chat大模型训练与推理实战

目录 一、引言 二、模型简介 2.1 Qwen1.5 模型概述 2.2 Qwen1.5 模型架构 三、训练与推理 3.1 Qwen1.5 模型训练 3.2 Qwen1.5 模型推理 四、总结 一、引言 Qwen是阿里巴巴集团Qwen团队的大语言模型和多模态大模型系列。现在,大语言模型已升级到Qwen1.5&…

使用 Scapy 库编写 ICMP 重定向攻击脚本

一、介绍 ICMP重定向攻击(ICMP Redirect Attack)是一种网络攻击,攻击者通过发送伪造的ICMP重定向消息,诱使目标主机更新其路由表,以便将数据包发送到攻击者控制的路由器或其他不可信任的设备上。该攻击利用了ICMP协议…

springboot配置集成RedisTemplate和Redisson,使用分布式锁案例

文章要点 自定义配置属性类集成配置RedisTemplate集成配置分布式锁Redisson使用分布式锁简单实现超卖方案 1. 项目结构 2. 集成RedisTemplate和Redisson 添加依赖 依赖的版本与继承的spring-boot-starter-parent工程相对应&#xff0c;可写可不写 <!--spring data redis…

Spring boot 集成mybatis-plus

Spring boot 集成mybatis-plus 背景 Spring boot集成mybatis后&#xff0c;我们可以使用mybatis来操作数据。然后&#xff0c;我们还是需要写许多重复的代码和sql语句&#xff0c;比如增删改查。这时候&#xff0c;我们就可以使用 mybatis-plus了&#xff0c;它可以极大解放我…

沐风老师3DMAX顶点切线控制插件VertexTangants安装使用方法

3DMAX顶点切线控制插件VertexTangants安装使用方法 3DMAX顶点切线控制插件VertexTangants&#xff0c;用于轻松控制图形顶点切线的工具。 【主要功能】 -脚本具有获取选定顶点的自动检测功能&#xff0c;您可以随时使用“获取按钮”获取选定顶点。 -有一个用于激活撤消的ON按…

项目资源管理

目录 1.概述 2.六个过程 2.1. 规划资源管理 2.2. 估算活动资源 2.3. 获取资源 2.4. 建设团队 2.5. 管理团队 2.6. 控制资源 3.应用场景 3.1.十个应用场景 3.2.软件开发项目 3.2.1. 资源规划 3.2.2. 资源分配 3.2.3. 资源获取 3.2.4. 资源优化 3.2.5. 资源监控与…

如何在外网http访问内网邮件server?

不少公司选择用winmail搭建部署内部邮箱服务器&#xff0c;对于邮件管理员&#xff0c;不但需要在局域网内&#xff0c;常常需要在外网也能访问到邮箱服务管理。winmail本身系统功能可以开启http访问管理&#xff0c;但当需要在外网http访问内网邮箱服务时&#xff0c;需要用到…

vue3通过Vite实现工程化

1. vue3简介 Vue 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建&#xff0c;并提供了一套声明式的、组件化的编程模型&#xff0c;帮助你高效地开发用户界面。无论是简单还是复杂的界面&#xff0c;Vue 都可以胜任。官网为:Vue.js - 渐进…

秋招突击——算法打卡——6/3——复习{最低通行费、(状态压缩DP)小国王}——新做:{罗马数字转整数、最长公共前缀}

文章目录 复习背包模型——最低通行费题目内容实现代码 &#xff08;状态压缩DP&#xff09;小国王检查状态本身是否存在两个连续的1计算所有的合法状态已经所有合法状态之间的转移动态规划过程 新作罗马数字转整数个人实现实现代码 参考做法实现代码 最长公共前缀个人实现参考…

Docker无法stop或者rm指定容器

Docker无法stop或者rm指定容器 今日准备重启一下docker 容器部署的 Nginx 时&#xff0c;使用的命令是 docker exec -it ir-nginx nginx -s reload 结果发现无法重启报错 然后想着关闭再启动&#xff0c;结果发现 docker restart 、docker stop 、docker kill 、docker exec 都…

【科学文献计量】使用Endnote软件打开中国知网导出的文献期刊解析不正确问题解决

使用Endnote软件打开中国知网导出的文献期刊解析不正确问题解决 问题解决问题 新建一个Endnote的材料库,然后把下载好的中国知网文献数据(知网数据导出的是Endnote格式样式)导入进来。找到文件所在路径,导入的类型选择是“Endnote import”,然后点击确定,界面结果如下 …

汇编:数据定义数据填充

数组的定义 在32位汇编语言中&#xff0c;定义数组时&#xff0c;通常使用定义数据指令&#xff08;如 DB, DW, DD,DQ &#xff09;和标签来指定数组的名称和内容。DB定义字节数组&#xff08;每个元素占1字节&#xff09;、DW定义字数组&#xff08;每个元素占2字节&#xff…

CAD 文件(DXF / DWG)转换为(DXF / PDF / PNG / SVG)

方法一Github 这个是ezdxf出品的&#xff0c;可以使用命令行的方式进行转换 ezdxf draw -o file.<png|svg|pdf> <file.dxf>也可以自己改动代码 examples/addons/drawing/pdf_export.py 但是直接运行会有误&#xff0c;以下是我改动后的代码&#xff1a; from ez…