광고


프로세스의 시작과 종료 과정 시스템



이번 문서는 프로세스의 시작과 종료 과정에 대해 상세하게 정리하고자 한다.

프로세스의 시작

시스템이 프로세스를 생성하면 시스템은 아래와 같은 과정으로 프로세스를 시작한다.
  1. Usage count가 1인 프로세스 커널 오브젝트를 생성한다.
  2. 새로운 프로세스를 위한 가상 주소 공간을 생성한다.
  3. 실행 파일의 코드와 데이터 및 수행에 필요한 추가적인 DLL 파일들을 프로세스의 가상 주소 공간 상에 로드한다.
  4. 주(main) thread를 위한 쓰레드 커널 오브젝트를 생성한다 (usage count = 1)
  5. 주 쓰레드는 링커에 의해 진입점으로 지정된 C/C++ 런타임 시작 코드를 실행한다.
위 과정에서 5. C/C++ 런타임 시작 코드를 실행한다고 하였다.

우리 눈에는 보이지 않지만, 커널은 우리가 만든 main 함수가 아닌, 아래 함수들을 호출하여 그 함수들이 main 함수를 호출하게 한다.

GUI 어플리케이션은 유니코드 여부에 따라 WinMainCRTStartup (wWinMainCRTStartup)
CUI 어플리케이션은 유니코드 여부에 따라 mainCRTStartup (wmainCRTStartup) 함수를 호출한다.

xxxCRTStartup 함수는 다음과 같은 일들을 수행한다.
  1. 전체 명령행(commandLine)을 가리키는 포인터를 획득한다.
  2. 환경 변수를 가리키는 포인터를 획득한다.
  3. C/C++ 런타임 라이브러리의 전역 변수를 초기화한다.
  4. C/C++ 런타임 라이브러리의 메모리 할당 함수(malloc, calloc...)와 저수준 입출력 루틴이 사용하는 힙을 초기화한다.
  5. 모든 전역 오브젝트와 static c++ 클래스 오브젝트의 생성자를 호출한다.
  6. 사용자가 정의한 진입점 함수(WinMain 또는 main)를 호출한다.

위에서 살펴 보았듯이, 우리가 작성하는 main 함수에 진입하기까지 많은 초기화 과정을 거치게 된다.
특히, 전역/static 오브젝트가 main에 진입하기 이전에 초기화됨을 명심하라.


프로세스의 종료

프로세스는 다음과 같이 네 가지 방법으로 종료될 수 있다.
  1. 주(main) 쓰레드의 진입점 함수(main)가 반환되었다. (Best)
  2. 프로세스 내의 어떤 쓰레드가 ExitProcess 함수를 호출하였다 (Not recommended)
  3. 다른 프로세스의 쓰레드가 TerminateProcess 함수를 호출하였다 (Not recommended)
  4. 프로세스 내의 모든 쓰레드가 종료되었다.

1. 주 쓰레드의 진입점 함수 반환

이 방법만이 유일하게 주 쓰레드의 리소스들이 적절히 해제되는 것을 보장할 수 있다.
주 쓰레드의 진입점 함수가 반환되면, 다음과 같은 작업들을 수행한다.
  1. 주 쓰레드에 의해 생성된 C++ 오브젝트들이 소멸된다.
  2. 쓰레드 스택 용도로 할당된 메모리 공간을 적절히 해제한다.
  3. 진입점 함수의 반환 값으로 ExitProcess 함수를 호출한다. 
즉, 주 쓰레드의 시작 과정에서 생성된 모든 것들이 적절히 소멸되는 것이다.

2. ExitProcess / 3. TerminateProcess

ExitProcess와 TerminateProcess의 차이점은 다음과 같다.
  • ExitProcess : 자신의 프로세스만이 대상
  • TerminateProcess : 모든 프로세스(자신이 아니어도 됨)가 대상
ExitProcess와 TerminateProces는 사용을 가급적 피해야 한다.

그 이유는 두 함수가 호출되면, 해당 프로세스는 그 즉시 종료된다.
함수가 호출된 그 이후의 코드는 아무 것도 절대로 수행되지 않는다는 뜻이다.

아래 예제 코드를 보자.
  1. class Test
  2. {
  3. public:
  4.     Test() { printf("Constructor\n"); }
  5.     ~Test() { printf("Destructor\n"); }
  6. };
  7.  
  8. Test g_Test;    // 전역 객체
  9.  
  10. void main()
  11. {
  12.     Test local_Test;    // 지역 객체
  13.  
  14.     ExitProcess(0);
  15.  
  16.     // 컴파일러는 이 함수의 마지막에 자동적으로
  17.     // local_Test의 파괴자를 호출하는 코드를 추가하게 되는데,
  18.     // ExitProcess 이후의 코드는 절대 수행되지 않으므로, 파괴자가 호출되지 못한다.
  19. }

위 예제 코드에서 Test 클래스는 두 개의 객체(전역1, 지역1)가 선언되었다.

하지만, 지역 객체인 local_Test 객체는 ExitProcess()가 호출된 이후 소멸되어야 하는데,
ExitProcess 이후의 코드는 절대 수행되지 않으므로 소멸될 수 있는 기회를 얻지 못한다.

왜냐하면, ExitProcess/TerminateProcess 로 프로세스를 종료하면,
그 즉시 프로세스가 종료되어 버리기 때문에 (종료 이후의 과정이 바로 진행되기에)
C/C++ 런타임 라이브러리가 관리하는 리소스를 정리할 수 있는 기회를 받지 못하기 때문이다.

하지만, 전역 객체인 g_Test 객체는 반드시 소멸이 이루어진다.
이후 정리할 프로세스가 종료된 이후의 과정에서도 설명 되겠지만, 
ExitProcess가 호출되면 atexit가 호출되어 모든 전역/static이 추후에 정리가 된다.
(static 객체 역시 마찬가지로 정리가 잘 된다)


4. 모든 쓰레드의 종료

프로세스는 쓰레드를 담고 있는 컨테이너나 마찬가지이다.

운영 체제는 프로세스의 주소 공간 상에 위치하는 코드를 수행할 쓰레드가 없기 때문에, 프로세스를 종료시킨다.


프로세스가 본격 종료되는 과정은...

ExitProcess가 호출된 이후 프로세스가 본격적으로 종료되는 과정은 아래와 같다.
  1. 남아 있는 모든 쓰레드가 종료된다.
  2. 프로세스에 의해 할당된 모든 사용자 오브젝트(전역, static), GDI 오브젝트가 삭제되고, 모든 커널 오브젝트는 usage count가 1 감소한다. (atexit 함수에 의해 수행된다)
  3. 프로세스의 종료 코드는 STILL_ACTIVE에서 ExitProcess/TerminateProcess 호출시 설정한 종료 코드로 변경된다.
  4. 프로세스 커널 오브젝트의 상태가 시그널 상태로 변경된다.
  5. 프로세스 커널 오브젝트의 usage count가 1 감소한다.
특히, 전역/static이 진입점 함수가 반환된 이후에 소멸됨을 명심하라.
시작 과정에서도 살펴 보았듯이, 전역/static 오브젝트들은 우리가 제어할 수 있는 영역 밖에서 생성/소멸된다.

      핑백

      • 수까락의 프로그래밍 이야기 : DLL의 진입점 함수 2013-01-01 01:31:37 #

        ... C/C++ 런타임 시작 코드를 수행할 수 있도록 하여,이는 결국 실행 모듈의 진입점 함수를 호출하게 된다.(실행 모듈의 진입점 함수를 호출하는 자세한 과정은 프로세스의 시작과 종료 페이지를 참고) 위 과정 중 하나의 DLL이라도 초기화에 실패하여, DllMain 함수가 FALSE를 반환하게 되면,시스템은 해당 프로셋를 종료하게 된 ... more

      덧글

      댓글 입력 영역