让程序脱离磁盘运行
Jeffrey Richter
问:在你的那本Advanced Windows(127页)中,你提到当Windows 95 或Windows NT从软盘运行一个可执行的映像文件时,系统实际上是将整个文件的映像装入RAM中。系统这样做是为了能让盘片从驱动器里取出是不影响应用程序。
这看起来是个好主意,我很高兴微软把这个功能放进操作系统中。我还能想象出许多种这样做的好处。例如,当映像文件位于网络驱动器或是光盘驱动器时,这样做也很有必要。我还想让文件在压缩或加密时装入到RAM中(Windows NT5.0)。有没有一种方法能让我强制系统将某一指定的文件映像装入到RAM中?
Frank Merrow
答: Windows NT 4.0启动时,微软在装载程序中加入这样的支持。当你创建了一个可执行的映像时,(.EXE或DLL),你可以在链接器中加入以下的开关说明。:
/SWAPRUN:NET
这个开关告诉链接器打开某个标志位。当装载程序装载你的映像文件时,装载文件要检查这个位是否已经设为开,还有映像是否是从网络驱动器装载。如果是,则装载程序将文件的映像装入RAM以防止在网络上频繁交换。
链接器还支持这种格式的SWAPRUN 开关:
/SWAPRUN:CD
这个开关告诉装载程序如果文件映像是在光盘驱动器中运行的,则强制将映像装入RAM中。注意要想让这些开关工作,你的链接器必须支持这个开关而装入程序必须认识这个开关作出正确的响应。Windows NT 4.0及以后的版本都能认识/SWAPRUN:NET 和 /SWAPRUN:CD 位;Windows? 95 不能。
现在,装载程序不支持将压缩或加密的映像文件装入到RAM中。但是自己编制一个强行将整个映像文件装入RAM的函数是非常简单的。图4显示了一个这样的函数-RunImageLocally。这个函数只有一个参数,就是可执行映像的HINSTANCE (或 HMODULE) 。这个参数也要传递给你的WinMain 函数, DllMain 函数,从LoadLibrary这个调用函数中返回的也是这个值。函数然后循环调用VirtualQuery检查包括可执行映像的所有内存区域。
图 4: RunImageLocally.cpp
/******************************************************************************
Module name: RunImageLocally.cpp
Notices: Written 1998 by Jeffrey Richter
Description: Forces an executable image to be run from the paging file.
******************************************************************************/
#include
///////////////////////////////////////////////////////////////////////////////
void WINAPI RunImageLocally(HINSTANCE hinst) {
DWORD cp= 0;
static DWORD s_dwPageSize = 0;
// Get the system's page size (do this only once)
if (s_dwPageSize == 0) {
SYSTEM_INFO si;
GetSystemInfo(&si);
s_dwPageSize = si.dwPageSize;
}
PBYTE pbAddr = (PBYTE) hinst;
MEMORY_BASIC_INFORMATION mbi;
VirtualQuery(pbAddr, &mbi, sizeof(mbi));
// Perform this loop until we find a region beyond the end of the file.
while (mbi.AllocationBase == hinst) {
// We can only force committed pages into RAM.
// We do not want to trigger guard pages and confuse the application.
if ((mbi.State == MEM_COMMIT) && ((mbi.Protect & PAGE_GUARD) == 0)) {
// Determine if the pages in this region are nonwriteable
BOOL fNonWritableRgn =
(mbi.Protect == PAGE_NOACCESS) ||
(mbi.Protect == PAGE_READONLY) ||
(mbi.Protect == PAGE_EXECUTE) ||
(mbi.Protect == PAGE_EXECUTE_READ);
DWORD dwOrigProtect = 0;
if (fNonWritableRgn) {
// Nonwriteable region, make it writeable (with the least protection)
VirtualProtect(mbi.BaseAddress, mbi.RegionSize,
PAGE_EXECUTE_READWRITE, &dwOrigProtect);
} else {
// This is a writeable page, we can leave the protections alone.
}
// Write to every page in the region.
// This forces the page to be in RAM and swapped to the paging file.
for (DWORD cbRgn = 0; cbRgn < mbi.RegionSize; cbRgn += s_dwPageSize) {
// Volatile so that the optimizer won't remove this code
volatile PDWORD pdw = (PDWORD) &pbAddr[cbRgn];
*pdw = *pdw;
cp++;
}
if (dwOrigProtect != 0) {
// If we changed the protection, change it back
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, dwOrigProtect,
&dwOrigProtect);
}
}
// Get next region
VirtualQuery(pbAddr += mbi.RegionSize, &mbi, sizeof(mbi));
}
GetLastError();
}
///////////////////////////////////////////////////////////////////////////////
int WINAPI WinMain(HINSTANCE hinstExe, HINSTANCE hinstExePrev,
LPSTR lpCmdLine, int nCmdShow) {
RunImageLocally(hinstExe);
return(0);
}
///////////////////////////////// End Of File /////////////////////////////////
对于每个区域,检查页是否被提交及是否是保护页。如果页还没有被提交,则说明没有要写入RAM中的内容。如果页有PAGE_GUARD 属性,对它进行操作将会引起一个STATUS_GUARD_PAGE_VIOLATION 异常,如果你不想让你的应用程序混乱,就也不能够将这些页写到RAM中。
对于这些所有的页,我检查它们看是否是可写的。如果不是,我将改变对这些页的保护以使我能够对它们进行写操作。我使用 PAGE_EXECUTE_READWRITE 因为这是所有的保护中最自由的一种。一旦我知道页是可写的,我就能在一个区域的每一页中都写入单个的DWORD。执行这个写操作令系统把RAM中的页做一个拷贝。在区域中这些所有的页都被写了之后,我将页的保护方式改回到原来的方式(如果必要的话)。最后,重复这个操作直到包含可执行映像文件的所有区域都完成为止。
应当注意的是:这个程序不是线程安全的,也没有办法使它安全。进程中的其他线程有可能会改变会改变文件映像所在页的保护方式或者改动页中的数据。仅仅是执行代码或碰触数据根本就不是问题。但是如果其他线程改变了页的保护方式,这将是个潜在的问题。
我只在Windows NT上测试了这段代码,但是它应当在Windows 95一样运行良好。由于Windows 95不支持写时拷贝的机制,所以在Windows 95 上使用这项技术就不大合适。当你将一个映像装入到Windows 95上时,装载程序会为映像文件的可写页自动分配RAM (及交换文件区) 。