windows驱动开发-内存概述

news2024/9/24 4:19:41

“90%的程序问题都是由内存引起的,剩下的10%是使用内存引起的!”这是一句非常经典的论证,实际上,在程序开发中,内存问题就是最大的问题,没有之一。

现代的计算机体系中,内存承载了太多的功能,内存既是数据的载体,也是指令的载体,甚至还因为虚拟内存技术和磁盘挂钩,故上面那句话的意思是,所有的程序问题都可以从内存方面找到一部分原因。

注意: 下面的讨论中,我们使用windows 10 x64版本来讨论64位地址空间下的内存话题。

内存地址

先看一下地址空间的定义: 虚拟地址空间是指CPU能够寻址到的最大理论地址范围,主要由CPU的寻址方式决定,但虚拟地址空间的大小也由操作系统决定,32位的操作系统虚拟地址空间的大小为 2^32 字节,也就是 4G,64 系统的操作系统虚拟地址空间大小为 2^64 字节,这是一个非常大的数,实际上,windows并不支持那么大的虚拟地址空间,windows 10的地址空间范围如下表:

注意: 虚拟地址和物理内存本身不是一一对应的,但是描述物理内存和虚拟地址的对应关系和管理方案,那部分留到内核源代码剖析里面讲会比较好,在这里,我们仅仅简单讨论虚拟地址和物理地址即可,虽然中间还隔着线性地址,但是这一部分其实在编程开发中基本用不到。

虚拟地址

在C/C++编程的时候,我们可以获取一个变量或者函数的地址,并直接访问这个地址,然后使用这个地址进行运算:

   #define MAX_VALUE 256
 
   char *buffer = new char[MAX_VALUE];

   // 此时,我们可以下断点,看到buffer的值是一个64位的无符号数值

   for(int i = 0; i < MAX_VALUE , i++)
   {
        *(buffer + i) = i;
   }
   ...

   delete [] buffer;

所有在应用层看到的地址都是虚拟地址,这个地址并不代表真实的物理地址。使用虚拟地址的好处在于可以支持多个进程,如上面所说的,进程的可用地址空间必然小于或者等于系统的地址空间,那么这种情况下windows如何支持多进程呢?

答案是系统将进程的地址空间分为两部分,一部分是系统地址空间,这部分对于每个操作系统都是一样的,另一部分就是进程的私有空间,在系统中存在三种不同的表,全局描述符表GDT(Global Descriptor Table)、局部描述符表LDT(Local Descriptor Table)和中断描述符表IDT(Interrupt Descriptor Table)。在整个系统中,全局描述符表GDT和中断描述符表IDT只有一张,局部描述符表可以有若干张,每个进程可以有一张。

切换不同的进程只需要切换不同进程的LDT即可,注意,这部分的切换是相对于处理起来说的,这就意味着每个处理器某一个时刻都可以自行选择自己需要切换的LDT,真正实现并行。

处理器在读取或写入内存位置时使用虚拟地址。 在这些操作期间,处理器将虚拟地址转换为物理地址。

使用虚拟地址访问内存有几个好处:

1. 程序可以使用连续的虚拟地址范围来访问物理内存中的大型非连续内存缓冲区。

2. 程序可以使用一系列虚拟地址来访问大于可用物理内存的内存缓冲区。 当物理内存不足时,内存管理器将物理内存页 (通常) 4 KB 保存到磁盘文件。 系统根据需要在物理内存和磁盘之间移动数据或代码页。

3. 不同进程使用的虚拟地址是隔离的。 一个进程中的代码无法更改另一个进程或操作系统正在使用的物理内存。

每个用户模式进程都有其各自的专用虚拟地址空间。

  • 32 位进程的虚拟地址空间通常位于 2 GB 范围内,0x00000000 0x7FFFFFFF。
  • 64 位 Windows 上的 64 位进程的虚拟地址空间在 128 TB 范围内,0x000'000000000 到 0x7FFF'FFFFFFFF。

下面的图显示了两个 64 位进程的虚拟地址空间:Notepad.exe 和 MyApp.exe。 每个进程都有自己的虚拟地址空间,范围从 0x000'0000000 到 0x7FF'FFFFFFFF。 每个阴影块都表示虚拟内存或物理内存的一个页(大小为 4 KB)。 记事本进程使用三个连续页面的虚拟地址,从 0x7F7'93950000 开始。 但是,这三个连续的虚拟地址页映射到物理内存中的不连续页面。 此外,这两个进程都使用从 0x7F7'93950000 开始的虚拟内存页,但这些虚拟页映射到物理内存的不同页。

在用户模式下运行的代码可以访问用户空间,但无法访问系统空间。 此限制可防止用户模式代码读取或更改受保护的操作系统数据结构。 在内核模式下运行的代码可以访问用户空间和系统空间。 

在内核模式下运行的驱动程序在直接读取或写入用户空间中的地址时必须小心。 下面的案例说明了原因:

1. 用户模式程序发起从设备读取某些数据的请求。 程序提供用于接收数据的缓冲区的起始地址。

2. 内核模式下运行的设备驱动程序例程启动读取操作并将控制权返回给其调用程序。

3. 稍后,设备会中断当前运行的线程,以指示读取操作已完成。 内核模式驱动程序例程处理此任意线程(属于任意进程)上的中断。

4. 此时,驱动程序不得将数据写入用户模式程序在步骤 1 中提供的起始地址。 此地址位于发起请求的进程虚拟地址空间中,这可能与当前进程不同。

物理地址

介绍虚拟地址的时候,讨论过处理器使用的是虚拟地址,但是注意,真正寻址的时候,其实使用的肯定是物理地址,这一步是如何转换的呢?

intel 使用了段表+多级页表的方式,包括32位和64位地址模式。X86给每个进程分配一个LDT来记录进程使用的段,段主要用于控制访问权限,是平坦段,故段实际上影响权限而不是转换。

虚拟地址通过段表和2层页表进行转化,32为地址的前10个位第一层页表索引,中间10个位用于第二层页表索引,后12位是页内的偏移。每个页表的元素占用4个字节,页大小是4KB,一个物理页可以容纳第1层级页表和第二层级页表,第二层页表的个数取决于所属段的长度(即第一层页表的元素的个数)。

64位虚拟地址空间可以延伸到64位,为了简化地址转换,当前很多处理器只用了48位,能够满足256TB的内存空间使用,采用段表和4层页表进行转换,9位+9位+9位+9位 + 12位,前4个9位分别用于4层页表的索引,最后一个12bit用于页内偏移,总计48位。

物理地址获取到之后,已经可以直接使用地址总线去访问了,为什么驱动需要使用物理地址,就是因为驱动是可以直接使用地址总线寻址的,此时地址总线只认物理地址。

在DMA中,CPU会让出地址总线,此时驱动可以使用地址总线进行数据传输。

内存种类

内存管理器创建系统用于分配内存的以下内存池:非分页池分页池。 这两个内存池都位于为系统保留并映射到每个进程的虚拟地址空间的地址空间区域中。 非分页池由虚拟内存地址组成,只要分配了相应的内核对象,这些地址就保证驻留在物理内存中。 分页池由虚拟内存组成,可以分页进出系统。 为了提高性能,具有单个处理器的系统具有三个分页池,多处理器系统具有五个分页池。

内核对象的句柄存储在分页池中,因此可以创建的句柄数取决于可用内存。最佳内存分配例程是 MmAllocateNonCachedMemory、 MmAllocateContiguousMemorySpecifyCache、 AllocateCommonBuffer (如果驱动程序的设备使用总线主 DMA 或系统 DMA 控制器的自动初始化模式)或 ExAllocatePoolWithTag。

注意: 这里隐含了一个问题,分配的内存是否可以执行代码,事实上在win8之前,分配的非分页内存是可执行的,这给恶意代码带来了机会,所以从win 8之后,需要明确设置NonPagedPoolExecute标志才能分配到可执行代码的页面。

分页内存池

分页内存也叫换页内存,它的含义是,这些内存可能在一段时间后被换出内存,交换到磁盘上,这样做的意义是为了实现更多的虚拟内存。

32位系统可以非常精准的描述这个问题,32系统的地址空间是4GB,其中操作系统部分会占用很大一部分,用户一般可以使用2GB,但是假设内存仅有1GB,此时操作系统就可以使用1GB的物理内存区映射4GB的地址空间,这里操作系统有很大一部分是常驻内存,同时应用程序自己还需要用掉一部分,故实际可用的虚拟内存一定小于2GB,那么问题来了,由于操作系统那部分肯定不可能被换出内存,那么1GB的内存中,真正被用于程序自身的那部分可能非常小,举个例子,仅有100MB物理内存空闲可以用于进程,那么操作系统如何保障进程还能申请到200MB的虚拟内存呢?

答案就在于虚拟内存是可以换页的,按照上面的描述的条件,我们页交换文件中分配200MB的虚拟内存,然后将其中10MB映射到物理页面中,此时,当CPU执行到对剩下未映射到的190MB页面的某个地址访问时,会触发一个缺页中断,中断处理程序会将那个地址附近的10MB内存页面换入物理内存中,然后再把控制权交回系统,系统继续访问那个地址,此时由于内存也已经换如内存,访问继续。

我们会发现,此时是可以使用10MB物理页面就能映射200MB的虚拟内存,通过使用页交换文件以及缺页中断。

这里也可以解释清楚为什么不能在DISPATCH_LEVEL中断级别上访问分页内存,因为缺页中断恰好位于DISPATCH_LEVEL级别,如果在DISPATCH_LEVEL级别上访问,会发现此时DISPATCH_LEVEL的其他中断被禁止,缺页中断会形成死锁。

非分页内存池

非分页内存池和分页内存池相对,非分页内存池中,所有虚拟地址都能被正确的解析为物理地址,故在任何级别上都可以无障碍的访问这些内存页面。

这带来另外的问题,非换页内存池的容量是不可能很大的,所以这是非常珍贵的资源,一般情况下,制约驱动最大问题就是非分页内存池,由于它是实实在在在系统全局范围内消耗物理内存,故我们不可能无节制的申请它们,但是一些必要的地方,例如驱动设备扩展、DMA操作中往往又实打实需要它们,所以这个时候我们需要非常小心的申请和使用它们。

1. 非分页内存池的大小是以页面为单位的,故内存结构的紧凑就非常重要,某些情况下,哪怕申请1个字节的非分页内存,实际上也会返回1个页面,所以尽可能将结构紧凑,

2. 非分页内存池不仅仅是驱动可以使用,系统也会使用,比如内核的栈空间使用的就是非分页内存池,故不要在驱动编程中使用迭代编程技术这种会造成系统极大的空间负担的技术。

3. 每次调用 MmAllocateNonCachedMemory 或 MmAllocateContiguousMemorySpecifyCache 始终返回系统的页大小的满倍(非分页系统空间内存),无论请求的分配大小如何。对小于页的请求将向上舍入为整页,并且页面上的任何剩余字节将被浪费;调用分配函数的驱动程序无法访问它们,其他内核模式代码无法使用它们。

4. 每次调用 AllocateCommonBuffer 都使用至少一个适配器对象映射寄存器,该寄存器映射至少一个字节,最多映射一个页面。

内核堆栈

内核模式堆栈的大小限制为大约三个页面。 因此,将数据传递给内部例程时,驱动程序不能在内核堆栈上传递大量数据。

若要避免内核模式堆栈空间不足,请使用以下设计准则:

1. 如果每个例程在内核堆栈上传递数据,请避免从一个内部驱动程序例程向另一个内部驱动程序例程进行深度嵌套调用。

2. 如果设计的驱动程序具有递归例程,请确保限制可能发生的递归调用数。

换句话说,驱动程序的调用树结构应该是相对平坦的。 可以调用 IoGetStackLimits 和 IoGetRemainingStackSize 例程来确定可用的内核堆栈空间,或 调用 KeExpandKernelStackAndCallout 将其展开。 请注意,内核模式堆栈的大小可能因不同的硬件平台和操作系统的不同版本而异。

内核堆栈空间不足会导致严重的系统错误。 因此,驱动程序分配系统空间内存比耗尽内核堆栈空间更好。 但是非分页池也是有限的系统资源。

内存应用技术

下面会简单的描述一些驱动编程中的内存应用技术。

寄存器映射

许多设备都使用寄存器来控制参数的设置读写以及功能实现,设备的寄存器地址和在windows平台上的驱动寄存器地址之间是存在一种映射关系的。

设备访问系统的完整虚拟地址空间的能力各不相同。 设备使用逻辑 (设备) 地址空间中的地址。 每个 HAL 使用 映射寄存器 将设备或逻辑地址转换为物理地址, (物理 RAM) 中的某个位置。 对于设备硬件,映射寄存器执行与 MDL (和页表) 对软件 (驱动程序) 执行的相同功能:将地址转换为物理内存。

由于这些地址空间是单独寻址的,因此驱动程序无法使用虚拟地址空间中的指针来寻址物理内存中的位置,反之亦然。 驱动程序必须先将虚拟地址转换为物理地址。 同样,设备不能使用逻辑地址直接访问物理内存,设备必须首先转换地址。

HAL 必须为不同计算机上的各种 DMA 设备和 I/O 总线设置支持 DMA 的适配器对象。 例如,大多数 ISA DMA 控制器、从属设备和总线主设备的地址线不足,无法访问 32 位处理器的完整 4 GB 系统物理地址空间,或者访问以 36 位 PAE 模式运行的 x86 处理器的 64 GB 系统物理地址)。 相比之下,PCI DMA 设备通常具有足够的地址行来访问 32 位处理器中的完整系统物理地址空间。 因此,每个 HAL 提供 DMA 设备可以访问的 逻辑地址 范围与每台计算机 的物理地址 范围之间的映射。

每个适配器对象都与一个或多个映射寄存器相关联,具体取决于要传输的数据量和可用内存量。 在 DMA 传输期间,HAL 使用每个映射寄存器将设备可访问的逻辑页别名为 CPU 中物理内存页的别名。 实际上,地址寄存器为使用 DMA 的驱动程序提供scatter/gather支持,无论其设备是否具有scatter/gather功能。

下图演示了没有scatter/gather功能的 ISA DMA 设备的驱动程序的此类物理到逻辑地址映射。

注意: scatter/gather指的在多个缓冲区上实现一个简单的I/O操作,比如从通道中读取数据到多个缓冲区,或从多个缓冲区中写入数据到通道。

上图显示了以下类型的映射:

1. 每个地址寄存器将一系列物理地址 (由实线) 指向,映射到 ISA DMA 设备的虚线 (虚线) 的低序逻辑地址。

此处,三个映射寄存器用于将系统物理内存中三页的数据范围别名为 ISA DMA 设备的三个页面大小的低序逻辑地址范围。

2. ISA 设备使用映射的逻辑地址在 DMA 操作期间访问系统内存。

对于类似的 PCI DMA 设备,三个映射寄存器还将用于三个页面大小的数据范围。 但是,映射的逻辑地址范围不一定与相应的物理地址范围相同,因此 PCI 设备也将使用逻辑地址访问系统内存。

3. MDL 中的每个条目将虚拟地址空间中的位置映射到物理地址。

请注意映射寄存器与 MDL 中虚拟到物理条目之间的对应关系:

  • MDL 中的每个映射寄存器和每个虚拟条目最多映射 DMA 传输操作的完整物理数据页。
  • MDL 中的每个映射寄存器和每个虚拟条目映射的数据可能少于整页。 例如,MDL 中的初始虚拟条目可以映射到与物理页边界的偏移量,如前面的 物理、逻辑和虚拟地址映射 图所示。
  • MDL 映射中的每个映射寄存器和每个虚拟条目至少一个字节。

在请求读取或写入操作的 IRP 中,Irp-MdlAddress> 的不透明驱动程序 MDL 中的每个虚拟条目表示用户缓冲区的系统物理内存中的页边界。 同样,单个 DMA 传输所需的每个附加映射寄存器都表示设备可访问的逻辑地址范围中的一个页面边界,该范围别名为系统物理内存。

在每个 Windows 平台上,每个适配器对象都有一组关联的一个或多个映射寄存器,这些寄存器位于特定于平台的 (不透明到驱动程序) 基址。 从驱动程序的角度来看,图中演示ISA DMA 设备地址映射 的映射寄存器基地址是一组映射寄存器的句柄,这些映射寄存器可以是芯片、系统 DMA 控制器或总线主适配器中的硬件寄存器,甚至可以是系统内存中 HAL 创建的虚拟寄存器。

适配器对象可用的映射寄存器数可能因不同的设备和 Windows 平台而异。 例如,HAL 可以向在某些平台上使用系统 DMA 的驱动程序提供更多的映射寄存器,而不是在其他平台上使用,因为不同 Windows 平台上的 DMA 控制器具有不同的功能。

总线的地址映射

某些处理器实现单独的内存和 I/O 地址空间,而其他处理器则不实现。 由于硬件平台存在这些差异,驱动程序用于访问 I/O 或内存驻留设备资源的机制因平台而异。

驱动程序请求设备 I/O 和内存资源,以响应 PnP 管理器的 IRP_MN_QUERY_RESOURCE_REQUIREMENTS IRP。 根据硬件体系结构,HAL 可以在 I/O 空间或内存空间中分配 I/O 资源,并且可以在 I/O 空间或内存空间中分配内存资源。

如果 HAL 使用总线相对内存空间访问设备资源 (例如设备寄存器)),驱动程序必须将 I/O 空间映射到虚拟内存,以便可以访问这些资源。 驱动程序可以通过检查 PnP 管理器在设备启动时传递给驱动程序的已转换资源来确定资源是 I/O 还是内存驻留资源。 如果 HAL 使用 I/O 空间,则无需映射。

具体而言,当驱动程序收到IRP_MN_START_DEVICE请求时,它应检查 IrpSp-> Parameters.StartDevice.AllocatedResources和 IrpSp->Parameters.StartDevice.AllocatedResourcesTranslated 中的结构,它们分别描述 PnP 管理器分配给设备的原始 (总线相对) 和转换的资源。 驱动程序应保存设备扩展中每个资源列表的副本,以辅助调试。

资源列表 CM_RESOURCE_LIST 结构中,其中原始列表的每个元素对应于已映射列表的同一个元素。 例如,如果 AllocatedResources.List[0] 描述原始 I/O 端口范围, 则 AllocatedResourcesTranslated.List[0] 在转换后描述相同的范围。 每个已翻译的资源都包含一个物理地址和资源类型。

如果为驱动程序分配了已转换的内存资源 (CmResourceTypeMemory) ,则必须调用 MmMapIoSpace 将物理地址映射到可以访问设备寄存器的虚拟地址。 若要使驱动程序以与平台无关的方式运行,它应检查每个返回的已转换资源,并在必要时对其进行映射。

内核模式驱动程序应执行以下步骤,以响应IRP_MN_START_DEVICE请求,以确保访问所有设备资源

  • 将 IrpSp-Parameters.StartDevice.AllocatedResources> 复制到设备扩展。
  • 将 IrpSp-Parameters.StartDevice.AllocatedResourcesTranslated> 复制到设备扩展。
  • 在循环中,检查 AllocatedResourcesTranslated 中的每个描述符元素。 如果描述符资源类型为 CmResourceTypeMemory,请调用 MmMapIoSpace,传递已转换资源的物理地址和长度。

当驱动程序收到来自 PnP 管理器 的IRP_MN_STOP_DEVICE 或 IRP_MN_REMOVE_DEVICE 请求时,它必须通过在类似的循环中调用 MmUnmapIoSpace 来释放映射。 如果驱动程序必须使IRP_MN_START_DEVICE请求失败,则驱动程序还应调用 MmUnmapIoSpace。

原始资源类型指示驱动程序应调用哪个 HAL 访问例程 (READ_REGISTER_XXX、 WRITE_REGISTER_XXX、 READ_PORT_XXXWRITE_PORT_XXX) 。 大多数驱动程序不必检查原始资源列表来确定要使用哪些例程,因为驱动程序本身请求了资源,或者驱动程序编写者知道设备硬件的性质所需的类型。

对于 I/O 空间中的资源 (CmResourceTypePort、CmResourceTypeInterrupt、CmResourceTypeDma) ,驱动程序应使用返回的物理地址的低序 32 位来访问设备资源,例如,通过 HAL 的读取和写入READ_REGISTER_XXX、WRITE_REGISTER_XXX、READ_PORT_XXXWRITE_PORT_XXX 例程。

注意: HAL提供WRITE_REGISTER_ULONG这样的函数,虽然看上去是对某个地址的读写,但绝对不要使用* (addr)= xxx;的方式去替换,这完全是两码事。

MDL

事实上,我们很难一次分配到例如100MB这样的连续内存,更多的情况下,内存池无论是否分页,都是越来越零散的,故windows提供了一种新的技术保障我们使用连续内存,这项技术就是MDL。

跨一系列连续虚拟内存地址的 I/O 缓冲区可以分布在多个物理页中,并且这些页面可以是不连续的。 操作系统使用 内存描述符列表 (MDL) 来描述虚拟内存缓冲区的物理页面布局。

MDL 由 MDL 结构组成,该结构后跟描述 I/O 缓冲区所在的物理内存的数据数组。 MDL 的大小因 MDL 描述的 I/O 缓冲区的特征而异。 系统例程可用于计算 MDL 的所需大小,以及分配和释放 MDL。

MDL 结构是半不透明的。 驱动程序应仅直接访问此结构的 Next 和 MdlFlags 成员,其余成员是不透明的。 请勿直接访问 MDL 的不透明成员。 请改用操作系统提供的以下宏来对结构执行基本操作:

MmGetMdlVirtualAddress 返回 MDL 描述的 I/O 缓冲区的虚拟内存地址。

MmGetMdlByteCount 返回 I/O 缓冲区的大小(以字节为单位)。

MmGetMdlByteOffset 返回 I/O 缓冲区开头的物理页内的偏移量。

可以使用 IoAllocateMdl 例程分配 MDL。 若要释放 MDL,请使用 IoFreeMdl 例程。 或者,可以分配非分页内存块,然后通过调用 MmInitializeMdl 例程将此内存块格式化为 MDL。

IoAllocateMdl 和 MmInitializeMdl 都不会初始化紧跟在 MDL 结构后面的数据数组。 对于驻留在驱动程序分配的非分页内存块中的 MDL,请使用 MmBuildMdlForNonPagedPool 初始化此数组,以描述 I/O 缓冲区所在的物理内存。

对于可分页内存,虚拟内存和物理内存之间的对应关系是临时的,因此 MDL 结构后面的数据数组仅在特定情况下有效。 调用 MmProbeAndLockPages 将可分页内存锁定到位,并为当前布局初始化此数据数组。 在调用方使用 MmUnlockPages 例程之前,内存不会分页,此时数据数组的内容不再有效。

MmGetSystemAddressForMdlSafe 例程将指定 MDL 描述的物理页面映射到系统地址空间中的虚拟地址(如果尚未映射到系统地址空间)。 此虚拟地址对于可能必须查看页面以执行 I/O 的驱动程序非常有用,因为原始虚拟地址可能是只能在其原始上下文中使用的用户地址,并且可以随时删除。

请注意,使用 IoBuildPartialMdl 例程生成部分 MDL 时, MmGetMdlVirtualAddress 返回部分 MDL 的原始起始地址。 如果 MDL 最初是由于用户模式请求而创建的,则此地址是用户模式地址。 因此,地址在发起请求的进程上下文之外没有相关性。

通常,驱动程序通过调用 MmGetSystemAddressForMdlSafe 宏来映射部分 MDL 来创建系统模式地址。 这可确保驱动程序可以继续安全地访问页面,而不考虑进程上下文。

当驱动程序调用 IoAllocateMdl 时,它可以通过将指向 IRP 的指针指定为 IoAllocateMdl 的 Irp 参数,将 IRP 与新分配的 MDL 相关联。 IRP 可以有一个或多个与之关联的 MDL。 如果 IRP 有一个与之关联的 MDL,则 IRP 的 MdlAddress 成员指向该 MDL。 如果 IRP 有多个与之关联的 MDL, 则 MdlAddress 指向与 IRP 关联的 MDL 链接列表中的第一个 MDL,称为 MDL 链。 MDL 由其 Next 成员链接。 链中最后一个 MDL 的 Next 成员设置为 NULL。

如果驱动程序在调用 IoAllocateMdl 时为 SecondaryBuffer 参数指定 FALSE,则 IRP 的 MdlAddress 成员设置为指向新的 MDL。 如果 SecondaryBuffer 为 TRUE,则例程会在 MDL 链的末尾插入新的 MDL。

IRP 完成后,系统会解锁并释放与 IRP 关联的所有 MDL。 系统先解锁 MDL,然后再将 I/O 完成例程排队,并在执行 I/O 完成例程后释放它们。

驱动程序可以使用每个 MDL 的 Next 成员来访问链中的下一个 MDL,从而遍历 MDL 链。 驱动程序可以通过更新 “下一步 ”成员来手动将 MDL 插入链中。

MDL 链通常用于管理与单个 I/O 请求关联的缓冲区数组。 (例如,网络驱动程序可以为网络操作中的每个 IP 数据包使用一个缓冲区) 数组中的每个缓冲区在链中都有自己的 MDL。 驱动程序完成请求后,会将缓冲区合并到单个大型缓冲区中。 然后,系统会自动清理请求的所有已分配的 MDL。

I/O 管理器是 I/O 请求的源。 当 I/O 管理器完成 I/O 请求时,I/O 管理器释放 IRP 并释放附加到 IRP 的任何 MDL。 其中一些 MDL 可能已由位于设备堆栈中 I/O 管理器下的驱动程序附加到 IRP。 同样,如果驱动程序是 I/O 请求的源,则当 I/O 请求完成时,驱动程序必须清理 IRP 和附加到 IRP 的任何 MDL。

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

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

相关文章

解决“该扩展程序未列在 Chrome 网上应用店中,并可能是在您不知情的情况下添加的”的方法

一、问题 安装插件出现时“该扩展程序未列在 Chrome 网上应用店中&#xff0c;并可能是在您不知情的情况下添加的” 二、解决方法 1、把需要安装的第三方插件&#xff0c;后缀.crx 改成 .rar&#xff0c;然后解压&#xff0c;得到一个文件夹 2、再打开chrome://extensions/谷歌…

Visual Studio Code使用

目录 1.python的调试 2.c的运行 方法1&#xff1a; 方法2&#xff1a; 3.c的调试 3.1调试方法一&#xff1a;先生成执行文件&#xff0c;再调试 3.2调试方法二&#xff1a;同时生成执行文件&#xff0c;调试 4.tasks.json 与launch.json文件的参考 4.1C生成执行文件tas…

linux之进程通信

目录 一、进程通信介绍 1.目的 2.发展 3.进程通信是什么&#xff0c;怎么通信&#xff1f; 二、管道 1.介绍 2.匿名管道 1.单向通信管道原理 2.代码实现 3.管道特征 4.管道的四种情况 5.管道的应用场景 使用管道实现一个简易版本的进程池 3.命名管道 1.思考 2.…

燃冬之yum、vim和你

了解了很多指令和权限&#xff0c;搞点真枪实弹来瞅瞅 学Linux不是天天就在那掰扯指令玩&#xff0c;也不是就研究那个权限 准备好迎接Linux相关工具的使用了么码农桑~ yum 软件包 什么是软件包呢&#xff1f; 首先来举个生活中常见点的例子&#xff1a;比如我的手机是华为…

PLC无线通讯技术在汽车喷涂车间机械手臂上的应用

一、项目背景 在汽车生产装配工艺中&#xff0c;机械臂目前已经广泛地应用于装配、搬运等工业生产中&#xff0c;在机械臂系列产品中&#xff0c;汽车喷漆自动控制喷涂机械装置以其独特的优势&#xff0c;能够根据油漆喷涂量的大小&#xff0c;严格控制喷嘴与喷漆面之间距离等…

【函数式接口使用✈️✈️】配合策略模式实现文件处理的案例

目录 &#x1f378;前言 &#x1f37b;一、功能描述 &#x1f37a;二、面向对象设计模式 &#x1f379;三、策略模式 &#x1f366;四、策略 VS 面向对象 &#x1f368;章末 &#x1f378;前言 小伙伴们大家好&#xff0c;上周初步了解了下函数式接口&#xff0c;Consume…

「最没存在感」港姐冠军入行10年不受捧,与相恋4年男友分手

昨日&#xff08;4月21日&#xff09;一众歌手艺人齐集红馆举行《全港运动全城跃动第九届全港运动会开幕礼》录影&#xff0c;TVB亦派出不少的歌手艺人小花表演。其中一部分是邵珮诗与黄婧灵大跳拉丁舞&#xff0c;同属身材丰满的二人跳起上来视觉极夸张。 而平常经常露出姣好身…

powershell@命令行提示符样式配置自定义@pwsh重写prompt显示电量内存时间等信息

文章目录 abstract流行的powershell prompt模块示例 powershell原生修改Prompt函数配置文档Prompt命令来自哪里 简单修改带上电量和时间的Prompt 复杂修改预览FAQ:没有必要修改相关仓库地址样式选择平衡样式花哨样式响应性能 小结 abstract 在 PowerShell 中&#xff0c;可以通…

【git】git ignore如何添加core/config.py忽略

在Git中&#xff0c;.gitignore文件用于指定不被Git追踪的文件和文件夹。要添加core/config.py文件到.gitignore中&#xff0c;你需要编辑.gitignore文件并添加以下行&#xff1a; core/config.py这行表示Git应该忽略名为config.py的文件&#xff0c;它位于core目录下。确保在…

Codeforces Round 821 (Div. 2) D2. Zero-One

题目 #include <bits/stdc.h> using namespace std; #define int long long #define pb push_back #define fi first #define se second #define lson p << 1 #define rson p << 1 | 1 const int maxn 1e5 5, inf 1e18, maxm 4e4 5; const int N 1e6;c…

vue项目前端axios跨域请求处理问题

在我的服务器里面新建了一个txt文档&#xff0c;但在vue项目里面对这个文档发起请求的时候因为是ip地址请求&#xff0c;跨域请求失败&#xff0c;在配置了vue.config.js的请求代理后得以解决 报错示例&#xff1a; 解决方法&#xff1a; 1、在vue.config.js中配置跨越请求代…

microk8s拉取pause镜像卡住

前几天嫌服务器上镜像太多占空间&#xff0c;全部删掉了&#xff0c;今天看到 microk8s 更新了 1.30 版本&#xff0c;果断更新&#xff0c;结果集群跑不起来了。 先通过 microk8s.kubectl get pods --all-namespaces 命令看看 pod 状态。 如上图可以看到&#xff0c;所有的业…

【触摸案例-触摸事件介绍 Objective-C语言】

一、触摸事件 1.接下来,我们来说这个,触摸事件, iOS当中的事件,可以分为三大类: 1)触摸事件 2)加速计事件 3)远程控制事件 事件呢,这个里面呢,使用app的过程当中呢,产生各种各样的事件,事件呢,分为三大类,在iOS里边儿啊,分为三大类,首先,有一个叫做触摸事…

NLP大模型的训练

NLP模型的训练主要分成两步&#xff1a; 1.先进行通用任务的训练&#xff1b;无监督的样本是无穷无尽的&#xff1b; 这里列举两种&#xff1a;MLM和NSP,NSP由于在某些论文中被证明是无效的&#xff0c;所以用的少&#xff1b; MLM: 接下来会在特定任务上进行finetune>su…

Nacos分布式配置中心

<?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 https://…

社交巨头与去中心化:解析Facebook在区块链的角色

在数字化时代&#xff0c;社交媒体已经成为人们日常生活中不可或缺的一部分。作为全球最大的社交媒体平台&#xff0c;Facebook 在社交领域的影响力无可置疑。然而&#xff0c;随着区块链技术的崛起&#xff0c;Facebook 也开始探索如何将这一技术应用于其平台&#xff0c;以适…

【无监督+自然语言】GPT,GPT-2,GPT-3 方法概述 (Generative Pre-Traning)

主要参考 【GPT&#xff0c;GPT-2&#xff0c;GPT-3 论文精读【李沐论文精读】-2022.03.04】 https://www.bilibili.com/video/BV1AF411b7xQ/ 大语言模型综述&#xff1a; http://t.csdnimg.cn/4obR4 发展节点 2017.06 Transformer: 所有大语言模型LLMs的基础结构 , Attent…

windows服务器iis系统部署https

源地址&#xff1a;https://www.ctvol.com/seoomethods/1418785.html https是网页常用的一种网络安全机制&#xff0c;在部署其他服务器https&#xff0c;我们在前面文章中已经提到过。下面我们来说说windows服务器iis系统部署https步骤&#xff1a; 1、到服务提供商下载所需…

WPF2022终结版系列课程笔记 1 WPF 基本布局

本笔记为B站 微软系列技术教程 WPF项目实战合集(2022终结版) 项目记录 WPF 基本布局 WPF布局原则 一个窗口中只能包含一个元素 不应显示设置元素尺寸 不应使用坐标设置元素的位置 可以嵌套布局容器 WPF布局容器 StackPanel: 水平或垂直排列元素、Orientation属性分别: Hor…

森林消防装备:高压消防接力水泵/恒峰智慧科技

在广袤无垠的森林中&#xff0c;每一份绿色都是大自然赋予我们的宝贵财富。然而&#xff0c;这些美丽的绿色也可能因为一场突如其来的火灾而被瞬间吞噬。为了保护这片生命的绿洲&#xff0c;我们需要一种高效、可靠的消防装备——高压消防接力水泵。 这款森林消防装备采用本田汽…