QNX RTOS: 5-10. Shared Memory

2025. 12. 15. 15:04운영체제/QNX

1. Shared Memory(공유 메모리) 개념

  • 공유 메모리(shared memory): 두 개 이상 프로세스(process)가 같은 물리 메모리(physical memory) 영역을 각자 가상주소(virtual address)로 매핑(map)하여 함께 접근하는 IPC 방식.
  • 목적: 큰 데이터(large data)를 메시지 복사 없이 공유하여 성능 개선.
  • 메모리는 연속(contiguous)일 수도 있고 아닐 수도 있음(대개 “같은 메모리를 본다”가 핵심이지, 연속성은 보장하지 않음).


2. POSIX 방식: shm_open() 기반 Named Shared Memory(이름 있는 공유 메모리)

2.1 이름 규칙(Name rule)

  • POSIX 규칙:
    • leading slash(/) 없으면 undefined behavior
    • 중간 slash(embedded /)는 implementation-defined
  • QNX 동작:
    • leading slash 없으면 현재 작업 디렉토리(CWD)를 앞에 붙이는 방식으로 해석할 수 있음 → 원치 않는 동작 가능
  • 결론:
    • 항상 절대 경로 형태로 /name 사용 권장
    • 필요하면 /group/sub/name처럼 하위 분류로 namespace 충돌 방지 가능

3. “생성자(creator)”가 하는 절차 (POSIX Named)

공유메모리를 “처음 만들고 초기화”하는 쪽 기준 절차:

Step A. 생성: shm_open()

  • shm_open("/myname", O_RDWR | O_CREAT, 0600)
    • O_CREAT: 없으면 생성
    • O_EXCL 함께 사용 가능: O_CREAT | O_EXCL
      • 누가 먼저 만들지 모르는 다중 프로세스 환경에서 “최초 1명만 생성자” 판별 용도
    • 0600: 소유자만 읽기/쓰기 가능(권한 관리)

Step B. 크기 할당: ftruncate()

  • 생성 직후 shared memory object는 크기 0 → 실제 메모리 할당 필요
  • ftruncate(fd, size)
    • 요청 크기는 페이지(page) 크기 단위로 올림(round up)
    • QNX 일반 구현: 4KB(4K) 페이지
    • 예: 21KB 요청 → 24KB 할당

Step C. 매핑: mmap()

  • mmap(..., PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0)
    • MAP_SHARED: 진짜 공유(서로 같은 메모리 봄)
    • MAP_PRIVATE: copy-on-write/사본 → IPC 공유 목적과 반대
    • PROT_*는 MMU(Memory Management Unit) 보호 비트 설정
    • mmap 권한은 shm_open의 open mode와 일치해야 함
      (읽기 전용으로 열고 쓰기 매핑하면 실패)

Step D. 초기화(중요)

  • 새로 할당된 메모리는 zero-fill(0으로 초기화됨)
  • 그 다음에 사용자 정의 구조체/버퍼/동기화 객체 등을 초기화

Step E. FD 정리

  • mmap 후에는 보통 fd는 더 이상 필요 없으므로 close(fd) 가능
    (매핑은 유지됨)

4. “접근자(Accress)”가 하는 절차 (이미 만들어진 Named)

다른 프로세스가 이미 만들어 둔 공유메모리를 붙는 경우:

  1. shm_open("/myname", O_RDWR, 0)
    • 생성이 아니므로 O_CREAT 없음
    • mode 인자(세 번째)는 의미 없지만 함수 시그니처상 넣어야 하므로 0 넣는 관례
  2. mmap()
    • 필요 시 read-only로 매핑 가능
    • 전체가 아니라 부분 구간만 매핑도 가능
  3. close(fd)
    • 매핑 이후 fd는 닫아도 됨

부분 매핑 주의

  • offset/length로 일부만 매핑 가능하지만,
  • 실제 매핑/보호 단위는 페이지 정렬(page-aligned)
    • 예: offset 6KB로 매핑 요청 → 내부적으로 4KB 경계부터 매핑될 수 있음
    • 반환 포인터는 요청 offset 기준으로 맞춰주지만, 페이지 단위로 더 넓게 매핑될 수 있음

5. 언제 메모리가 “진짜로” 해제되는가? Reference Count(참조 카운트)

QNX 프로세스 매니저(Process Manager)가 참조 카운트(reference count)로 수명 관리:

참조는 보통 3종류:

  1. 이름(name): pathname 공간에 남아있는 엔트리
  2. 파일 디스크립터(fd): shm_open()로 얻은 핸들
  3. 매핑(mapping): mmap()으로 프로세스 주소공간에 매핑된 상태

정리 포인트:

  • 프로세스 종료 시:
    • fd는 자동 close
    • mapping은 자동 munmap
  • 하지만 이름(name)은 자동으로 제거되지 않음
    • 파일과 동일: 이름이 남아 있으면 객체도 남아 있음(단, 공유메모리는 “부팅 동안만” 유효)

이름 제거(삭제)

  • 표준 POSIX: shm_unlink("/myname")
  • QNX 디버그 편의:
    • 이름 있는 공유메모리는 /dev/shm 아래 파일처럼 보임
    • 예: shm_open("/myname") → /dev/shm/myname 같은 형태
    • 따라서 쉘에서 ls /dev/shm, rm /dev/shm/myname로 정리 가능

이름을 먼저 지우면?

  • shm_unlink()를 먼저 해도,
    • 이미 매핑된 프로세스가 있으면 메모리는 계속 존재
    • 단지 “이름으로 새로 찾는 것”만 불가능해짐
      → “익명화(anonymous-like)” 효과

6. Shared Memory의 대표 문제 2가지

(1) Access(접근) 문제

  • 이름 충돌(namespace collision): 서로 다른 팀/회사 코드가 같은 /myname 사용
  • 보안(security): 유닉스 권한(permissions)만으로 세밀한 접근 통제가 어렵다

(2) Synchronization(동기화) 문제

  • 공유메모리는 결국 “멀티프로세스에서의 공유 데이터”
    → 단일 프로세스 내 스레드 동기화 문제가 그대로 확장됨
  • 해결: 공유메모리 내부에 동기화 객체를 두고 사용

7. 공유메모리에서 동기화 객체 쓰는 법

7.1 세마포어(semaphore)

  • unnamed semaphore를 sem_init()로 만들 때:
    • pshared 인자를 0이 아닌 값으로 설정해야 프로세스 간 공유 가능

7.2 pthread 동기화(mutex/cond/rwlock 등)

  • pthread_mutexattr_*, pthread_condattr_* 등 attribute(속성 구조체) 사용
  • 반드시:
    • PTHREAD_PROCESS_SHARED 설정 필요
      (pthread_mutexattr_setpshared() 등)

7.3 단순 플래그/카운터

  • atomic(원자) 연산(atomic functions) 사용 가능

8. “프로세스가 죽으면 mutex가 잠긴 채로 남는” 문제와 Robust Mutex(강건 뮤텍스)

문제 상황

  • 프로세스 A가 공유메모리 mutex를 lock한 상태에서 프로세스 A가 크래시
  • 프로세스 B는 계속 살아 있고 그 mutex를 lock하려고 함
  • mutex는 논리적으로 “잠긴 상태” → 시스템이 교착/정지 위험

해결: Robust Mutex

  • mutex 생성 시 속성으로 robust 설정:
    • pthread_mutexattr_setrobust()
    • 공유메모리이면 추가로 pthread_mutexattr_setpshared()도 필요
      process-shared + robust 조합

동작 핵심

  • 프로세스가 죽은 상태에서 보호 데이터가 불완전할 수 있으므로,
  • 다음 lock 시에:
    • pthread_mutex_lock()가 EOWNERDEAD 반환
    • 의미: “이 mutex 소유자가 죽었고, 보호 데이터는 unknown state(알 수 없는 상태)”

복구 절차

  • 복구 가능하면:
    • 데이터를 known-good 상태로 복원
    • pthread_mutex_consistent() 호출 후 unlock
  • 복구 불가능하면:
    • pthread_mutex_consistent() 호출하지 않고 unlock
    • 이후 lock은 ENOTRECOVERABLE (복구 불가) 반환
    • 기다리던 다른 스레드들도 ENOTRECOVERABLE로 깨워질 수 있음
      → 상위 수준에서 재초기화/프로세스 재시작 같은 “메타 복구” 필요

9. 실무에서 자주 쓰는 패턴: “IPC로 제어, Shared Memory로 데이터”

  • MsgSend()/MsgReceive()/MsgReply()는 자체적으로 동기화 성질이 있음
    • client는 reply 받을 때까지 reply-blocked
    • 서버가 receive~reply 구간에서만 shared memory를 읽는 규칙을 두면 동기화가 쉬움
  • 그래서 흔한 설계:
    • 제어/메타데이터(control/metadata): 메시지(IPC)로 전달
    • 대용량 데이터(bulk data): shared memory로 전달
  • 예: 그래픽 서버
    • client가 공유메모리에 이미지 생성 → 메시지로 “image #5 보여줘” 전달 → 서버가 처리 후 reply
    • 데이터 복사 오버헤드 감소 + 반복 재사용 가능

10. Anonymous Shared Memory(익명 공유메모리) + Handle(핸들) 방식

이름 기반(/myname)의 문제(충돌/보안)를 줄이기 위한 방식.

핵심 아이디어

  • pathname으로 찾지 않고,
  • 서버가 만든 익명 shared memory를 특정 client에게만 유효한 handle로 전달하여 사용하게 함.

생성

  • shm_open(SHM_ANON, ...)
    • 이름이 없음
    • 각 SHM_ANON 호출은 서로 다른 객체(“같은 이름이면 같은 객체”가 아님)

handle 생성: shm_create_handle()

  • 입력:
    • 익명 shm의 fd
    • 허용 대상 프로세스 pid(사용자 pid)
    • client에게 허용할 최대 권한(permissions)
  • 출력:
    • handle 값(성공 시 갱신됨)

client에서 열기

  • shm_open_handle(handle, flags)
  • 또는 더 안전한:
    • shm_open_handle_pid(handle, server_pid, flags)
    • “이 handle이 내가 믿는 서버 pid에서 온 게 맞는지” 확인(중간자 공격 방지 개념)

'운영체제 > QNX' 카테고리의 다른 글

QNX RTOS: 6-2. IPC 선택 기준  (0) 2025.12.15
QNX RTOS: 6-1. IPC Methods  (0) 2025.12.15
QNX RTOS: 5-9. Event Delivery  (0) 2025.12.15
QNX RTOS: 5-8. Deadlock Avoidance  (0) 2025.12.03
QNX RTOS: 5-7. Server Designs  (0) 2025.12.02