반응형

프로세스는 일반적으로 수행 중인 프로그램의 인스턴스(instance)라고 정의하며, 두 개의 컴포넌트로 구성된다.

  • 프로세스를 관리하기 위한 목적으로 OS가 사용하는 커널 오브젝트. 시스템은 프로세스에 대한 통계 정보를 프로세스 커널 오브젝트에 저장하기도 한다.
  • 실행 모듈이나 DLL(dynamic-link library)의 코드와 데이터를 수용하는 주소공간. 스레드 스택, 힙 할당과 같은 동적 메모리 할당에 사용되는 공간도 포함.
인스턴스(instance) : 클래스의 구조로 컴퓨터 저장공간에서 할당된 실체를 의미이며, OOP에서 객체는 클래스와 인스턴스를 포함한 개념

 

스레드(thread)

프로세스는 자력으로 수행될 수 없다. 반드시 프로세스의 컨텍스트(context) 내에서 수행되는 스레드(thread)가 있어야 한다. 스레드는 프로세스의 주소 공간 상에 위치하고 있는 코드를 수행할 책임이 있다. 하나의 프로세스는 다수의 스레드를 가질 수 있으며, 이러한 스레드들은 주소 공간 내에서 동시에 코드를 수행한다. 스레드는 자신만의 CPU Register 집합과 stack을 가져야만 한다. 각 프로세스는 코드를 수행하기 위해 적어도 한 개의 스레드를 가지고 있다. 프로세스가 생성되면 자동적으로 주 스레드(primary thread)가 생성된다. 이 스레드는 추가적으로 스레드를 생성할 수 있다. 만약 프로세스의 주소 내의 코드를 수행할 스레드가 없다면 시스템은 자동적으로 프로세스와 프로세스 주소 공간을 파괴한다.

 

CPU 시간

모든 스레드가 동시에 수행될 수 있도록 하기 위해서 운영체제는 CPU 시간을 조금씩 나눠준다. 라운드 로빈(Round-Robin) 방식으로 주어지는 단위 시간(퀀텀 : quantum) 만큼 수행될 수 있다. 마치 동시에 수행되고 있는것 처럼 보인다.

라운드 로빈

다수의 CPU를 갖고 있는 머신에 경우에 각 스레드에게 공평하게 CPU시간을 나눠주는 것은 복잡하다. 윈도우의 경우에는 CPU별로 서로 다른 스레드를 수행하도록 스케줄링하고 있다. 따라서 다수의 CPU를 가지고 있는 머신의 장점을 사용하기 위해 코드를 변경해야 할 필요는 없지만, 여러개의 CPU를 가진 머신의 장점을 최대한 살리기 위해 애플리케이션의 알고리즘을 적절히 변경하는 것은 유효한 방법이다.

 

 


 

Section 01 첫 번째 윈도우 애플리케이션 작성

 

그래픽 유저 인터페이스 GUI(Graphical User Interface)

그래픽 폰트를 사용하며, 윈도우를 만들 수 있고, 다이얼로그 박스를 통해 사용자와 상호작용을 수행한다. 윈도우의 표준화된 모든 요소들을 사용한다.

콘솔 유저 인터페이스 CUI(Console User Interface)

텍스트 기반이다. 윈도우를 생성하지 않고, 메시지를 처리하지도 않으며, GUI를 피룡로하지 않는다. 애플리케이션도 ㅎ단일 윈도우를 생성하지만 텍스트만을 출력한다.

GUI와 CUI의 경계는 명확하지 않다. CUI 애플리케이션도 다이얼로그 박스를 사용하는 것이 가능하다. GUI 기반의 애플리케이션도 텍스트 문자열을 콘솔 창에 출력할 수 있다. 단지 개발자 예전방식인 CUI 보다는 GUI를 선호하는 편이다.

링커 스위치(linker switch)

Visual Studio를 통해 애플리 케이션을 생성하면, 실행 파일의 형태에 맞는 서브시스템(sub system)타입을 실행 파일에 포함시킬 수 있도록 다양한 링커 스위치(linker switch)를 설정한다. CUI 기반의 링커 스위치는 /SUBSYSTEM:CONSOLE이며, GUI의 링커 스위치는 /SUBSYSTEM:WINDOWS이다. 애플리케이션을 실행하면 OS의 로더(loader)는 실행 파일의 헤더를 확인하며 서브시스템 값을 가져온다. 애플리케이션이 수행되면 OS는 애플리케이션의 형태에 대해 신경쓰지 않는다.

 

윈도우 애플리케이션은 수행 시작시 진입점 함수를 가져야 한다. C/C++은 2가지의 형태의 진입점 함수를 갖는다. 진입점 함수는 어떤 유니코드 문자열을 사용하는지에 따라서 달라진다. 사실 OS는 우리가 작성한 진입점 함수를 직접 호출하지 않으며, 런타임에 의해 구현된 런타임 시작함수(runtime startup function)을 호출한다. 이는 리크시 -entry: 명령행 옵션(command-line option)을 통해 설정된다. C/C++ 런타임 시작 함수는 malloc 이나 free와 같은 함수가 호출될 수 있도록 런타임 라이브러리에 대한 초기화를 수행한다. 또한 각종 전역 오브젝트나 static으로 선언된 c++ 오브젝트들을 코드가 수행되지 전에 적절히 생성하는 역할을 수행한다.

애플리케이션 타입 진입점 실행 파일에 포함되는 런타임 시작 함수
ANSI를 사용하는 GUI _tWinMain (WinMain) WinMainCRTStartup
유니코드를 사용하는 GUI _tWinMain (wWinMain) wWinMainCRTStartup
ANS를 사용하는 CUI _tmain (main) mainCRTStartup
유니코드를 사용하는 CUI _tmain (wmain) wmainCRTStartup

링커는 실행 파일을 링크하는 단계에서 런타임 시작함수를 선택해야한다. GUI 기반인 경우와 CUI의 경우는 위 표와 같다. 만약 함수가 존재하지 않을 경우는 "unresolved external symbol"에러를 반환한다. 만약에 링커 스위치를 제거하면 링커는 자동적으로 애플리케이션에 적합한 설정 값을 찾아낸다. 

모든 C/C++ 런타임 시작 함수는 기본적으로 동일한 작업을 수행한다. 차이점이라면 C런타임 라이브러리의 초기화 이후 수행해야 할 진입점 함수가 어떤것이냐에 따라서 ANS나 유니코드를 처리해야 한다는 점이다. 아래는 시작 함수가 수행하는 작업들을 간단히 요약한 것이다.

  • 새로운 프로세스의 전체 명령행을 가리키는 포인터를 흭득
  • 새로운 프로세스의 환경변수를 가르키는 포인터를 흭득
  • 전역변수를 초기화한다. 사용자 코드가 stdlib.h 인클루드 하면 이 변수에 접근할 수 있다.
 변수명 타입   설명과 이 변수를 대체하는 윈도우 함수
 _osver  unsigned int  운영체제의 빌드 버전.
 예) 비스타 RTM은 6000이므로 _osver는 6000값을 가진다
 _winmajor  unsigned int  16비트로 나타낸 윈도우 major버전.
 윈도우 비스타의 경우 6.
 GetVersionEx 대체 사용 가능
 _winminor  unsigned int  16비트로 나타낸 윈도우 minor 버전.
 윈도우 비스타의 경우 0.
 GetVersionEx 대체 사용 가능
 _winver  unsinged int  (_winmajor << 8) + _winminor.
 GetVersionEx 대체 사용 가능
 __argc  unsigned int  명령행을 통해 전달된 인자의 개수.
 GetCommandLine을 대체 사용 가능
 __argv
 _wargv
 char
 wchar_t
 ANSI / 유니코드 문자열을 가리키는 __argc 크기의 배열
 배열의 각 요소는 명령행 인자를 가리킨다.
 _UNICODE가 정의되어 있으면 __argv가 NULL, 정의되어 있지 않으면 __wargv가 NULL이다.
 GetCommandLine을 대체 사용 가능
 _environ
 _wenviron
 char
 wchar_t
 ANSI/유니코드를 가리키는 배열. 각 배열 요소는 환경변수 문자열을 가리킨다. _UNICODE가 정의되어 있으면 _environ이 NULL, 정의되어 있지 않으면 _wenviron 이 NULL이다.
 GetEnviromentStrings, GetEnvironmentVariable 대체 사용 가능
 _pgmptr
 _wpgmptr
 char
 wchar_t
 ANSI/유니코드로 표현되는 수행 중인 프로그램의 전체 경로와 이름.
 _UNICODE가 정의도어 있으면 _pgmptr이 NULL, 정의되어 있지 않으면 _wpgmptr이 NULL이다.
 GetModuleFileName의 첫 번째 매개변수로 NULL을 전달하는 형태로 대체 사용 가능
표 출처: 
https://elishaz.tistory.com/23#
  • 메모리 할당 함수(malloc, calloc)와 저수준 입출력 루틴이 사용하는 힙을 초기화 한다.
  • 모든 전역 오브젝트와 static c++ 클래스 오브젝트의 생성자를 호출

이러한 과정이 완료된 후, 애플리케이션의 진입점 함수를 호출한다. 

진입점 함수가 반환되면 시작함수는 진입점 함수의 반환 값(nMainRetVal)을 인자로 하여 C/C++ 런타임 라이브러리의 exit 함수를 호출한다. exit 함수는 다음과 같은 작업을 수행한다.

  • _onexit함수를 이용해 등록해 두었던 함수를 호출
  • 모든 전역 클래스 오브젝트와 static c++ 클래스 오브젝트 파괴자를 호출
  • DEBUG 빌드의 경우 _CRTDBG_LEAK_CHECK_DF 설저되 있으면, 메모리에서의 메모리 누수 상황을 _CrtDumpMemoryLeaks 함수를 호출해 나열해 준다
  • nMainRetVal 값을 인자로 하여 ExitProcess함수를 호출한다. 이 함수를 호출하면 OS는 프로세스를 종료하고 프로세스의 종료 코드를 설정한다.

[1] 프로세스 인스턴스 핸들

모든 실행 파일과 DLL 파일은 프로세스의 메모리 공간 상에 로드될 때 고유의 인스턴스 핸들을 할당받는다.이 핸들 값은 보통 리소스를 로드할 때 사용된다. 예)(w)WinMain의 첫 번째 매개변수 int WINAPI _itWinMain(HINSTANCE hInstanceExe, ...)

HICON LoadIcon(HINSTANCE hInstacne, PCTSTR pszIcon);

LoadIcon의 첫 번째 매개변수에는 리소스가 포함된 파일의 인스터스 팬드을 지정하면 된다. (w)WinMain의 hInstanceEx 매개변수를 전역변수에 저장해 두어 전체 소스에서 쉽게 접근할 수 있도록 하곤 한다.

실행파일이 로드될 시작 주소는 링커에 의해 결정된다. 서로 다른 링커는 서로 다른 기본 시작 주소를 가질 수 있다. 

WinMain의 hInstanceExe 매개변수의 실제 값은 시스템이 프로세스의 메모리 주소 공간 상에 실행 파일을 로드할 시작 메모리 주소(base memory address)다. 예를 들어 시스템이 실행 파일을 열어서 그 내용을 0x00400000에 로드하고자 한다면 (w)WinMain의 hInstanceExe 매개변수는 0x00400000을 가지게 된다.

실행 파일이 로드될 시작 주소는 링커에 의해 결정된다. 서로 다른 링커는 서로 다른 기본 시작 주소를 가질 수 있다. Visual Studio의 링커는 역사적인 이유로 0x00400000을 기본 시작 주소로 사용하고 있는데, 0x00400000은 윈도우 98에서 실행 파일을 로드할 수 있는 가장 하단의 메모리 주소였다. 애플리케이션이 로드되는 시작 주소는 마이크로소프트 링커의 경우 /BASE:address 옵션을 사용하여 변경할 수 있다.

 아래의 GetModuleHandle 함수는 실행 파일이나 DLL 파일이 프로세스의 메모리 공간 상의 어디에 로드되어 있는지를 가리키는 핸들/시작 주소를 반환한다.

HMODULE GetModuleHandle(PCTSTR pszModule);

이 함수를 호출할 때에는 호출하는 프로세스의 주소 공간에 로드되어 있는 실행 파일명이나 DLL 파일명을 '\0'으로 끝나는 문자열로 전달하면 된다. 시스템이 지정한 실행 파일이나 DLL 파일을 찾아내면 GetModuleHandle 함수는 파일이 로드된 시작 주소를 반환한다. 반면 시스템이 해당 파일을 찾을 수 없다면 NULL을 반환한다. GetModuleHandle을 호출할 때 pszModule 매개변수로 NULL 값을 전달할 수도 있는데, 이 경우 GetModuleHandle은 현재 수행 중인 실행 파일이 로드된 시작 주소를 반환한다. 만일 이 함수가 DLL 내에서 호출된다면 어떤 모듈에 포함되어 코드가 수행 중인지 알아내기 위한 두 가지 방법이 있다.

① 링커에 의해 정의되는 가상변수인 __ImageBase가 현재 수행 중인 모듈의 시작 주소를 가리키고 있다는 사실을 활용할 수 있다. 이 변수 값은 앞서 알아본바와 같이 C 런타임 시작 코드가 (w)WinMain 함수를 호출할 때 사용되는 값이다.

② 첫 번째 매개변수로 GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS를 두 번째 매개변수로 현재 수행 중인 함수의 주소를 지정하여 GetModuleHandleEx함수를 호출한다. 마지막 매개변수로 전달되는 값은 HMODULE을 가리키는 포인터 값인데, 두번째 매개변수로 전달한 함수를 포함하고 있는 DLL의 시작주소를 반환해 준다.

※ 실제로 HMODULE과 HINSTANCE는 완전히 동일하다. 어떤 함수가 HMODULE을 요구한다면 HINSTANCE를 넘겨줘도 무방하며, 그 반대도 마찬가지다. 16비트 윈도우에서는 HMODULE과 HINSTANCE가 완전히 구분되는 자로형으로 존재했지만 지금은 혼용하고 있다.

출처: https://elishaz.tistory.com/23# 

 

 

반응형

'이론공부 > 이것저것 공부' 카테고리의 다른 글

netstat  (0) 2022.04.19
데이터베이스 솔루션 정리  (0) 2021.06.18
포인터 의 크기 in c/c++  (0) 2021.01.04
커널 오브젝트  (0) 2020.12.14
volatile  (0) 2018.03.18

+ Recent posts