boost::interprocess::managed_shared_memory의 동작에 관하여
주의 사항
이 글은 예전 블로그에서 옮겨온 오래 된 글입니다. 현재 상황과는 다를 수 있으며, 잘못 된 정보가 있을 수 있습니다.
boost::interprocess::managed_shared_memory(이하 msm)에 관하여.
msm는 내부적으로 Red-Black Tree 를 이용하여 구현되어 있다.
서로 다른 프로세스에서 공유 가능한 std::map<string, void*> 라고 보면 간단하게 이해된다. 공유메모리를 map의 형태로 기능확장한 것.
msm 는 생성시 mutex를 가지게 됨. Msm의 rb-tree를 구성하는 boost::rbtree_best_fit이 boost::interprocess::interprocess_mutex 를 상속받은 header_t 구조체를 소유하고 있음.
header_t 구조체는 추가적으로 msm의 헤더를 관리하기위한 멤버를 소유하고 있고, 이것들은 메모리 세그먼트를 관리할때 자동으로 scoped lock 이 걸림
msm 생성시 POXIS에서는 shm_open 를 이용하고, 윈도우에서는 CreateFile를 이용한다.
즉 msm 생성 과정에서는 OS에 의해서 보호된다. 여기서 의문인 것은 왜 CreateFileMapping 을 이용한 Windows OS의 공유메모리 메커니즘을 사용 하지 않았는가 이다.
물론 FILE_ATTRIBUTE_TEMPORARY 플래그를 주어 파일을 메모리에 생성 하긴 했지만 그다지 효율적으로 보이지는 않는다. 궂이 예측 하자면 CreateFileMapping이 생성시의 이름을 어떻게 주느냐에 따라 세션관련 제어 기능을 가지게 되는데 그것을 의도적으로 피하고자 함이 아닌가 싶다.
boost의 기본 설계 방침은 “플랫폼 특화” 된 기능을 막아서라도 같은 인터페이스를 제공 하는 것이라고 하니까. 아니면 그냥 단순히 POSIX랑 똑같이 하고 싶어서 일수도 있고.
msm는 윈도우에서는 추가적인 과정이 있는데, 만약 공유위반으로 인해 생성이 실패 할 경우 250ms 간격으로 최대 3번까지 재시도 하게 된다.
이는 MSDN 에 명시된 동작으로 실제 사용 하지 않더라도 OS의 백그라운드 조각모음 때문에 일시적으로 파일이 잠길 수 있다고 하고 있다. 또한 FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_SHARE_DELETE 권한이 있다 하더라도 파일 생성 중에는 lock이 걸려 공유위반이 발생 할 수 있다. 단 이것은 임시파일이 아닐 경우인데, 실제 테스트 결과 임시파일에서는 아무리 빠르게 반복해도 관련 현상을 재현할 수 없었다.
어쨋든 Windows, POSIX 양쪽 다 OS 차원에서 “파일 생성” 이라는 부분은 완벽히 동기화 시켜주기 때문에 절대로 “같은 이름을 가진 msm 객체가 생성 될 수 없다” 또한 open_or_create를 사용 하여 생성 할 경우 반드시 성공한다는 것을 보장 하게 된다.
msm 의 읽기,쓰기 동작(find, construct 등)도 일부러 nolock 버전의 함수를 호출하지 않는 이상 thread safe 하다. 즉 find → add 의 동작을 반복 할 필요가 없다.
construct 는 이미 존재하는 Key 대해 요청 할 경우 exception 을 발생 시킨다.
이것의 예외 처리는 매우 중요한데, 만약 한쪽에서 construct 가 중복 실행되어 exception 이 발생 될 경우 예외처리를 제대로 하지 않으면 msm 내부의 lock이 풀리지 않아 무한히 대기 하게 된다. Windows , POSIX 양쪽 다 프로세스가 Crash 되어 종료된 상황에서도 lock 이 해제되지 않는것이 확인되었다. 이런 경우를 방지하기 위해 find_or_construct 를 제공 하므로 이쪽을 사용 하는 것이 바람직하다.
위의 내용에 덧붙이면 사실 윈도우에서 제공하는 모든 동기화 객체는 lock의 재진입을 지원하고 프로세스 레벨의 레퍼런스 카운팅을 제공하여 프로세스가 죽으면 그 프로세스가 획득한 모든 Lock이 해제되도록 되어있다. 문제는 boost의 구현방법에 있는데 posix 에서는 pthread의 mutex를 사용하는것이 기본인 반면 Windows 시스템에서는boost가 자체 구현한 spin_mutex를 사용하는것이 default로 되어 있다.
그런데 이녀석은 커널 레벨에서 시스템 전체를 관리하는게 아니라 프로세스 레벨에서 atomic operation을 이용해 임의로 구현한 기능이므로, 프로세스가 Exception에 의해 Crash 되었을 경우의 뒷처리를 전혀 할 수 없다. 즉 Windows 시스템의 안정적인 관리 기능을 사용하려면 boost::interprocess::windows_mutex를 명시적으로 선언해야 한다.
이는 공유메모리도 마찬가지로, 기본적으로는 위에 적은것 처럼 CreateFile 을 사용 하고 있지만 boost::interprocess::windows_shared_memory 를 명시적으로 사용하면 CreateFileMapping를 이용하는 공유메모리가 생성되며 Windows에서 제공하는 기능 전체를 사용할 수 있다.