커널 객체란, 이벤트 객체, Mutex, 프로세스, 스레드 등과 같이, 오직 커널만 접근 할 수 있는 객체들을 말한다. 이 객체들은 커널에서만 접근이 가능 하기 때문에, 사용하기 위해서는 Handle을 사용해야 한다. 핸들을 사용해서 커널에게 어느 객체를 사용하고 싶은지 소통이 가능해진다. 이 객체들은 단순한 메모리 블록으로, 그 메모리 블록에는 모든 객체가 공통적으로 가지고 있는 정보가 있는가 하면 객체의 종류의 따라 다른 정보들이 저장되어있기도 한다.
CloseHandle()
을 사용해서 참조 횟수를 감소시킨다. CloseHandle()
을 호출하면, 함수가 리턴 하기 전에 핸들테이블의 해당 핸들 행을 지우기 때문에, CloseHandle()
을 호출 한 뒤에는 핸들 값을 사용할 수 없다.프로세스가 만들어 질 때, 모든 프로세스는 핸들 테이블을 갖게 된다. 이 테이블에는 커널 객체의 핸들과 해당 객체의 정보가 저장 된다. 처음에 프로세스가 시작되면, 이 테이블이 비어있지만, 해당 프로세스의 스레드가 CreateFileMapping
과 같은 커널 객체를 생성하는 함수를 호출하게 되면, 커널은 메모리 블록을 할당해서 객체를 초기화하고, 객체 핸들 테이블의 행에 정보를 입력한다. 테이블의 행에는 메모리 블록의 주소, Access Mask, Flags 와 같은 정보가 저장된다.
커널 객체를 만들 때 반환되는 핸들 값은 각 프로세스의 핸들 테이블 인덱스 값과 연관이 있기 때문에, 해당 핸들 값은 그 핸들 값을 구한 프로세스에서만 사용이 가능하다. 만약 다른 프로세스에서 같은 핸들 값을 사용하게 된다면, 해당 프로세스의 핸들 테이블에서 같은 인덱스 값에 있는 객체를 사용하게 된다.
커널 객체를 만드는 Create...()
함수들 중, 실패 했을 경우 NULL(0)을 반환하는 함수들도 있고, INVALID_HANDLE_VALUE(-1)을 반환하는 함수들도 있기 때문에, 예외 처리를 할 때, 사용하는 함수가 실패 할 경우 어떤 값을 반환하는 지 자세히 확인 할 필요가 있다. 예를 들어 CreateFile
의 경우 실패하면 INVALID_HANDLE_VALUE를 반환하지만, CreateMutex
의 경우 실패하면 NULL을 반환한다.
커널 객체 핸들은 프로레스에 소속된 값이기에 다른 프로세스에서 같은 객체를 사용하기 위해서는 특별한 방법이 필요하다.
이 방법은 두 프로세스가 부모/자식 관계인 경우에만 가능하다. 이 방법을 사용하기 위해서는 몇가지 조건이 붙는다.
bInheritHandle
값을 TRUE
로 설정해야한다.SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHAndle = TRUE;
HANDLE hMutex = CreateMutex(&sa, FALSE, NULL);
CreateProcess
의 인자 중, bInheritHandles
의 값을 TRUE
로 설정 한다.SetHandleInformation
을 이용해서 핸들의 속성을 바꿀 수 있다.SetHandleInformation(hObj, HANDLE_FLAG_INHERIT, 0); // 0을 하면 더 이상 상속하지 않는다.
상속하는 것 이외에도, 커널 객체를 만들 때 이름을 짓는 방법이 있다. 대부분의 커널 객체의 경우, 문자열로 된 이름을 지어줄 수 있다. 다만 주의할 점은, 모든 종류의 객체들이 같은 namespace를 사용하기 때문에, 다른 종류의 객체라도, 같은 이름을 사용 할 수 없다. 만약 커널 객체를 생성하는데, 같은 이름의 커널 객체가 이미 존재한다면,
1. 새롭게 생성하는 객체와 같은 이름을 가진 객체가 같은 종류의 객체이고, 해당 프로세스가 존재하는 객체를 사용할 권한이 있다면 이미 존재하던 객체의 핸들을 반환한다.
- 이 경우, 나중에 객체를 생성하려고 했던 프로세스가 Create...()
함수에 포함시킨 인자들은 모두 무시한다. 새로 만들어진 객체가 아니라면 GetLastError()
을 호출 할 경우 ERROR_ALREADY_EXISTS를 반환한다.
2. 새롭게 생성하는 객체와 같은 이름을 가진 객체가 다른 종류이거나, 해당 프로세스가 권한이 부족하다면 함수는 실패하고 NULL을 반환한다.
커널 객체들이 만들어지는 Namespace에는 크게 두 가지가 있다. Global Namespace는 모든 클라이언트 세션에서 접근이 가능한 커널 객체들이 사용하는 것인데, 주로 서비스들이 사용한다. Global Namespace에 추가로 클라이언트 마다 하나의 Session Namespace도 있는데, 이 Session Namespace는 다른 세션에 있는 객체에 접근 할수 없다. 커널 객체를 만들 때 객체를 만드는 프로세스에 따라 다른 Namespace에 생성된다. 서비스가 만드는 커널 객체는 언제나 Global Namespace에 생성되고, 터미널 서비스의 어플리케이션이 만드는 커널 객체는 Session Namespace에 생성된다. 이 객체들이 생성되는 곳을 임의로 정하고 싶다면, 객체의 이름을 "Global\" 혹은 "Local\" 으로 시작하면 각각 Global Namespace와 Session Namespace에서 생성된다.
확실하게 다른 프로세스들과 겹치지 않기 위해서는, Private Namespace를 만드는 것도 가능하다. 이 경우, Boundary Descriptor를 만든 후, 아래와 같이 만들 수 있다.
HANDLE OpenPrivateNamespace(PVOID pvBoundaryDescriptor, PCTSTR pszAliasPrefix);
마지막으로는 DuplicateHandle
함수를 이용해서 한 프로세스의 핸들 테이블에 있는 객체의 정보를 다른 프로세스의 핸들 테이블에 복사하여 저장하는 방법이 있다. 이 함수에는 복사하려는 객체를 가지고 있는 프로세스에 대한 핸들과, 복사해서 받으려는 프로세스에 대한 핸들이 모두 필요하며, 만약 이 함수를 호출하는 프로세스와, 객체를 받으려는 프로세스가 다를 경우, 이 함수를 호출한 프로세스에서 객체를 받은 프로세스한테 복사가 완료 되었다는 것을 알려줘야 한다.