本課中我們將學習:什麼是進程?如何產生和終止一個進程?
初步知識:
進程是什麼?下面是我從WIN32 API指南中節選的解釋:
“一個進程是一個正在執行的應用程序,它包含有:私有的虛擬地址空間、代碼、數據和其它的操作系統資源,譬如進程可以存取的管道、文件和同步對象等等。”
從上面的定義中您可以看到,一個進程擁有幾個對象:地址空間、執行模塊和其它該執行程序打開或創建的任何對象或資源。至少,一個進程必須包含可執行模塊、私有的地址空間和一個以上的線程。什麼是線程呢?一個線程實際上是一個執行單元。當WINDOWS產生一個進程時,它自動為該進程產生一個主線程。該線程通常從模塊的第一條指令處開始執行。如果進程需要更多的線程,它可以隨後顯式地產生。
當WINDWOS 接收到產生進程的消息時,它會為進程生成私有內存地址空間,接著把可執行文件映射到該空間。在WIN32下為進程產生了主進程後,您還可以調用函數CreateProcess來為您的進程產生更多的線程。
CreateProcess的原型如下:
CreateProcess proto lpApplicationName:DWORD,\
lpCommandLine:DWORD,\
lpProcessAttributes:DWORD,\
lpThreadAttributes:DWORD,\
bInheritHandles:DWORD,\
dwCreationFlags:DWORD,\
lpEnvironment:DWORD,\
lpCurrentDirectory:DWORD,\
lpStartupInfo:DWORD,\
lpProcessInformation:DWORD
不要被這麼多的參數嚇倒,其實您可以忽略其中的大多數的參數(讓它們有缺省值)。
lpApplicationName --> 可執行文件的名稱(含或不含路徑)。如果該參數為NULL,那必須在參數lpCommandLine中傳遞文件名稱。
lpCommandLine --> 傳遞給欲執行的文件的命令行參數。如果lpApplicationName為NULL,那必須在該參數中指定,譬如:"notepad.exe readme.txt" 。
lpProcessAttributes 和 lpthreadAttributes --> 指定進程和主線程的安全屬性。您可以把它們都設成為NULL,這樣就設置了缺省的安全屬性。
bInheritHandles --> 標志位。用來設置新進程是否繼承創建進程所有的打開句柄。
dwCreationFlags --> 有幾個標志可以在此處設置以決定欲創建進程的行為,譬如:您可能想創建進程後並不想讓它立刻運行,這樣在它真正運行前可以作一些檢查和修改工作。您還可以在此處設置新進程中的所有線程的優先級,通常我們把它設置為NORMAL_PRIORITY_CLASS。
lpEnvironment --> 指向環境塊的指針,一般地環境塊包含幾個環境字符串。如果該參數為NULL,那麼新進程繼承創建進程的環境塊。
lpCurrentDirectory --> 指向當前目錄以及為子進程設置的“當前目錄”的路徑。如果為NULL, 則繼承創建進程的“當前目錄”路徑。
lpStartupInfo --> 指向新進程的啟動結構體STARTUPINFO的指針。STARTUPINFO告訴WINDOWS如何顯示新進程的外觀。該參數有許多的成員變量,如果您不想新進程有什麼的特別之處,可以調用GetStartupInfo函數來用創建進程的啟動參數來填充STARTUPINFO結構體變量。
lpProcessInformation --> 指向結構體PROCESS_INFORMATION的指針,該結構體變量包含了一些標識該進程唯一性的一些成員變量:
PROCESS_INFORMATION STRUCT
hProcess HANDLE ? ; handle to the child process
hThread HANDLE ? ; handle to the primary thread of the child process
dwProcessId DWORD ? ; ID of the child process
dwThreadId DWORD ? ; ID of the primary thread of the child process
PROCESS_INFORMATION ENDS
進程句柄和進程ID是兩個不同的概念。進程ID好似一個唯一值,而進程句柄是調用相關的WINDOWS API 後得到的一個返回值。不能用進程句柄來標識一個進程的唯一性,因為這個值並不唯一。在調用CreateProcess產生新進程後,該進程就被創建,而且CerateProcess函數立即返回。您可以調用函數GetExitCodeProcess來檢驗進程是否結束。該函數的原型如下:
GetExitCodeProcess proto hProcess:DWORD, lpExitCode:DWORD
如果調用成功,lpExitCode中包含了所查詢進程的狀態碼。如果等於STILL_ACTIVE就表明該進程依舊存在。 您可以調用函數TerminateProcess來強制終止一個進程。該函數的原型如下:
TerminateProcess proto hProcess:DWORD, uExitCode:DWORD
您可以指定任意一個退出值。用該函數結束一個進程並不好,因為該進程加載的動態連接庫並不會得到進程正退出的消息。
例子:
在下面的例子中,當用戶選擇菜單項“crate process”時我們創建一個新進程。它會去執行“"msgbox.exe”。如果用戶想要終止新進程,可以選擇菜單項“terminate process”。這時,應用程序檢查欲終止的進程是否仍存在,若存在則調用TerminateProcess函數來終止它。
.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.const
IDM_CREATE_PROCESS equ 1
IDM_TERMINATE equ 2
IDM_EXIT equ 3
.data
ClassName db "Win32ASMProcessClass",0
AppName db "Win32 ASM Process Example",0
MenuName db "FirstMenu",0
processInfo PROCESS_INFORMATION <>
programname db "msgbox.exe",0
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hMenu HANDLE ?
ExitCode DWORD ? ; contains the process exitcode status from GetExitCodeProcess call.
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
invoke GetMenu,hwnd
mov hMenu,eax
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL startInfo:STARTUPINFO
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_INITMENUPOPUP
invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
.if eax==TRUE
.if ExitCode==STILL_ACTIVE
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_ENABLED
.else
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
.endif
.else
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
.endif
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_CREATE_PROCESS
.if processInfo.hProcess!=0
invoke CloseHandle,processInfo.hProcess
mov processInfo.hProcess,0
.endif
invoke GetStartupInfo,ADDR startInfo
invoke CreateProcess,ADDR programname,NULL,NULL,NULL,FALSE,\
NORMAL_PRIORITY_CLASS,\
NULL,NULL,ADDR startInfo,ADDR processInfo
invoke CloseHandle,processInfo.hThread
.elseif ax==IDM_TERMINATE
invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
.if ExitCode==STILL_ACTIVE
invoke TerminateProcess,processInfo.hProcess,0
.endif
invoke CloseHandle,processInfo.hProcess
mov processInfo.hProcess,0
.else
invoke DestroyWindow,hWnd
.endif
.endif
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
分析:
應用程序創建主窗口,保存菜單句柄以備後用。當用戶在主菜單中選擇了“Process”菜單項後,消息處理過程中接收到WM_INITMENUPOPUP消息,我們在此處修改彈出式菜單中的菜單項的“使能”和“非使能”,以便同一菜單有不同的顯示。
.ELSEIF uMsg==WM_INITMENUPOPUP
invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
.if eax==TRUE
.if ExitCode==STILL_ACTIVE
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_ENABLED
.else
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
.endif
.else
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
.endif
我們之所以處理該消息的目的就是讓菜單顯示時有不同的外觀以方便用戶的使用。譬如;新進程尚未運行時,我們就變亮(使能)“菜單項“start process”,而變灰(非使能)菜單項“terminate process”。當新進程運行起來後,菜單的外觀就應該是相反的。
首先我們調用GetExitCodeProcess函數,其中傳入由CreateProcess返回的句柄。如果GetExitCodeProcess返回FALSE,則表示進程尚未運行,我們就讓菜單項“terminate process”變灰;如果返回TRUE,表示新進程已經啟動了,我們再檢測是否正在運行,這通過比較ExitCode是否等於STILL_ACTIVE 來完成,如果相等,表示進程仍在運行,我們就讓菜單項“start process”變灰,因為在我們的簡單的應用程序中不提供同時運行多個進程的能力。
.if ax==IDM_CREATE_PROCESS
.if processInfo.hProcess!=0
invoke CloseHandle,processInfo.hProcess
mov processInfo.hProcess,0
.endif
invoke GetStartupInfo,ADDR startInfo
invoke CreateProcess,ADDR programname,NULL,NULL,NULL,FALSE,\
NORMAL_PRIORITY_CLASS,\
NULL,NULL,ADDR startInfo,ADDR processInfo
invoke CloseHandle,processInfo.hThread
當用戶選擇了菜單項“start process”時,我們先檢測結構體PROCESS_INFORMATION中的成員變量hPRocess是否已經關閉。如果是第一次啟動應用程序,那該變量為0,因為我們在.data分段定義結構體時已經初始化該值為0。如果該值不為0,則表明新進程已經結束,但是我們尚未關閉該進程的句柄(以減少該進程的引用記數),我們在此處完成該動作。
我們調用GetStartupInfo函數來填充啟動信息的結構體變量,而該變量將被傳遞到CreateProcess函數中去。調用CreateProcess生成新進程,我們不檢查該函數的返回值為的是讓問題簡化一些,在實際應用中,必須做該項工作。在調用CreateProcess後,我們立即關閉在進程信息結構體參數中返回的主線程句柄,關閉線程句柄為的是減少該內核對象的引用記數,否則即使該線程退出後,其內核對象仍慘存在內核中得不到釋放,這會引起資源洩露。進程其實也是一樣,之所以我們不在該處關閉進程的句柄是因為稍後我們還要用該句柄去得到一些和進程相關的信息,至於線程,我們的應用程序不需要其相關信息。
.elseif ax==IDM_TERMINATE
invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
.if ExitCode==STILL_ACTIVE
invoke TerminateProcess,processInfo.hProcess,0
.endif
invoke CloseHandle,processInfo.hProcess
mov processInfo.hProcess,0
當用戶選擇了菜單項“terminate process”後,我們調用函數GetExitCodeProcess來檢查新進程是否還存在,如果還存在我們就調用函數TerminateProcess來結束它。另外我們把它的句柄關閉掉,因為我們再也不用它了。