2010年8月13日星期五

內存釋放的那些事兒

一直用memempty.exe,國產的,幾年了
今天偶爾發現minimem,.net的,ildasm上
定位到emptyworkingset這個API。
查了下,早有人研究了。

轉帖過來。

EmptyWorkingSet和内存整理
2008-07-14 17:52

网络上找了很多关于内存整理的文章,不外乎都是使用EmptyWorkingSet来实现。就如下面这段代码。

#include "stdafx.h"
#include
#include
#include
#pragma comment (lib,"psapi.lib")

BOOL EmptyAllSet()
{
HANDLE SnapShot=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if(SnapShot==NULL)
{
return FALSE;
}
PROCESSENTRY32 ProcessInfo;//声明进程信息变量
ProcessInfo.dwSize=sizeof(ProcessInfo);//设置ProcessInfo的大小
//返回系统中第一个进程的信息
BOOL Status=Process32First(SnapShot, &ProcessInfo);
while(Status)
{
HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS,TRUE,ProcessInfo.th32ProcessID);
if(hProcess)
{
SetProcessWorkingSetSize(hProcess,-1,-1);
//内存整理
EmptyWorkingSet(hProcess);
CloseHandle(hProcess);
}
//获取下一个进程的信息
Status=Process32Next(SnapShot,&ProcessInfo);
}
return TRUE;
}

int main(int argc, char* argv[])
{
EmptyAllSet();
return 0;
}

但在实际使用上,只能清理当前帐户启动的进程。其他进程,如SYSTEM帐户启动的程序,都是无法清理。这是因为权限的关系。这个时候只要加一段提升权限的代码即可。

void AdjustTokenPrivilegesForNT()
{
HANDLE hToken;
TOKEN_PRIVILEGES tkp;

// Get a token for this process.

OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);

// Get the LUID for the EmptyWorkingSet privilege.

LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tkp.Privileges[0].Luid);

tkp.PrivilegeCount = 1; // one privilege to set
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

// Get the EmptyWorkingSet privilege for this process.

AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES)NULL, 0);
}

然后在main的EmptyAllSet();调用之前加入AdjustTokenPrivilegesForNT();即可。这段代码与关机所用的那段很相似,有什么区别的话比较一下就知道了。

另外,很多文章说外面很多内存整理的工具不过如此,我觉得是很不负责任的。如果是使用EmptyWorkingSet实现的话当然是没什么技术含量,但是只要比较一下实现过程就会知道,很多好的内存整理工具的实现方法肯定不是这样的。运行上面这段代码耗时极短,硬盘几乎不进行读写。但是其他的整理工具往往会进行大量的硬盘读写,将内存数据转存到硬盘的分页文件里以达到整理内存的目的。孰优孰劣我不敢说,但是相对来说我还是觉得用EmptyWorkingSet实现来的快一点。

目前为止我还没有发现EmptyWorkingSet执行后会产生什么问题,如果有高人知道这个函数的缺点的话,望不吝赐教。谢谢。

http://hi.baidu.com/xuzhhua/blog/item/05dfa34ac9008ef582025cd2.html



 


記憶體回收 (EmptyWorkingSet)
11
Aug

在 LifeHacker 上看到一個叫作 Minimem 的工具,主要是用來清除系統中不用的記憶體,可以設定幾秒執行一次,也可以設定針對哪一些使用記憶體量比較大的程式進行記憶體回收的動作。下載來稍微玩了一下,發現有點用處,類似的程式有 Freemem 之類的,以前一直想瞭解這些程式的運作原理,剛好 Minimem 是 .NET Framework 3.5 的程式,所以就可以拿它來 “解剖” 一下,看看實際的工作原理是甚麼。

用 Reflector 看了一下,主要是呼叫一個叫作 EmptyWorkingSet 的 Method 來進行,而它僅是一個 Interface,調用 psapi.dll 裡的 EmptyWorkingSet 這個 Method。

[DllImport("psapi.dll")]
private static extern int EmptyWorkingSet(IntPtr hwProc);

再 Google 一下 “EmptyWorkingSet”,發現它就是利用 Windows 提供的 API 來進行記憶體回收,僅此而已,沒有其他令人驚豔的程式碼與功能,原來這僅僅是一個系統 API 而已呀。先前以為記憶體回收是一個怎麼樣困難的技術,現在發現原來困難的技術已經被包成系統 API (“某些”系統,就是需要這些 API),很多的工具都可以利用呼叫這個 API 來達到回收效果,下回自己寫的程式吃太多記憶體的話,可以試一下寫一個會自我回收記憶體的程式。

[Reference]
http://msdn.microsoft.com/en-us/library/ms682606.aspx
http://www.google.com.hk/codesearch?q=EmptyWorkingSet&ie=UTF-8&oe=utf-8&rls=org.mozilla:zh-TW:official&client=firefox&um=1&sa=X&oi=codesearch_group&resnum=4&ct=title

至於 Minimem 嘛,仍有它的功能在,作為一個設定固定時間、針對 Process 記憶體使用量執行的介面程式,不過我還是覺得它應該要寫成 Windows 的 Service 比較好。

[Update 2008-09-24]

出差在飯店晚上無聊的時候弄了一個 Service 的版本,暫時叫作 More Memory V1.0,可以在下面的連結下載:
http://groups.google.com/group/Autorun-DIY/web/MoreMem100.zip?hl=zh-TW
安裝很簡單,就是建一個目錄,把檔案都放進去,然後跑一下 Install.bat,接著到[控制台][系統管理工具][服務] 裡頭就可以看到 FETAG – More Memory Service 把它設為 “啟動” 就可以了。要移除的話就執行 UnUnstall.bat 即可。
http://webcache.googleusercontent.com/search?q=cache:jr7C5y_CDH4J:coolfire.fetag.org/%3Fp%3D334+emptyworkingset&cd=5&hl=zh-CN&ct=clnk&lr=lang_zh-TW|lang_en|lang_zh-CN&client=firefox-a




api說明:
http://msdn.microsoft.com/en-us/library/ms682606(VS.85).aspx


老外搞了AHK,果然萬能的AHK..
http://www.autohotkey.com/forum/topic53543.html

 代碼複製學習

; =============== Auto-execute ===============
#NoEnv
SendMode, Input
SetWorkingDir, %A_ScriptDir%
SetTitleMatchMode, 3
DetectHiddenWindows, On

IfExist, ram.ico
   Menu, Tray, Icon, ram.ico
Menu, Tray, NoStandard
Menu, Tray, Add, Show Window, ShowWindow
Menu, Tray, Add, Exit, GuiClose
Menu, Tray, Default, Show Window

; =============== Variables ===============
Version = 1.1.0

; =============== GUI ===============
GoSub, ReadINI

Gui, Add, Button, x15 y395 w80 h30, Start
Gui, Add, Button, x175 y395 w70 h30, Hide
Gui, Add, Button, x255 y395 w70 h30, Exit
Gui, Add, Tab, x0 y5 w340 h385, Status|Settings|About
Gui, Tab, Status
   Gui, Add, Edit, x20 y60 w300 h300 +ReadOnly -Wrap
Gui, Tab, Settings
   Gui, Font, S12 Bold,
   Gui, Add, Text, x130 y30, Interval
   Gui, Add, Text, x135 y90, Type
   Gui, Add, Text, x130 y280, Startup
   Gui, Font,,
   Gui, Add, Edit, x110 y60 w100 h20 vRunInterval, %RunInterval%
   Gui, Add, Text, x+10 y60 w100 h30, min
   Gui, Add, Radio, vRunType x10 y120, All processes (Exclusions)
   Gui, Add, Radio, x170 y120, Specific process (Inclusions)
   Gui, Add, Edit, vExclusions x15 y150 w150 h100 -Wrap, %Exclusions%
   Gui, Add, Edit, vInclusions x175 y150 w150 h100 -Wrap, %Inclusions%
   Gui, Add, Checkbox, vAutorun Checked%Autorun% x90 y310, Auto-run
   Gui, Add, Checkbox, vHide Checked%Hide% x180 y310, Start hidden
   Gui, Add, Button, x120 y355, Save Settings
Gui, Tab, About
   Gui, Font, S14
   Gui, Add, Text, x36 y100 w250 h150 , Written by:`n`nMatthew Giffin`nstrictlyfocused02@gmail.com
   Gui, Font,,
Gui, Show, h430 w340, Memory Cleanup v%Version%

If RunType = 1
   Control, Check,, Button4, Memory Cleanup v%Version%
If RunType = 2
   Control, Check,, Button5, Memory Cleanup v%Version% 
If Hide = 1
   GoSub, ButtonHide
If Autorun = 1
   GoSub, ButtonStart

Return

; =============== GUI Sub-routines ===============
ButtonExit:
GuiClose:
   ExitApp

GuiSize:
   If A_EventInfo = 1
      WinHide, Memory Cleanup v%Version%
   Return

ButtonHide:
   WinHide, Memory Cleanup v%Version%
   Return

ShowWindow:
   WinShow, Memory Cleanup v%Version%
   WinWait, Memory Cleanup v%Version%
   WinActivate, Memory Cleanup v%Version%
   Return

ButtonStart:
   Gui, Submit, NoHide
   GoSub, MemoryCleanup
   GoSub, SetInterval
   Return
 
SetInterval:
   AHKRunInterval := RunInterval * 60000
   SetTimer, MemoryCleanup, %AHKRunInterval%
   Return

ReadINI:
   IfExist, settings.ini
   {
      IniRead, RunInterval, settings.ini, Interval, Interval
      IniRead, RunType, settings.ini, Type, RunType
      IniRead, Exclusions, settings.ini, Type, Exclusions
      IniRead, Inclusions, settings.ini, Type, Inclusions
      IniRead, Autorun, settings.ini, Launch, Autorun
      IniRead, Hide, settings.ini, Launch, Hide
      If not Exclusions
         Exclusions = One process name per line
      If Exclusions
         StringReplace, Exclusions, Exclusions, `,, `n, All
      If not Inclusions
         Inclusions = One process name per line
      If Inclusions
         StringReplace, Inclusions, Inclusions, `,, `n, All
      Return
   }
   IfNotExist, settings.ini
   {
      RunInterval = 30
      RunType = 1
      Exclusions = One process name per line
      Inclusions = One process name per line
      Autorun = 0
      Hide = 0
      Return
   }

ButtonSaveSettings:
   Gui, Submit, NoHide
   If Exclusions = One process name per line
      Exclusions =
   If Inclusions = One process name per line
      Inclusions =
   StringReplace, Exclusions, Exclusions, `n, `,, All
   StringReplace, Inclusions, Inclusions, `n, `,, All
   IniWrite, %RunInterval%, settings.ini, Interval, Interval
   IniWrite, %RunType%, settings.ini, Type, RunType
   IniWrite, %Exclusions%, settings.ini, Type, Exclusions
   IniWrite, %Inclusions%, settings.ini, Type, Inclusions
   IniWrite, %Autorun%, settings.ini, Launch, Autorun
   IniWrite, %Hide%, settings.ini, Launch, Hide
   MsgBox, Settings saved!`n`nTip: Press "Start" now to apply these settings without having to restart the application.
   Return

; =============== Bread and Butter ===============
MemoryCleanup:
   ProcList := CMDret("tasklist /fo CSV")  ;Send "tasklist" to cmdret
   Loop, Parse, ProcList, `n
   {
      If A_Index < 4  ;The first 4 lines are always junk\column|row seperators\blank
         Continue
      StringSplit, ProcArray, A_LoopField, `,  ;Split apart using a comma delimeter
      StringReplace, ProcArray1, ProcArray1, `",, 1  ;Process Name
      StringReplace, ProcArray2, ProcArray2, `",, 1  ;PID
      If RunType = 1
      {
         StringReplace, Exclusions, Exclusions, `n, `,, All
         If ProcArray1 in %Exclusions%
            Continue
         Else
         {
            GO := CleanMemory(ProcArray2)
            If GO = 1  ;Function returned 1 (i.e. success)
            {
               ControlGetText, OnScrLog, Edit1, Memory Cleanup v%Version%
               ControlSetText, Edit1, %OnScrLog%`r`n`r`n%A_MMM%-%A_DD%-%A_YYYY% %A_Hour%:%A_Min%:%A_Sec%:%A_MSec% - Cleaned "%ProcArray1%", Memory Cleanup v%Version%
               SendMessage, 0x115, 7, 0, Edit1, Memory Cleanup v%Version%
            }
         }
      }
      If RunType = 2
      {
         StringReplace, Inclusions, Inclusions, `n, `,, All
         If ProcArray1 in %Inclusions%
         {
            GO := CleanMemory(ProcArray2)
            If GO = 1  ;Function returned 1 (i.e. success)
            {
               ControlGetText, OnScrLog, Edit1, Memory Cleanup v%Version%
               ControlSetText, Edit1, %OnScrLog%`r`n`r`n%A_MMM%-%A_DD%-%A_YYYY% %A_Hour%:%A_Min%:%A_Sec%:%A_MSec% - Cleaned "%ProcArray1%", Memory Cleanup v%Version%
               SendMessage, 0x115, 7, 0, Edit1, Memory Cleanup v%Version%
            }
         }
      }
   }
   Return
 
; =============== Functions ===============

;A function that retreives the process handle from a PID and sends it to the EmptyWorkingSet API.  Returns 1 for success, NULL (blank) on failure
CleanMemory(PID)  ;Written with help from "Temp01" on the AHK IRC chat (thank you again, temp01!!!)
{
   Process, Exist  ;Sets ErrorLevel to the PID of this running script
   h := DllCall("OpenProcess", "UInt", 0x0400, "Int", false, "UInt", ErrorLevel)  ;Get the handle of this script with PROCESS_QUERY_INFORMATION (0x0400)
   DllCall("Advapi32.dll\OpenProcessToken", "UInt", h, "UInt", 32, "UIntP", t)  ;Open an adjustable access token with this process (TOKEN_ADJUST_PRIVILEGES = 32)
   VarSetCapacity(ti, 16, 0)  ;Structure of privileges
   NumPut(1, ti, 0)  ;One entry in the privileges array...
   DllCall("Advapi32.dll\LookupPrivilegeValueA", "UInt", 0, "Str", "SeDebugPrivilege", "Int64P", luid)  ;Retrieves the locally unique identifier of the debug privilege:
   NumPut(luid, ti, 4, "int64")
   NumPut(2, ti, 12)  ;Enable this privilege: SE_PRIVILEGE_ENABLED = 2
   DllCall("Advapi32.dll\AdjustTokenPrivileges", "UInt", t, "Int", false, "UInt", &ti, "UInt", 0, "UInt", 0, "UInt", 0)  ;Update the privileges of this process with the new access token:
   DllCall("CloseHandle", "UInt", h)  ;Close this process handle to save memory

   hModule := DllCall("LoadLibrary", "Str", "Psapi.dll")  ;Increase performance by preloading the libaray
   h := DllCall("OpenProcess", "UInt", 0x400|0x100, "Int", false, "UInt", pid)  ;Open process with: PROCESS_QUERY_INFORMATION (0x0400) | PROCESS_SET_QUOTA (0x100)
   e := DllCall("psapi.dll\EmptyWorkingSet", "UInt", h)
   DllCall("CloseHandle", "UInt", h)  ;Close process handle to save memory
   DllCall("FreeLibrary", "UInt", hModule)  ;Unload the library to free memory
   Return e
}

;CMDret can be used to retrieve and store output from console programs in a variable without displaying the console window
CMDret(CMDin, WorkingDir=0)  ;by corrupt (http://www.autohotkey.com/forum/topic8606.html)
{
  Global cmdretPID
  tcWrk := WorkingDir=0 ? "Int" : "Str"
  idltm := A_TickCount + 20
  CMsize = 1
  VarSetCapacity(CMDout, 1, 32)
  VarSetCapacity(sui,68, 0)
  VarSetCapacity(pi, 16, 0)
  VarSetCapacity(pa, 12, 0)
  Loop, 4 {
    DllCall("RtlFillMemory", UInt,&pa+A_Index-1, UInt,1, UChar,12 >> 8*A_Index-8)
    DllCall("RtlFillMemory", UInt,&pa+8+A_Index-1, UInt,1, UChar,1 >> 8*A_Index-8)
  }
  IF (DllCall("CreatePipe", "UInt*",hRead, "UInt*",hWrite, "UInt",&pa, "Int",0) <> 0) {
    Loop, 4
      DllCall("RtlFillMemory", UInt,&sui+A_Index-1, UInt,1, UChar,68 >> 8*A_Index-8)
    DllCall("GetStartupInfo", "UInt", &sui)
    Loop, 4 {
      DllCall("RtlFillMemory", UInt,&sui+44+A_Index-1, UInt,1, UChar,257 >> 8*A_Index-8)
      DllCall("RtlFillMemory", UInt,&sui+60+A_Index-1, UInt,1, UChar,hWrite >> 8*A_Index-8)
      DllCall("RtlFillMemory", UInt,&sui+64+A_Index-1, UInt,1, UChar,hWrite >> 8*A_Index-8)
      DllCall("RtlFillMemory", UInt,&sui+48+A_Index-1, UInt,1, UChar,0 >> 8*A_Index-8)
    }
    IF (DllCall("CreateProcess", Int,0, Str,CMDin, Int,0, Int,0, Int,1, "UInt",0, Int,0, tcWrk, WorkingDir, UInt,&sui, UInt,&pi) <> 0) {
      Loop, 4
        cmdretPID += *(&pi+8+A_Index-1) << 8*A_Index-8
      Loop {
        idltm2 := A_TickCount - idltm
        If (idltm2 < 10) {
          DllCall("Sleep", Int, 10)
          Continue
        }
        IF (DllCall("PeekNamedPipe", "uint", hRead, "uint", 0, "uint", 0, "uint", 0, "uint*", bSize, "uint", 0 ) <> 0 ) {
          Process, Exist, %cmdretPID%
          IF (ErrorLevel OR bSize > 0) {
            IF (bSize > 0) {
              VarSetCapacity(lpBuffer, bSize+1)
              IF (DllCall("ReadFile", "UInt",hRead, "Str", lpBuffer, "Int",bSize, "UInt*",bRead, "Int",0) > 0) {
                IF (bRead > 0) {
                  TRead += bRead
                  VarSetCapacity(CMcpy, (bRead+CMsize+1), 0)
                  CMcpy = a
                  DllCall("RtlMoveMemory", "UInt", &CMcpy, "UInt", &CMDout, "Int", CMsize)
                  DllCall("RtlMoveMemory", "UInt", &CMcpy+CMsize, "UInt", &lpBuffer, "Int", bRead)
                  CMsize += bRead
                  VarSetCapacity(CMDout, (CMsize + 1), 0)
                  CMDout=a   
                  DllCall("RtlMoveMemory", "UInt", &CMDout, "UInt", &CMcpy, "Int", CMsize)
                  VarSetCapacity(CMDout, -1)   ; fix required by change in autohotkey v1.0.44.14
                }
              }
            }
          }
          ELSE
            break
        }
        ELSE
          break
        idltm := A_TickCount
      }
      cmdretPID=
      DllCall("CloseHandle", UInt, hWrite)
      DllCall("CloseHandle", UInt, hRead)
    }
  }
  IF (StrLen(CMDout) < TRead) {
    VarSetCapacity(CMcpy, TRead, 32)
    TRead2 = %TRead%
    Loop {
      DllCall("RtlZeroMemory", "UInt", &CMcpy, Int, TRead)
      NULLptr := StrLen(CMDout)
      cpsize := Tread - NULLptr
      DllCall("RtlMoveMemory", "UInt", &CMcpy, "UInt", (&CMDout + NULLptr + 2), "Int", (cpsize - 1))
      DllCall("RtlZeroMemory", "UInt", (&CMDout + NULLptr), Int, cpsize)
      DllCall("RtlMoveMemory", "UInt", (&CMDout + NULLptr), "UInt", &CMcpy, "Int", cpsize)
      TRead2 --
      IF (StrLen(CMDout) > TRead2)
        break
    }
  }
  StringTrimLeft, CMDout, CMDout, 1
  Return, CMDout
}


/*
>>>>CHANGELOG<<<<

1.0 (12-23-2009)
   - Original script
1.1 (10-24-2009)
   - Removed need for empty.exe
   -- Added CleanMemory function which uses the same API (EmptyWorkingSet) found in empty.exe

沒有留言:

發佈留言