들어가며
지난 글에서 FIFO (Named Pipe)를 다뤘는데, 실습하면서 한 가지 아쉬운 점이 있었습니다. 바로 속도였죠. 간단한 메시지 주고받기에는 충분했지만, 큰 데이터를 전송하려니 느렸습니다.
왜 느릴까요? FIFO는 Kernel을 거쳐서 데이터를 복사하기 때문입니다.
FIFO 방식:
Writer → [User Space]
↓ (복사!)
[Kernel Space]
↓ (또 복사!)
[User Space] → Reader
→ 2번 복사! 느림!
"메모리를 그냥 공유하면 안 되나?" 라는 생각이 들었습니다. 그게 바로 오늘 다룰 Shared Memory (공유 메모리)입니다.
Shared Memory란?
개념
Shared Memory는 여러 프로세스가 같은 메모리 영역을 공유하는 IPC 방식입니다. 복사 없이 직접 접근하기 때문에 IPC 중 가장 빠릅니다.
Shared Memory 방식:
Process A ──→ [Shared Memory] ←── Process B
(공유된 메모리)
→ 복사 없음! 빠름!
성능 비교 (1MB 데이터 전송)
직접 벤치마크를 돌려봤습니다:
FIFO: 8.5 ms
Shared Memory: 0.8 ms
→ 약 10배 차이
FIFO vs Shared Memory
| 특징 | FIFO | Shared Memory |
|---|---|---|
| 속도 | 보통 (~8ms) | 매우 빠름 (~0.8ms) |
| 복사 | 2번 (User→Kernel→User) | 0번 |
| 동기화 | 자동 (Kernel이 처리) | 수동 (Semaphore 필요) |
| 사용 난이도 | 쉬움 | 중간 |
| 데이터 크기 | 작은 메시지 | 큰 데이터 |
언제 뭘 쓸까?
- 간단한 통신 → FIFO
- 대용량 데이터 → Shared Memory
- 실시간 성능 중요 → Shared Memory
실전: Shared Memory 구현
이론은 여기까지! 직접 만들어 봅시다.
POSIX vs System V
Shared Memory에는 두 가지 API가 있습니다:
System V: 오래된 방식 (shmget, shmat)
POSIX: 최신 방식 (shm_open, mmap)
→ POSIX를 사용합니다 (더 깔끔함)
1단계: Shared Memory 생성
#include <sys/mman.h>
#include <fcntl.h>
#define SHM_NAME "/my_shm"
#define SHM_SIZE 4096
int main(void)
{
// 1. Shared Memory 객체 생성
int fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
if (fd == -1) {
perror("shm_open");
exit(1);
}
// 2. 크기 설정
ftruncate(fd, SHM_SIZE);
// 3. 메모리 매핑
void *ptr = mmap(NULL, SHM_SIZE,
PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) {
perror("mmap");
exit(1);
}
printf("Shared Memory created!\n");
printf("Address: %p\n", ptr);
// 이제 ptr을 일반 메모리처럼 사용!
strcpy(ptr, "Hello, Shared Memory!");
return 0;
}
2단계: Writer 프로그램
// shm_writer.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#define SHM_NAME "/chat_shm"
#define SHM_SIZE 4096
int main(void)
{
// SHM 생성
int fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
ftruncate(fd, SHM_SIZE);
// 메모리 매핑
char *ptr = mmap(NULL, SHM_SIZE,
PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
printf("=== Writer ===\n");
printf("Enter messages (type 'quit' to exit):\n");
while (1) {
printf("You: ");
fgets(ptr, SHM_SIZE, stdin);
if (strncmp(ptr, "quit", 4) == 0)
break;
printf("Written to shared memory!\n");
sleep(1); // Reader가 읽을 시간
}
// 정리
munmap(ptr, SHM_SIZE);
close(fd);
shm_unlink(SHM_NAME);
return 0;
}
3단계: Reader 프로그램
// shm_reader.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#define SHM_NAME "/chat_shm"
#define SHM_SIZE 4096
int main(void)
{
// SHM 열기
int fd = shm_open(SHM_NAME, O_RDONLY, 0666);
if (fd == -1) {
perror("shm_open");
exit(1);
}
// 메모리 매핑
char *ptr = mmap(NULL, SHM_SIZE,
PROT_READ,
MAP_SHARED, fd, 0);
printf("=== Reader ===\n");
printf("Reading from shared memory...\n");
char prev[SHM_SIZE] = {0};
while (1) {
// 내용이 바뀌었으면 출력
if (strcmp(ptr, prev) != 0) {
printf("Received: %s", ptr);
strcpy(prev, ptr);
if (strncmp(ptr, "quit", 4) == 0)
break;
}
usleep(100000); // 100ms마다 체크
}
// 정리
munmap(ptr, SHM_SIZE);
close(fd);
return 0;
}
컴파일 & 실행
# 컴파일 (중요: -lrt 옵션!)
$ gcc shm_writer.c -o writer -lrt
$ gcc shm_reader.c -o reader -lrt
# 터미널 1
$ ./reader
=== Reader ===
Reading from shared memory...
# 터미널 2
$ ./writer
=== Writer ===
Enter messages:
You: Hello!
Written to shared memory!
# 터미널 1 출력
Received: Hello!
함정: 동기화 문제
처음 실행했을 때 이상한 현상이 발생했습니다.
Writer: "Hello World!"
Reader: "Hello Wor"
Reader: "Hello World!"
Reader: "Hello World!!@#$%" ← 쓰레기 값!
문제: Writer가 쓰는 도중에 Reader가 읽음!
Race Condition
T0: Writer: ptr[0] = 'H'
T1: Writer: ptr[1] = 'e'
T2: Reader: 읽기! → "He" (불완전!)
T3: Writer: ptr[2] = 'l'
...
이게 바로 Race Condition입니다. FIFO는 Kernel이 알아서 처리해줬는데, Shared Memory는 우리가 직접 처리해야 합니다!
해결책: Semaphore
Semaphore로 동기화를 구현합니다.
#include <semaphore.h>
sem_t *sem;
// Writer
sem = sem_open("/my_sem", O_CREAT, 0666, 1);
sem_wait(sem); // Lock
strcpy(ptr, msg); // 쓰기
sem_post(sem); // Unlock
// Reader
sem_wait(sem); // Lock
strcpy(buf, ptr); // 읽기
sem_post(sem); // Unlock
완전한 코드:
// shm_writer_sync.c
int main(void)
{
int fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
ftruncate(fd, SHM_SIZE);
char *ptr = mmap(NULL, SHM_SIZE,
PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
// Semaphore 생성
sem_t *sem = sem_open("/chat_sem", O_CREAT, 0666, 1);
while (1) {
printf("You: ");
char msg[256];
fgets(msg, sizeof(msg), stdin);
if (strncmp(msg, "quit", 4) == 0)
break;
// Critical Section
sem_wait(sem);
strcpy(ptr, msg);
sem_post(sem);
printf("Written safely!\n");
}
// 정리
sem_close(sem);
sem_unlink("/chat_sem");
munmap(ptr, SHM_SIZE);
close(fd);
shm_unlink(SHM_NAME);
return 0;
}
이제 안전합니다!
Shared Memory의 특징
1. 파일처럼 보이지만 메모리
# SHM 확인
$ ls -l /dev/shm/
-rw-r--r-- 1 user user 4096 Feb 27 15:00 chat_shm
→ /dev/shm에 파일처럼 생김
→ 실제로는 RAM!
2. 프로세스 종료 후에도 남아있음
$ ./writer # 실행 후 종료
$ ls /dev/shm/
chat_shm # 여전히 존재!
→ 명시적으로 삭제 필요: shm_unlink()
3. 크기 제한
# 시스템 최대 크기 확인
$ cat /proc/sys/kernel/shmmax
18446744073692774399 # 약 16EB (충분함)
# 실제 사용 중인 SHM
$ ipcs -m
실무 활용 사례
1. 고성능 데이터베이스
PostgreSQL, Redis 등:
- Index를 SHM에 캐싱
- 여러 프로세스가 동시 접근
- 빠른 읽기 성능
2. 멀티미디어 처리
비디오/오디오 스트리밍:
- 프레임 버퍼를 SHM에
- Encoder/Decoder가 공유
- 복사 없이 처리 → 실시간 가능
3. IPC 최적화
대용량 데이터 전송:
- 파일 공유
- 메모리 캐시
- 공유 큐
주의사항
1. 동기화 필수!
// ❌ 위험
strcpy(shm_ptr, data); // Race Condition!
// ✅ 안전
sem_wait(sem);
strcpy(shm_ptr, data);
sem_post(sem);
2. 크기 설정 필수
// ftruncate() 없으면 크기가 0!
shm_open(...);
ftruncate(fd, SIZE); // 필수!
mmap(...);
3. 정리 필수
// 프로그램 종료 시
munmap(ptr, SIZE);
close(fd);
shm_unlink(SHM_NAME); // 삭제!
안 하면 /dev/shm에 파일 계속 남음!
성능 측정
직접 벤치마크를 돌려봤습니다.
테스트 환경
CPU: Apple M1
RAM: 16GB
OS: macOS (Linux 유사)
데이터: 1MB
결과
// FIFO
for (int i = 0; i < 1000; i++) {
write(fifo_fd, data, 1024);
}
→ 평균: 8.5ms
// Shared Memory
for (int i = 0; i < 1000; i++) {
memcpy(shm_ptr, data, 1024);
}
→ 평균: 0.8ms
속도 차이: 약 10배!
디버깅 팁
SHM 확인
# 현재 SHM 목록
$ ls -lh /dev/shm/
# SHM 크기 확인
$ du -h /dev/shm/chat_shm
4.0K /dev/shm/chat_shm
# SHM 내용 보기
$ cat /dev/shm/chat_shm
Hello, Shared Memory!
ipcs 명령어
# System V SHM (옛날 방식)
$ ipcs -m
# 특정 SHM 삭제
$ ipcrm -m <shmid>
gdb로 메모리 확인
$ gdb ./writer
(gdb) break main
(gdb) run
(gdb) print ptr
$1 = 0x7f1234567000
(gdb) x/10s 0x7f1234567000
0x7f1234567000: "Hello"
마치며
Shared Memory를 구현하면서 속도와 안전성의 트레이드오프를 배웠습니다.
배운 점:
- 복사 없는 직접 접근 → 10배 빠름
- 동기화는 직접 해야 함 (Semaphore)
- ftruncate() 필수
- shm_unlink()로 정리 필수
- /dev/shm에서 확인 가능
FIFO는 간단하지만 느리고, Shared Memory는 빠르지만 복잡합니다. 상황에 맞게 선택하는 게 중요합니다.
'Dev > System Programming' 카테고리의 다른 글
| FIFO (Named Pipe) : 프로세스 간 통신의 기본 (0) | 2026.02.22 |
|---|
