API/MFC

프로세스 - 생성과 종료 그리고 이것 저것

by 너울 posted Oct 12, 2011
?

단축키

Prev이전 문서

Next다음 문서

ESC닫기

크게 작게 위로 아래로 게시글 수정 내역 댓글로 가기 인쇄

http://blog.naver.com/isadrastea?Redirect=Log&logNo=140092848471


BOOL CreateProcess(

   PCTSTR pszApplicationName,

   PTSTR pszCommandLine,

   PSECURITY_ATTRIBUTES psaProcess,

   PSECURITY_ATTRIBUTES psaThread,

   BOOL bInheritHandles,

   DWORD fdwCreate,

   PVOID pvEnvironment,

   PCTSTR pszCurDir,

   PSTARTUPINFO psiStartInfo,

   PPROCESS_INFORMATION ppiProcInfo);

 

스레드가 CreateProcess 를 호출하면, 시스템은 프로세스 커널 객체를 usage 카운터를 1 로 하여, 생성한다. 이 프로세스 커널 객체는 프로세스 자체가 아니다. 그러나, 작은 데이터 구조체로 시스템은  이것으로 프로세스를 관리한다. 이것은 프로세스에 대한 정적 정보들로 구성된 작은 데이터 구조체이다. 시스템은 새로 생성한 프로세스를 위한 가상 주소 공간을 만들고 여기에 실행 파일과 DLL 을 위한 코드와 데이터를 로드한다.


시스템은 새로운 프로세스의 primary 스레드 커널 객체 (카운터를 1로) 를 만든다. 이것은 프로세스 커널 객체와 같이, 시스템에서 스레드를 관리하기 위한 작은 데이터 구조체이다. 이 primary 스레드는 C/C++ run-time startup 코드로 WinMain 이나 wWinMain, main 또는 wmain 과 같은 entry-point 가 실행된다. 만약, 시스템이 성공적으로 새로운 프로세스와 이것의 primary 스레드를 생성하면, CreateProcess 는 TRUE 를 반환한다.

 

pszApplicationName 과 pszCommandLine 파라미터들은 실행할 파일의 이름과 새로운 프로세스의 명령줄 문자열이다.

psaProcess, psaThread 는 SECURITY_ATTRIBUTE 구조체이다. 둘다 NULL 로 설정할 수 있다. bInheritHandles 은 부모 프로세스로 부터 상속할 수 있는 커널 객체를 상속할 것인지를 나타내는 플래그 이다.

 

다음은 커널 객체 핸들을 상속관련 프로세스 생성예제이다. 예제에서 프로세스 B 는 생성시 보안 특성 구조체를 NULL 하고, 프로세스 보안 특성을 상속하고, 스레드 보안특성 구조체는 상속을 하지 않도록 했다. 그리고, 프로세스 B 를 생성시 bInheritHandles 를 FALSE 로 해서 부모 프로세스 A 로 부터 상속할 수있는 커널 객체핸들을 상속하지 않는다. 그런데, 프로세스 C 는 보안 특성을 모두 NULL 하고, 부모 프로세스 A 로 부터 상속할 수 있는 커널 객체 핸들을 상속하도록 설정했다. 그러므로, 프로세스 A 가 가지고 있는 상속가능한 커널 객체중 프로세스 B 핸들을 상속할 수 있을 것이라고 생각하지만, 프로세스 C 는 프로세스 B 의 프로세스 커널 객체 핸들을 상속할 수 없다. 이유는 프로세스 B 를 생성시 스레드 보안 특성 속성 상속을 FALSE 했고, 프로세스 A 가 프로세스 B 를 생성할 때, bInheritHandle 을 FALSE 로 하였으므로, 프로세스 B 의 프로세스 커널 객체 핸들을 상속할 수 있다하더라도, 프로세스 B 의 스레드 커널 객체를 상속할 수 없어, 프로세스 C 는 부모 프로세스 A 의 상속가능한 커널 객체 핸들 프로세스 B 의 프로세스 커널 객체 핸들을 상속하지 못하고, 핸들 테이블이 비어 있게 된다.

/************************************************************
Module name: Inherit.c
Notices: Copyright (c) 2000 Jeffrey Richter
************************************************************/
#include <Windows.h>

int WINAPI WinMain (HINSTANCE hinstExe, HINSTANCE,
   PSTR pszCmdLine, int nCmdShow) {

   // Prepare a STARTUPINFO structure for spawning processes.
   STARTUPINFO si = { sizeof(si) };
   SECURITY_ATTRIBUTES saProcess, saThread;
   PROCESS_INFORMATION piProcessB, piProcessC;
   TCHAR szPath[MAX_PATH];

   // Prepare to spawn Process B from Process A.
   // The handle identifying the new process 
   // object should be inheritable.
   saProcess.nLength = sizeof(saProcess);
   saProcess.lpSecurityDescriptor = NULL;
   saProcess.bInheritHandle = TRUE;

   // The handle identifying the new thread 
   // object should NOT be inheritable.
   saThread.nLength = sizeof(saThread);
   saThread.lpSecurityDescriptor = NULL;
   saThread.bInheritHandle = FALSE;

   // Spawn Process B.
   lstrcpy(szPath, TEXT("ProcessB"));
   CreateProcess(NULL, szPath, &saProcess, &saThread,
      FALSE, 0, NULL, NULL, &si, &piProcessB);

   // The pi structure contains two handles 
   // relative to Process A:
   // hProcess, which identifies Process B's process 
   // object and is inheritable; and hThread, which identifies 
   // Process B's primary thread object and is NOT inheritable.

   // Prepare to spawn Process C from Process A.
   // Since NULL is passed for the psaProcess and psaThread
   // parameters, the handles to Process C's process and 
   // primary thread objects default to "noninheritable."

   // If Process A were to spawn another process, this new 
   // process would NOT inherit handles to Process C's process 
   // and thread objects.

   // Because TRUE is passed for the bInheritHandles parameter,
   // Process C will inherit the handle that identifies Process 
   // B's process object but will not inherit a handle to 
   // Process B's primary thread object.
   lstrcpy(szPath, TEXT("ProcessC"));
   CreateProcess(NULL, szPath, NULL, NULL,
      TRUE, 0, NULL, NULL, &si, &piProcessC);

   return(0);
}
fdwCreate 파라미터는 어떻게 새로운 프로세서를 생성하는 방법에 영향을 미치는 플래그 조합니다.

- DEBUG_PROCESS : 이 플래그는 시스템에게 부모 프로세스가 자식 프로세스를 디버그하길 원하고, 모든 프로세스를 자식 프로세스로 복제할 것이라는 것을 알린다. 이 플래그는 자식 프로세스들이 어떤 이벤트를 발생시키면, 시스템이 부모 프로세스에게 이를 알린다.

- DEBUG_ONLY_THIS_PROCESS : 플래그는 위의 플래그 DEBUG_PROCESS 와 유사하면, 디버거가 특정 이벤트가 자식 프로세스에서 발생할 경우 이를 부모 프로세스에 알린다. 만약 자식 프로세스가 다른 부수적인 프로세스를 복제할 경우, 디버거는 이들 복제된 프로세스에 이벤트는 알리지 않는다.

- CREATE_SUSPENDED : 이것은 새로운 프로세스의 primary 스레드를 일시 정지모드로 둔다. 이것은 부모 프로세스가 자식 프로세스의 주소 공간의 메모리를 변경하거나, 자식 프로세스의 primary 스레드의 우선순위를 변경할 수 있도록 허용한다.  프로세스가 잡을 추가하고, 어떤 코드를 수행할 수 있도록 기회를 할량한다.  부모 프로세스가 일시 정지된 스레드를 Resume Thread 로 재실행 할 수 있다.

- DETACHED_PROCESS : 이것은 CUI 근간의 프로세스의 접근으로 이것의 부모 콘솔 윈도우에서 블록되게 한다. 그리고, 시스템은 출력을 위해 새로운 콘솔 윈도우를 만든다.  만약, CUI 근간의 프로세스가 다른 CUI 기반의 프로세스로 부터 생성된 경우, 새로운 프로세스는 기본적으로 부모 콘솔 윈도우를 사용한다. 이 플래그를 사용하면, 새로 생성한 자식 프로세스는 자신의 새로운 출력창을 가지게 된다.

- CREATE_NEW_CONSOLE : 이것은 시스템에게 새로운 프로세스를 위한 새로운 콘솔 윈도우를 만들게 한다. CREATE_NEW_CONSOLE 와  DETACHED_PROCESS 를 조합해 사용하면, 오류가 발생한다.

- CREATE_NO_WINDOW : 이것은 시스템에게 어플리케이션을 위한 어떤 콘솔 윈도우도 생성하지 말것을 알린다. 이 플래그는 사용자 인터페이스 없이 어플리케이션을 실행할 경우 사용한다.

- CREATE_NEW_PROCESS_GROUP : 이것은 Ctrl+C 와 Ctrl+Break 키를 사용자가 눌렀을 때 프로세스들에게 알린다. CUI 기반의 프로세스들은 이들 키조합을 눌러질 경우, 시스템은 모든 프로세스 그룹내에 프로세스들에게 현재 작업을 중지할 것을 알린다. 만약 이 플래그를 CUI기반 프로세스 생성시 사용하고,프로세스 그룹을 만들면, 사용자가 이들 키 조합을 누르면,  시스템은 오직 이 프로세스 그룹에만 적용한다.

- CREATE_DEFAULT_ERROR_MODE : 이것은 시스템이 새로운 프로세스가 부모의 오류 모드를 상속하지 않게 한다.

- CREATE_SEPARATE_WOW_VDM : 이것은 윈도우 2000 에서 16 비트 윈도우 어플리케이션에 사용되는 것이다. 이것은 시스템에게 VDM 가상 도스 머신을 만들것을 알리고, 16 비트 윈도우 어플리케이션을 VDM 에서 실행한다. 기본적으로 모든 16 비트 윈도우 어플리케이션은 하나의 VDM 을 공유한다. 그러나, VDM 을 구분하면 어플리케이션의 오동작에도 오류가 발생한 VDM 에게만 영향을 미치게 된다. 또한 16 비트 어플리케이션은 구분된 VDM 과 구분된 입력 큐를 가진다. 이러한 점외에도 VDM 으로 인해 많은 공간을 사용하게 된다. (참고, 윈도우 98 의 경우는 단 하나의 VDM 을 사용하므로, 이 플래그를 사용할 수 없다.)

- CREATE_SHARED_WOW_VDM : 위와 같은 용도이지만, 단 하나의 VDM 만 생성하고 공유한다. 레지스트리 조작을 통해 HKEY_LOCAL_MACHINESystemCurrentControlSetControlWOWHowever 을 yes 로해서, 위와 같이 구분된 VDM 을 사용하고자 한들, 이 플래그는 단 하나의 VDM 을 공유한다. (레지스트리를 변경할 경우, 시스템을 리부팅해야 한다.)
- CREATE_UNICODE_ENVIRONMENT : 이것은 시스템에게 자식 프로세스의 환경변수 블록이 유니코드 문자를 가지고 있음을 알린다.

- CREATE_FORCEDOS : 이것은 시스템에게 16 비트 OS/2 어플리케이션에 임베딩된 MSDOS 어플리케이션을 실행하도록 알린다.

- CREATE_BREAKAWAY_FROM_JOB : 이것은 잡에 프로세스를 복제해 새로운 프로세스를 생성할 수 있도록 한다. 이 프로세스튼 잡과 연동되지 않는다.

 

또한 이 파라미터는 우선순위를 지정할 수 있다.

Priority ClassFlag Identifier
IdleIDLE_PRIORITY_CLASS
Below normalBELOW_NORMAL_PRIORITY_CLASS (윈도우 2000 용)
NormalNORMAL_PRIORITY_CLASS
Above normalABOVE_NORMAL_PRIORITY_CLASS (윈도우 2000 용)
HighHIGH_PRIORITY_CLASS
RealtimeREALTIME_PRIORITY_CLASS

 

이 우선순위 클래스들은 어떻게 프로세스 안에 스레드들을 다른 프로세스의 스레드들과 스케줄 할 것인가를 결정한다.

 

pvEnvironment 파라미터는 메모리 블록의 포인터로 새로운 프로세스에서 사용될 환경변수 문자열들을 포함하고 있다. 대부분 이것은 NULL 하는데, 자식 프로세스는 부모로 부터 이것을 상속받거나, GetEnvironmentString 으로 조회할 수 있기 때문이다.

 

PVOID GetEnvironmentStrings();

 

이 함수는 환경변수 문자열의 포인터를 반환한다. 이 블록이 더이상 필요 없을 경우, 다음 함수 FreeEnvironmentStrings 로 해제 한다.

 

BOOL FreeEnvironmentStrings(PTSTR pszEnvironmentBlock);

pszCurDir 파라미터는 부모 프로세스가 자식 프로세스의 현재 드라이브와 디렉터리를 설정할 수 있게 한다. 이것이 NULL 이면, 새로운 프로세스의 작업 디렉터리는 어플리케이션의 새로운 복제된 프로세스의 그것과 동일하다. 만약, 이 파라미터가 NULL 이 아닌경우, 문자열로 드라이브와 디렉터리를 지정해야 한다. 드라이브 문자는 반드시 path 변수에 있는 것이여야 한다.

 

다음 psiStartInfo 파라미터는 STARTUPINFO 구조체의 포인터이다.

 

typedef struct _STARTUPINFO {
   DWORD cb;
   PSTR lpReserved;
   PSTR lpDesktop;
   PSTR lpTitle;
   DWORD dwX;
   DWORD dwY;
   DWORD dwXSize;
   DWORD dwYSize;
   DWORD dwXCountChars;
   DWORD dwYCountChars;
   DWORD dwFillAttribute;
   DWORD dwFlags;
   WORD wShowWindow;
   WORD cbReserved2;
   PBYTE lpReserved2;
   HANDLE hStdInput;
   HANDLE hStdOutput;
   HANDLE hStdError;
} STARTUPINFO, *LPSTARTUPINFO;

 

윈도우는 이 구조체 멤버를 이용해 새로운 프로세스를 만든다. 대부분 어플리케이션은 단순히 기본값들로 어플리케이션을 복제하기 원한다. 그러나 최소한 모든 멤버들은 0 또는 cb 멤버는 구조체 크기로 설정해줘야 한다.

 

STARTUPINFO si = { sizeof(si) };
CreateProcess(..., &si, ...);

 

만약, 구조체의 내용을 0 으로 하지 않으면, 쓰레기 값으로 채워져 스레드의 스택에 포함된다. 쓰레기 값들이 새로 생성한 프로세스에 악영향을 미친다.

 

MemberWindow, Console, or BothPurpose
cbBothContains the number of bytes in the STARTUPINFO structure. Acts as a version control in case Microsoft expands this structure in the future. Your application must initialize cb to sizeof(STARTUPINFO).
lpReservedBothReserved. Must be initialized to NULL.
lpDesktopBothIdentifies the name of the desktop on which to start the application. If the desktop exists, the new process is associated with the specified desktop. If the desktop does not exist, a desktop with default attributes is created with the specified name for the new process. If lpDesktop is NULL (which is most common), the process is associated with the current desktop.
lpTitleConsoleSpecifies the window title for a console window. If lpTitle is NULL, the name of the executable file is used as the window title.
dwX
dwY
BothSpecify the x and y coordinates (in pixels) of the location where the application's window should be placed on the screen. These coordinates are used only if the child process creates its first overlapped window with CW_USEDEFAULT as the x parameter of CreateWindow. For applications that create console windows, these members indicate the upper left corner of the console window.
dwXSizeBothSpecify the width and height (in pixels) of an dwYSize application's window. These values are used only if the child process creates its first overlapped window with CW_USEDEFAULT as the nWidth parameter of CreateWindow. For applications that create console windows, these members indicate the width and height of the console window.
dwXCountChars
dwYCountChars
ConsoleSpecify the width and height (in characters) of a child's console windows.
dwFillAttributeConsoleSpecifies the text and background colors used by a child's console window.
dwFlagsBothSee the following section and the table below.
wShowWindowWindowSpecifies how the child's first overlapped window should appear if the application's first call to ShowWindow passes SW_SHOWDEFAULT as the nCmdShow parameter. This member can be any of the SW_* identifiers normally used with theShowWindow function.
cbReserved2BothReserved. Must be initialized to 0.
lpReserved2BothReserved. Must be initialized to NULL.
hStdInput
hStdOutput
hStdError
ConsoleSpecify handles to buffers for console input and output. By default, the hStdInput identifies a keyboard buffer;hStdError hStdOutput and identify a console window's buffer.

 

dwFlags 멤버는 다음과 같은 플래그를 가질 수 있다.

 

FlagMeaning
STARTF_USESIZEUse the dwXSize and dwYSize members.
STARTF_USESHOWWINDOWUse the wShowWindow member.
STARTF_USEPOSITIONUse the dwX and dwY members.
STARTF_USECOUNTCHARSUse the dwXCountChars and dwYCountChars members.
STARTF_USEFILLATTRIBUTEUse the dwFillAttribute member.
STARTF_USESTDHANDLESUse the hStdInputhStdOutput, and hStdError members.
STARTF_RUN_FULLSCREENForces a console application running on an x86 computer to start in full-screen mode.

 

이외에도 두개의 플래그가 더 있다. STARTF_FORCEONFEEDBACK 와 STARTF_FORCEOFFFEEDBACK 은 새로운 프로세스를 만들려고 할때, 마우스 커서를 제어할 수 있게 한다. 이것은 프로세스 생성 중에도 다른 프로그램은 사용자 인터페이스를 처리할 수 있도록 하기 위한 것이다.

 

위와 같은 동작에서 CreateProcess 는 아이콘을 화살표에서 모래시계와 함께 출력한다.

 

이 커서는 시스템을 사용하기 위해 잠시동안 기다려라는 의미이다. STARTF_FORCEOFFEEDBACK 플래그는 이런 마우스 아이콘 변경하지 않는다.

 

STARTF_FORCEONFEEDBACK 는 새로운 프로세스의 초기와를 모니터링한다. 만약, 이 플래그가 주어지면, CreateProcess 는 아이콘을 변경하고, 2 초후 새로운 프로세스가 GUI 를 만들지 않을 경우, 커서를 원래 화살표로 변경한다.

 

만약에 프로세스가 GUI 호출을 2 초안에 만들면, CreateProcess 는 어플리케이션 윈도우가 나타날때까지 기다린다. 이것은 프로세스가 5 초 안에 GUI 호출을 만들어야 한다. 만약, 윈도우가 나타나지 않으면, CreateProcess 가 커서를 리셋한다. 윈도우가 나타나면 CreateProcess 는 화살표와 모래시계를 다시 5 초동안 나타낸다. 만약, 어플리케이션이 GetMessage 함수를 호출하면, 초기화가 끝난것으로 CreateProcess 는 즉시 커서를 원래 화살표로 되돌린다. 그리고, 새로운 프로세스에 대한 모니터링을 완료한다.

 

결론적으로, STARTUOINFO 구조체의 wSchowWindow 멤버 대해 이야기 하는 것인데, 이 멤버는 WinMain의 마지막 파라미터로 전달된다. 즉, nCmdShow 이다.  이 멤버는 새로운 프로세스의 WinMain함수의 마지막으로 전달되기 원하는 값을 설정하면 된다. 기본적으로 이 값은 SW_SHOWNORMAL 또는 SW_SHOWWINNOACTIVE 이지만,SW_SHOEDEFAULT 로 사용할 수있다.

 

어플리케이션이 Explorer 로 부터 실행되어질 때, 어플리케이션의 WinMain 함수는 SW_SHOWNORMAL 과 함께 호출된다. 만약, 어플리케이션의 바로가기를 보면 알 수 있겠지만, 바로가기의 속성 페이지에 어플리케이션이 어떻게 나타나길 원하는지 설정할 수 있다.

 

 

부모 프로세스에서 초기화된 STARTUPINFO 구조체는 다음 함수로 복사본을 얻을 수 있다.

 

VOID GetStartupInfo(LPSTARTUPINFO pStartupInfo);

 

ppiProcInfo 파라미터는 PROCESS_INFORMATION 구조체의 포인터로 반드시 할당해야 한다. CreateProcess 는 반환 전에 이 구조체 멤버들을 초기환 한다.

 

typedef struct _PROCESS_INFORMATION {
   HANDLE hProcess;
   HANDLE hThread;
   DWORD  dwProcessId;
   DWORD  dwThreadId;
} PROCESS_INFORMATION;

 

이미 언급했듯이, 새로운 프로세스를 만들기 위해 시스템은 프로세스 커널 객체를 만들과 스레드 커널 객체를 만든다. 생성시, 시스템은 각 객체의 초기 사용 카운터를 1 로 한다. 그런 다음 CreateProcess 를 반환하기 전에 함수는 프로세스 객체와 스레드 객체를 열고, 프로세스 관련 핸들들을 각 hProcess 와 hThread 멤버들에 설정한다. 이때 내부적으로 이들 카운터가 2 로 증가하게 된다.

 

이것은 시스템이 이전 프로세스 객체를 제거하기 전에, 프로세스는 반드시 종료되어야 한다. (이때 카운터를 1 로 변경된다.) 그리고, 부모 윈도우는 반드시 CloseHandle 을 호출해야 한다. (이로써 카운터는 1 에서 0 으로 된다. 카운터가 0 이 되면, 시스템이 커널 객체를 제거한다.) 유사하게, 스레드 객체를 제거하기 위해, 스레드는 반드시 종료되어야 하고, 부모 프로세스는 스레드 객체를 닫아야 한다.

 

프로세스 종료 방법은 다음 네가지가 있다.


- primary 스레드의 entry-point 함수를 반환한다. (권장함)
- 프로세스내의 하나의 스레드가 ExitProcess 함수를 호출.(권장하지 않음)
- 다른 프로세스의 스레드에서 TerminateProcess 함수 호출 (권장하지 않음)
- 프로세스내 모든 스레드가 스스로 종료. (거의 발생하지 않음)

 

primary 스레드의 Entry-point 를 반환하는 방식은,  어플리케이션 디자인에 달리 문제라 할 수있다.
이 방식만이 유일하게 스레드의 리소스를 제대로 정리할 수 있게 해준다.

 

Primary 스레드의 entry-point 함수 반환은 다음과 같은 과정을 거친다.


- 모든 스레드에 의해 생성된 C++ 객체들을 그들의 소멸자로 안전하게 제거한다.
- 운영체제는 스레드 스택으로 사용한 메모리를 안전하게 제거한다.
- 시스템은 프로세스의 종료 코드를 스레드의 entry-point 함수의 반환 값으로 설정한다.
- 시스템은 프로세스 커널 객체의 사용 카운터를 감소 시킨다.

 

ExitProcess 함수를 이용한 종료

 

이 방식은 다음과 같은 과정을 거친다. ExitProcess 로 다음과 같이 호출한다.

 

VOID ExitProcess(UINT fuExitCode);

 

이 함수는 프로세스를 종료하고, 종료 코드를 매개변수로 전달된 fuExitCode 로 설정한다. 이 함수는 어떤 값도 반환하지 않는다. 그리고, 이후의 코드는 실행하지 않는다. 이것은 entry-point 함수의 반환 방식과 달리, C/C++ run-time startup 코드에 의해 초기화된 객체와 리소스들을 정리하지 않기 때문에, 잘못된 종료와 다른 연관 스레드에 오동작을 야기할 수 있다.

 

윈도우 플랫폼 SDK 문서는 프로세스는 모든 내부 스레드가 종료될 때까지 종료되지 않는 다고 한다. 운영체제의 입장에서는 이것은 사실이다. 그러나, primary 스레드가 종료되면, 프로세스는 종료된다. 다른 스레드가 아직 실행중이였다고 한들 말이다. 이것은 ExitProcess 대신 ExitThread 를 entry-pont 함수에서 사용해보면 알 수있는데, ExitThread 후에도 프로세스가 종료되지 않는다. 멀티 스레드 프로세스라면, 스레드 중 하나라도 여전히 실행 중이라면, 프로세스는 실행되지 않지만, 종료되지 않는 것이다. 즉, 모든 스레드가 종료되면 종료되어야할 프로세스이므로, entry-point 에서도 이것을 확인해야 겠지만, 그렇게 되어 있지 않다. 단지, ExitProcess 로 끝을 내는 것이다.

 

다음은 간단한 예제로 객체를 생성하고, 바로 ExitProcess 를 호출하여 종료하는 경우이다.

 

#include <windows.h>
#include <stdio.h>

class CSomeObj {
public:
   CSomeObj()  { printf("Constructorrn"); }
   ~CSomeObj() { printf("Destructorrn"); }
};

CSomeObj g_GlobalObj;

void main () {
   CSomeObj LocalObj;
   ExitProcess(0);     // This shouldn't be here

   // At the end of this function, the compiler automatically added
   // the code necessary to call LocalObj's destructor.
   // ExitProcess prevents it from executing.
}

 

위의 코드를 실행하면, 단지 생성자 호출만 볼 수 있고, 소멸자 호출이 안되는 것을 알 수 있다. 즉, 메모리 정리가 안되고 leak 가 발생하는 것이다.

 

Constructor
Constructor

 

 정확히 entry-point 에서 프로세스가 종료된다면, 다음과 같이 처리되어야 할 것이다.

 

Constructor
Constructor
Destructor
Destructor

 

TerminateProcess 함수를 이용한 종료

 

BOOL TerminateProcess(
   HANDLE hProcess, 
   UINT fuExitCode);

 

이 함수는 ExitProcess 와 다르게 다른 스레드에서 이 함수를 호출해 다른 프로세스 또는 자신의 프로세스를 종료하는 방식이다.
파라미터로 전달하는 것은 프로세스의 핸들이고, 이 핸들로 해당 프로세스를 종료하게 된다. 종료코드도 전달한다.

이것은 좀더 그런 것이 종료되며 어떤 통지도 없다는 것이다. 그래서, 메모리 정리나 자기자신의 프로세스를 죽이는 경우를 방지하지 못하는 것이다.


그래서, ExitProcess 처럼 결국 메모리 leak 가 발생할 수 있다.

 

프로세스가 종료되면, 시스템은 프로세스가 자신의 일부를 가지고 떠돌지 않는다고 생각한다. 이것은 어떻게해서도 이 프로세스에 대한 정보를 얻을 수 없기 때문이다. 프로세스는 종료되면, leak 가 없어야 한다. 그러나, 이것은 종료 방식에 따라, 하나의 바램에 지나지 않을 수 도 있다.


또한 TerminateProcess 함수는 비동기 함수로 호출후, 프로세스가 종료되었는지 여부를 보장할 수 없다. 그래서, 이 함수 호출이후 WaitForSingleObject 로 프로세스 핸들을 기다리거나, 이벤트 등으로 프로세스의 종료를 감지할 수 있다.

 

모든 프로세스의 스레드가 종료된 경우

 

만약 프로세스의 모든 스레드가 종료된 경우, ExitThread 나 TerminateThread 로 말이다.  시스템은 프로세스 주소 공간을 유지해야할 의미가 없으므로, 프로세스 주소 공간에 더이상 스레드가 실행중이 아님을 확인하고, 프로세스를 종료한다. 이러한 현상이 발생하며, 프로세스의 종료 코드는 마지막으로 종료된 스레드의 종료코드로 설정된다.

 

프로세스가 종료될 경우

 

- 모든 남겨진 스레드들은 종료된다.


- 프로세스에 의해 할당된 모든 사용자와 GDI 객체들은 제거되고, 모든 커널 객체를 닫는다.
- 프로세스의 종료코드가 STILL_ACTIVE 에서 ExitProcess 또는 TerminateProcess 로 전달된 코드로 설정된다.
- 프로세스 커널 객체의 상태가 신호상태로 된다. 
- 프로세스 커널 객체의 사용 카운터가 1 로 감소된다.

 

프로세스 커널 객체는 항상 프로세스 자신이 살아 있는 동안 유지된다. 프로세스가 종료되면,시스템은 해당 프로세스의 카운터를 프로세스의 핸들로 0으로 하고, 프로세스를 제거한다.  프로세스 커널 객체의 카운터는 만약, 다른 프로세스에서 핸들을 열어 사용하고, 닫지 않은 경우 바로 0 으로 가지 않는다. 간혹 부모 프로세스에서 자식 프로세스로 전달한 핸들을 제대로 닫지 않을 경우 발생한다. 이것은 버그가 아니다. 프로세스는 시스템에 정적인 정보로 관리된다. 이 정보는 종료된 프로세스의 정보를 얻는 데도 유용하다. GetExitCodeProcess 는 프로세스의 종료코드를 조회한다.

 

BOOL GetExitCodeProcess(
   HANDLE hProcess, 
   PDWORD pdwExitCode);

 

이 함수는 프로세스 커널 객체와 커널 객체안에 그 멤버를 풀어 프로세스의 종료 코드를 조사한다. 종료코든 DWORD 포인터로 pdwExitCode 파라미터로 반환된다.

 

이 함수는 언제든지 호출할 수 있다. 프로세스가 만약 종료되지 않았다면, 이 함수는 DWORD 로 STILL_ACTIVE 를 채운다. 만약, 프로세스가 종료되었다면, 실제 종료 코드가 반환된다.

 

마지막으로 잊지 말아야 할 것은 반드시 시스템에게 프로세스의 정적 데이터 공간이 필요하지 않다면, CloseHandle 을 호출해 이를 알려야 한다. 만약, 프로세스가 이미 종료된 경우라면, CloseHandle 은 커널 객체의 사용 카운터를 감소시키고, 이것을 제거한다.

 

다음은 프로세스 생성의 한 예제이다.

 

PROCESS_INFORMATION pi;
DWORD dwExitCode;

// Spawn the child process.
BOOL fSuccess = CreateProcess(..., &pi);
if (fSuccess) {

   // Close the thread handle as soon as it is no longer needed!
   CloseHandle(pi.hThread);

   // Suspend our execution until the child has terminated.
   WaitForSingleObject(pi.hProcess, INFINITE);

   // The child process terminated; get its exit code.
   GetExitCodeProcess(pi.hProcess, &dwExitCode);

   // Close the process handle as soon as it is no longer needed.
   CloseHandle(pi.hProcess);
}

예제에서 CreateProcess 를 호출하고 바로 자식 프로세스의 primary 스레드의 커널 객체를 바로 닫는 것은 자식 프로세스의 prmary 스레드를 종료하는 것이 아니라, 자식 프로세스의 primary 스레드가 복제되고 난 뒤, primary 스레드를 종료하는 것이다. 즉, 자식 프로세스의 primary 스레드 객체의 사용 카운터를 감소시키는 것이다. 이로서 부모 프로세스에서 자식의 primary 스레드 객체를 가지지 않는 다면, 시스템은 바로, 자식의 primary 스레드 객체를 삭제할 수 있게 된다. 즉, 복제로 인해 증가하는 카운터를 감소시키고, 복제한 객체를 없애서, 자식 프로세스가 종료되면서, 카운터를 0으로 감소시키면, 시스템이 자식 프로세스를 제거하는 것이다.

 

프로세스가 생성되고, 실행되면, 부모 프로세스는 이것과 통신을 필요가 없거나, 부모 프로세스가 계속 작업하기 위해 그것이 종료될 때까지 기다릴 만한 이유가 없는 경우가 대부분이다. 단지, 어떤 작업을 위해 하나더 프로세스를 만들거나 다른 목적을 위해 생성한 것일 것이다. 그러므로, 프로세스를 생성하고 제대로 생성되었는지만 확인해주는 경우라면, CreateProcess 의 반환 값으로 프로세스의 생성과 동작을 알 수 있다. 종료가 되면, 언급했듯이 PROCESS_INFORMATION 구조체의 hProcess 핸들이 신호상태가 되고, 이것을 감지해 종료할 수 있다.

PROCESS_INFORMATION pi;

// Spawn the child process.
BOOL fSuccess = CreateProcess(..., &pi);
if (fSuccess) {

   // Allow the system to destroy the process & thread kernel
   // objects as soon as the child process terminates.
   CloseHandle(pi.hThread);
   CloseHandle(pi.hProcess);
}

Articles

1 2 3 4 5 6 7 8 9 10