Shared Memory의 Segment는 Local Memory에 Mapping 되면서 별도의 주소 공간을 할당받는다.
물론 한 프로세스 영역에서는 한번 attach 된 이후에는 같은 주소 번지를 계속 유지하게 되지만, 프로세스가 다시 뜨는 경우, 혹은 다른 프로세스가 해당 메모리에 접근하는 경우 똑같은 주소 번지를 반드시 가지게 되는 것은 아니다.
Shared Memory가 System Memory의 특정 address에 들어있는 것은 맞겠지만 프로세스는 그 메모리를 자신의 메모리 영역에 mapping 해서 쓰기 때문에 프로세스가 보는 주소 번지는 매번(어쩔때는 항상 똑같은 주소가 나오기도 한다) 달라진다.
이것은 Shared Memory가 Mapping된 프로세스를 pmap으로 보면 명확히 알 수 있는데,
이게 매번 실행시마다 바뀌는 것이 아니라 어떤 때는 항상 같은 메모리 번지에 매핑되는 리턴하는 경우도 있다. 가끔 착각하기 쉬운게 로컬 메모리처럼 Shared Memory상에서 pointer를 사용해 Shared Memory를 가리키게 되면, 다시 실행할 때 번지가 바껴버리는 경우 그 pointer를 찾지 못하게 되는 것이다.
.. 요거, 가끔 잊어먹어서 삽질을 하게 되는 경우가 있다.
결국 Shared Memory상의 주소공간은 pointer를 그대로 쓸 수 없다는 문제에 봉착하게 된다. 어쨌든 결론은 쉽다. offset을 쓰면 되니까.
특정 pointer가 Shared Memory의 시작 번지에서 얼마나 떨어져 있는지를 계산해서 Shared Memory의 특정 번지에 적어두고, 해당 값을 읽어들인 이후 다시 Shared Memory의 시작 번지를 더하게 되면 pointer를 다시 읽어올 수 있다. (간단한 걸 정말 어렵게 설명한다;)
// em이 가리키는, sp로부터 시작하는 Shared Memory 상의 포인터를 offset으로 변환한다.
// sp는 shmat로 받아온 Shared Memory 매핑 번지이다.
// +1을 해준 것은 sp 자체를 pointer로 만들면 0이 되기 때문인데, 보통 0은 빈, 할당되지 않은 것을 의미하기 때문에 혼동의 여지가 있다.
#define POINTER_TO_OFFSET(sp, em) \
((((void *)(em)) - (sp)) + 1)
// eo가 가리키는 offset을, sp로부터 시작하는 Shared Memory 상의 포인터로 변환한다.
// 역시 -1을 해줘야 동일하게 역으로 변경된다.
#define OFFSET_TO_POINTER(sp, eo) \
(((void *)sp) + (eo - 1))
단점은, 가끔 헷갈린다는 거다. 이 시점에 offset인지, pointer인지 정확히 구분하지 않으면 나중에 꼬여버린다. offset 형태의 주소는 포인터에 넣지 않는 것이 낫다. 그거야 뭐 알아서 할 일이고.
요걸 이용해서 libavl을 Shared Memory에 올렸었지만, 그때의 구현체는 남아있는 것이 없다. 그렇게 변경한다면, 수정할 부분이 좀 많긴 하지만 그다지 어렵지는 않다.