Windows内核编程基础(3)

内存分配

在应用层编程时,系统提供了GlobalAlloc/HeapAlloc/LocalAlloc等函数。C/C++库提供了malloc函数,以及new操作符在堆上分配内存。

在我前面一个关于Windows页交换文件的博客中,介绍了虚拟内存,

虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。

堆内存是基于虚拟内存上更小粒度的分割,这个分割由堆管理器管理,根据需求,堆管理器会申请 一页(或多页虚拟内存),然后对这块虚拟内存进行更小粒度的内存分割与管理,以满足开发者对内存的需求。

堆的大小是在应用程序启动时设置,但可以随着空间的需要而增长(分配器从操作系统请求更多内存)。

与应用层的堆概念类似,在内核中有一种称为“池(Pool)"的概念,我们可以从Pool中申请内存

WDK提供了一系列内存分配函数,其中最基本的是ExAllocatePoolWithTag,函数原型如下:

1 NTKERNELAPI
2 PVOID
3 NTAPI
4 ExAllocatePoolWithTag (
5     _In_ __drv_strictTypeMatch(__drv_typeExpr) POOL_TYPE PoolType,
6     _In_ SIZE_T NumberOfBytes,
7     _In_ ULONG Tag
8     );

PoolType:表示 需要申请哪种类型的内存,PoolTypePOOL_TYPE枚举类型

POOL_TYPE定义如下:

 1 typedef _Enum_is_bitflag_ enum _POOL_TYPE {
 2     NonPagedPool,
 3     NonPagedPoolExecute = NonPagedPool,
 4     PagedPool,
 5     NonPagedPoolMustSucceed = NonPagedPool + 2,
 6     DontUseThisType,
 7     NonPagedPoolCacheAligned = NonPagedPool + 4,
 8     PagedPoolCacheAligned,
 9     NonPagedPoolCacheAlignedMustS = NonPagedPool + 6,
10     MaxPoolType,
11     NonPagedPoolBase = 0,
12     NonPagedPoolBaseMustSucceed = NonPagedPoolBase + 2,
13     NonPagedPoolBaseCacheAligned = NonPagedPoolBase + 4,
14     NonPagedPoolBaseCacheAlignedMustS = NonPagedPoolBase + 6,
15     NonPagedPoolSession = 32,
16     PagedPoolSession = NonPagedPoolSession + 1,
17     NonPagedPoolMustSucceedSession = PagedPoolSession + 1,
18     DontUseThisTypeSession = NonPagedPoolMustSucceedSession + 1,
19     NonPagedPoolCacheAlignedSession = DontUseThisTypeSession + 1,
20     PagedPoolCacheAlignedSession = NonPagedPoolCacheAlignedSession + 1,
21     NonPagedPoolCacheAlignedMustSSession = PagedPoolCacheAlignedSession + 1,
22 
23     NonPagedPoolNx = 512,
24     NonPagedPoolNxCacheAligned = NonPagedPoolNx + 4,
25     NonPagedPoolSessionNx = NonPagedPoolNx + 32,
26 
27 } _Enum_is_bitflag_ POOL_TYPE;

常用的值是:NonPagedPool(非分页内存)与PagedPool(分页内存)。

在前面介绍过分页内存与非分页内存的概念。

非分页内存是指这块内存的内容不会被置换到磁盘上,非分页内存非常宝贵,一般用于高IRQL(>= DISPATCH_LEVEL) 的代码中。

分页内存是指这块内存的内容可以被转换到磁盘上。

除了NonPagedPoolPagedPool类型,我们还需要关心的类型是NonPagedPoolExecuteNonPagedPoolNx

NonPagedPoolExecute类型的内存属性为“可执行”,意味着开发者可以将这块内存写入二进制指令然后执行,这个机制虽然很灵活,但存在一定的安全隐患︰对于一些存在漏洞的代码来说,攻击者可以使用“缓存区溢出攻击”技术,在目标内存(缓冲区)中写入可执行指令,由于这块内存具有“可执行“属性,所以攻击者可以成功实施攻击 。

从Win8开始,推荐使用NonPagedPoolNx类型来替代NonPagedPool类型。

从Windows 10 2004开始,使用POOL_FLAG_NON_PAGED类型

NumberOfTypes:表示 需要申请的内存大小

Tag:一个4个字节的标志,用于标志一块内存的使用者,这个Tag—般用于问题排查,如内存泄露,系统蓝屏等。对于内存泄露的情况,可以通过WindbgPoolMon等一些小工具,查看系统中各Tag标志对应的内存大小,找到最大的或者持续增长的内存块。标记中的每个 ASCII 字符必须是0x7E (平铺) 0x20 空间范围内的值。

如果不需要使用Tag标志,可以传递0,或者调用ExAllocatePool函数。

返回值:成功,返回分配内存的首地址。失败返回NULL。

说明:在Windows 10 2004版本中,ExAllocatePoolWithTag函数已经弃用,替换为ExAllocatePool2函数,详细可以参考以下链接:

更新对 ExAllocatePool2 和 ExAllocatePool3 的已弃用 ExAllocatePool 调用 - Windows drivers | Microsoft Learn

内存使用完毕后需要释放,使用ExFreePoolWithTag函数,声明如下:

1 NTKERNELAPI
2 VOID
3 ExFreePoolWithTag (
4     _Pre_notnull_ __drv_freesMem(Mem) PVOID P,
5     _In_ ULONG Tag
6     );

P:需要释放的内存地址

Tag:内存申请 时的标记,如果分配内存时使用的Tag等于0,释放时也传0即可。

后备列表(Lookaside Lists)

在频繁使用ExAllocatePoolWithTag函数分配内存时,容易 造成”内存碎片“。为了提高性能,系统提供了一种被称为”后备列表(Lookaside Lists)“的内存分配方法。

注意:Lookaside的翻译在不同的地方可能有出入,知道这个概念就行了。在《Windows内核编程》一书中,使用的是”旁视列表“,官方文档上显示的是”后备列表“。官方的中文文档是机翻的,但是我这里还是使用了这个名称。

使用”后备列表“的步骤如下:

1、初始化一个”后备列表“

这里以非分页内存为例,分页内存使用方法基本一样

使用ExInitializeNPagedLookasideList函数初始化”后备列表“对象,声明如下:

 1 NTKERNELAPI
 2 VOID
 3 ExInitializeNPagedLookasideList (
 4     _Out_ PNPAGED_LOOKASIDE_LIST Lookaside,
 5     _In_opt_ PALLOCATE_FUNCTION Allocate,
 6     _In_opt_ PFREE_FUNCTION Free,
 7     _In_ ULONG Flags,
 8     _In_ SIZE_T Size,
 9     _In_ ULONG Tag,
10     _In_ USHORT Depth
11     );

Lookaside:表示 被初始化的”后备列表“对象的指针,在64位系统下,这个指针必须以16字节对齐。ExInitializeNPagedLookasideList执行后,Lookaside会被初始化。

Allocate:一个函数指针(回调函数),当我们从”后备列表“对象分配内存时,系统会调用这个函数。

ALLOCATE_FUNCTION声明如下:

1 PVOID
2 ALLOCATE_FUNCTION (
3     _In_ POOL_TYPE PoolType,
4     _In_ SIZE_T NumberOfBytes,
5     _In_ ULONG Tag
6     );

这个参数可以根据自己实际情况使用,不需要使用时,传NULL,系统会使用默认的内存分配函数。

Free:一个函数指针,当我们从”后备列表”释放申请的内存块时,系统会调用这个函数。

FREE_FUNCTION声明如下:

1 VOID
2 FREE_FUNCTION (
3     _In_ __drv_freesMem(Mem) PVOID Buffer
4     );

这个参数可以根据自己实际情况使用,不需要使用时,传NULL,系统会使用默认的内存释放函数。

Flags:内存分配行为。可选以下值

POOL_NX_ALLOCATION:表示分配的非分页内存的属性为“不可执行”,类似上一节介绍的NonPagedPoolNx标志。
POOL_RAISE_IF_ALLOCATION_FAILURE:表示如果内存失败,将抛出一个异常。
0:如果没有特殊要求,可以把Flags参数设置为0。

Size:每次从“后备列表”对象中申请内存的固定大小,单位是字节,这个值不能小于LOOKASIDE_MINIMUM_BLOCK_SIZE

 LOOKASIDE_MINIMUM_BLOCK_SIZE = ((((LONG)__builtin_offsetof(SLIST_ENTRY, Next)) + (sizeof(((SLIST_ENTRY*)0)->Next))))

在64位系统下,LOOKASIDE_MINIMUM_BLOCK_SIZE的值为8。

Tag:表示 内存分配时所使用的标记,与ExAllocatePoolWithTag中的Tag参数一样。

Depth:保留参数,传0即可。

2、需要内存时,直接从”后备列表“对象申请 内存

 申请内存使用ExAllocateFromNPagedLookasideList函数,该函数声明如下:

1 PVOID
2 ExAllocateFromNPagedLookasideList (
3     _Inout_ PNPAGED_LOOKASIDE_LIST Lookaside
4     )

Lookaside:“后备列表”对象指针

返回值:执行成功,返回相应的内存块首地址,否则 返回NULL

ExAllocateFromNPagedLookasideList分配的内存大小为ExInitializeNPagedLookasideList函数所指定的Size

3、使用完成后,通过”后备列表“回收这些内存

 释放内存使用ExFreeToNPagedLookasideList函数,该函数声明如下:

1 VOID
2 ExFreeToNPagedLookasideList (
3     _Inout_ PNPAGED_LOOKASIDE_LIST Lookaside,
4     _In_ __drv_freesMem(Mem) PVOID Entry
5     )

Lookaside:“后备列表“对象指针

Entry:表示需要释放的内存块

4、当不再需要”后备列表“时,将其对象删除。

 删除”后备列表“时,使用ExDeleteNPagedLookasideList函数。该函数声明如下:

1 NTKERNELAPI
2 VOID
3 ExDeleteNPagedLookasideList (
4     _Inout_ PNPAGED_LOOKASIDE_LIST Lookaside
5     );

Lookaside:表示需要删除的”后备列表“对象指针

 5、完整的示例

下面使用简单的代码演示一下,如何使用”后备列表“

//直接申请内存
PVOID pAllocFromPool = ExAllocatePoolWithTag(NonPagedPoolNx, 10240, 0);

if (pAllocFromPool != NULL)
    ExFreePoolWithTag(pAlloc, 0);


//使用后备列表(Lookaside Lists)
PNPAGED_LOOKASIDE_LIST pLookasideList = 
(PNPAGED_LOOKASIDE_LIST)ExAllocatePoolWithTag(NonPagedPool, sizeof(NPAGED_LOOKASIDE_LIST), 'urfh');

if (pLookasideList != NULL)
{
    memset(pLookasideList, 0, sizeof(NPAGED_LOOKASIDE_LIST));
    //初始化
    ExInitializeNPagedLookasideList(pLookasideList, NULL, NULL, 0, 1024, 'urfh', 0);

    //分配
    PVOID pAlloc = ExAllocateFromNPagedLookasideList(pLookasideList);

    if (pAlloc != NULL)
    {
        DbgPrint("Memory Allocate First:%p", pAlloc);

        //释放
        ExFreeToNPagedLookasideList(pLookasideList, pAlloc);
    }

    //再次分配
    pAlloc = ExAllocateFromNPagedLookasideList(pLookasideList);

    if (pAlloc != NULL)
    {
        DbgPrint("Memory Allocate Second:%p", pAlloc);

        //释放
        ExFreeToNPagedLookasideList(pLookasideList, pAlloc);
    }

    //删除Lookaside Lists
    ExDeleteNPagedLookasideList(pLookasideList);
    //释放
    ExFreePoolWithTag(pLookasideList, 'urfh');

注意:如果编译报错,请将ExAllocatePoolWithTag替换为ExAllocatePool2函数

1 PVOID pAllocFromPool = ExAllocatePool2(POOL_FLAG_NON_PAGED, 10240, 0);

 运行结果:

参考资料

比较内存分配方法

https://learn.microsoft.com/zh-cn/windows/win32/memory/comparing-memory-allocation-methods

What and where are the stack and heap?

https://stackoverflow.com/questions/79923/what-and-where-are-the-stack-and-heap

使用后备列表

使用后备列表 - Windows drivers | Microsoft Learn

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

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

相关文章

Unity开发绘画板——03.简单的实现绘制功能

从本篇文章开始,将带着大家一起写代码,我不会直接贴出成品代码,而是会把写代码的历程以及遇到的问题、如何解决这些问题都记录在文章里面,当然,同一个问题的解决方案可能会有很多,甚至有更好更高效的方式是…

Go容器化微服务系统实战

1-1 本课的go微服务有什么不同? 聚焦于容器化可观测的购物微服务系统实战,通过介绍Go语言的应用趋势、容器化优势及微服务适用性,旨在解决学习微服务过程中遇到的难点。课程内容涵盖微服务整体架构、技术工具框架及容器平台等关键技术&#…

Java之路--瓦解逻辑控制与方法使用已是瓮中捉鳖

嗨嗨大家!今天我们来学习逻辑运算和方法的使用~ 目录 一 逻辑控制 1 分支结构 1.1 if语句 1.2 switch 语句 2 循环结构 2.1 while 循环 2.2 for 循环 2.3 do while 循环 2.4 break 2.5 continue 3. 输出输入 二、方法的使用 1 方法定义语法 2 实参和…

苹果macOS 15.0 Sequoia正式版发布:iPhone应用镜像玩、手机消息电脑知

9月17日苹果向 Mac 电脑用户推送了 macOS 15 更新(内部版本号:24A335),除了引入数个 iOS 18 的新功能外,macOS 15 Sequoia 还带来了全新的 Continuity 功能 ——iPhone 镜像。 iPhone 镜像功能可以让用户直接在 Mac 上…

[Linux] Linux操作系统 进程的状态

标题:[Linux] Linux操作系统 进程的状态 个人主页:水墨不写bug (图片来源于网络) 目录 一、前置概念的理解 1.并行和并发 2.时间片 3.进程间具有独立性 4.等待的本质 正文开始: 在校的时候,你一定学过《…

图解Transformer就这30页PPT,你们真不看啊

图解Transformer就这30页PPT,你们真不看啊 主要介绍了Seq2Seq模型,慢慢引出了transformer的整体模型架构,比较具体的介绍了编码器部分的数据处理过程,包括了位置编码、多头注意力机制、残差连接、Layer Norm以及前馈网络等基本结…

支付宝沙箱环境 支付

一 什么是沙箱: 沙箱环境是支付宝开放平台为开发者提供的安全低门槛的测试环境 支付宝正式和沙箱环境的区别 : AI: 从沙箱到正式环境: 当应用程序开发完成后,需要将应用程序从沙箱环境迁移到正式环境。 这通常涉及…

如何查看线程

1、首先找到我们的电脑安装jdk的位置,这里给大家展示一下博主本人的电脑jdk路径下的jconsole位置。 2、 ok,那么找到这个jconsole程序我们直接双击打开就可以查看我们电脑的本地进程: jconsole 这里能够罗列出你系统上的 java 进程&#xff0…

古代经典名方目录数据库-支持经典名方检索!

"古代经典名方目录"是指一系列历史上流传下来的,被认为具有一定疗效的中药方剂的汇总。这些方剂多来源于历代医学典籍,经过长期临床实践的检验,部分已被收录于官方的目录之中,以便于现代医疗实践中的参考和应用。 目前…

手机在网状态查询接口如何用C#进行调用?

一、什么是手机在网状态查询接口? 手机在网状态查询接口是利用实时数据来对手机号码在运营商网络中的状态进行查询的工具,包括正常使用状态、停机状态、不在网状态、预销户状态等。 二、手机在网状态查询适用哪些场景? 例如:商…

设计模式-结构型-11-代理模式

文章目录 1. 基本介绍2. 静态代理2.1 基本介绍UML 类图 2.2 应用实例定义接口目标对象代理对象调用代理 2.3 静态代理优缺点 3. 动态代理3.1 基本介绍3.2 JDK 中生成代理对象的 API参数说明UML类图 3.3 应用实例定义接口目标对象代理工厂调用代理 4. Cglib 代理4.1 基本介绍4.2…

求一个数的因子数(c语言)

1.计算并输出给定整数n的所有因子(不包括1与n自身)之和。规定n的值不大于1000。(因子是能整除n的数 即n%i0) // 例如,在主函数中从键盘给n输入的值为856,则输出为: sum763。 2.第一步我们先输入n的数&…

Koa (下一代web框架) 【Node.js进阶】

koa (中文网) 是基于 Node.js 平台的下一代 web 开发框架,致力于成为应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石; 利用 async 函数 丢弃回调函数,并增强错误处理,koa 没有任何预置的中间件,可快速…

mysql安装教程(新手版)

本教程不需要手动设置配置文件,比较简单,适合新手,过程需联网。 1.找到mysql官网 mysql官网 一.mysql的安装 1.界面如下图,点击箭头所指。 2.选择mysql版本,系统,安装。 3.下载完成后双击打开&#xff0…

golang操作mysql利器-gorm

1、傻瓜示例 GORM通过将数据库表中的数据映射到面向对象的模型中,简化了数据库操作,使得开发者可以很方便的使用代码来操作数据库,而无需编写SQL语句。 目前有个mysql表:miniprogram_orders,其存储了所有用户对应的订…

Android SystemUI组件(07)锁屏KeyguardViewMediator分析

该系列文章总纲链接:专题分纲目录 Android SystemUI组件 本章关键点总结 & 说明: 说明:本章节持续迭代之前章节的思维导图,主要关注左侧上方锁屏分析部分即可。 为了更好理解本文的内容,优先说明下SystemUI中与Ke…

CoreDNS实现跨集群service解析实践

CoreDNS实现跨集群service解析实践 背景介绍使用条件实现方案 CoreDNS是一款使用Go语言实现的专为云原生应用而生的DNS服务器。本文介绍CoreDNS在特定实际场景下的一种进阶使用实践,也许能为其他也在使用CoreDNS做服务发现的同学提供一些启发和思考。 背景介绍 在…

luceda ipkiss教程 76:设计光栅耦合器

案例分享:设计光栅耦合器 全部代码如下: from si_fab import all as pdk from ipkiss3 import all as i3 import numpy as npclass grating_coupler(i3.PCell):"""SOI grating coupler."""_name_prefix "grating_c…

mysql 05 InnoDB数据页结构

01.数据页结构的快速浏览 02.记录在页中的存储 在页的7个组成部分中,我们自己存储的记录会按照我们指定的 行格式 存储到 User Records 部分。但是在一开始生成页的时候,其实并没有 User Records 这个部分,每当我们插入一条记录&#xff0c…

单词记忆的化境:用思想的流水去淹没坚硬的石块

其实,鹅卵石通常都是很硬的。但是河底的石子,几乎大多都成了鹅卵石,它们被流水淹没,日复一日、夜以继日的冲刷着,没有了棱角。 在单词的记忆过程中,我们有太多的人,都有着不堪回首的往事&#x…