다양한 공유 메모리(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에 공유 메모리를 통해 프로젝트간 데이터를 공유할 수 있습니다.
★ 동일한 개념이므로 첫번째 예시에만 코드 설명을 첨부하고 나머지는 생략하겠습니다.
[★★★ 코드 테스트시 주의점 ★★★]
아래 예시 코드를 작성하여 실행할 때 ! 무 ! 조 ! 건 ! (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);
댓글