C++/C++ 적용 예제

C++ Windows 공유 메모리(Shared Memory) 사용 가이드 완벽 이해 : 프로세스 간 데이터 공유에 대한 종합 가이드

쉽코딩 2023. 3. 19.

 

 

 

 

 

 

다양한 공유 메모리(Shared Memory)

공유 메모리는 여러 프로세스가 데이터를 읽고 쓰기 위해 공통 메모리 공간에 액세스하고 공유할 수 있도록 하는 IPC(프로세스 간 통신) 방법입니다. C++에서 공유 메모리는 다양한 라이브러리와 메커니즘을 사용하여 얻을 수 있습니다. 여러 프로세스 또는 스레드가 함께 작업하여 문제를 해결하거나 작업을 보다 효율적으로 수행하는 병렬 프로그래밍에서 특히 유용합니다.

▶ Memory mapping:
C++는 파일의 일부 또는 전체 파일을 프로세스의 가상 주소 공간에 매핑할 수 있는 메모리 매핑된 파일을 지원합니다. 이 기술은 동일한 파일을 주소 공간에 매핑하여 프로세스 간에 메모리를 공유하는 데 사용할 수 있습니다. Boost.Interprocess 라이브러리 또는 POSIX mmap() 및 Windows MapViewOfFile() 함수를 사용하여 메모리 매핑을 수행할 수 있습니다.

 

 POSIX shared memory:
POSIX 공유 메모리는 Linux 및 macOS를 포함한 Unix 계열 운영 체제에서 지원되는 기능입니다. 공유 메모리 객체를 생성하고 관리하기 위한 API를 제공합니다. POSIX 공유 메모리 작업을 위한 주요 함수는 shm_open(), ftruncate(), mmap(), munmap() 및 shm_unlink()입니다. 이러한 함수를 사용하면 공유 메모리 객체를 생성, 크기 조정, 매핑, 매핑 해제 및 삭제할 수 있습니다.

 System V shared memory:
System V 공유 메모리는 유닉스 계열 운영 체제에서 사용할 수 있는 오래된 IPC 메커니즘입니다. 공유 메모리 세그먼트를 생성, 연결, 분리 및 제어하는 일련의 기능을 제공합니다. 주요 기능에는 shmget(), shmat(), shmdt() 및 shmctl()이 포함됩니다. POSIX 공유 메모리에 비해 덜 현대적이고 덜 유연하다고 여겨지지만 여전히 널리 사용되고 지원됩니다.

 Windows shared memory:
Windows는 메모리 매핑된 파일과 CreateFileMapping(), MapViewOfFile() 및 UnmapViewOfFile() 함수를 사용하여 고유한 공유 메모리 메커니즘을 제공합니다. 이를 통해 Windows에서 실행되는 프로세스 간에 공유 메모리를 생성하고 관리할 수 있습니다.

 C++ Standard Library (C++20):
C++20은 std::jthread 및 병렬 및 동시 프로그래밍에 보다 쉽게 액세스할 수 있도록 하는 협력 동기화 기능을 도입했습니다. C++ 표준 라이브러리는 기본 제공 공유 메모리 메커니즘을 제공하지 않지만 이러한 새로운 스레딩 기능을 Boost.Interprocess와 같은 기존 공유 메모리 라이브러리와 결합하여 효율적인 병렬 및 동시 프로그램을 개발할 수 있습니다.

공유 메모리로 작업할 때 동기화를 고려하고 데이터 무결성이 유지되는지 확인하는 것이 중요합니다. 뮤텍스, 세마포어 및 조건 변수와 같은 다양한 동기화 프리미티브를 사용하여 서로 다른 프로세스 또는 스레드 간에 공유 메모리 영역에 대한 액세스를 조정할 수 있습니다.

 

 

Windows shared memory 예시

[★★★ 중요 포인트 ★★★]

★ 지금부터 프로세스1에서(From) 프로세스2로(To) 데이터를 공유할 것입니다.

★ (From) 영역에서는 OpenFileMapping 개념을 (To) 영역에서는 CreateFileMapping 개념을 사용합니다.

★ (그림1) 서로 다른 프로젝트의 프로세스1과 프로세스2에 공유 메모리를 통해 프로젝트간 데이터를 공유할 수 있습니다.

★ 동일한 개념이므로 첫번째 예시에만 코드 설명을 첨부하고 나머지는 생략하겠습니다.

그림1

 

[★★★ 코드 테스트시 주의점 ★★★]

아래 예시 코드를 작성하여 실행할 때 ! 무 ! 조 ! 건 ! (To) 영역의 코드를 "먼저(first)" 실행하고

(From) 영역의 코드를 "실행"(second)해야됩니다.

 

그 이유(reason)는 공유 메모리의 특성상 (From) 영역의  OpenFileMapping이 실행하기 위해선 (To) 영역의 CreateFileMapping이 먼저 생성되어 있어야되기 때문입니다.

 

이 순서를 반대로 했을 경우 에러 메시지를 호출하며 데이터 공유가 이루어지지 않습니다.

 

 

 

■ 프로세스1: Shared memory: char-type data 

// Declare a HANDLE variable for the shared memory object
HANDLE HanValue1;

// Open the shared memory object named "IPCValue1" with read and write access
HanValue1 = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, "IPCValue1");

// Check if the shared memory object was opened successfully
if (HanValue1 == NULL) {
    // If not, print an error message and exit the program
    std::cout << currentDateTime() << " -- " << "Could not read HanValue1 : " << GetLastError() << std::endl;
    exit(0);
}

// Map the shared memory object into the address space of the current process
char* chkValue1 = (char*)MapViewOfFile(HanValue1, FILE_MAP_WRITE, 0, 0, 0);

[코드 설명]

이 코드는 Windows 시스템에서 공유 메모리를 사용하는 프로세스 간 통신(IPC)용입니다. 여기에는 OpenFileMapping() 및 MapViewOfFile()의 두 가지 주요 기능이 포함됩니다. 이 코드의 목적은 공유 메모리 개체를 열고 현재 프로세스의 주소 공간에 매핑하여 프로세스가 공유 메모리를 읽거나 수정할 수 있도록 하는 것입니다.

▶ Declaration of HANDLE HanValue1 (HANDLE HanValue1 선언):
HANDLE은 파일, 프로세스 또는 공유 메모리와 같은 운영 체제 리소스에 대한 "핸들"을 나타내는 Windows 데이터 유형입니다. 이 경우 파일 매핑 개체(공유 메모리)에 대한 핸들을 나타냅니다.

Opening the file mapping object (파일 매핑 개체 열기):
OpenFileMapping() 함수는 FILE_MAP_ALL_ACCESS 액세스 권한이 있는 "IPCValue1"이라는 기존 공유 메모리 개체를 열기 위해 호출됩니다. 즉, 프로세스가 공유 메모리에 대한 읽기 및 쓰기 액세스 권한이 있음을 의미합니다. 함수가 실패하면 NULL을 반환하고 후속 오류 검사에서 오류 메시지를 인쇄하고 프로그램을 종료합니다.

Mapping the shared memory object into the process's address space (공유 메모리를 프로세스의 주소 공간에 매핑):
MapViewOfFile() 함수는 공유 메모리 객체(HanValue1)를 현재 프로세스의 주소 공간에 매핑하기 위해 호출됩니다. 이 함수는 매핑된 보기의 시작 주소에 대한 포인터를 반환하고 코드 조각은 이 포인터를 char* chkValue1 변수에 저장합니다. 매핑이 성공하면 이제 chkValue1을 사용하여 공유 메모리에서 데이터를 읽고 쓸 수 있습니다.

 

 

[관련 문서]

https://learn.microsoft.com/ko-kr/windows/win32/memory/creating-named-shared-memory

 

 

□ 프로세스2: Shared memory: char-type data 

// Declare a HANDLE variable for the shared memory object
HANDLE HanValue1;

// Create a new shared memory object named "IPCValue1" with read and write access and a size of 16 bytes
HanValue1 = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(char) * 16, "IPCValue1");

// Check if the shared memory object was created successfully
if (HanValue1 == NULL) {
    // If not, print an error message and exit the program
    std::cout << currentDateTime() << " -- " << "Could not read HanCoilID : " << GetLastError() << std::endl;
    exit(0);
}

// Map the shared memory object into the address space of the current process
char* recvValue1 = (char*)MapViewOfFile(HanValue1, FILE_MAP_WRITE, 0, 0, 0);

[코드 설명]

이 코드의 목적은 공유 메모리 개체를 만든 다음 현재 프로세스의 주소 공간에 매핑하여 프로세스가 공유 메모리를 읽거나 수정할 수 있도록 하는 것입니다.

Declaration of HANDLE HanValue1 (HANDLE HanValue1 선언):
HANDLE은 파일, 프로세스 또는 공유 메모리와 같은 운영 체제 리소스에 대한 "핸들"을 나타내는 Windows 데이터 유형입니다. 이 경우 파일 매핑 개체(공유 메모리)에 대한 핸들을 나타냅니다.

Creating the file mapping object (파일 매핑 개체 만들기):
CreateFileMapping() 함수는 PAGE_READWRITE 액세스 권한이 있는 "IPCValue1"이라는 새 공유 메모리 개체를 만들기 위해 호출됩니다. 즉, 공유 메모리를 읽고 쓸 수 있습니다. 공유 메모리의 크기는 sizeof(char) * 16으로 지정되며 각 문자는 1바이트의 메모리를 차지하므로 16바이트입니다. 함수가 실패하면 NULL을 반환하고 후속 오류 검사에서 오류 메시지를 인쇄하고 프로그램을 종료합니다.

Mapping the shared memory object into the process's address space (공유 메모리 프로세스의 주소 공간에 매핑):
MapViewOfFile() 함수는 공유 메모리 객체(HanValue1)를 현재 프로세스의 주소 공간에 매핑하기 위해 호출됩니다. 이 함수는 매핑된 보기의 시작 주소에 대한 포인터를 반환하고 코드 조각은 이 포인터를 char* recvValue1 변수에 저장합니다. 매핑이 성공하면 이제 recvValue1을 사용하여 공유 메모리에 데이터를 읽고 쓸 수 있습니다.

 

이 코드는 현재 프로세스가 공유 메모리 객체 "IPCValue1"을 생성 및 액세스하고 포인터 recvValue1을 사용하여 데이터를 읽거나 쓸 수 있도록 합니다. UnmapViewOfFile() 및 CloseHandle() 함수를 각각 사용하여 완료되면 공유 메모리를 올바르게 닫고 매핑 해제해야 합니다.

 

[관련 문서]

https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createfilemappinga

 

 

♧ currentDateTime function code

const std::string currentDateTime() {
	time_t     now = time(0); //현재 시간을 time_t 타입으로 저장
	struct tm  tstruct;
	char       Buffer[80];
	tstruct = *localtime(&now);
	strftime(Buffer, sizeof(Buffer), "%Y-%m-%d_%H:%M:%S", &tstruct); // YYYY-MM-DD.HH:mm:ss 형태의 스트링

	return Buffer;
}

 


 

프로세스1: Shared memory: int32_t-type data

HANDLE HanValue1;
	HanValue1 = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, "IPCValue1");
	if (HanValue1 == NULL) { std::cout << currentDateTime() << " -- " << "Could not read HanValue1 : " << GetLastError() << endl;	exit(0); }

int32_t* chkValue1 = (int32_t*)MapViewOfFile(HanValue1, FILE_MAP_WRITE, 0, 0, 0);

 

□ 프로세스2: Shared memory: int32_t-type data

HANDLE HanValue1;
		HanValue1 = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(char) * 16, "IPCValue1");
		if (HanValue1 == NULL) { std::cout << currentDateTime() << " -- " << "Could not read HanCoilID : " << GetLastError() << endl;	exit(0); }

int32_t* recvValue1 = (int32_t*)MapViewOfFile(HanValue1, FILE_MAP_WRITE, 0, 0, 0);

 


 

프로세스1: Shared memory: char-type and int32_t-type data

HANDLE HanValue1;
	HanValue1 = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, "IPCValue1");
	if (HanValue1 == NULL) { std::cout << currentDateTime() << " -- " << "Could not read HanValue1 : " << GetLastError() << endl;	exit(0); }

HANDLE HanValue2;
	HanValue2 = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, "IPCValue2");
	if (HanValue2 == NULL) { std::cout << currentDateTime() << " -- " << "Could not read HanValue2 : " << GetLastError() << endl;	exit(0); }
   
char* chkValue1 = (char*)MapViewOfFile(HanValue1, FILE_MAP_WRITE, 0, 0, 0);
int32_t* chkValue2 = (int32_t*)MapViewOfFile(HanValue2, FILE_MAP_WRITE, 0, 0, 0);

 

□ 프로세스2: Shared memory: char-type and int32_t-type data

HANDLE HanValue1;
		HanValue1 = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(char) * 16, "IPCValue1");
		if (HanValue1 == NULL) { std::cout << currentDateTime() << " -- " << "Could not read HanCoilID : " << GetLastError() << endl;	exit(0); }

HANDLE HanValue2;
		HanValue2 = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(int32_t), "IPCValue2");
		if (HanValue2 == NULL) { std::cout << currentDateTime() << " -- " << "Could not read HanLength : " << GetLastError() << endl;	exit(0); }

char* recvValue1 = (char*)MapViewOfFile(HanValue1, FILE_MAP_WRITE, 0, 0, 0);
int32_t* recvValue2 = (int32_t*)MapViewOfFile(HanValue2, FILE_MAP_WRITE, 0, 0, 0);

 


 

프로세스1: Shared memory: one char-type and three int32_t-type data

HANDLE HanValue1;
	HanValue1 = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, "IPCValue1");
	if (HanValue1 == NULL) { std::cout << currentDateTime() << " -- " << "Could not read HanValue1 : " << GetLastError() << endl;	exit(0); }

HANDLE HanValue2;
	HanValue2 = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, "IPCValue2");
	if (HanValue2 == NULL) { std::cout << currentDateTime() << " -- " << "Could not read HanValue2 : " << GetLastError() << endl;	exit(0); }

HANDLE HanValue3;
	HanValue3 = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, "IPCValue3");
	if (HanValue3 == NULL) { std::cout << currentDateTime() << " -- " << "Could not read HanCoilValue3 : " << GetLastError() << endl;	exit(0); }
	
HANDLE HanValue4;
	HanValue4 = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, "IPCValue4");
	if (HanValue4 == NULL) { std::cout << currentDateTime() << " -- " << "Could not read HanCoilValue4 : " << GetLastError() << endl;	exit(0); }

char* chkValue1 = (char*)MapViewOfFile(HanValue1, FILE_MAP_WRITE, 0, 0, 0);
int32_t* chkValue2 = (int32_t*)MapViewOfFile(HanValue2, FILE_MAP_WRITE, 0, 0, 0);
int32_t* chkValue3 = (int32_t*)MapViewOfFile(HanValue3, FILE_MAP_WRITE, 0, 0, 0);
int32_t* chkValue4 = (int32_t*)MapViewOfFile(HanValue4, FILE_MAP_WRITE, 0, 0, 0);

 

□ 프로세스2: Shared memory: one char-type and three int32_t-type data

HANDLE HanValue1;
		HanValue1 = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(char) * 16, "IPCValue1");
		if (HanValue1 == NULL) { std::cout << currentDateTime() << " -- " << "Could not read HanCoilID : " << GetLastError() << endl;	exit(0); }

HANDLE HanValue2;
		HanValue2 = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(int32_t), "IPCValue2");
		if (HanValue2 == NULL) { std::cout << currentDateTime() << " -- " << "Could not read HanLength : " << GetLastError() << endl;	exit(0); }

HANDLE HanValue3;
		HanValue3 = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(int32_t), "IPCValue3");
		if (HanValue3 == NULL) { std::cout << currentDateTime() << " -- " << "Could not read HanCoilStatus : " << GetLastError() << endl;	exit(0); }

HANDLE HanValue4;
		HanValue4 = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(int32_t), "IPCValue4");
		if (HanValue4 == NULL) { std::cout << currentDateTime() << " -- " << "Could not read HanCoilSpeed : " << GetLastError() << endl;	exit(0); }

char* recvValue1 = (char*)MapViewOfFile(HanValue1, FILE_MAP_WRITE, 0, 0, 0);
int32_t* recvValue2 = (int32_t*)MapViewOfFile(HanValue2, FILE_MAP_WRITE, 0, 0, 0);
int32_t* recvValue3 = (int32_t*)MapViewOfFile(HanValue3, FILE_MAP_WRITE, 0, 0, 0);
int32_t* recvValue4 = (int32_t*)MapViewOfFile(HanValue4, FILE_MAP_WRITE, 0, 0, 0);

 

댓글