프로세스(process)는 프로그램을 실행하기 위해 필요한 데이터와 리소스를 담는 컨테이너이다. 프로그램은 동작 코드가 기계어로 컴파일된 이미지에 불과하며, 윈도우에서의 대표적인 프로그램 확장자로 EXE 및 DLL 등이 존재한다. 운영체제는 실행하려는 프로그램 이미지를 (필요한 라이브러리들과 함께) 커널에 의해 생성된 가상 주소 공간에 로드하여 프로세스를 실행한다.
시스템 프로세스(system processes)는 운영체제 구동을 위해 반드시 존재하는 프로세스들을 가리킨다. 이들은 작업 관리자의 세부 정보 탭이나 프로세스 탐색기를 통해 직접 확인이 가능하다.
-
유휴 프로세스(Idle process)
프로세서가 아무런 작업을 하지 않고 있음을 나타내기 위한 실체가 없는 PID 0의 가짜 프로세스이다. 만일 유휴 프로세스의 프로세서 사용량이 90%로 집계된 경우, 이는 반대로 프로세서의 10%만이 실제로 실행 중인 프로세스를 처리하는 작업에 동원되고 있음을 의미한다. 이러한 이유로 유휴 프로세스의 스레드 개수는 시스템의 논리 프로세서 개수와 일치한다.
-
시스템 프로세스(System process)
Ntoskrnl.exe 또는 로드된 드라이버 등의 커널 스레드를 반영하기 위한 PID가 4로 고정된 특수한 프로세스이다. 시스템 프로세스는 "프로세스 자체"를 가리키는 게 아닌, 커널 모드에서 생성된 "시스템 스레드의 집합"을 지칭한다. 시스템 스레드는 일반 사용자 모드 스레드와 동일한 속성과 컨텍스트을 지니고 있으나, 프로세스 공간의 주소가 존재하지 않으며 오로지 시스템 공간의 코드만 실행한다.
즉 어떤 프로세스라도 장치 드라이버를 통해 스레드를 생성하면 이 또한 시스템 스레드가 되기 때문에 트러블슈팅 과정에서 유의해야 한다.
-
메모리 압축 프로세스(Memory Compression process)
프로세스의 가상 주소 공간으로부터 페이징 아웃 될 페이지를 HDD 또는 SSD와 같은 보조기억장치로 보내기 전에 압축시켜 물리 메모리에 상주시키는 기법을 활용할 수 있다. 이때 타 프로세스의 작업 집합으로부터 방출되어 압축된 대기 메모리가 바로 메모리 압축 프로세스의 사용자 주소 공간에 저장된다. 그러므로 메모리 압축 프로세스의 작업 집합은 작업 관리자의 메모리 성능에 표시된 "(압축)" 크기와 일치한다.
가상 주소 공간(virtual address space; VAS)은 프로세스마다 가상 메모리가 할당되는 주소 공간이다. 프로세스마다 주어지는 개별적인 메모리 공간이기 때문에, 일반적으로 타 프로세스에서 접근이 불가하여 어떠한 영향을 미치거나 받지 않는다. 비록 프로세스별 주어지는 주소 공간이지만 사용자 공간 그리고 커널 공간으로 나뉘어지며 이들에 대한 설명은 다음과 같다:
-
사용자 공간(user space)
개별 프로세스의 데이터 및 리소스가 저장되는 "프로세스 공간"이다. 사용자 공간은 모든 프로세스마다 각각 주어지며, 타 프로세스에서 단순 접근이 절대로 불가한 구조이다. 프로세스가 충돌하여도 시스템 및 나머지 프로세스에는 아무런 타격이 없으며 온전히 실행된다. 즉, 가상 주소 공간의 격리성은 바로 사용자 공간의 특성에서 비롯된 것이다.
-
커널 공간(kernel space)
프로세스가 실행되기 위해 반드시 필요한 운영체제 커널의 데이터 및 리소스가 저장되는 "시스템 공간"이다. 커널 공간은 성능 및 효율성을 위해 모든 프로세스가 하나의 커널 공간을 공용하여 동일한 정보를 가진다. 그러나 프로세스는 (드라이버를 활용하지 않는 이상) 하드웨어 차원의 보안 조치에 의해 커널 공간을 함부로 접근할 수 없다.
가상의 메모리 공간이므로 설치된 물리 메모리 용량에 제약을 받지 않는다. 32비트와 64비트 운영체제는 이론상 각각 4 GB (= 232) 그리고 16 EB (= 264) 범위의 주소를 표현할 수 있다.
프로세스 | 32비트 운영체제 | 64비트 운영체제 | |
---|---|---|---|
32비트 | 사용자 공간 | IMAGE_FILE_LARGE_ADDRESS_AWARE 해제 (기본값)
| IMAGE_FILE_LARGE_ADDRESS_AWARE 해제 (기본값)
|
커널 공간 | IMAGE_FILE_LARGE_ADDRESS_AWARE 해제 (기본값)
|
| |
64비트 | 사용자 공간 | - | IMAGE_FILE_LARGE_ADDRESS_AWARE 해제
|
커널 공간 | - |
|
여기서 IMAGE_FILE_LARGE_ADDRESS_AWARE 플래그는 프로세스의 사용자 공간을 2 GB 제한보다 확장할 지 여부를 결정하는 어플리케이션 빌드 항목이다. 비주얼 스튜디오에서는 프로젝트 속성 중 /LARGEADDRESSAWARE
구성을 아래 그림과 같이 참고한다.
참고: Pushing the Limits of Windows: Handles - Microsoft Community Hub
핸들(handle)은 프로세스 차원에서 파일이나 레지스트리 등의 커널 객체 리소스를 접근할 수 있도록 하는 "추상적인" 참조이다. 할당된 커널 객체의 포인터는 사용자 모드에 직접 노출되지 않는 대신 연동된 핸들을 통해 객체를 참조하도록 설계되었다. 커널 모드에는 각 프로세스마다 주어진 핸들 테이블이 존재하며, 핸들과 해당 커널 객체의 포인터를 아래와 같이 관리한다.
인덱스 | 커널 객체 포인터 | 접근 마스크 | 플래그 |
---|---|---|---|
1 | 0xF0000000 | 0x???????? | 0x00000000 |
2 | NULL | (N/A) | (N/A) |
3 | 0xF0000010 | 0x???????? | 0x00000001 |
커널 객체의 접근을 위해 CreateFile, CreateNamedPipe 등의 함수를 호출할 시, 만일 핸들 테이블에 존재하지 않으면 (접근 마스크 및 플래그 일치 여부 포함) 해당 커널 객체를 위한 메모리를 초기화하고 새로운 인덱스 번호를 할당 및 반환한다. 즉, 테이블의 인덱스가 바로 핸들이며 테이블에 의해 매핑된 커널 객체로 접근이 가능한 것이다.
핸들은 포인터와 달리 추상적으로 참조하여 다음과 같은 특성을 지닌다.
- 핸들 테이블은 프로세스마다 각각 존재하며, 이들의 인덱스(즉, 핸들)와 커널 객체 포인터 간의 매핑은 서로 상이한다.
- 한 커널 객체에 대한 핸들은 프로세스마다 다르기 때문에, 해당 데이터의 무단 접근은 기존 핸들을 단순히 타 프로세스로 전달하는 걸로 불가하다.
커널 객체의 포인터에 핸들을 연동 및 끊는 행위를 "핸들을 열다(open)" 그리고 "핸들을 닫다(close)"라고 표현한다. 만일 더 이상 사용하지 않는 핸들을 제때 닫지 않으면 커널 객체가 잔여하여 핸들 누수가 일어나고, 상황에 따라 메모리 누수를 함께 야기한다.
스레드(thread)는 프로세스의 프로그램 이미지 코드를 실행하기 위해 CPU에서 처리할 수 있는 작업 흐름의 단위이다. 프로세스는 기본적으로 하나의 스레드를 갖는데 개발자의 설계에 의해 추가로 생성하여 두 개 이상의 스레드를 활용할 수 있고, 동일한 가상 주소 공간에 상주하기 때문에 서로의 리소스를 아무런 제약없이 공유할 수 있다. 그러나 프로세스에는 최소한 하나의 스레드가 존재해야 하므로, 모든 스레드가 종료되면 해당 프로세스는 자동적으로 함께 종료된다.