Shared Memory
Shared Memory (공유 메모리)
1. 개요
공유 메모리(Shared Memory)란 여러 프로세스가 동일한 메모리 영역에 접근하여 데이터를 읽고 쓸 수 있도록 설정된 프로세스 간 통신(IPC, Inter-Process Communication) 메커니즘이다.
일반적으로 운영체제는 프로세스 간의 메모리 격리를 통해 보안과 안정성을 유지하지만, 공유 메모리는 특정 영역을 예외적으로 공유함으로써 데이터 복사 과정 없이 매우 빠른 속도로 정보를 교환할 수 있게 한다. 이는 파이프(Pipe)나 메시지 큐(Message Queue)와 같이 커널을 통해 데이터를 전달하는 방식과 달리, 한 번 설정되면 커널의 개입 없이 사용자 공간에서 직접 메모리에 접근한다는 점에서 차별화된다.
2. 장점과 단점
장점
- 최고의 성능: 데이터 복사(Copy) 과정이 없으므로 IPC 방식 중 전송 속도가 가장 빠르다.
- 대용량 데이터 효율성: 대규모 데이터를 여러 프로세스가 공유할 때 메모리 낭비를 최소화한다.
단점
- 동기화 복잡성: 개발자가 직접 세마포어 등을 이용해 동기화를 구현해야 하며, 실수 시 데드락(Deadlock)이나 데이터 오염이 발생한다.
- 관리 책임: 프로세스 종료 후에도 메모리가 남을 수 있어 명시적인 자원 회수 로직이 필수적이다.
3. 동작 원리 및 구조
공유 메모리는 가상 메모리 관리 체계의 페이지 테이블(Page Table) 매핑 원리를 이용한다. 각 프로세스는 자신만의 가상 주소 공간을 가지지만, 운영체제는 서로 다른 프로세스의 가상 주소를 물리 메모리의 동일한 프레임(Frame)에 매핑함으로써 공유 영역을 생성한다. 이때 각 프로세스에서 접근하는 가상 주소(Virtual Address)는 서로 다를 수 있지만, 물리 주소(Physical Address)는 동일하다.
메모리 접근 방식 비교
| 구분 | 일반적인 메모리 접근 (Isolated) | 공유 메모리 접근 (Shared) |
|---|---|---|
| 주소 공간 | 프로세스별로 완전히 독립된 가상 주소 할당 | 서로 다른 가상 주소가 동일한 물리 주소를 가리킴 |
| 데이터 전달 | 커널을 통한 복사 (User $\rightarrow$ Kernel $\rightarrow$ User) | 직접 접근 (User $\rightarrow$ Shared Memory $\rightarrow$ User) |
| 접근 권한 | 자신의 메모리 영역 외 접근 시 Segmentation Fault 발생 | 허가된 공유 영역에 대해 읽기/쓰기 권한 공유 |
| 속도 | 컨텍스트 스위칭 및 데이터 복사 비용 발생 | 메모리 참조 속도와 동일 (매우 빠름) |
4. 주요 구현 방식
유닉스 계열 시스템에서는 크게 두 가지 표준 구현 방식이 사용된다.
4.1 System V Shared Memory
초기 유닉스 표준으로, 정수 형태의 키(Key)를 사용하여 공유 메모리 식별자를 생성한다.
- shmget(): 공유 메모리 세그먼트를 생성하거나 식별자를 얻음.
- shmat(): 생성된 세그먼트를 프로세스의 주소 공간에 연결(Attach).
- shmdt(): 연결을 해제(Detach).
- shmctl(): 세그먼트 제어 및 삭제.
4.2 POSIX Shared Memory
파일 시스템 기반의 인터페이스를 제공하며, 경로 이름(Path name)을 사용하여 더 직관적으로 관리한다.
- shm_open(): 공유 메모리 객체를 생성하거나 연다. (가상 파일 생성)
- ftruncate(): 공유 메모리의 크기를 설정한다.
- <a href="/doc/%EA%B8%B0%EC%88%A0/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D/API/mmap" class="wiki-link">mmap</a>(): 파일 디스크립터를 프로세스의 메모리 주소 공간에 매핑한다.
- munmap(): 매핑을 해제한다.
- shm_unlink(): 공유 메모리 객체 이름을 제거한다.
C/C++ 구현 예시 (POSIX 기준)
#include <stdio.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main() {
const char* name = "/my_shm"; // 공유 메모리 이름
const int SIZE = 4096; // 크기
// 1. 공유 메모리 객체 생성
int shm_fd = shm_open(name, O_CREAT | O_RDWR, 0666);
ftruncate(shm_fd, SIZE);
// 2. 프로세스 주소 공간에 매핑
void* ptr = mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
// 3. 데이터 쓰기
const char* message = "Hello, Shared Memory!";
// snprintf를 사용하여 버퍼 오버플로우 방지
snprintf((char*)ptr, SIZE, "%s", message);
printf("Data written to memory: %s\n", (char*)ptr);
// 4. 정리 및 해제
munmap(ptr, SIZE);
close(shm_fd);
shm_unlink(name);
return 0;
}
5. OS별 구현 차이
공유 메모리는 운영체제의 메모리 관리 방식에 따라 구현 세부 사항이 다르다.
- Linux/Unix: 위에서 언급한 POSIX 및 System V 표준을 따르며,
/dev/shm과 같은 가상 파일 시스템(tmpfs)을 통해 메모리 맵핑을 관리한다. - Windows: '파일 매핑(File Mapping)' 개념을 사용한다.
CreateFileMapping함수로 공유 메모리 객체를 생성하고,MapViewOfFile함수를 통해 프로세스 주소 공간에 연결한다. 이름이 지정된 공유 메모리(Named Shared Memory)를 통해 프로세스 간 식별이 가능하다.
6. 동기화 문제와 해결 방안
공유 메모리는 커널이 데이터 접근을 제어하지 않으므로, 여러 프로세스가 동시에 쓰기 작업을 수행할 때 경쟁 상태(Race Condition)가 발생하여 데이터 무결성이 파괴될 수 있다. 이를 방지하기 위해 반드시 동기화 도구를 함께 사용해야 한다.
동기화 도구 비교
| 도구 | 작동 방식 | 특징 | 적합한 사례 |
|---|---|---|---|
| 뮤텍스 (Mutex) | 상호 배제(Mutual Exclusion) 잠금 | 한 번에 하나의 프로세스만 접근 가능 | 단순한 임계 영역 보호 |
| 세마포어 (Semaphore) | 카운터 기반의 신호 체계 | 지정된 개수의 프로세스 접근 허용 가능 | 생산자-소비자 패턴, 자원 개수 제한 |
| 스핀락 (Spinlock) | 락을 얻을 때까지 루프를 돌며 대기 | 컨텍스트 스위칭 비용이 없으나 CPU 점유율 높음 | 매우 짧은 시간 동안의 락 유지 시 |
| 조건 변수 (Condition Variable) | 특정 조건이 충족될 때까지 대기 | 이벤트 기반의 프로세스 깨움 가능 | 복잡한 상태 변화 알림 |
동기화 도구 사용 예시
- 뮤텍스: 공유 메모리에 기록 중인 프로세스가 있을 때, 다른 프로세스가 접근하지 못하도록 잠금(Lock)을 걸어 데이터 오염을 방지한다.
- 세마포어: 공유 메모리를 큐(Queue)처럼 사용할 때, '데이터가 들어있는 슬롯의 개수'를 세마포어로 관리하여 소비 프로세스가 데이터가 있을 때만 읽어가도록 제어한다.
- 스핀락: 멀티코어 환경에서 아주 짧은 데이터를 업데이트할 때, 프로세스를 휴면 상태로 전환하는 비용보다 잠시 기다리는 비용이 적을 때 사용한다.
- 조건 변수: 프로세스 A가 공유 메모리에 특정 계산 결과를 기록한 후, 대기 중이던 프로세스 B에게 "데이터 준비 완료" 신호를 보내 즉시 처리하게 한다.
7. 메모리 누수 방지 및 해제 방법
공유 메모리는 프로세스가 종료되어도 커널 내에 상주하는 영속성(Persistence)을 가진다. 따라서 명시적으로 해제하지 않으면 시스템 메모리가 낭비되는 메모리 누수가 발생한다.
- 명시적 해제:
- POSIX:
shm_unlink()를 호출하여 이름 기반 객체를 제거해야 한다. - System V:
shmctl(shmid, IPC_RMID, NULL)을 통해 세그먼트를 삭제해야 한다. - 참조 횟수 관리:
munmap이나shmdt는 현재 프로세스의 연결만 끊는 것이며, 실제 메모리 삭제는 모든 프로세스가 연결을 끊고 삭제 명령이 내려졌을 때 수행된다. - 명령어 도구 활용 (Linux):
ipcs -m: 현재 생성된 공유 메모리 세그먼트 목록 확인.ipcrm -m [shmid]: 특정 식별자의 공유 메모리 강제 삭제.
8. 활용 사례
- 고성능 데이터베이스 엔진: MySQL, PostgreSQL 등의 버퍼 풀(Buffer Pool)은 디스크에서 읽어온 데이터를 공유 메모리에 올려 여러 쿼리 프로세스가 빠르게 접근하도록 한다.
- 실시간 비디오/오디오 스트리밍: 고해상도 프레임 데이터를 캡처 프로세스에서 처리 프로세스로 전달할 때 복사 비용을 줄이기 위해 사용한다.
- 대규모 데이터 분석 시스템: Apache Plasma와 같이 여러 언어(Python, C++, Java)로 작성된 프로세스들이 거대한 데이터프레임을 공유하여 분석하는 환경에서 활용된다.
- 운영체제 커널과 유저 공간 통신: 일부 고성능 드라이버는 링 버퍼(Ring Buffer) 형태의 공유 메모리를 통해 커널과 데이터를 주고받는다.
이 문서는 AI 모델(gemma-4-31b)에 의해 생성된 콘텐츠입니다.
주의사항: AI가 생성한 내용은 부정확하거나 편향된 정보를 포함할 수 있습니다. 중요한 결정을 내리기 전에 반드시 신뢰할 수 있는 출처를 통해 정보를 확인하시기 바랍니다.