Coding Memo

Windows - CreateMutex - 커널 모드 동기화 본문

etc

Windows - CreateMutex - 커널 모드 동기화

minttea25 2024. 9. 27. 18:23

프로그램 내의 스레드 간 공유 자원에 대한 접근을 제한하는 것처럼 프로세스에서도 어떤 자원에 대해, 같은 응용 프로그램이 동시에 변경을 하거나, 작업을 하면 안되는 경우가 있다.

 

어떻게 방지 할 수 있을까?

 

여러 방법 중 하나로, Windows에서 제공하는 mutex를 활용하는 방법이 있다.

HANDLE CreateMutexW(
  [in, optional] LPSECURITY_ATTRIBUTES lpMutexAttributes,
  [in]           BOOL                  bInitialOwner,
  [in, optional] LPCWSTR               lpName
);

 

HANDLE OpenMutexW(
  [in] DWORD   dwDesiredAccess,
  [in] BOOL    bInheritHandle,
  [in] LPCWSTR lpName
);

 

CreateMutex는 뮤텍스를 생성하고 해당 핸들을 얻는 함수이고, OpenMutex는 이미 생성되어 있는 뮤텍스에 대해 접근을 얻는 함수이다.

 

CreateMutex를 자세히 살펴보자.


 

Arguments

 

1. lpMutexAttributes

이 값이 NULL이면 자식 프로세스에서 만들어진 뮤텍스에 대한 핸들을 상속할 수 없다. 즉, NULL이면 기본 SECURITY_ATTRIBUTES 구조체를 사용한다.

 

Note: SECURITY_ATTRIBUTES는 해당 핸들이 상속 가능한지 여부를 지정하는 security descriptor를 포함한다. CreateFile, CreatePipe, CreateProcess, RegCreateKeyEx 또는 RegSaveKeyEx와 같은 함수로 생성된 개체에 대한 보안 설정을 제공한다. (https://learn.microsoft.com/ko-kr/previous-versions/windows/desktop/legacy/aa379560(v=vs.85))

 

2. bInitialOwner

말 그대로 초기 소유자에 대한 지정이다. BOOL 값인데, 이 값이 true이고 호출 스레드가 이 뮤텍스를 만든 경우 이에 대한 소유권을 얻는다.

 

3. lpName

만드는 뮤텍스 개체의 이름이다. 이름 비교는 대/소문자를 구분한다고 나와있다.

만약 NULL 값이면 이름 없이 뮤텍스 개체가 만들어진다.

이 이름은 동일한 네임스페이스를 공유하기 때문에 기존의 다른 이벤트나 세마포어 등의 이름과 일치하면 안된다. (return ERROR_INVALID_HANDLE)

 

Note: 'Global\'과'Local\'을 이름앞에 붙일 수 있다.

'Global\'은 시스템 전체에서 접근 가능한 네임스페이스로, 모든 사용자 세션에 걸쳐 유효하다. 즉, 시스템에서 실행 중인 모든 프로세스가 같은 이름을 사용할 수 있고, 이에 통해 자원 공유를 할 수가 있다.

'Local\'은 현재 사용자 세션 내에서만 유효한 네임스페이스이다. 같은 세션 내의 프로세스끼리만 접근이 가능하다. 이를 이용해 동일한 이름을 가진 개체에 대한 세션 간 충돌을 방지할 수 있다.

 

 

Return

 

성공 시,만들어진 mutex의 핸들 반환

이미 명명되어 있는 mutex를 생성하려고 했을 시, 해당 mutex의 핸들 반환

실패 시, NULL 반환

 

윈도우 함수이므로, GetLastError를 통해 오류 내용을 확인 할 수 있다.

 

Note: OpenMutex도 비슷하다!


Sample Code

 

간단하게 CreateMutex를 이용하여 핸들을 얻었는지의 여부를 활용하여 같은 프로그램의 인스턴스가 동시에 존재하도록 실행되는 것을 막아본다.

 

일반적으로 사용하는 뮤텍스와 동일하다고 생각해도 된다. 이 뮤텍스를 전역으로 생성하는 방법을 이용하면 프로그램 시작 시에 이 뮤텍스를 확인하면서 두 프로그램의 동시 실행을 막을 수 있을 것이다. 물론 실행 자체를 막지는 못한다.

#include <iostream>
#include <windows.h>

int main()
{
	HANDLE hMutex = CreateMutex(NULL, TRUE, L"AppMutex");

	if (GetLastError() == ERROR_ALREADY_EXISTS)
	{
		std::cout << "Application is already running!" << std::endl;
		return 1; // close application
	}

	std::cout << "Application is running now!" << std::endl;

	std::cin.get();

	// release mutex
	ReleaseMutex(hMutex);
	CloseHandle(hMutex);

	return 0;
}

 

위 코드를 빌드하고 프로그램을 하나 실행시킨 상태에서 또 다시 같은 프로그램을 실행 시키면, "Application is already running!"이라는 메시지를 출력하고 종료된다.

 


How?

 

이 뮤텍스는 스레드나 프로세스 내에 존재하는 뮤텍스가 아니라, 커널 객체이다. 커널은 뮤텍스의 이름 (대/소문자 구분)을 key로 하여 전역 테이블(Global Object Table)에 등록한다. OS 단위에서 동기화가 이루어진다.

 

예를 들어, 프로세스 A가 뮤텍스를 소유하고 있다면, 프로세스 B는 같은 뮤텍스를 소유할 수 없다. 대신에 프로세스 B는 WaitForSingleObject와 같은 함수로 해당 뮤텍스의 소유권을 얻을 때까지 기다릴 수 있다. 프로세스 A가 ReleaseMutex를 호출하여 뮤텍스를 해제하면 그제서야 프로세스 B가 뮤텍스를 얻는 것이다.

 

전역 테이블에는 HANDLE이 포함되어 있지 않다. 커널 개체에 접근할 수 있는 HANDLE은 프로세스 별로 독립적으로 존재한다. 즉, 프로세스는 가상 주소를 가지고 있기 때문에 서로 다른 프로세스가 같은 커널 개체에 접근하더라도 접근하는 핸들은 같은 값이 아닐 수 있다. 단순한 커널개체에 대한 참조일 뿐이다.

 

 

Note: 커널 객체는 시스템 자체에서 유지되는 객체이고, 여러 프로세스 간에 공유될 수 있다.

 

Note2: 추가적으로, 여기서 나오는 핸들(HANDLE)은 이 커널 객체에 대한 포인터 역할을 의미한다. 간단히 말해서 커널 개체 조작에 대한 윈도우 API 이다.

(IOCP 프로젝트에서 HANDLE을 사용했던 것과 연관지으니 이해가 되었다.)

 

Note3: 커널 개체는 C++의 shared_ptr과 같이 reference_count 기반으로 관리된다. 각 프로세스에서 커널 개체에 접근 할 때마다 카운트가 증가하고 핸들을 닫으면 (CloseHandle) 감소한다. 마찬가지로 카운트가 0이되면 해당 개체를 커널에서 삭제하고 전역 테이블에서 제거한다.


Reference

https://learn.microsoft.com/ko-kr/windows/win32/api/synchapi/nf-synchapi-createmutexw

 

CreateMutexW 함수(synchapi.h) - Win32 apps

명명되거나 명명되지 않은 뮤텍스 개체를 만들거나 엽니다. (유니코드)

learn.microsoft.com

 

 

'etc' 카테고리의 다른 글

Windows - 같은 프로그램 실행 방지  (1) 2024.10.01
Shuffle 알고리즘 (Fisher-Yates Shuffle, Knuth Shuffle)  (0) 2024.09.26
[Linux] 리눅스 파일 유형  (0) 2024.02.29
패킷 암호화(비트연산, AES)  (0) 2023.12.14
C++ vs. C# ??  (0) 2023.10.13