在Win32中管理虚拟内存

Windows虚拟内存的使用(四) 锁定页面和解锁页面 1 通过调用VirtualLock方法,可以将页面锁定在物理内存中,从而防止虚拟内存管理机制将页面交换至页面文件,而引起不必要的硬盘和物理内存之间的低效页面交换。 2 通过调用VirtualUnlock方法,可以解锁页面,允许系统对页面进行交换操作 3 锁定页面时系统会根据当前可用实际RAM情况,以及进程工作集配额判定当前最大可锁定的页面的实际数量,超过此数量会引起一个错 阅读详情

在Win32中管理虚拟内存

Randy Kath Microsoft Developer Network 技术小组 创建于:1993年1月20日

单击此处以打开或复制 ProcessWalker 示例程序中的文件。该Win32 示例程序要求Microsoft Windows NT 的环境。

摘要

在Microsoft Windows NT 操作系统中,假如您对每组函数的功能,以及它们每个函数的作用,没有足够的认识,那么在Win32 应用程序中,决定使用哪个函数,或者哪组函数来管理内存将是困难的。为了简化这个问题,本篇技术文章主要是围绕Win32虚拟内存管理函数的:它包括哪些函数是可用的、如何使用它们,以及使用它们会对操作系统产生什么影响。本文将讨论如下的主题:

  • 保留、提交,和释放虚拟内存
  • 在虚拟内存页上改变保护
  • 锁定虚拟内存页
  • 查询一个进程的虚拟内存

在Microsoft Developer Network CD中有一个叫作ProcessWalker的示例程序,它将会在本篇技术文章中出现。该示例程序对于探索一个进程的内存地址空间是很有用的。它还使用了虚拟内存函数来实现一个相互链接的列表结构。

 

概述

本文是三篇相关技术文章中的其中一篇,这三篇文章分别是棗“在Win32中管理虚拟内存”、“在Win32中管理内存-映射文件”,以及快要完成的“在Win32中管理堆(heap)内存”棗它们解释了如何在Win32编程接口的应用程序中管理内存。在每篇文章的概述部分,要指明Win32编程模型中基本的内存部件,并且指出如果您对特殊领域的较有兴趣,那么应该参考哪一篇文章。

Microsoft Windows 操作系统的第一个版本介绍了基于一个单个的全局堆(global heap)和多个专有的局部堆(local heaps)来管理动态内存的方法,所有应用程序和系统共存该全局堆(global heap),而每一个应用程序具有其单独的局部堆(local heaps)。同时还提供了全局和局部的内存管理函数,为这种新的内存管理系统提供了扩展的特性。最近,Microsoft C的运行时(CRT)库被修改以包含如下功能,即使用如malloc和free这样的纯粹的CRT函数来管理Windows中的堆。所以,现在开发者应该作出选择了棗要么学习作为Windows 3.1版本的一部分来提供的新的应用程序编程接口(API),要么坚持使用可移植的、典型的、并且为人所熟悉的CRT函数在为Windows 3.1所编写的应用程序中管理内存。

随着Win32 API内容的不断增加,选择机会也随之增加。Win32提供了三个附加的函数组来管理应用程序的内存:内存-映射(memory-mapped)文件函数、堆内存(heap memory)函数,以及虚拟内存函数。这些新的函数并不替代在Windows 3.1中现存的内存管理函数;相反它们提供了新的特性,使得开发者在为他们的Win32应用程序编写内存管理部分时,日子会轻松得多。

图1. Win32 API为应用程序编程的多样性提供了不同级别的内存管理。

总之,如图1所看到的,在Win32中有六组内存管理函数,所有这些函数都被设计成单独使用。所以,您应该使用哪种函数呢?要回答这一问题主要依靠以下两件事:您希望的内存管理类型是什么,以及与之相关联的函数在操作系统中是如何实现的。换句话说,您是否是正在建立一个大的数据库应用程序,因而希望操作一个大的内存结构的子集合,或者您正计划一些简单的动态内存结构,例如链接列表或二进制树(binary trees)。在这两种情况下,您都需要搞清楚哪些函数提供的功能最适合您的意向,并确切地了解在使用每个函数时要占用多少资源。

表1将Win32中的内存管理函数组进行了分类,并且分别指出在本系列的三篇技术文章中,每一篇所描述的相关的组的行为。在每篇技术文章中,通过描述作为对使用这些函数的响应的系统行为,重点强调了这些函数对系统所产生的影响。

表1. 在Win32中可用的内存管理函数

内存设置受影响的系统资源相关的技术文章
虚拟内存函数一个进程的虚拟地址空间

系统页文件

系统内存

硬盘空间

“在Win32中管理虚拟内存”
内存-映射文件函数一个进程的虚拟地址空间

系统页文件

标准文件I/O

系统内存

硬盘空间

“在Win32中管理内存-映射文件”
堆(Heap)内存函数一个进程的虚拟地址空间

系统内存

进程堆资源结构

“在Win32中管理堆内存”
全局堆内存函数一个进程的堆资源结构“在Win32中管理堆内存”
局部堆内存函数一个进程的堆资源结构“在Win32中管理堆内存”
C运行时参考库一个进程的堆资源结构“在Win32中管理堆内存”

每篇技术文章都是围绕Win32函数的用途问题而展开讨论的。要想对Windows NT操作系统如何管理系统内存有更详细的了解,请参阅Microsoft Developer Network CD上的“The Virtual-Memory Manager in Windows NT”(技术文章,Win32和Windows NT的文章)。

 

Windows NT内存系统概述

Windows NT使用一个以页为基础的虚拟内存系统,该系统使用32位线性地址。在内部,系统管理被称为页的4096字节段中的所有内存。每页的物理内存都被备份 ?/FONT> 对于临时的内存页使用页文件(pagefile),而对于只读的内存页,则使用磁盘文件。在同一时刻,最多可以有16个不同的页文件。代码、资源和其它只读数据都是通过它们创建的文件直接备份。

Windows NT为系统中的每一个应用程序(进程)提供一个独立的、2 GB的用户地址空间。对于应用程序来说,好象是有2 GB的可用内存,而不用考虑实际可用的物理内存的量。如果某个应用程序要求的内存比可用的内存更多时,Windows NT是这样满足这种要求的,它从这个和/或其他的进程把非关键内存分页(paging)到一个页文件,并且释放这些物理内存页。结果,在Windows NT中,全局堆不再存在。相反,每一个进程都有其自己的32位地址空间,在其中,该进程的所有内存被分配, 包括代码、资源、数据、DLL(动态链接库),和动态内存。实际上,系统仍然要受到可用的硬件资源的限制,但是实现了与系统中应用程序无关的、对于可用资源的管理。

 

在Win32中的虚拟内存

Windows NT在内存和地址空间之间作出了区分。每个进程分配到2 GB的用户地址空间,而不管对于该进程的实际可用物理内存有多少。而且,所有进程都使用相同范围的线性32位地址,范围从0000000016-7FFFFFFF16,而不考虑可用内存的地址。Windows NT负责在适当的时间把内存页映射(paging)到磁盘以及从磁盘页映射回内存,使得每个进程都确保能够寻址到它所需要的内存。尽管有可能出现两个进程试图同时访问同一虚拟地址上的内存,但是,实际上Windows NT虚拟内存管理程序是在不同的物理位置描述这两个内存的位置。而且这两个地址都不见得与原始的虚拟地址一致。这就是虚拟内存。

因为虚拟内存的存在,一个应用程序能够管理它自己的地址空间,而不必考虑在系统中对于其它进程的影响。在Windows NT中的内存管理程序负责查看在任何给定的时间里,所有的应用程序是否有足够的物理内存进行有效的操作。与在Windows 3.1版本或更早的版本中不同,Windows NT操作系统下的应用程序不必考虑和其它应用程序共享系统内存这个问题。并且,即使在应用程序自己的地址空间内,它们仍能够与其它的应用程序共享内存。

区分内存和地址空间的一个好处是,为应用程序提供了将非常大的文件加载到内存的能力。不必将一个大的文件读进内存中,Windows NT为应用程序保留该文件所需的地址范围提供了支持。然后,在需要的时候,该文件部分就可以被浏览了(物理性地读进内存)。通过虚拟内存的支持,对于大段的动态内存的分配同样可以做到这一点。

在Windows的早期版本中,在一个应用程序能够操作内存中的地址之前,该应用程序必须首先分配内存。在Windows NT中,每一个进程的地址空间已经分配好了,是否有内存与该段地址空间中的地址相关联是另外的问题。Win32虚拟内存管理函数为分别管理进程的地址和内存提供了低级别的支持。

Win32虚拟内存函数的全体是:

  • VirtualAlloc 和 VirtualFree
  • VirtualLock 和 VirtualUnlock
  • VirtualQuery 或 VirtualQueryEx
  • VirtualProtect 或 VirtualProtectEx

对于每个函数,如有与之相对应的,则它们共同组成一组。分配内存请使用VirtualAlloc,一旦已经分配,则必须使用VirtualFree来释放。类似地,对于被使用VirtualLock锁定的页,当不再需要时,则必须用VirtualUnlock来解除锁定。VirtualQuery和VirtualProtect没有与之相对应的函数,但它们俩都有完整功能(complementary)的函数(在函数名字上的Ex扩展来指示)。这样,就允许它们在除调用进程之外的其他进程中使用,但是,此时调用进程需要有适当的特权才可以这样。这些函数将在下面的适当的上下文中解释。

 

自由的、保留的和已提交的虚拟内存

在任意给定的时间,进程中每个地址都可以被当作是自由的、保留的或已提交的。进程开始时,所有地址的都是自由的,意味着它们都是自由空间并且可以被提交到内存,或者为将来使用而保留起来。在任何自由的地址能够被使用前,它必须首先被分配为保留的或已提交的。试图访问一个保留的或已提交的地址都将产生一个访问冲突异常(access violation exception)。

一个进程中的所有2 GB的地址要么为了使用而是自由的、要么为了将来的使用而是保留的、要么已提交到特定的内存(在使用的)。图2描述了一个假设的进程,它包含自由的、保留的和已提交的地址。

图 2. 一个进程的2 GB的虚存地址空间被分配为自由的、保留的和已提交的内存区位置。

 

保留的地址

当在一个进程中保留地址时,没有物理内存页被提交,并且,也许更为重要的是,在页文件中没有为备份该内存而保留空间。而且,保留一个地址范围将不会保证将来会有可用的物理内存来提交给这些地址。实际上,它只是保存了一个指定的自由地址地址,一直到需要使用它时,而阻止了其它分配对该段地址的请求。如果没有这种类型的保护,那么例程操作(routine operations),例如加载一个DLL或者资源,可能会占有指定的地址,并且危害以后对它的使用。

保留地址是一个快速的操作,完全与被保留的地址范围的大小没有关系。不论保留1 GB的地址范围,还是保留4K的地址范围,该函数的速度都非常快。这并不令人惊奇,因为在此操作期间,没有资源被分配。该函数只是进入进程的虚拟地址描述符(VAD)树。有关VAD的详细信息,请参阅 Developer Network CD 的“The Virtual-Memory Manager in Windows NT”(技术文章,Win32和Windows NT文章)

要想保留一段地址范围,需要参照下列代码来调用VirtualAlloc函数:

/* 保留 10 MB 的地址空间 */

lpBase = VirtualAlloc (NULL,

10485760,

MEM_RESERVE,

PAGE_NOACCESS);

如这里所示,第一个参数,lpAddress,使用的是NULL值,指导该函数在某一个最方便的位置保留地址范围。另外,也可能一个指定的地址已经被传递,为要保留的范围指示一个准确的初始地址。无论两种方法中的哪一种,该函数的返回值都指示出被保留的地址范围的开始位置,除非该函数无法完成请求。要是这样,VirtualAlloc函数的返回值将是一个错误状态值。

第二个参数指示函数应该分配的地址范围。该值的大小可以是从一页到2 GB的任意值,但是VirtualAlloc实际上被限制为一个较小的范围。能够被保留的最小值为64K,而能够被保留的最大值为该进程中最大的连续自由地址空间。请求的保留地址,结果是得到64K的地址范围。反之,请求2 GB的范围将会失败,因为在任何给定的时间里,有那么多的可用地址空间是不可能的。(请记住,加载一个应用程序的动作也要使用初始2 GB地址空间中的一部分。)

注意 Windows NT在每一个进程的地址空间中生成一个保护设施(safeguard)。每一个进程的顶端65,536字节和低端65,536字节都被系统永久地保留。这些地址空间部分被保留为陷阱迷失指针(trap stray pointers)棗试图在0000000016-0000FFFF16或7FFF000016-7FFFFFFF16范围内寻址内存的指针。并不是巧合,在这个范围内,只需忽略这些地址中的低四位(最右边的两个字节)就可很容易地检测到该指针。从根本来讲,如果高四位是000016或7FFF16,那么这个指针是无效的;所有其它的值都表示有效的地址。

在VirtualAlloc函数中的最后两个参数,dwAllocationType和dwProtect被用来决定如何分配地址以及与它们相关联的保护。地址可被分配为MEM_COMMIT或者MEM_RESERVE类型。PAGE_READONLY、PAGE_READWRITE和PAGE_NOACCESS是三种可以被应用到虚拟内存的保护。无论何值被传递到该函数,被保留的地址总是PAGE_NOACCESS,这是系统强制的默认值。已提交的页可以是只读的、也可以可读写的,或者是不能访问的。

 

已提交的内存

要使用保留的地址,内存首先必须被提交给该地址。提交内存到地址与保留内存相类似棗调用VirtualAlloc,并且在调用时设置dwAllocation参数等于MEM_COMMIT。在这一时刻,资源被提交到地址上。每一次,内存可以按一页的大小被提交。能够被提交的最大内存值仅仅取决于连续的自由或者保留地址的最大范围(但两者不可组合在一起),无须考虑系统的可用物理内存的大小。

当内存被提交时,内存物理页被分配,并且该段空间被保留在在一个页文件中。也就是说,已提交的内存页总是以物理内存页或者在已经被分页的磁盘上的页文件的形式存在。当提交一个大块内存时,在初始阶段,其部分或者全部内存没有驻留在物理内存中也是有可能的。某些内存页一开始驻留在页文件中,直到它被访问。在系统中,一旦内存页已提交,虚拟内存管理器象对待所有其它的内存页一样对待它们。

在Windows NT虚拟内存系统中,使用了页表(page tables)来访问物理内存页。每个页表本身也是一个内存页,象已提交的页一样。偶而,当提交内存时,同时还必须对页表分配附加的页。所以,提交一页内存的请求可能需要为页表分配一页,为请求的页分配一页,并且在页文件中需要两页空间来备份这些页中的每一页。因此,VirtualAlloc完成一个内存提交请求所需要的时间变化很大,它取决于系统的状态以及请求的空间大小。

下面的示例演示了如何将上例中被保留地址的指定页提交到一个内存页中。

/* 为第3页地址提交内存。 */

lpPage3 = VirtualAlloc (lpBase + (2 * 4096),

4096,

MEM_COMMIT,

PAGE_READWRITE);

请注意,对于lpAddress没有指定为NULL,而是指定了一个特定的地址来准确地指示被保留地址的哪一页会变成提交给内存的页。而且,初始时该内存页被赋予PAGE_READWRITE保护,而不是象在前面示例中的PAGE_NOACCESS。该函数的返回地址是第一页已提交地址的虚拟地址。

 

释放虚拟内存

一旦地址被以保留的或者已提交的形式分配,VirtualFree是唯一可以释放它们的方法棗那就是,将它们返回到自由的地址。VirtualFree还可以用来对已提交的页解除提交,同时,返回这些地址到保留状态。当解除地址的提交时,所有与该地址相关的物理内存和页文件空间都被释放。下面的示例演示如何对在前一个示例中已提交的内存页解除提交。

/* 对第3页地址解除提交内存。 */

VirtualFree (lpBase + (2 * 4096),

4096,

MEM_DECOMMIT,

PAGE_NOACCESS);

只有已提交的地址才能被解除提交。当您需要对一个大范围的地址解除提交时,牢记这一点是非常重要的。例如,假设您有一定范围的地址,在其中多个地址的子集已被提交,而其它部分被保留。要想使全部范围的地址保留,唯一方法就是一个一个地对每个被提交的地址子集解除提交。如果试图对整个范围的地址解除提交将会失败,因为保留的地址无法解除提交。

与之相反,在一次操作中相同范围的地址都能被释放。因为在地址被释放时,一个地址的状态并不重要。下面的示例演示释放在第一个示例中被保留的10 MB范围的地址。

/* 释放整个 10 MB 范围的地址。 */

VirtualFree (lpBase,

10485760,

MEM_RELEASE,

PAGE_NOACCESS);

 

改变虚拟内存页的保护

Win32提供VirtualProtect函数,作为对已提交内存改变页保护的一个方法。例如,一个应用程序可以按PAGE_READWRITE来提交一个页的地址,并且立即将数据填写到该页中。然后,该页的保护将被改变为PAGE_READONLY,这样可以有效地保护数据不被该进程中的任何线程重写。下面的示例使用VirtualProtect函数使一个不能被访问的页可用。

/*将页保护改变成可读/写。*/

VirtualProtect (lpStack + 4096,

4096,

PAGE_READWRITE,

lpdwOldProt);

可以将下面的环境看作是使用该函数的环境。一个用于缓冲数据的应用程序接收到一组大小变化的数据流,根据特定的硬件配置和其它的软件应用程序对CPU时间的竞争,数据流可能某时(at times)超出进程的能力。为了防止这种现象发生,应用程序设计了一个内存系统,可以在开始时为一个缓冲提交一些内存页。然后,应用程序则使用PAGE_NOACCESS保护来保护内存的顶端页,使得任何想要访问该内存的请求都会产生一个异常。应用程序也在该代码的外层代码中使用一个异常处理程序来处理访问冲突。

当一个访问冲突发生时,应用程序能够确定缓冲区是否已经到了其极限。该应用程序通过将页保护改变为PAGE_READWRITE来响应,允许该缓冲区接收任何附加的数据,并且继续不间断的执行。同时,应用程序加载另一个线程来减缓数据流,直到该缓冲区恢复到一个理想的操作范围。当情况恢复到正常,顶端的页又返回到PAGE_NOACCESS,并且附加的线程也结束了。这种情况描述了在Win32中,如何将页保护和异常处理程序结合使用来提供独一无二的内存管理机会。

 

锁定虚拟内存页

在Windows NT中的进程有一个被称为工作组(working set)的最小页,是为了进程能够顺利地运行,在运行时在内存中必须被提供。Windows NT在启动时为一个进程分配了默认数量的页数,并且逐渐地调整该数,使得系统中所有激活的进程的性能达到一种平衡的最优。当一个进程正在运行时(实际上是,是一个进程的线程正在运行时),Windows NT在“努力工作”以确保该进程的工作组页总是驻留在物理内存中。

在Windows NT中的进程被授权使用VirtualLock和VirtualUnlock函数来巧妙地影响该系统的行为。从根本上讲,一个进程可以建立特定的页将它锁定到工作组中。然而,这并不给进程的工作组以自由的范围。它不能影响组成它的工作组的页数,(系统为每一个进程按规范来调整工作组),并且它无法控制何时工作组在内存中以及何时不在内存中。每次一个进程的工作组锁住的页数码最多不超过32。如果一个应用程序将被提交的内存页锁定在工作组中,这样做可能利大于弊,因为这样做可能会迫使该进程中的其他关键页被替代。如果那样的话,页可能会页映射到磁盘,在被访问的任何时候都会发生页故障。于是,该进程将花费许多的CPU时间却只是将关键页映射到内存和映射出内存。

请牢记,在Win32中锁定一页内存并不意味着该页内存将不能被页映射到磁盘。相反,它意味着当进程正在运行时,被锁定的内存页将是在一段物理内存中。不仅仅是有可能,而是非常可能,当一个进程处于理想情况时,该进程的整个页的工作组将被页映射到磁盘。当该进程的工作情况变差时,则工作组页将立即被页映射回内存中,包括 VirtualLocked 页。

下面的例子在进程运行时,锁定一段地址到内存中。

/* 锁定关键地址(critical addresses)到内存中。*/

VirtualLock (lpCriticalData, 1024);

请注意,在本例中被锁定到内存的地址范围小于一页。并不是必需要整段范围都在内存的单个页中。最后的结果(net result)是包含该地址中的数据的整页的内存被锁定到内存,而不仅仅是指出的地址的数据被锁定到内存中。如果该数据跨越了页的边界,则两页都将被锁定。

 

查询一个进程的虚拟内存

给定一个进程的地址空间为2 GB,如果没有查询地址信息的能力,那么想要管理地址的全部范围将是困难的。因为地址本身代表独立的内存,对于它们,这些内存有可能被提交,或者不被提交,对它们进行查询,仅仅是读取保存它们状态的数据结构一件事罢了。在Windows NT中,该结构是以前提到过的虚拟地址描述符树。Win32在VirtualQuery和VirtualQueryEx函数中提供了“遍历VAD结构”的能力。同样,Ex后缀指出哪个函数可从一个进程中被调用来查询另一个进程棗假如调用进程有足够的安全特权来执行该函数。下面的示例是从ProcessWalker示例程序中分离出来的:

/*查询子进程中的下一个内存区。*/

VirtualQueryEx (hChildProcess,

lpMem,

lpList,

sizeof (MEMORY_BASIC_INFORMATION));

ProcessWalker应用程序的主函数是遍历一个进程的地址空间,标识它的每一个唯一的地址区,并且显示有关每个区的特定状态信息。它是通过每次枚举从进程底部到上部的每个区域来完成的。LpMem被用来指出每个区域的位置。开始时它被设为0,从每个新区域的查询返回后,它通过查询的区域大小而递增。该过程一直重复下去,直到lpMem达到系统最高的保留区域。

LpList是一个指向MEMORY_BASIC_INFORMATION结构的指针,该结构被VirtualQueryEx函数所填充。当该函数返回时,该结构代表被查询区域的有关信息。该结构包含下列成员:

typedef struct _MEMORY_BASIC_INFORMATION { /* mbi */

PVOID BaseAddress; /* 区域的基本地址 */

PVOID AllocationBase; /* 分配基本地址 */

DWORD AllocationProtect; /* 初始访问保护 */

DWORD RegionSize; /* 区域的字节大小 */

DWORD State; /* 已提交的、保留的、自由的 */

DWORD Protect; /* 当前访问保护 */

DWORD Type; /* 页类型 */

} MEMORY_BASIC_INFORMATION;

VirtualQuery函数将为任何连接的地址区域返回这种状态信息。函数确定区域的底层边界和区域的大小,以及该区域中的地址的确切状态。它用于确定该区域的地址可以是该区域中的任何地址。因此,如果您想确定在任何给定的时间里有多少堆栈空间已经被提交,请按照这些步骤进行:

  1. 为问题中的线程取得线程环境。
  2. 调用VirtualQuery函数,将线程环境信息中的堆栈指针地址作为函数中lpMem参数来提供。

该查询返回被提交内存的大小,并且分别以RegionSize和BaseAddres的形式返回在MEMORY_BASIC_INFORMATION结构中基本堆栈的地址。

内存区域,由VirtualQueryRegions来定义,是一个连续的地址范围,其保护、类型和基本分配是相同的。类型和保护值在本篇技术文章的前面曾做过描述。基本分配是lpAddress参数值,当整个内存区域通过VirtualAlloc函数第一次被分配时该参数被使用。在MEMORY_BASIC_INFORMATION结构中,它作为AllocationBase字段被表示出来。

当自由的地址成为保留的或已提交的情况时,那时它们的基本分配被确定。从任何角度来说,内存区域都不是静态的。一旦在保留地址区域中的一个单页被提交,则该区域被分成一个或多个保留区域和一个被提交区域。当页内存改变状态时,这种情况就会继续下去。类似地,当多个PAGE_READWRITE的已提交页中的一个被改变成PAGE_READONLY保护时,该区域也被分成多个较小的区域。

 

总结

在Win32中的虚拟内存管理函数提供在Windows NT中直接管理虚拟内存的能力。每个进程的2 GB用户地址空间被分成为保留的、已提交的或者自由的虚拟地址内存区。一个区被定义为一个连续的地址范围,其保护、类型和每个地址的基本分配是相同的,在每个区中是一个或多个地址页,也可携带保护和页锁定标志状态位。

虚拟内存管理函数为应用程序提供了转换虚拟地址空间页状态的能力。一个应用程序可以把内存类型从已提交的改变为保留的,或把保护模式从 PAGE_READWRITE 改变为 PAGE_READONLY,从而防止对某段地址空间的访问。一个应用程序可以锁定一页内存到工作组,使得一个进程最少地分页关键内存页。虚拟内存函数被认为是低级函数,意味着它们的速度相对较快,但是缺乏很多高级函数所具有的功能。

(来源:http://www.microsoft.com/china/MSDN/library/archives/technic/develop/win32ram.asp)

JSP中集成FCKeditor富文本编辑器的详细指南 本文还有配套的精品资源,点击获取 简介:FCKeditor是一款功能强大的开源富文本编辑器,特别适用于Web环境,类似于Word的文本编辑功能。在JSP开发中,FCKeditor常用于提供可视化文本编辑界面。本文章详细介绍了如何在JSP中引入和配置FCKeditor,包括静态和动态两种引入方法,以及如何设置编辑器的语言,实现源代码编辑、丰富的编辑功能、插件扩展和确保浏览器兼... 阅读详情

相关推荐

Spring Boot + JSP实战:从零实现BBS论坛与敏感词过滤部署

在Web应用开发中,单体架构依然是快速交付业务系统的可靠选择。Spring Boot作为主流后端框架,通过自动配置降低了工程搭建门槛,而JSP配合jQuery的经典组合在内部管理系统、社区论坛等场景中仍被广泛使用。理解从浏览器请求到Controller、Service、DAO再到视图渲染的完整链路,是掌握后端技术体系的关键基础。本文以BBS论坛项目为实例,讲解Spring Boot单体Web工程如何设计用户、帖子、回复等核心数据表,如何实现会话管理、发帖回复和分页搜索,并重点剖析基于DFA算法的敏感词过滤机

weixin_42525551的博客 144

任鸟飞逆向C++高级篇

【课程简介】本课程为任鸟飞逆向C++高级篇,注重在逆向中运用的技巧和思维逻辑,掌握后相关工具与手法可以熟练运用,在反汇编的世界里如鱼得水。 本套课程不只是一套深入学习C++的课程,更是一套深入学习汇编逆向课程、外挂与反外挂、软件安全的课程。 逆向思路技术随笔,请关注我的优快云博客:https://blog.youkuaiyun.com/qq_36553941 欢迎大家来学习交流,活动折扣咨询等请在博客私聊我。 PS: 课程中工具包、源码、课件请关注第一章的第一节的随课课件,工欲望善其事,必先利其器! 【学前预备知识】任鸟飞逆向C++系列课程是一条精心安排为掌握汇编逆向、外挂与反外挂、软件安全以及软件编程的学习路线,包括但不限于 基础篇、进阶篇、高级篇(本篇)、实战篇等,所以为保证您能学有所成请在学习本篇之前务必掌握基础篇与进阶篇!!! 基础篇优快云学院链接:https://edu.youkuaiyun.com/course/detail/30509进阶篇优快云学院链接:https://edu.youkuaiyun.com/course/detail/30680 【学员学成收获】认真学习并练习后将掌握:1、熟练游戏逆向思想与技巧。2、熟练通用辅助程序设计。3、熟练诸多逆向工具使用。4、熟练常见数据结构逆向分析。5、熟悉智能主线脚本编写。 

SSM学生信息管理系统项目部署与JSP开发实战

在Java Web开发领域,SSM框架(Spring+SpringMVC+MyBatis)凭借清晰的分层架构和灵活的配置方式,成为众多企业级应用与课程设计的首选。其核心原理在于通过Spring管理业务对象,SpringMVC负责请求转发与控制,MyBatis完成数据库映射,最终由JSP渲染动态页面。理解这一技术链路,不仅能帮助开发者解决环境配置、依赖冲突、Tomcat部署等常见问题,还能提升项目迭代效率。在实际应用场景中,SSM项目常被用于信息管理系统的快速搭建,例如学生信息管理、教务管理等。本文以工贸学生

weixin_42528780的博客 169

VirtualQueryEx详解

DWORD VirtualQueryEx( HANDLE hProcess, // 想要查询的地址空间所属的进程的句柄 LPCVOID lpAddress, // 区域地址 PMEMORY_BASIC_INFORMATION lpBuffer, // 结构缓冲区 SIZE_T dwLength

baidu_25539425的博客 1万+

网站整站下载器:从链接遍历到离线镜像的完整指南

在网站运维与内容管理中,整站备份与离线归档是常见需求。无论是静态博客迁移、技术文档留存,还是前端资源分析,将在线站点完整镜像到本地,都依赖一种基于网络爬虫原理的工具——网站整站下载器。这类工具通过递归遍历页面中的链接关系,自动抓取HTML、CSS、JavaScript、图片与字体等资源,并完成链接改写与路径映射,生成可在本地离线浏览的完整目录结构。理解其核心机制,有助于在HTTrack、Wget等主流工具中做出合理选型,并规避抓取过程中的常见坑点。本文从网络爬虫的基础概念出发,解析整站下载器的工作原理、资源

weixin_29047545的博客 269

JavaWeb教学标本:SSM+JSP+MySQL购物系统解析

JavaWeb是高校软件工程与计算机专业核心实践课程,其本质是理解HTTP请求响应周期、MVC分层架构与传统Web容器运行机制。本文围绕一个典型教学级购物系统,深入剖析JSP动态页面渲染原理、Servlet单例生命周期与线程安全陷阱、MyBatis XML映射与SQL注入防御、Spring事务边界控制等关键技术点。项目采用SSM(Spring+SpringMVC+MyBatis)经典组合,严格遵循web.xml配置规范与MyEclipse→IDEA环境迁移路径,聚焦于可教学、可答辩、可复现的工程实践闭环。通

weixin_34208283的博客 365

Windows VirtualLock()的正确使用方法

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 Windows VirtualLock函数的正确使用方法前言一、Windows虚拟内存是什么?二、Windows 工作集1.SetProcessWorkingSetSize三、测试申请大块进程虚拟内存空间并且锁定到物理内存中的正确做法如下 前言 当我们需要对Windows的内存进行锁定防止物理内存上的数据被交换到页文件时就可以使用Windows提供的这个函数 VirtualLock()但是这个函数正确的方法应该如下这样使用 一、Win.

u011213040的博客 1720

从零到一:uniPush2.0全链路集成与多厂商通道配置实战

本文提供了uniPush2.0从零开始的全链路集成与多厂商通道配置实战指南。详细讲解了DCloud平台配置、uniCloud云函数开发、客户端CID获取与消息监听,并重点攻克了华为、小米、OPPO、vivo等主流厂商推送通道的配置难点与避坑技巧,帮助UniApp开发者实现“一次集成,全端覆盖”的高效消息推送方案。

onion的博客 431

使用IDEA2022.2.2 创建JavaWeb项目 使用JSP实现登录功能

在百度搜索框中输入“电脑背景图片”——单击下方“图片”按钮——选择心仪图片——单击下载按钮。4,输入images,创建目录,用于存放后续需要使用到的图片。1,在webapp目录上右击——new——directory。1,右击webapp包——new——jsp/jspx。2,单击运行按钮,试运行一下项目,是否可以正常跑起。2,输入css,创建目录,存放每个页面的外部样式表。2,输入login,选择CSS file,按回车。2,在命名文本框中输入,login,按回车。3,输入js,创建目录,存放js代码文件。

梁辰兴的博客 1954

树莓派5新手必看:保姆级ibus中文输入法安装与配置全流程(含.bashrc设置避坑)

本文详细介绍了在树莓派5上安装和配置ibus中文输入法的完整流程,包括系统环境准备、ibus框架安装、图形界面配置及常见问题解决方案。通过清晰的步骤说明和避坑指南,帮助新手快速实现中文输入功能,提升开发效率。

weixin_29159441的博客 527

CVPR 2026 即插即用 | 注意力篇 | MSEF:超轻注意力,空间-通道双分支并行融合,既增强特征又抑制噪声!

Multinex提出了一种超轻量级微光图像增强框架,其核心创新是MSEF模块。MSEF采用双分支并行架构,结合空间局部特征(深度可分离卷积)和全局通道注意力(改进SE),通过Tanh激活实现特征增强与抑制。相比传统注意力模块,MSEF在几乎不增加计算量的情况下显著提升性能,特别适合轻量级网络和边缘设备部署。实验表明,该模块能有效提升PSNR约3dB,同时保持极低的参数量(0.7K-45K)。

主要更新底层视觉(去噪、超分等)相关的科研内容,形式为【论文精读】+【论文复现】 603

页面性能测试

1页面性能测试概述 页面性能测试则是针对于页面性能优化而开展的一种性能测试,目的是对Web系统的页面进行测试以确认系统页面是否会影响系统的性能并为页面的优化提供依据与建议,最终提升系统的整体性能表现,提高用户体验满意度。他的关注点是在与页面,不在于后端。有别于我们通常说的性能测试。 相对于C/S架构的应用系统,Web应用系统所有数据都需要从服务器端下载,虽然浏览器有缓存机制,但客户每次访问仍然...

jie1521836的博客 3857

2021.07.05【R语言】丨预测lncRNA并绘制venn图

目录摘要环境与方法依赖工具预测工具绘制工具使用命令预测lncRNA统计noncoding_ID绘制Venn图结果展示总结 摘要 接到一个单样品测lncRNA数据的项目,正好拿来练练手,梳理一下lncRNA流程。这里记录预测lncRNA并生成venn图的过程 环境与方法 R version 3.6.3 (2020-02-29) 依赖工具 预测工具 CPC2 CNCI Pfam CPAT 绘制工具 R:VennDiagram 使用命令 预测lncRNA CPC2 #更换env conda activate py

2035

基于HTML+CSS+JS+JSP+Mysql的书城购书商城设计与实现 文档+项目源码及数据库文件

由于现在没有设置权限,故只要不违背基本的限制,所有的操作都是可以进行的,可是对于本系统需要的数据,却是有数据长度限制的,尤其是有说明的部分,所以测试人员在具体进行数据测试时,要注意数据输入格式。编写详细设计说明书是软件开发过程必不可少的部分,其目的是为了使开发人员在完成概要设计说明书的基础上完成概要设计规定的各项模块的具体实现的设计工作。联系(Relationship):用菱形表示,菱形框内写明联系名,并用无向边分别与有关实体连接起来,同时在无向边旁标上联系的类型(1 : 1,1 : n或m : n)。

毕业作品网站 2493

【GlobalMapper精品教程】084:tif(tfw)、jpg(jgw)、png(pgw)的区别与联系

摘要:本文介绍了TFW、JGW、PGW等地理参考文件的基本原理和应用。这些文件通过简单的文本格式存储栅格图像的地理坐标信息,适用于平面坐标系下的GIS数据处理。文章详细说明了文件各行的含义,并提供了在ArcGIS和Globalmapper中生成这些文件的具体操作步骤,包括导出TIFF、JPG、PNG格式时勾选生成对应坐标文件的选项。这些文件虽然简单易用,但仅适用于平面坐标系,且功能相对有限。配套视频教程提供了更详细的操作演示。

智慧空间实验室 2401

AUTOSAR OS Counter讲解

Counter(计数器)以tick为单位记录操作系统中发生了多少“事情”。tick(滴答)是一个抽象的单位。时间,例如毫秒,微妙,分钟等,然后计数器计算过去了多少时间旋转,例如以度或分钟为单位,计数器计算物体旋转了多少按钮按下,计数器计算按钮按下多少次错误,计数器计算错误发生的频次中断(有时是任务)用于驱动一个计数器。该驱动程序负责进行正确的OS API调用来滴答计数器,或者告诉OS计数器已经滴答到一个所需的值。

酒无忧的博客 1070

html载入很慢如何优化,优化页面加载速度的方法 js css使用外部引入

css样式加载慢,怎么解决。CSS布局HTML小编今天和大家分享大神帮忙。在开发时遇到了一个问题,正常打开一个jsp页面 没什么问题,但是按住Ctr提高网站页面的加载速度的方法其实有很多,那本文主要从下面四个角度进行讨论,分享常用的提高网页加载速度的技巧: 一、网页压缩技术 对于网页压缩而言,相信各位站长都比较熟悉,主要是启用服务器Gzip,对页面Gzip压缩,减少元素的体积。网站性能优化 页面打...

weixin_31633071的博客 2949

157、飞控中的鲁棒自适应控制

本文总结了飞控系统中鲁棒自适应控制的实践经验。作者通过炸机案例引出传统PID控制的局限性,分析了鲁棒性与自适应性的本质矛盾,提出"用鲁棒性兜底,用自适应性提性能"的设计原则。重点介绍了模型参考自适应控制(MRAC)的实现方法,包括参考模型设计、自适应律调参技巧(建议初始增益设为0.01)、直接/间接自适应的应用场景差异,以及防止参数漂移的鲁棒修正方法。文章还分享了实际调试中的三大关键问题(初始值设定、激励条件、执行器饱和)及解决方案,并推荐了"鲁棒PID+自适应前馈"的混合架构。最后强调调试顺序应为先保证鲁

2401_88858637的博客 27

JAVA WEB 部分课后题汇总

2.在 ResultSetHandler接口中,提供了一个方法handle(java.sql.ResultSetrs),可以通过自定义一个实现ResultSetHandler接口的类,然后通过重写handle()方法,实现结果集的处理。下面操作中,能实现查询指定记录的选项是(D)___是Web中一种功能强大的脚本语言,常用来为网页添加各式各样的动态功能,它不需要进行编译,直接嵌入在HTML页面中,就可以把静态的页面转变成支持用户交互并响应事件的动态页面。应用注释标记方便查找、对比及理解代码。

m0_72988150的博客 1066

带有HSE组件的S32系列芯片中各子系统如何依次启动?

仔细阐述了上电后各个子系统的启动顺序,并给出了详细的启动步骤流程图。

车载系统攻城狮 1702

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

j7-前端基础&通信基础&Servlet-请求与响应&Cookie和Session&JSP&El表达式和JSTl标准标签库&JDBC&数据库连接池与DBUtils工具&文件的上传和下载

j7-前端基础&通信基础&Servlet-请求与响应&Cookie和Session&JSP&El表达式和JSTl标准标签库&JDBC&数据库连接池与DBUtils工具&文件的上传和下载

猛志的博客 410

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

Web应用开发技术笔记

Web应用开发技术笔记第一章1.1 WEB技术概述Web的三要素静态网页动态网页(Applet)动态网页动态Web的发展静态网页动态网页的比较1.2 Web应用程序的工作原理C/S结构优缺点对比B/S结构优缺点对比Web应用程序的开发1.3 动态Web技术PHPASP/ASP.NETJSP1.4 JAVA Web开发的方式与体系结构JSP的工作原理JAVA开发Web程序几种方式 第一章 1.1 WEB技术概述 Web的三要素 展示资源:超文本技术(HTML) 资源地址:统一资源定位技术(URL) 获取资源

weixin_45871130的博客 3247

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

Ajax技术特点:怎样达到不刷新页面即可数据更新

                 中间媒介商Ajax是个很神奇的中介商,由于他的好客,热情,服务态度好,导致效率很高,现在很多网站都喜欢和他合作,我们看看他的神奇地方。                 上次我们聊了Ajax在其间所扮演的角色,Ajax引擎的功能巨大,消除了网络交互中的处理--等待--处理--等待的确定啊,它是怎么实现的呢,话说这点我也是很好奇,因为我对web的实验,在jsp的改...

u012763405的博客 1201

(引用学习)提升Web应用程序性能的最佳实践

提升Web应用程序性能的最佳实践 2011-09-20 07:02 | 347次阅读 | 来源:优快云综合整理 【已有1条评论】发表评论 关键词:Web应用 | 作者:夏梦竹 | 收藏这篇资讯 导读:作为开发人员,Web页面加载或刷新的速度对其网站至关重要。在浏

745

提升Web应用程序性能的6种基本方式

1.减少HTTP请求数 每个HTTP请求都有开销,包括查找DNS、创建连接及等待响应,因此削减不必要的请求数可减少不必要的开销。要减少请求数: 合并文件。将总是同时使用的脚本合并到同一个文件中,不会减小总大小,但将会减少请求数。还可以同样方法合并CSS文件和图片。可以实现

552
上一篇: 精通 Visual Basic .NET(收藏)
下一篇: Application.Exit()并不是在什么地方都能终止程序的运行
AntingZ
博客等级 码龄23年 13粉丝 28原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值