Process

MSDN에서는 프로세스를 간단히 말해 “실행 중인 프로그램”으로 정의하고 있다. 하나의 프로세스는 하나 이상의 스레드(thread)를 갖는데, 이 중 첫 번째로 실행된 스레드를 primary 스레드라고 부른다. 프로세스의 주소 공간에 저장된 코드가 실제로 실행되기 위해서는 이를 실행하기 위한 스레드가 필요하다. 따라서 프로세스가 실행되면 주 스레드가 생성되고, 프로세스 내 모든 스레드가 종료될 경우 프로세스도 종료된다.

 

Process 구성 요소

프로세스는 크게 두 개의 컴포넌트로 구성된다.

  • 프로세스 커널 오브젝트
  • 사용할 코드와 데이터를 수용하기 위한 주소 공간

프로세스 커널 오브젝트는 프로세스 자체가 아니다. 프로세스를 관리하기 위한 목적으로 운영체제에서 사용되는 커널 오브젝트이며, 프로세스에 대한 통계 정보를 가지는 메모리 블록이다. 따라서 프로세스 생성과 종료 과정 시 가장 처음으로 생성되고, 가장 마지막으로 삭제된다.

각 프로세스는 프로세스 별 주소 공간을 가진다. 이 주소 공간에는 프로세스의 실행에 필요한 코드와 데이터가 저장될 공간이 있으며, 실행에 사용되는 Thread Stack이나 Heap 할당을 위한 메모리 공간도 포함된다.

 


Process 생성 과정

프로세스 생성 시 CreateProcess API를 이용하여 새로운 프로세스를 생성할 수 있다.

BOOL CreateProcess(
  [in, optional]      LPCTSTR               lpApplicationName,
  [in, out, optional] LPTSTR                lpCommandLine,
  [in, optional]      LPSECURITY_ATTRIBUTES lpProcessAttributes,
  [in, optional]      LPSECURITY_ATTRIBUTES lpThreadAttributes,
  [in]                BOOL                  bInheritHandles,
  [in]                DWORD                 dwCreationFlags,
  [in, optional]      LPVOID                lpEnvironment,
  [in, optional]      LPCTSTR               lpCurrentDirectory,
  [in]                LPSTARTUPINFOT        lpStartupInfo,
  [out]               LPPROCESS_INFORMATION lpProcessInformation
);
// T는 사용되는 문자 타입에 따라 None(ANSI)나 W(Unicode)가 될 수 있다.

 

이 API는 다음의 과정으로 Process를 생성한다.

  1. 프로세스 커널 오브젝트 생성
  2. 새 프로세스를 위한 가상 주소 공간 생성
  3. 생성된 가상 주소 공간에 실행 파일 코드와 데이터, 사용되는 DLL 파일 로드
  4. 새 프로세스의 primary 스레드에 대한 스레드 커널 오브젝트 생성
  5. primary 스레드에 의해 C/C++ 런타임 시작 함수 실행

여기서 5번 단계를 거쳐 실행된 C/C++ 런타임 시작 함수는 다음의 작업을 수행한다.

  • C/C++ 런타임 라이브러리 초기화: 전역변수와 Heap 초기화
  • 코드 상에서 선언된 전역 오브젝트, static 클래스 오브젝트 생성자 호출

 

이렇게 초기화 과정을 모두 완료하고 난 후 애플리케이션의 진입점 함수를 호출해 본 프로그램을 시작한다.

 

참고. Linker와 Loader

Linker와 Loader는 운영체제의 구성 요소로 프로그램을 개발하고 실행할 때 사용되는 요소이다. Linker는 프로그램 개발 시 컴파일 단계에서 생성된 여러 오브젝트 파일과 라이브러리 파일들을 하나의 실행 파일로 결합하기 위해 사용된다. Loader는 실행 파일의 코드와 데이터 등을 프로세스의 가상 주소 공간에 로드하기 위해 사용된다.

앞서 언급된 프로세스 생성 과정 중 Linker와 Loader가 기여하는 파트는 다음과 같다.

Linker는 프로세스 생성 과정 중 5번 단계에 사용되는 C/C++ 런타임 시작 함수를 결정한다. 이 때 결정의 기준이 되는 정보가 개발된 프로그램에 대한 Subsystem 링커 스위치이다. 이 링커 스위치는 실행 파일의 형태에 따른 필요 서브시스템 정보를 가지는데, CUI 프로그램일 경우 /SUBSYSTEM:CONSOLE 링커 스위치를 가지고 GUI 프로그램일 경우 /SUBSYSTEM:WINDOWS 링커 스위치를 가진다.

프로그램 빌드 단계에서 Linker는 이 서브시스템 정보를 확인한 후 CONSOLE 링커 스위치가 설정된 경우 프로그램 내 구현된 main이나 wmain 함수를 찾고, 이 함수가 존재하는 경우 mainCRTStartup이나 wmainCRTStartup 함수를 런타임 시작 함수로 설정한다. 만일 서브시스템 정보가 WINDOWS 링커 스위치로 설정된 경우 프로그램 내 WinMain이나 wWinMain 함수가 구현되었는지 확인하고, WinMainCRTStartup이나 wWinMainCRTStartup 함수를 런타임 시작 함수로 설정한다. 이렇게 설정된 런타임 시작 함수가 프로세스 생성 단계 5번에서 실행된다.

 

Loader는 3번 단계를 수행하는데 핵심적인 역할을 하며, 5번 단계에서 C/C++ 런타임 시작 함수가 실행되기 전 프로그램 실행에 필요한 환경을 구성하는 역할을 한다.

이 때에도 마찬가지로 프로그램의 서브시스템 정보를 확인한다. Windows의 PE 파일 헤더의 필드 중 subsystem 정보를 얻을 수 있는데, 이를 이용하여 실행에 필요한 서브시스템 정보를 얻을 수 있다. 그리하여 서브시스템 정보가 CONSOLE일 경우 프로그램 실행 전에 콘솔 윈도우를 생성하거나 기존에 열려있던 콘솔 윈도우를 사용하여 실행에 필요한 환경을 구성한다. 서브시스템 정보가 WINDOWS라면 곧바로 애플리케이션을 로드한다.

 


Process 종료 과정

프로세스는 여러 요인에 의해 종료될 수 있다. 정상적으로 프로그램이 시작하고 끝나면서 종료되는 경우도 있겠지만, 프로그램 내에서 호출된 ExitProcess 함수에 의해 종료될 수도 있고 다른 프로세스에서 호출된 TerminateProcess 함수에 의해서도 종료될 수 있다. 이 중 프로그램의 진입점 함수가 반환되면서 프로그램이 graceful 종료되는 상황에 대해 설명한다.

프로세스의 실행 과정을 보면 프로세스가 생성된 후 런타임 시작 함수가 호출되고, 런타임 시작 함수의 실행 과정에서 프로그램의 진입점 함수가 호출되며 프로그램이 실행되는 것을 알 수 있다. 그렇기에 함수의 실행 과정을 고려했을 때 진입점 함수가 반환 된 뒤 프로그램의 제어가 다시 런타임 시작 함수로 돌아올 것임을 알 수 있다. 그렇기에 진입점 함수가 반환 된 후 런타임 시작 함수로 제어가 돌아간 이후 단계부터 정리하였다.

진입점 함수가 반환되면 진입점 함수의 반환값을 인자로 갖는 C/C++ 런타임 라이브러리의 exit 함수를 호출한다. exit 함수는 다음의 작업을 수행한다.

  • _onexit 함수를 통해 프로그램 종료 시 호출되도록 등록해둔 루틴 수행
  • 모든 전역 클래스 오브젝트와 static C++ 클래스 오브젝트의 파괴자 호출
  • 진입점 함수의 반환값(종료 코드)을 인자로 갖는 ExitProcess 함수 호출

 

마지막 과정인 ExitProcess 함수를 호출하고 나면 운영체제에 의해 프로세스가 종료된다.

프로세스가 종료된 뒤에는 다음의 작업이 수행된다.

  1. 프로세스 내에 남아있는 스레드 종료
  2. 프로세스에 의해 할당되었던 모든 사용자 오브젝트와 GDI 오브젝트 삭제
  3. 프로세스에서 사용된 모든 커널 오브젝트에 대한 사용 카운트 1 감소
  4. 프로세스의 종료 코드를 STILL_ALIVE에서 반환된 종료 코드로 변경
  5. 프로세스 커널 오브젝트의 상태를 시그널 상태로 변경
  6. 프로세스 커널 오브젝트의 사용 카운트 1 감소

여기서 6번 단계 이후 프로세스 커널 오브젝트의 사용 카운트가 0이 되어야 프로세스의 커널 오브젝트까지 삭제되면서 프로세스의 모든 정보가 온전히 파괴된다. 만약 여전히 다른 프로세스에서 해당 프로세스의 정보를 이용하고자 프로세스 커널 오브젝트에 대한 핸들을 사용 중이라면 사용 카운트가 0이 되지 않아 곧바로 파괴되지 않을 것이다.

 


- 참고: 제프리 리처의 Windows via C/C++

'OS' 카테고리의 다른 글

[Windows] Session 0 isolation  (0) 2024.01.23
[Windows] Kernel Object  (0) 2023.12.27

+ Recent posts