Interview/OS

Multiprocess 간 IPC 중 Pipe 통신

김 정출 2024. 9. 27. 17:51

Multiprocess 간의 통신(IPC, Inter-Process Communication)은 서로 다른 프로세스가 데이터를 주고받거나 리소스를 공유하는 방법입니다. 운영체제에서 프로세스는 독립된 메모리 공간을 가지기 때문에 직접적으로 데이터를 공유할 수 없습니다. 따라서 IPC 기법을 사용하여 프로세스 간의 데이터를 전달하거나 동기화해야 합니다.

LinuxUnix에서 사용하는 다양한 IPC 방법들을 아래에 설명합니다.

1. 파이프 (Pipe)

  • 파이프는 두 프로세스 간에 단방향 통신을 위한 방법입니다. 일반적으로 한 프로세스는 데이터를 쓰고, 다른 프로세스는 해당 데이터를 읽습니다.
  • _익명 파이프(Anonymous Pipe)*_와 이름 있는 파이프(Named Pipe, FIFO) 두 가지 종류가 있습니다.

익명 파이프 (Anonymous Pipe)

  • 부모 프로세스와 자식 프로세스 간에 통신할 때 주로 사용됩니다.
  • pipe() 시스템 호출을 통해 읽기/쓰기용 파일 디스크립터를 생성합니다.
  • 단방향이므로 양방향 통신이 필요하면 두 개의 파이프를 사용해야 합니다.
  • int pipefd[2];
  • pipe(pipefd); // pipefd[0]은 읽기, pipefd[1]은 쓰기
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main() {
    int fd[2];  // 파이프용 파일 디스크립터 배열: fd[0] = 읽기, fd[1] = 쓰기
    pid_t pid;
    char write_msg[] = "Hello from parent!";
    char read_msg[100];

    // 파이프 생성
    if (pipe(fd) == -1) {
        perror("pipe failed");
        return 1;
    }

    // 자식 프로세스 생성
    pid = fork();

    if (pid < 0) {
        perror("fork failed");
        return 1;
    }

    if (pid > 0) {  // 부모 프로세스
        close(fd[0]);  // 읽기 끝을 닫음
        // 자식 프로세스에 메시지 전송
        write(fd[1], write_msg, strlen(write_msg) + 1);
        close(fd[1]);  // 쓰기 끝을 닫음
    } else {  // 자식 프로세스
        close(fd[1]);  // 쓰기 끝을 닫음
        // 부모로부터 메시지 읽기
        read(fd[0], read_msg, sizeof(read_msg));
        printf("Received from parent: %s\n", read_msg);
        close(fd[0]);  // 읽기 끝을 닫음
    }

    return 0;
}

코드 설명

  • pipe(fd)는 두 개의 파일 디스크립터를 생성합니다. fd[0]은 읽기 전용, fd[1]은 쓰기 전용입니다.
  • fork()를 사용하여 프로세스를 생성합니다. 자식 프로세스와 부모 프로세스가 생성된 후, 두 프로세스는 서로 독립적으로 실행됩니다.
  • 부모 프로세스는 fd[1]을 사용하여 데이터를 자식 프로세스로 보냅니다.
  • 자식 프로세스는 fd[0]을 통해 부모로부터 데이터를 읽고 출력합니다.
  • 양쪽에서 사용하지 않는 파이프의 읽기 또는 쓰기 끝은 닫습니다.

이름 있는 파이프 (Named Pipe, FIFO)

  • FIFO는 파이프에 이름을 붙여 서로 다른 관계의 프로세스 간에도 통신을 가능하게 한 방식입니다.
  • mkfifo() 시스템 호출을 사용하여 파이프 파일을 생성하고, 서로 다른 프로세스들이 해당 파이프를 열어 읽기/쓰기 작업을 할 수 있습니다.

mkfifo() 동작 방식

  1. 파일 시스템에 이름 있는 파이프 생성:
    • mkfifo() 호출 시, 지정된 경로에 FIFO 특수 파일이 생성됩니다.
    • 이 파일은 일반적인 파일처럼 파일 시스템에 존재하지만, 실제로는 파이프 통신을 위한 특수한 파일입니다.
    • 프로세스는 이 파이프 파일을 열어 읽기/쓰기 작업을 통해 통신할 수 있습니다.
  2. 읽기/쓰기 동작:
    • 이름 있는 파이프는 일반적인 파일처럼 open(), read(), write() 등을 사용하여 접근합니다.
    • 하나의 프로세스는 파이프에 데이터를 쓰고, 다른 프로세스는 해당 파이프에서 데이터를 읽습니다.
    • `FIFO단방향 통신이므로, 양방향 통신을 하려면 두 개의 FIFO 파일을 만들어 각각의 방향에서 읽고 쓰는 식`으로 구현해야 합니다.
  3. 블로킹 동작:
    • 읽기 프로세스가 없을 때 쓰기 프로세스는 블로킹 상태가 됩니다. 즉, 쓰는 프로세스는 읽을 프로세스가 준비될 때까지 대기합니다.
    • 마찬가지로, 쓰기 프로세스가 없을 때 읽기 프로세스는 블로킹됩니다. 이는 파이프가 서로 다른 프로세스 간에 동기화된 통신을 보장하도록 도와줍니다.
    • 읽기와 쓰기 프로세스가 양방향으로 블로킹됨
  4. 메시지 순서 유지:
    • 파이프에서 데이터는 순차적으로 전달됩니다. 즉, FIFO 구조(First In, First Out)에 따라 먼저 입력된 데이터가 먼저 출력됩니다.
    • 파이프를 통한 데이터는 스트림으로 다뤄지며, 메시지의 경계를 따로 구분하지 않기 때문에 연속된 바이트로 읽고 써야 합니다.
#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);

mkfifo("/tmp/myfifo", 0666);

// 쓰기 모드로 FIFO 열기
int fd = open(fifo_path, O_WRONLY);

// 데이터 쓰기
write(fd, "Hello from writer!", 18);
close(fd);

---
// 다른 프로세스
char buffer[100];

// 읽기 모드로 FIFO 열기
int fd = open(fifo_path, O_RDONLY);

// 데이터 읽기
read(fd, buffer, sizeof(buffer));
printf("Received: %s\n", buffer);