Windows 운영체제는 프로세스, 쓰레드, 파일과 같은 리소스 (Resource) 들을
관리하기 위해 필요한 정보를 커널 오브젝트에 저장한다
커널
- 운영체제의 핵심이 되는 소프트웨어
- 운영체제의 가장 아래 계층
- 일반적으로 커널과 운영체제를 같은 의미로 사용
리소스
- 프로세스
- 쓰레드
- 파이프 또는 메일슬롯
- 파일
- 등등
커널 오브젝트
- 커널에서 관리하는 중요한 정보를 저장하는 데이터 블록 (구조체)
- 리소스가 생성될 때마다 커널 오브젝트 구조체 생성
- 리소스의 정보를 구조체에 저장, 참조 및 변경 가능
커널 오브젝트의 구조
- 커널 오브젝트의 종류에 따라 서로 다른 구조체 기반으로 생성
프로세스 커널 오브젝트
- 프로세스는 운영체제가 관리 (생성, 소멸, 상태 변화, 우선순위 변화 등)
- CreateProcess 함수 호출 시 Windows 는 요구에 맞는 프로세스를 생성
- 프로세스를 관리하기 위해 프로세스 상태, 우선순의 등의 정보를 저장
다른 커널 오브젝트
- 쓰레드 커널 오브젝트
- 파이프 또는 메일슬롯 커널 오브젝트
- 파일 커널 오브젝트
- 등등
커널 오브젝트의 조작
- 프로그래머는 커널 오브젝트를 직접 조작할 수 없다
- 시스템 함수를 이용해 간접적으로 커널 오브젝트를 조작할 수 있다
핸들 (HANDLE)
typedef void * HANDLE;
- 커널 오브젝트에 할당되는 숫자
- Windows 는 커널 오브젝트를 생성할 때마다 핸들을 부여
- 핸들을 통해 커널 오브젝트에 접근 가능
리소스 | 커널 오브젝트 | 핸들 |
프로세스 | 프로세스 커널 오브젝트 | 프로세스 커널 오브젝트 핸들 |
쓰레드 | 쓰레드 커널 오브젝트 | 쓰레드 커널 오브젝트 핸들 |
... | ... | ... |
핸들을 얻는 방법
- 커널 오브젝트의 종류에 따라 다양
GetCurrentProcess 함수
HANDLE GetCurrentProcess();
- 현재 프로세스의 핸들 반환
SetPriorityClass 함수
BOOL SetPriorityClass(
[in] HANDLE hProcess,
[in] DWORD dwPriorityClass
);
- 프로세스의 우선순위를 변경
- hProcess : 프로세스에 대한 핸들
- dwPriorityClass : 프로세스의 우선 순위 클래스
자식 프로세스
// High Priority Child Process
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
int _tmain(void)
{
SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
while (1)
{
for (int i = 0; i < 10000; i++)
{
for (int j = 0; j < 10000; j++)
;
}
_tprintf(_T("Child\n"));
}
return 0;
}
- SetPriorityClass 함수를 통해 프로세스의 우선순위를 HIGH_PRIORITY_CLASS 로 변경
- 일반적인 프로세스에 비해 높은 우선순위
- Child 반복 출력
- for 문을 통해 출력 속도 감소
부모 프로세스
// Normal Priority Parent Process
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
int _tmain(void)
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
SecureZeroMemory(&si, sizeof(si));
SecureZeroMemory(&pi, sizeof(pi));
si.cb = sizeof(si);
TCHAR commandLine[] = _T("HighPriorityChildProcess.exe");
if (!CreateProcess(NULL, commandLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
{
_tprintf(_T("CreateProcess failed (%d).\n"), GetLastError());
return -1;
}
while (1)
{
for (int i = 0; i < 10000; i++)
{
for (int j = 0; j < 10000; j++)
;
}
_tprintf(_T("Parent\n"));
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return 0;
}
- High Priority Child Process 실행
- Parent 반복 출력
- for 문을 통해 출력 속도 감소
컴파일러 최적화
- 비주얼 스튜디오의 프로젝트 속성 - C/C++ - 최적화에서 최적화를 비활성화
- 출력 속도를 늦추기 위해 for 문을 사용
- Busy Waiting 상태로, 프로세스가 Blocked 상태가 되지 않는다
- CPU 는 for 문에 해당하는 코드를 수행한다
- 컴파일러 최적화를 활성화하는 경우
- for 문에 대한 코드 생성이 되지 않는다
실행 결과
- 자식 프로세스는 부모 프로세스보다 높은 우선순위
- 따라서 부모 프로세스보다 더 많이 실행된다
- 멀티 코어 시스템이므로 부모 또한 실행된다
싱글 코어 시스템에서는 부모 프로세스의 출력이 나뉘어 처리될 수 있다
...
Child
paChild
Childr
entChild
...
- 출력 함수 호출이 완료되기 이전에 다른 프로세스에게 CPU 할당 시간을 넘겨주기 때문
- 함수가 호출되어 실행되는 도중에도 CPU 의 할당 시간을 다른 프로세스에게 넘겨줄 수도 있다
커널 오브젝트의 종속 관계
- 커널 오브젝트는 Windows 운영체제에 종속적
- 커널 오브젝트의 소멸은 운영체제가 관리
- 하나의 커널 오브젝트에 여러 프로세스가 접근 가능
핸들의 종속 관계
- 핸들은 프로세스에 종속적
- 핸들의 소멸은 프로세스가 관리
- 같은 커널 오브젝트라도 프로세스마다 핸들이 다를 수 있다
부모 프로세스에서 자식 프로세스의 우선순위 변경
// Normal Priority Parent Process
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
int _tmain(void)
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
SecureZeroMemory(&si, sizeof(si));
SecureZeroMemory(&pi, sizeof(pi));
si.cb = sizeof(si);
TCHAR commandLine[] = _T("HighPriorityChildProcess.exe");
if (!CreateProcess(NULL, commandLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
{
_tprintf(_T("CreateProcess failed (%d).\n"), GetLastError());
return -1;
}
DWORD count = 0;
while (1)
{
for (int i = 0; i < 10000; i++)
{
for (int j = 0; j < 10000; j++)
;
}
_tprintf(_T("Parent\n"));
count += 1;
if (count == 16)
SetPriorityClass(pi.hProcess, NORMAL_PRIORITY_CLASS);
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return 0;
}
- 프로세스 생성 시 전달한 PROCESS_INFORMATION 구조체
- 멤버 변수 hProcess 에 자식 프로세스의 커널 오브젝트 핸들이 저장된다
- 부모 프로세스는 핸들을 통해 자식 프로세스의 커널 오브젝트에 접근 가능
- 하나의 커널 오브젝트에 여러 프로세스가 접근 가능
- 일정 시간 뒤 자식 프로세스의 우선순위를 NORMAL_PRIORITY_CLASS 로 변경
실행 결과
- 초기에는 자식 프로세스가 더 많이 실행된다
- 일정 시간 이후 자식 프로세스의 우선순위가 감소한다
- 부모 프로세스와 자식 프로세스가 비슷한 비율로 실행된다
PROCESS_INFORMATION 구조체
- 운영체제는 프로세스 생성 시 구분을 위한 ID 를 할당한다
- ID 와 핸들을 통해 프로세스와 프로세스 커널 오브젝트에 각각 접근 가능하다
쓰레드
- 운영체제는 프로세스 생성 시 main 함수를 실행하는 쓰레드를 내부적으로 생성
- ID 와 핸들을 통해 쓰레드와 쓰레드 커널 오브젝트에 각각 접근 가능하다
Usage Count
- 커널 오브젝트를 참조하는 핸들의 수
- 커널 오브젝트의 멤버로 존재
- 커널 오브젝트의 Usage Count 가 0인 경우 Windows 는 커널 오브젝트를 소멸
CloseHandle 함수
BOOL CloseHandle(
[in] HANDLE hObject
);
- 커널 오브젝트에 대한 핸들을 소멸시키고 커널 오브젝트의 Usage Count 를 감소시킨다
- 핸들이 소멸되어도 커널 오브젝트는 소멸되지 않을 수 있다
- 리소스가 소멸되어도 커널 오브젝트는 소멸되지 않을 수 있다
부모 프로세스에서 자식 프로세스의 핸들 소멸
// Close Handle Parent Process
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
int _tmain(void)
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
SecureZeroMemory(&si, sizeof(si));
SecureZeroMemory(&pi, sizeof(pi));
si.cb = sizeof(si);
TCHAR commandLine[] = _T("HighPriorityChildProcess.exe");
if (!CreateProcess(NULL, commandLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
{
_tprintf(_T("CreateProcess failed (%d).\n"), GetLastError());
return -1;
}
SetPriorityClass(pi.hProcess, NORMAL_PRIORITY_CLASS);
for (int i = 0; i < 10000; i++)
{
for (int j = 0; j < 10000; j++)
;
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
_tprintf(_T("Close Handle\n"));
return 0;
}
- 자식 프로세스를 생성하고 일정 시간 뒤 자식 프로세스의 핸들을 소멸시킨다
실행 결과
- 부모 프로세스에서 자식 프로세스의 핸들을 소멸시켰지만 자식 프로세스는 계속해서 실행된다
- 자식 프로세스와 자식 프로세스의 커널 오브젝트는 계속해서 존재한다
프로세스 커널 오브젝트 소멸 시점
- 프로세스가 종료되었고
- 프로세스 커널 오브젝트의 Usage Count 가 0인 경우
프로세스 종료 코드
- 프로세스의 실행 결과
- 프로세스의 커널 오브젝트에 저장
- 종료 코드 0은 프로세스가 성공적으로 완료되었음을 의미
- 프로세스 종료 방법
- main 함수의 return 문
- exit 함수 호출
- abort 함수 호출
GetExitCodeProcess 함수
BOOL GetExitCodeProcess(
[in] HANDLE hProcess,
[out] LPDWORD lpExitCode
);
- 프로세스의 종료 코드를 검색
- hProcess : 프로세스에 대한 핸들
- lpExitCode : 프로세스 종료 코드를 저장할 변수의 포인터
자식 프로세스의 종료 코드 확인
// Exit Code Child Process
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
int _tmain(void)
{
_tprintf(_T("Exit Code Child Process\n"));
return -1;
}
- 프로세스 커널 오브젝트의 종료 코드를 -1 로 설정
자식 프로세스 실행 결과
- 종료 코드로 -1 출력
부모 프로세스에서 자식 프로세스의 종료 코드 확인
// Exit Code Parent Process
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
int _tmain(void)
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
DWORD code;
SecureZeroMemory(&si, sizeof(si));
SecureZeroMemory(&pi, sizeof(pi));
si.cb = sizeof(si);
TCHAR commandLine[] = _T("ExitCodeChildProcess.exe");
if (!CreateProcess(NULL, commandLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
{
_tprintf(_T("CreateProcess failed (%d).\n"), GetLastError());
return -1;
}
WaitForSingleObject(pi.hProcess, INFINITE);
GetExitCodeProcess(pi.hProcess, &code);
_tprintf(_T("Get Exit Code : %d\n"), code);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return 0;
}
- Exit Code Child Process 실행
- GetExitCodeProcess 함수를 통해 자식 프로세스 커널 오브젝트의 종료 코드 검색
실행 결과
- 자식 프로세스가 소멸되었지만 자식 프로세스의 커널 오브젝트는 계속해서 존재한다
- 프로세스가 소멸되어도 커널 오브젝트는 소멸되지 않을 수 있다
프로세스 생성 시
- Usage Count 는 1로 초기화
- GetCurrentProcess 함수를 통해 자신의 커널 오브젝트 핸들을 얻을 수 있다
- 초기화 이후 프로세스 커널 오브젝트에 접근 가능한 핸들이 증가할 때마다 Usage Count 증가
- 부모 프로세스가 자식 프로세스를 생성한 경우 자식 프로세스의 Usage Count 값은 2
- 부모 프로세스가 프로세스 생성 시 자식 프로세스의 핸들을 얻기 때문
프로세스 커널 오브젝트의 Usage Count 가 감소하는 시점
- 프로세스가 종료되는 시점
- 프로세스 커널 오브젝트의 핸들이 소멸되는 시점
- CloseHandle 함수를 통해 Usage Count 감소
핸들을 반환하지 않는 경우
- 커널 오브젝트가 소멸되지 않고 메모리에 남아있게 된다
- 프로세스 종료 이전 사용했던 핸들을 모두 소멸시켜야 한다
쓰레드의 커널 오브젝트
- 같은 방식으로 동작
참고 자료
https://learn.microsoft.com/ko-kr/windows/win32/api/handleapi/nf-handleapi-closehandle
https://learn.microsoft.com/ko-kr/cpp/cpp/program-termination?view=msvc-170
https://www.youtube.com/playlist?list=PLVsNizTWUw7E2KrfnsyEjTqo-6uKiQoxc
'Windows' 카테고리의 다른 글
커널 오브젝트의 상태 (0) | 2024.05.07 |
---|---|
메일 슬롯 방식 IPC (0) | 2024.05.07 |
표준 검색경로 (0) | 2024.04.11 |
프로세스 생성 (0) | 2024.04.10 |
오류 확인 (0) | 2024.04.09 |