Kernel Object 구조

 위 그림은 WINDOW에서 운영되고 있는 프로세스 및 Kernel의 일반적인 모습을 표현합니다. 

 검은 점선의 윗 부분을 보통 USER 모드라고 검은 점선의 아랫 부분을 KERNEL 모드라고 말합니다. 

 

 커널(Kernel) 오브젝트란?

 일반적으로 윈도우 어플리케이션 개발자는 윈도우가 제공한 API로  커널 오브젝트를 생성 및 조작해서

 어플리케이션을 구현합니다. 그렇기 때문에 window로 개발을 하는 사람은 커널 오브젝트에 대한 기본적인 이해가

 필요 합니다. 

 "커널 오브젝트"를 이해하기 위해서는 우선 커널(Kernel)이 무엇 인지 알아 보도록 합니다. 

https://ko.wikipedia.org/wiki/%EC%BB%A4%EB%84%90_(%EC%BB%B4%ED%93%A8%ED%8C%85)

 

커널 (컴퓨팅) - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 커널이 응용 소프트웨어를 컴퓨터 하드웨어에 연결하고 있다. 컴퓨터 과학에서 커널(kernel)은 컴퓨터의 운영 체제의 핵심이 되는 컴퓨터 프로그램의 하나로, 시스템의 모든 것을 완전히 통제한다.[1] 운영 체제의 다른 부분 및 응용 프로그램 수행에 필요한 여러 가지 서비스를 제공한다. 핵심(核心)[2]이라고도 한다. 커널의 역할[편집] 커널은 운영 체제의 핵심 부분이므로, 커널의 역할 역시 운영 체제의 핵심 역할이라 할

ko.wikipedia.org

 위키백과에 정의된 커널의 정의는 "컴퓨터 과학에서 커널(kernel)은 컴퓨터의 운영 체제의 핵심이 되는 

 컴퓨터 프로그램의 하나로, 시스템의 모든 것을 완전히 통제한다. 운영 체제의 다른 부분 및 응용 프로그램 수행에

 필요한 여러 가지 서비스를 제공한다. 핵심이라고도 한다." 라고 정의 되어 있습니다.

커널의 기본적인 디자인

 즉 커널은 OS가 CPU, Memory, Device를 관리하기 위한 기반 시스템이라고 볼 수 있습니다.

 윈도우도 커널 기반으로 동작하는 OS이며 일반적인 사용자(User)는 직접적으로 CPU, Memory, Device에 접근하지

 못하고 커널에 의해서 제공된 방식으로 접근이 가능하도록 설계 되었습니다.

HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

 예를 들어서 이벤트 객체를 생성하도록 요청하는 CreateEvent 함수를 호출 하면 커널은 이벤트에 대한 커널 오브젝트를

 생성합니다.

 커널 오브젝트는 커널에 의해서 생성된 메모리 블록입니다. 커널 오브젝트는 커널에 의해서만 조작 될 수 있습니다. 

 그렇다면 사용자(User)는 어떻게 커널 오브젝트를 사용 할 수 있을까요?

 Window에서는 Create함수류의 리턴 값으로 HANDLE 값을 리턴해줍니다. HANDLE은 32비트 프로세스에서는 32비트

 64비트 프로세스에서는 64비트의 값을 가지는 메모리(숫자)로 구성 됩니다. 

 내부적으로 HANDLE값은 커널 오브젝트와 연동되어 있으며 WINDOW가 제공하는 API 함수를 호출 시에

 사용자는 커널 오브젝트 대신 HANDLE 값을 전달하여 호출이 합니다.

 (내부적으로 커널은 HANDLE을 기반으로 커널오브젝트에 접근합니다.)

 HANDLE값은 프로세스 마다 독립적으로 유지됩니다. 즉 다른 프로세스가 같은 커널 오브젝트를 접근한다고 해도

 같은 HANDLE 값을 가진다고 보장 할 수 없습니다.

 (프로세스는 자신만의 가상주소를 가지기 때문입니다.)

커널 오브젝트와 연동하는 HANDLE로 디자인 한 이유

 그렇다면 왜 WINDOW에서는 HANDLE 인자에 의한 API를 호출 통해서 커널 오브젝트에 접근 하도록 설계 하였을까요?

 이러한 방식은 커널 오브젝트의 데이터 구조체를 일관성 있게 유지 될 수 있도록 보호합니다.

 이러한 제약을 통해서 이미 WINDOW API로 개발된 어플리케이션에 영향을 미치지 않으면서 내부적으로

 커널 오브젝트를 수정 할 수 있도록 설계 하였습니다.

 이것은 마치 우리가 DLL 라이브러리를 제공할 때 외부의 사용자에게는 인터페이스만 제공하고 

 내부 구현에 대한 정보를 전혀 주지 않는 것과 같은 이치입니다. 

 (우리가 만드는 어플리케이션도 이렇게 유연한 디자인으로 설계 해야 합니다.)

 또한 직접적으로 커널 오브젝트를 조작하지 못하게 하여 안정성을 상승시킵니다. 

커널 오브젝트의 종류

 이벤트(Event), 파일(File), 파일 맵핑(File Mapping) , IOCP, JOB, 뮤텍스(mutex), 프로세스, 스레드, 세마포어 등등

 다양한 커널 오브젝트들이 존재합니다. 

커널 오브젝트의 공통 요소

사용카운트

 커널 오브젝트는 프로세스가 아닌 OS 커널에 의해서 소유됩니다. 만약에 특정 프로세스가 커널 오브젝트를 생성 후

 종료 한다고 하더라도 프로세스와 같이 삭제 된다는 보장이 없다는 뜻입니다. 그렇기 때문에 각 커널 오브젝트는

 내부에 사용 카운트 값을 가지고 있으며 최초로 생성 될 때 1로 셋팅합니다. 

 다른 프로세스에서 이미 생성된 커널 오브젝트를 획득하면 사용카운트를 1증가 시킵니다.

 한 프로세스가 종료 되거나 CloseHandle을 통해서 알리면 사용카운트를 1 감소시킵니다.

 이 값이 0이 되면 커널 오브젝트는 삭제 됩니다. 

 (동작하는 방식을 보면 C++ Shared Ptr의 동작과 비슷하다는 것을 알 수 있습니다.)

보 안 

 커널 오브젝트는 보안 디스크립터를 통해서 보호 될 수 있습니다. 

 커널 오브젝트를 생성하는 거의 대부분의 함수들은 "SECURITY_ATTRIBUTES" 구조체에 대한 포인터를 받아 들입니다. 

typedef struct _SECURITY_ATTRIBUTES
{
    DWORD nLength;
    LPVOID lpSecurityDescriptor;
    BOOL bInheritHandle;
} 	SECURITY_ATTRIBUTES;

 해당 포인터에서는 lpSecurityDescriptor 멤버만이 보안과 관련 되어 있습니다. 

 현재 프로세스의 보안 토큰을 사용 하려면 SECURITY_ATTRIBUTES값을 NULL 값을 전달해서 사용 할 수 있습니다.

커널 오브젝트 핸들 테이블 

 

커널 오브젝트 핸들테이블

 프로세스가 초기화 되면 OS는 프로세스 전용의 커널 오브젝트 핸들 테이블을 생성 합니다.

 초기화된 테이블은 비어 있습니다. 프로세스에서 커널 오브젝트 생성을 위한 CreateFile같은 함수를 호출하면 

 커널은 커널 오브젝트를 위한 메모리 블록을 할당하고 프로세스의 핸들 테이블을 검색해서 비어 있는 위치를 찾은 후

 핸들 테이블에 생성된 커널 오브젝트를 설정합니다. 그리고 함수의 리턴값을 HANDLE 값을 리턴합니다.

HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if(hEvent == NULL)
{
	// 커널 오브젝트 생성 함수 실패!!
}

HANDLE hFile = CreatFile(...);
if(hFile == INVALID_HANDLE_VALUE)
{
	// 일부 함수들은 실패가 INVALID_HANDLE_VALUE!!!
}

 이제 프로세스는 HANDLE값을 통해서 커널 오브젝트를 위한 API를 호출 할 수 있습니다. 

HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

// 이벤트가 활성화 될 때까지 대기한다. EVENT HANDLE로 API 함수 호출 
WaitForSingleObject(hEvent, INFINITY);

if(GetLastError() == ERROR_INVALID_HANDLE)
	// 비정상적인 HANDLE

 만약에 API를 호출 시 비정상적인 HANDLE값을 전달하면 함수가 실패하고 GetLastError로 ERROR_INVALID_HANDLE

 에러 코드를 반환 합니다. 그림과 같이 HANDLE은 프로세스에 종속되고 커널 오브젝트는 전역적으로 사용됩니다. 

 커널 오브젝트를 더 이상 사용하지 않을 경우에는 명시적으로 함수를 호출해서 반환 할 수 있습니다.

// Event Handle을 반환합니다. 
CloseHandle(hEvent);

 더 이상 커널 오브젝트에 접근 할 필요가 없어지면 CloseHandle 함수를 호출 하여 반환 할 수 있습니다. 

 CloseHandle 함수를 호출 하면 매개변수로 전달된 Handle의 핸들 테이블 위치를 검사해서 실제 커널 오브젝트가

 존재하는지 검사 한 후에 커널 오브젝트의 사용 카운트를 1 감소 시킵니다.(0 이되면 커널 오브젝트 삭제)

 커널 오브젝트에 대한 반환 작업이 끝나면 해당하는 핸들 테이블의 항목을 삭제합니다.

 이후로는 해당 HANDLE은 더 이상 유효하지 않는 값이 됩니다. (이후에 사용하면 안됨! NULL 초기화해야 안전)

 만약 프로세스가 CloseHandle함수를 호출 하지 않으면 어떻게 될까요? 커널 오브젝트 Leak이 발생 할까요?

 정답은 프로세스가 종료 될 때 OS에 의해서 모두 반환 됩니다.

 하지만 프로세스가 종료되지 않는다면 불필요한 커널 오브젝트가 남아 있기 때문에 사용 완료된 핸들에 대해서는

 꼭 CloseHandle 함수를 호출 해 주는것을 권장 합니다.

+ Recent posts