问题 是否可以取消Windows中的所有内存?


我有足够的RAM,但是,在启动和完成大量进程后,似乎大多数应用程序的虚拟内存已被分页到磁盘,并且切换到任何较旧的进程需要很长时间才能加载内存回到RAM。

有没有办法,通过Windows API或内核调用,让Windows取消所有(或尽可能多)内存?也许通过逐步运行进程列表并让内存管理器取消每个进程的内存?


6440
2018-05-30 17:31


起源



答案:


嗯,实现自己并不困难。使用 VirtualQueryEx() 发现进程使用的虚拟地址, ReadProcessMemory() 强制页面重新加载。

它根本不可能产生任何不同,它只是你的程序需要永远完成它的工作。缓慢重新加载页面的常见诊断是分段的页面文件。在Windows XP上常见,例如,磁盘未长时间进行碎片整理,并且允许频繁填充接近容量的磁盘。 SysInternals' PageDefrag 实用程序可以帮助解决问题。


8
2018-05-30 18:16



谢谢你们这两个指针!让我检查一下使用这两个函数是否对切换过程的性能有任何影响! - Kerrek SB
是的,因为在每个进程的每个字节上调用ReadProcessMemory肯定会比仅仅分页回来更好......哦,顺便说一句,你需要Admin私有并获得SE_DEBUG_NAME特权才能使用进程内存功能。 - Billy ONeal
只需每页读取一个字节。 - Hans Passant
@Kerrek SB: I'm surprised [...] paging memory [...] without actually requesting; 反过来我会感到惊讶。操作系统需要能够坚持它的工作。这本来就是 不民主。那说, VirtualLock 会做 接近你想要的 同 SetProcessWorkingSetSize。看到 kb108449 - sehe
就像一张纸条:该程序原来是 非常 很有用,并且完全符合我的要求:在我和我的笔记本电脑醒来,煮咖啡后,我会运行unpage命令,当我回来时,所有“未发布”的进程(浏览器,邮件,资源管理器)都是 非常 响应。 (单独浏览器在分页时会非常烦人,因为即使打开右键单击上下文菜单也会使UI暂停几秒钟。) - Kerrek SB


答案:


嗯,实现自己并不困难。使用 VirtualQueryEx() 发现进程使用的虚拟地址, ReadProcessMemory() 强制页面重新加载。

它根本不可能产生任何不同,它只是你的程序需要永远完成它的工作。缓慢重新加载页面的常见诊断是分段的页面文件。在Windows XP上常见,例如,磁盘未长时间进行碎片整理,并且允许频繁填充接近容量的磁盘。 SysInternals' PageDefrag 实用程序可以帮助解决问题。


8
2018-05-30 18:16



谢谢你们这两个指针!让我检查一下使用这两个函数是否对切换过程的性能有任何影响! - Kerrek SB
是的,因为在每个进程的每个字节上调用ReadProcessMemory肯定会比仅仅分页回来更好......哦,顺便说一句,你需要Admin私有并获得SE_DEBUG_NAME特权才能使用进程内存功能。 - Billy ONeal
只需每页读取一个字节。 - Hans Passant
@Kerrek SB: I'm surprised [...] paging memory [...] without actually requesting; 反过来我会感到惊讶。操作系统需要能够坚持它的工作。这本来就是 不民主。那说, VirtualLock 会做 接近你想要的 同 SetProcessWorkingSetSize。看到 kb108449 - sehe
就像一张纸条:该程序原来是 非常 很有用,并且完全符合我的要求:在我和我的笔记本电脑醒来,煮咖啡后,我会运行unpage命令,当我回来时,所有“未发布”的进程(浏览器,邮件,资源管理器)都是 非常 响应。 (单独浏览器在分页时会非常烦人,因为即使打开右键单击上下文菜单也会使UI暂停几秒钟。) - Kerrek SB


更新3: 我上传了完整的程序 到github


好的,基于到目前为止的回复,这里有一个天真的建议,试图让所有应用程序回到物理内存:

  1. 分配一小块内存X,可能是4MB。 (它应该是不可分页的吗?)
  2. 迭代所有进程:
    • 对于每个进程,将其内存块复制到X. (可能先暂停这个过程?)

假设你有2GB的RAM,进程实际上只需要1GB。如果一切都在物理内存中,那么你只能复制256个块,而不是世界末日。在一天结束时,所有进程现在完全在物理内存中的可能性很大。

可能的便利和优化选项:

  • 首先检查所需的总空间不超过总物理空间的50%。
  • (可选)仅在当前用户拥有的进程上运行,或在用户指定的列表上运行。
  • 首先检查每个内存块是否实际被分页到磁盘。

我可以使用EnumProcesses()迭代所有进程;如果有任何建议如何复制整个进程的内存块,我将不胜感激。


更新: 这是我的示例函数。它将进程ID作为其参数,并从进程的每个良好页面复制一个字节。 (第二个参数是最大进程内存大小,可通过GetSystemInfo()获得。)

void UnpageProcessByID(DWORD processID, LPVOID MaximumApplicationAddress, DWORD PageSize)
{
  MEMORY_BASIC_INFORMATION meminfo;
  LPVOID lpMem = NULL;

  // Get a handle to the process.
  HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processID);

  // Do the work
  if (NULL == hProcess )
  {
    fprintf(stderr, "Could not get process handle, skipping requested process ID %u.\n", processID);
  }
  else
  {
    SIZE_T        nbytes;
    unsigned char buf;

    while (lpMem < MaximumApplicationAddress)
    {
      unsigned int stepsize = PageSize;

      if (!VirtualQueryEx(hProcess, lpMem, &meminfo, sizeof(meminfo)))
      {
        fprintf(stderr, "Error during VirtualQueryEx(), skipping process ID (error code %u, PID %u).\n", GetLastError(), processID);
        break;
      }

      if (meminfo.RegionSize < stepsize) stepsize = meminfo.RegionSize;

      switch(meminfo.State)
      {
      case MEM_COMMIT:
        // This next line should be disabled in the final code
        fprintf(stderr, "Page at 0x%08X: Good, unpaging.\n", lpMem);

        if (0 == ReadProcessMemory(hProcess, lpMem, (LPVOID)&buf, 1, &nbytes))
          fprintf(stderr, "Failed to read one byte from 0x%X, error %u (%u bytes read).\n", lpMem, GetLastError(), nbytes);
        else
          // This next line should be disabled in the final code
          fprintf(stderr, "Read %u byte(s) successfully from 0x%X (byte was: 0x%X).\n", nbytes, lpMem, buf);

        break;
      case MEM_FREE:
        fprintf(stderr, "Page at 0x%08X: Free (unused), skipping.\n", lpMem);
        stepsize = meminfo.RegionSize;
        break;
      case MEM_RESERVE:
        fprintf(stderr, "Page at 0x%08X: Reserved, skipping.\n", lpMem);
        stepsize = meminfo.RegionSize;
        break;
      default:
        fprintf(stderr, "Page at 0x%08X: Unknown state, panic!\n", lpMem);
      }

      //lpMem = (LPVOID)((DWORD)meminfo.BaseAddress + (DWORD)meminfo.RegionSize);
      lpMem += stepsize;
    }
  }

  CloseHandle(hProcess);
}

问题:我增加的大小的区域最多只包含一页,还是我缺少页面?我是否应该尝试找出页面大小,并且只增加区域大小和页面大小的最小值?  更新2: 页面大小只有4kiB!我将上面的代码更改为仅以4kiB步进增量。在最终的代码中,我们将摆脱循环中的fprintf。


9
2018-05-30 20:39



经过一些测试,我发现这确实很有效!它当然具有这样的效果:这样“未发布”的应用程序再次变得响应并且不需要任何意外的硬盘访问。 - Kerrek SB
@Tomalak:我把另一个答案接受了,因为我的程序最终都是基于这个想法。在mplayer会话或任何Adobe程序页面将您的整个生命周期发送到磁盘后,它非常有用。 - Kerrek SB
有人可以提供此工具的二进制文件供下载和安装吗? - TMS
@Tomas:我在家里有源代码,如果你愿意,我可以把它上传到github。我发现它在当天非常有用。 (我从那以后得到了一台带有更多内存和SSD的新机器,问题就不那么紧迫了。) - Kerrek SB
如果你能为Windows XP / 7上传一个二进制文件,Kerrek会很棒!如果它可以允许只有具有给定PID的进程的交换(例如在命令行上)... :-) - TMS


不,Windows本身没有提供这样的功能。诸如Cacheman和RAM IDLE之类的程序通过简单地分配大量RAM来强制执行此操作,从而强制其他内容转到磁盘,从而有效地完成您想要的操作。


-2
2018-05-30 17:37



嗯,听起来好像它会与他想要的相反。
@Neil:他想要分页还是分页?很难从“未发布”中分辨出...如果他想要所有内容都在寻找Windows的默认行为,那么我认为他想要非默认行为,这会将所有内容分页到磁盘。 - Billy ONeal
对不起,如果不清楚。我想得到一切 成 物理内存, 离 硬盘。即使我有2GB的RAM,每次我将Alt-Tab转移到另一个进程时,似乎必须首先从磁盘检索该进程,我想通过将一个“将所有内容放回物理内存”调用来避免这种情况,喝咖啡,然后回到快速响应的系统。 - Kerrek SB
@Neil:毫无疑问,恢复过程的默认行为是什么?重点是在某个阶段我 上午 运行比我有物理RAM更多的进程,所以东西会被分页到磁盘,但是当大多数进程完成后,我似乎留下了大量的免费物理RAM,但我所有的旧进程仍然被分页到磁盘。 [未完待续] - Kerrek SB
@MSmith:我不认为这是那种问题;重新启动后,一切都很完美。只有在机器运行了几天之后,我才会运行(并完成)大量的视频编码,播放和3D游戏,我的“常量”进程(浏览器,电子邮件)似乎已被分页到磁盘而不是内存再次可用后恢复。即使是现在,拥有大约600MB的可用RAM,Opera的总页面错误为70M,每秒约有100个新错误。我可以找出Opera的内存在磁盘上有多少,并指示系统将其加载到RAM中吗? - Kerrek SB