윈도우 - 04. 프로세스

최준영·2021년 6월 3일
0

윈도우 공부

목록 보기
4/4

프로세스는 아래 두 가지 요소를 가지고 있으며, 프로그램을 실행 시키는 인스턴스를 말한다.

  • 시스템이 프로세스를 관리 할 수 있도록, 프로세스에 관한 정보를 가지고 있는 커널 객체
  • 해당 프로세스에서 사용되는 EXE와 DLL의 코드를 모두 포함한 메모리 공간 (Address Space)

또한 프로세스 자체는 아무것도 하지 않더라도, 해당 프로세스 안에서 실행되는 스레드가 해당 프로세스의 주소 공간 안에 있는 코드를 실행 시킨다.

윈도우 어플리케이션

윈도우에서 어플리케이션은 크게 두 가지로 나뉜다. 그래픽이 있는 GUI (Graphical User Interface)와, 콘솔 창에서 실행되는 CUI (Console User Interface)이다. 윈도우 어플리게이션을 만들때, Linker Switch 중 올바른 스위치를 사용해야지 원하는 어플리케이션이 만들어 진다.

  • CUI - /SUBSYSTEM:CONSOLE
  • GUI - /SUBSYSTEM:WINDOWS
    CUI의 경우 로더가 텍스트 콘솔을 띄워주고, GUI의 경우에는 따로 띄우는 것 없이 실행 시킨다. 또한, GUI인지 CUI인지에 따라, 프로그램을 실행 했을 경우, 프로그램 진입점_tWinMain 혹은 _tmain으로 한다. 이 함수들을 찾으면, ANSI 인지, UNICODE 인지에 따라 각각의 함수를 GUI일 경우 WinMainCRTStartup 혹은 wWinMainCRTStartup, CUI일 경우 mainCRTSTartup 혹은 wmainCRTStartup으로 프로그램에 포함시킨다.

만약 프로젝트에 /SUBSYSTEM 스위치가 아예 없다면, 링커가 프로그램에 포함되어 있는 함수들을 보고 알아서 빌드를 해준다.

프로세스 핸들

프로세스의 주소공간에 로딩되는 모든 exe와 dll은 모두 고유의 핸들을 가지게 된다. 아래의 함수와 같이 필요한 리소스를 호출 할때, 이 핸들 값을 이용해서 리소스를 어디서 가져오는지 명시할 수 있다.

HICON LoadIcon (HINSTANCE hInstance, PCTSTR pszIcon);

프로세스의 주소공간에 로딩되어 있는 exe 혹은 dll 이라면 GetModuleHandle(PCTSTR pszModule)을 이용해서 핸들을 가져올 수 있다. 이 함수는 HMODULE을 반환하는데, HMODULE과 HINSTANCE는 같은 변수이다. 같은 변수가 다른 이름으로 두개 있는 이유는, 16비트 윈도우 시적의 부산물이다.

GetModuleHandle을 호출할 때 인자로 NULL을 준다면, 해당 함수를 호출하는 exe의 기준주소를 반환한다. 이 때, 주의할 점은 만약 이 함수가 DLL에서 호출 되더라도, 해당 DLL의 기준 주소가 아닌, DLL이 로딩 되어있는 EXE의 기준 주소를 반환한다. 또한, 인자로 넣은 모듈이 만약 해당 exe에 로딩되어 있지 않다면, 다른 프로세스에 로딩 되어 있어도 NULL을 반환한다.

_tWinMain과 같은 프로그램 진입점 함수에 hPrevInstance 라는 인자가 포함되는 경우가 있따. 이 인자는 16비트 윈도우의 포팅을 쉽게 하기 위해 있는 것으로 사용되는 일은 없다.

프로세스의 커맨드 라인

프로레스의 커맨드 라인이란, 그 프로세스를 실행 시키기 위한 명령어다. exe의 경우, 해당 exe의 경로가 커맨드 라인 일 수가 있다. 이 커맨드 라인은 GetCommandLine() 혹은 프로그램 진입점의 pszCmdLine 인자로 알 수 있다.

프로세스의 환경 변수

모든 프로세스는 해당 프로세스의 환경 변수들이 저장되어 있는 메모리 블록을 가지고 있다. 이 메모리 공간에 변수들은 아래와 같이 저장되어 있으며 GetEnvironmentStrings 함수를 호출해서 구할 수 있고, 환경 변수들을 모두 사용 한 후에는 FreeEnvironmentStrings 함수를 호출해서 풀어준다.

=::=::\
Var1=Value1\0
Var2=Value2\0
Var3=Value3\0
\0

CUI 어플리케이션의 경우 TCHAR* env[] 인자를 통해 환경 변수를 확인 할 수도 있다. 이 인자는 문자열 포인터들로 이루어져 있으며, 각각 "Var1=Value1"의 형태로 저장되어 있다. 이 변수들의 값에는 공백도 포함된다.

프로세스의 에러 모드

모든 프로세스에는 시스템에 문제가 생길 경우 어떻게 처리할 지에 대한 플래그를 설정할 수 있다.

플래그설명
SEM_FAILCRITICALERRORSCritical-Error-Handler 메세지 창을 띄우지 않고, 호출한 프로세스에게 에러를 반환한다.
SEM_NOGPFAULTERRORBOXGeneral-Protection-Fault (일반 보호 오류) 메세지 창을 띄우지 않는다. 이 플래그는 예외 핸들러를 이용해서 일반 보호 오류를 해결하는 경우에만 설정합니다.
SEM_NOOPENFILEERRORBOX파일을 찾는데 실패 했을 경우 메세지 창을 띄우지 않는다.
SEM_NOALIGNMENTFAULTEXCEPT시스템이 메모리 정렬 오류를 자동으로 고치고 어플리케이션에게는 알려주지 않습니다. x86/x64 프로세서에서는 아무 효과가 없습니다.

프로세스의 생성

프로세스는 CreateProcess 함수를 호출해서 생성 할 수 있는데, 다음의 순서대로 만들어진다. 함수의 인자에 대한 설명은 MSDN에서 확인 할 수 있다.
1. 시스템이 참조 횟수 1인 프로세스 커널 객체를 만든다. 이 객체는 프로세스 자체가 아니라, 만들려고 하는 프로세스에 대한 정보가 들어있는 객체로, 시스템이 프로세스를 관리 하기 위한 객체이다.
2. 시스템이 가상 주소공간을 만들고, 프로세스에 필요한 코드와 데이터, 모듈들을 모두 로딩한다.
3. 시스템이 프로세스의 메인 스레드를 위한 스레드 커널 객체를 만든다. 이 객체는 위에서 만들어진 것과 비슷하게, 시스템이 스레드를 관리하기 위한 객체다.
4. 프로세스의 메인 스레드가 만들어지면, 링커가 설정한 프로그램 진입점을 스레드가 찾아서 실행시킨다.

프로세스 종료

프로세스가 종료 되는 방법에는 총 네가지가 있다

  • 메인 스레드의 프로그램 진입 함수가 반환한다.
    • 가장 이상적인 프로세스 종료 방법으로 메인 스레드에서 사용한 모든 리소스가 정상적으로 정리/삭제 된다고 보장할 수 있는 유일한 방법이다.
  • 프로세스의 스레드가 ExitProcess를 호출한다
    • ExitProcess 혹은 ExitThread를 사용하면, 운영체제의 입장에서는 아무 문제가 없지만, C/C++ 런타임이 리소스의 정리를 잘 못할 수도 있기 때문에, 이 방법은 자제하는 것이 좋다.
  • 다른 프로세스의 스레드가 TerminateProcess르 호출한다.
    • 작업 관리자의 "끝내기" 같은 함수. 이 함수를 이용해서 특정 프로세스를 종료하면, 종료되는 프로세스는 스스로 종료 된다는 것을 알 수 없어 리소스를 정리하지 않고 종료하지만, 시스템이 이 후에 모든것을 정리해준다
  • 프로세스의 스레드들이 스스로 종료된다.
    • 프로세스의 모든 스레드들이 ExitThread를 호출 하든 어떤 이유에서든 프로세스 안에 실행 중인 스레드가 없다면, 시스템은 프로세스를 종료시킨다.

프로세스가 종료 되면 다음과 같은 과정을 거쳐간다
1. 프로세스에 남아있는 모든 스레드들이 종료된다.
2. 프로세스에게 할당된 모든 유저 객체와 GDI객체들이 Free 된다.
3. 프로세스의 종료 코드가 STILL_ACTIVE에서 ExitProcess 혹은 TerminateProcess에 넘겨진 인자의 값으로 설정 된다.
4. 프로세스 커널 객체의 상태가 Signal 된다.
5. 프로세스 커널 객체의 참조 횟수가 감소한다.

자식 프로세스

코드가 동시에 여러 일을 하는 데는 스레드를 사용 할 수 있다. 하지만 스레드를 사용할 경우, 코드에 따라서 주소 공간이 오염될수 있기 때문에, 복잡한 일을 해야되고 프로세스의 주소 공간을 안전하게 하기 위해서는 자식 프로세스를 만드는 방법이 있다. 이때 자식 프로세스에서 메인 프로세스의 정보를 알아야 하는 경우가 있는데, 이때는 DDE, OLE, 파이프등 Inter-process Communication (IPC) 방법들을 이용 할 수 있다.

자식 프로세스를 생성할 때, 분리된 프로세스, 즉 부모와 소통 할 필요가 없는 프로세스를 만들 때가 많다. 이 경우 프로세스가 만들어지자 마자 부모 프로세스에서 자식 프로세스에 대한 핸들들을 모두 닫는다.

PROCESS_INFORMATION pi;

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

분리된 프로세스를 만드는게 아니라면, pi.hThread만 닫고, pi.hProcess는 더 이상 프로세스가 필요 없어지면 닫는다.

profile
다양하게 이것저것 배우는 개발자입니다.

0개의 댓글