2007. 2. 16. 12:30
DevX Win32 Assembly Tutorial 14: Process

Tutorial 14: Process

이 번장에서는, 프로세스가 무엇인지를 설명하고, 프로세스의 생성과 종료 방법을 설명한다.
 소스    리소스 스크립트    실행 결과  

Preliminary:

프로세스란 무엇인가? Win32API 레퍼런스로부터 이 정의를 인용해 본다.

프로세스는 , 해당 프로세스만이 자유롭게 사용할 수 있도록 허용된 가상 메모리 공간, 실행 코드, 데이터와 파일이나 파이프, 동기 오브젝트라고 하는 system resource로부터 완성되는 실행 어플리케이션이다.

위의 알수 없는 정의에서 알수 있듯이, 프로세스는 「자기 전용」의 몇개의 오브젝트가 있다. 메모리 공간, 실행 모듈과 실행 모듈이 생성, 오픈하는 실행 모듈이라는 것이다. 적어도, 프로세스에는 실행 모듈, 가상 메모리영역과 하나의 thread가 없으면 안 된다.
그렇다면 이번에는, thread란 무엇인가? thread는 실행되는 큐이다. Windows는, 프로세스를 생성할 경우에, 한개의 thread도 동시에 생성한다. 대부분의 경우, 이 thread로부터 실행된다. 프로세스에 2개 이상의 thread가 필요하다면, 명시적으로 thread를 생성할 수가 있다.

Windows가 프로세스를 생성하는 커맨드를 받았을 경우, 그 프로세스에, 고유의 메모리 공간을 할당한다. 그 후에, 프로세스의 메인 thread가 생성된다.
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
    프로세스와 메인 thread에 대한 보안속성을 지정한다. 이것이 NULL이면 디폴트설정이 사용된다.
  • bInheritHandles
    새로운 프로세스에, 원래 프로세스의 핸들을 상속시킬지를 결정하는 플래그.
  • dwCreationFlags
    생성한 프로세스의 동작을 결정하는 플래그를 지정한다. 간혹, 생성한 프로세스가 실행되기전에 어떤 수정을 하기 위해 생성 직후 정지상태로 할 경우 혹은, 새로운 프로세스의 thread에 우선 순위 클래스를 지정할 경우가 있다. 우선 순위 클래스에 의해서 , 어떤 프로세스에서 실행되는 thread의 우선 순위를 스케줄링 한다. 통상은 NORMAL_PRIORITY_CLASS를 사용한다.
  • lpEnvironment
    생성하는 프로세스에 적용되는 환경블록의 포인터. 이것이 NULL이면, 원래프로세스의 환경블록을 상속하게 된다.
  • lpCurrentDirectory
    생성하는 프로세스의 현재디렉토리를 지정한다. NULL이면 원래프로세스와 같은 디렉토리가 된다.
  • lpStartupInfo
    생성하는 프로세스의 메인윈도우가 어떻게 표시될지를 설정하는 STARTUPINFO 구조체의 포인터. STARTUPINFO 구조체는 윈도우의 표시를 제어하는 여러가지 멤버가 포함되어 있지만, 특별한 일을 하고 싶지 않다면, GetStartupInfo 함수를 호출 해서 원래프로세스의 값을 설정해도 OK다.
  • lpProcessInformation
    생성하는 프로세스에 관한 정보가 들어간, PROCESS_INFORMATION 구조체의 포인터. PROCESS_INFORMATION 구조체는 다음과 같은 멤버를 포함하고 있다.

PROCESS_INFORMATION STRUCT
   hProcess         HANDLE ?            ; child process에의 핸들
   hThread          HANDLE ?            ; child process의 메인 thread에의 핸들
   dwProcessId      DWORD  ?            ; child process ID
   dwThreadId       DWORD  ?            ; child process의 메인 thread의 ID
PROCESS_INFORMATION ENDS

프로세스핸들과 프로세스ID는 별개로서, 프로세스ID라는 것은 시스템에서 고유한 값이므로, 이 값으로 프로세스를 구별할 수 있게 되어 있다. 프로세스핸들은 API 함수를 사용할 경우에 사용되고 , Windows로부터 반환되는 값이다. 프로세스핸들은 유일한 것이 아니기 때문에, 프로세스를 구별할 수 없다.

CreateProcess 함수를 호출하면, 새로운 프로세스를 생성하자 마자 제어를 반환한다. 만약에 , 생성한 프로세스가 아직 동작중인지 아닌지를 체크하고 싶으면, GetExitCodeProcess 함수를 호출하면 된다. 이 함수는 다음과 같이 정의 되어 있다.

GetExitCodeProcess proto hProcess:DWORD, lpExitCode:DWORD

이 함수가 성공적으로 수행되면 인수의 lpExitCode에 알고 싶은 정보가 들어가게 된다. 만약 lpExitCode가 STILL_ACTIVE 라면, 그 프로세스는 아직 동작하고 있다.강제로 프로세스를 종료하고 싶다면, TerminateProcess 함수를 호출한다. 이 함수는 다음과 같이 정의 되어 있다.

TerminateProcess proto hProcess:DWORD, uExitCode:DWORD

이 함수로, 지정한 프로세스를 종료시키고, uExitCode에 종료 코드 지정할 수 있다. 이 함수로 종료시키는 것은 반강제적인 방법으로서 , 프로세스에 관계된 DLL에, 프로세스의 종료를 통지할 수 없기 때문에, 그다지 추천 할 만한 것은 아니다.

Example:

아래의 예제는 유저가 「create 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

Analysis:

메인 윈도우를 생성하고 , 나중에 사용하게 될 메뉴핸들을 저장해 둔다. 유저가 「Process」메뉴를 선택했을 때, WM_INITMENUPOPUP 메세지를 전송하므로, pop-up menu를 표시하기 전에, 메뉴 아이템을 선택 가능하게 하거나 회색처리하는 등의 처리를 한다.

.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」메뉴를 회색처리 한다. 생성한 프로세스가 동작중이라면 그 반대가 된다.

CreateProcess 함수로 설정되는 프로세스핸들을 인수로 해서 GetExitCodeProcess 함수를 호출 해서, 새롭게 생성한 프로세스가 현재 동작중인지를 체크한다. GetExitCodeProcess 함수가 FALSE를 반환하면 아직 새로운 프로세스가 생성되지 않은 것이므로, 「terminate process」를 회색처리하고, TRUE라면 프로세스가 생성된것은 알수 있지만, 프로세스가 활성상태인지를 체크해야 한다. 그렇기 때문에, 종료 코드를 검사해야 하며, 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로 이미 닫혀진 것인지를 체크한다. 만약, 처음이라면, hProcess는 항상 0 이므로, .data 섹션에서 PROCESS_INFORMATION 구조체를 정의한다. hProcess가 0 이 아니면, 생성한 프로세스가 종료된 것을 의미하지만, 아직 그 프로세스핸들은 닫혀지지 않았기 때문에, 프로세스 핸들을 닫는다.

CreateProcess 함수에 넘겨주는 startupinfo 구조체에 값을 설정하기 위해서 GetStartupInfo 함수를 호출 한다. 그 후부터, 새로운 프로세스를 생성할려면, CreateProcess 함수를 호출 한다. 예제에서는 CreateProcess 함수의 반환값은 체크하지 않는것을 조심하기바란다. 실제로는 반환값을 체크해야 하지만, 복잡하게 되기때문에 생략했을 뿐이다.

CreateProcess 함수를 호출 한 후에, processInfo 구조체의 멤버 hThread, 즉, 메인thread 핸들을 닫는 것은, thread 핸들을 참조할 필요가 없기 때문이다. thread 핸들을 닫았다해서 프로그램이 종료되는 것은 아니기 때문에, 필요가 없다면, 리소스의 낭비, 혹은 메모리누수가 될지도 모르기 때문에, 닫아주는것이 낫다.

.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 함수를 호출해서 프로세스를 종료시킨다. 그리고, 이젠 필요없으므로, 그 프로세스의 핸들도 닫는다.


Posted by openserver