본문 바로가기

IT

프로세스 간 통신의 이해 : 공유 메모리와 파이프

들어가며: 프로세스 간 통신이 필요한 이유

현대의 운영체제에서는 여러 프로세스가 동시에 실행되며, 이들은 종종 서로 데이터를 주고받을 필요가 있습니다. 예를 들어:

  • 웹 브라우저가 다운로드 관리자와 통신
  • 데이터베이스 서버가 여러 클라이언트 프로세스와 통신
  • 그래픽 인터페이스가 백그라운드 작업 프로세스와 통신

이러한 통신을 가능하게 하는 것이 프로세스 간 통신(Inter-Process Communication, IPC) 메커니즘입니다.

 

IPC의 주요 메커니즘

운영체제는 다양한 IPC 메커니즘을 제공합니다:

  • 공유 메모리 (Shared Memory)
  • 파이프 (Pipes)
  • 메시지 큐 (Message Queues)
  • 소켓 (Sockets)
  • 시그널 (Signals)

이 글에서는 가장 기본적이면서도 많이 사용되는 공유 메모리와 파이프에 대해 자세히 알아보겠습니다.

 

1. POSIX 공유 메모리 (Shared Memory)

1.1 공유 메모리란?

공유 메모리는 가장 빠른 IPC 메커니즘입니다. 두 개 이상의 프로세스가 동일한 메모리 영역을 공유하여 직접 데이터를 주고받을 수 있습니다.

 

POSIX 공유 메모리는 효율적인 IPC 메커니즘 중 하나입니다. POSIX(Portable Operating System Interface)는 유닉스 계열 운영체제의 표준을 정의합니다.

 

1.2 동작 원리

[프로세스 A] ↔ [공유 메모리 영역] ↔ [프로세스 B]
  • memory-mapped file 기술 사용
    • 파일을 메모리에 매핑하여 직접 접근
    • 파일 I/O 없이 메모리 접근만으로 데이터 공유
  • 커널의 개입 없이 직접 데이터 교환
  • 일반적인 메모리 접근과 동일한 속도

 

1.3 장단점

장점:

  • 매우 빠른 데이터 전송 속도
  • 큰 데이터 공유에 효율적
  • 구현이 상대적으로 단순

단점:

  • 동기화 메커니즘 필요
  • 잘못 사용 시 데이터 일관성 문제 발생
  • 보안 위험 가능성

 

1.4 구현 예제와 설명

Producer (데이터 생산자)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/mman.h>

int main()
{
    // 상수 정의
    const int SIZE = 4096;   // 공유 메모리 크기 (4KB)
    const char *name = "OS"; // 공유 메모리 식별자
    const char *message_0 = "Hello, ";
    const char *message_1 = "Shared Memory!\n";

    // 변수 선언
    int shm_fd;     // 공유 메모리 파일 디스크립터
    char *ptr;      // 공유 메모리 포인터

    /* 1단계: 공유 메모리 객체 생성 */
    shm_fd = shm_open(name, O_CREAT | O_RDWR, 0666);
    if (shm_fd == -1) {
        printf("공유 메모리 생성 실패\n");
        exit(1);
    }

    /* 2단계: 공유 메모리 크기 설정 */
    if (ftruncate(shm_fd, SIZE) == -1) {
        printf("공유 메모리 크기 설정 실패\n");
        exit(1);
    }

    /* 3단계: 메모리 매핑 */
    ptr = (char *)mmap(0, SIZE, 
                      PROT_READ | PROT_WRITE, 
                      MAP_SHARED, shm_fd, 0);
    if (ptr == MAP_FAILED) {
        printf("메모리 매핑 실패\n");
        exit(1);
    }

    /* 4단계: 데이터 쓰기 */
    sprintf(ptr, "%s", message_0);
    ptr += strlen(message_0);
    sprintf(ptr, "%s", message_1);

    return 0;
}

 

Consumer (데이터 소비자)

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/mman.h>

int main()
{
    const int SIZE = 4096;
    const char *name = "OS";
    
    int shm_fd;
    char *ptr;

    /* 1단계: 기존 공유 메모리 열기 */
    shm_fd = shm_open(name, O_RDONLY, 0666);
    if (shm_fd == -1) {
        printf("공유 메모리 열기 실패\n");
        exit(1);
    }

    /* 2단계: 메모리 매핑 (읽기 전용) */
    ptr = (char *)mmap(0, SIZE, 
                      PROT_READ, 
                      MAP_SHARED, shm_fd, 0);
    if (ptr == MAP_FAILED) {
        printf("메모리 매핑 실패\n");
        exit(1);
    }

    /* 3단계: 데이터 읽기 */
    printf("%s", (char *)ptr);

    /* 4단계: 정리 작업 */
    munmap(ptr, SIZE);
    shm_unlink(name);

    return 0;
}

 

1.5 디버깅 및 오류 처리 팁

  1. 메모리 누수 방지
    • munmap() 호출로 매핑 해제
    • shm_unlink()로 공유 메모리 객체 제거
  2. 권한 설정
    • 적절한 접근 권한 설정 (예: 0666)
    • 보안을 고려한 제한적 권한 사용
  3. 오류 상황 처리
    • 모든 시스템 콜의 반환 값 확인
    • 적절한 에러 메시지 출력
    • 자원 정리 후 종료

 

 

2. 파이프 (Pipes)

2.1 파이프의 개념

파이프는 UNIX 시스템의 가장 오래된 IPC 메커니즘 중 하나입니다. 두 프로세스 간의 데이터 통신을 위한 단순하면서도 효과적인 방법을 제공합니다.

 

2.2 파이프 구현 시 핵심 고려 사항

파이프를 구현할 때는 다음 세 가지 핵심 사항을 고려해야 합니다:

 

1. 통신 방향

파이프의 데이터 흐름 방향에 따라 세 가지 방식으로 구현할 수 있습니다:

  • 단방향 통신 (Simplex)
    • 한 방향으로만 데이터 전송
    • 구현이 단순하고 오류 가능성 적음
    • 예: 부모 프로세스가 자식 프로세스에 작업 지시
  • 반이중 통신 (Half-duplex)
    • 양방향 통신 가능하나 동시에는 불가
    • 한 번에 한 방향으로만 데이터 전송
    • 예: 채팅 프로그램의 기본 구현
  • 전이중 통신 (Full-duplex)
    • 동시에 양방향 통신 가능
    • 두 개의 파이프 필요
    • 구현이 복잡하나 효율적
    • 예: 실시간 양방향 통신 필요한 경우

2. 프로세스 관계

프로세스 간의 관계는 파이프 구현 방식에 큰 영향을 미칩니다:

  • 부모-자식 관계
    • Ordinary Pipes 사용 가능
    • fork() 후 파이프 공유
    • 보안성 높음
  • 독립적 프로세스
    • Named Pipes 필요
    • 프로세스 간 관계없어도 통신 가능
    • 파일 시스템을 통한 접근

3. 네트워크 통신

파이프의 네트워크 확장성도 중요한 고려 사항입니다:

  • 로컬 시스템 제한
    • 기본 파이프는 같은 시스템 내에서만 동작
    • 네트워크 통신 불가
    • 높은 성능과 안정성
  • 네트워크 통신 필요 시
    • 소켓 등 다른 IPC 메커니즘 사용 필요
    • Named Pipes의 네트워크 파일 시스템 활용 가능
    • 보안과 성능 트레이드오프 고려

 

2.2 파이프의 종류

Ordinary Pipes (일반 파이프)

[프로세스 A] → [파이프] → [프로세스 B]
  • 단방향 통신만 가능
  • 부모-자식 프로세스 간에만 사용 가능
  • 파일 시스템에 존재하지 않음

Named Pipes (이름 있는 파이프)

[프로세스 A] → [named pipe] → [프로세스 B]
(파일 시스템에 존재)
  • 관련 없는 프로세스 간 통신 가능
  • 파일 시스템에 실제 파일로 존재
  • mkfifo 명령어로 생성 가능

 

2.3 파이프 통신 예제

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>

#define BUFFER_SIZE 25
#define READ_END 0
#define WRITE_END 1

int main()
{
    char write_msg[BUFFER_SIZE] = "Hello, Pipe!";
    char read_msg[BUFFER_SIZE];
    int fd[2];
    pid_t pid;

    /* 1단계: 파이프 생성 */
    if (pipe(fd) == -1) {
        fprintf(stderr, "파이프 생성 실패\n");
        return 1;
    }

    /* 2단계: 프로세스 fork */
    pid = fork();
    
    if (pid < 0) {
        fprintf(stderr, "Fork 실패\n");
        return 1;
    }

    /* 3단계: 부모/자식 프로세스 각각의 동작 */
    if (pid > 0) { // 부모 프로세스
        close(fd[READ_END]);  // 읽기 끝 닫기
        write(fd[WRITE_END], write_msg, strlen(write_msg) + 1);
        close(fd[WRITE_END]); // 쓰기 끝 닫기
    }
    else { // 자식 프로세스
        close(fd[WRITE_END]); // 쓰기 끝 닫기
        read(fd[READ_END], read_msg, BUFFER_SIZE);
        printf("읽은 메시지: %s\n", read_msg);
        close(fd[READ_END]);  // 읽기 끝 닫기
    }

    return 0;
}

 

2.4 파이프 사용 시 주의 사항

  1. 파이프 종단점 관리
    • 사용하지 않는 종단점은 즉시 닫기
    • 모든 쓰기 종단점이 닫히면 읽기 시 EOF 반환
    • 모든 읽기 종단점이 닫힌 상태에서 쓰기 시 SIGPIPE 시그널 발생
  2. 버퍼 관리
    • PIPE_BUF 크기 고려 (시스템별로 다름)
    • 큰 데이터는 여러 번에 나누어 전송
    • 버퍼 오버플로우 방지
  3. 동기화 주의 사항
    • 읽기/쓰기 순서 보장 필요
    • 데드락 상황 방지
    • 적절한 에러 처리

 

정리 및 비교

공유 메모리 vs 파이프 비교

특성 공유 메모리 파이프
속도 매우 빠름 중간
구현 복잡도 중간 낮음
동기화 처리 프로그래머가 직접 구현 필요 자동으로 처리됨
데이터 지속성 프로세스 종료 시까지 일회성
양방향 통신 가능 단방향(두 개 필요)
버퍼 관리 직접 관리 필요 운영체제가 관리

 

사용 시나리오

  1. 공유 메모리 선택 시
    • 대용량 데이터 공유 필요
    • 최고의 성능 필요
    • 복잡한 데이터 구조 공유
  2. 파이프 선택 시
    • 간단한 데이터 스트림 전송
    • 부모-자식 프로세스 간 통신
    • 자동 동기화 필요

'IT' 카테고리의 다른 글

스레드의 이해  (1) 2025.02.24
프로세스 간 통신의 이해 : 소켓과 RPC  (1) 2025.02.23
프로세스 간 통신(IPC)  (0) 2025.02.22
프로세스의 생성  (0) 2025.02.20
프로세스의 이해  (0) 2025.02.20