시프 09- 파이프와 리다이렉션

강준호·2024년 6월 1일

시스템프로그래밍

목록 보기
6/18
post-thumbnail

파이프와 리다이렉션

프로세스 간 통신 (Interprocess Communication)

  • 이제 대 멀티프로세스 시대니까 -> IPC 가 중요. 파이프와 리다이렉션이 중요해짐.
  • 프로세스는 파이프를 통해 파일을 읽고 쓰기함

IPC 개요

  • UNIX 파이프는 프로세스 간 통신 (IPC)의 한 형태입니다.
  • IPC는 프로세스가 협력할 수 있는 메커니즘을 제공합니다.
  • POSIX 시스템에서 사용 가능한 다양한 IPC 형태:
    • 파이프 (Pipes)
    • 소켓 (Sockets)
    • 공유 메모리 (Shared Memory)
    • 신호 (Signals)
    • 프로세스 반환 값 (Process Return Values)
    • 환경 변수 (Environment Variables)

UNIX 파이프

파이프의 역사와 개념

  • UNIX 파이프는 1972년에 도입되었습니다.
  • 파이프는 IPC를 위한 파일과 같은 추상화를 제공합니다:
    • 파이프의 한 쪽 끝에 데이터를 쓰면 다른 쪽 끝에서 읽을 수 있습니다.
    • 읽기 및 쓰기 작업은 UNIX I/O 함수를 사용합니다.
    • 파이프는 파일 디스크립터로 표현됩니다.

표준 I/O와 파이프

  • 파이프는 어떤 파일 디스크립터에든 연결될 수 있습니다.
  • 특히, 표준 I/O 파일 디스크립터(0-2)에 파이프를 연결하면 유용합니다.
    • 많은 UNIX 유틸리티가 표준 입력(파일 디스크립터 0)에서 읽고, 표준 출력(파일 디스크립터 1)으로 씁니다.
    • 한 프로세스의 출력을 다른 프로세스의 입력으로 사용할 수 있습니다.

파이프 생성

  • pipe() 시스템 호출은 두 개의 파일 디스크립터로 구성된 파이프를 생성합니다.
  • 파이프를 생성하기 위해 int arr[2] 배열을 선언하고 pipe(arr)를 호출합니다.
    • arr[0]은 읽기 전용 파일 디스크립터.
    • arr[1]은 쓰기 전용 파일 디스크립터.
int pipefd[2];
if (pipe(pipefd) < 0) {
    perror("pipe");
}

파이프의 작동 메커니즘

커널 버퍼

  • 각 파이프는 파일 디스크립터를 통해 접근되는 커널 버퍼입니다.
  • 버퍼가 가득 차면 쓰기 작업이 블록되고, 버퍼가 비면 읽기 작업이 블록됩니다.
  • 단일 프로세스 내에서 파이프 통신은 데드락에 취약할 수 있습니다.

fork와 파이프

fork와 파이프의 동작

  • fork() 시스템 호출은 파일 디스크립터 테이블을 복제합니다.
  • 파일 디스크립터가 전역 열린 파일 테이블을 가리키기 때문에, 새 테이블에서 파일 메타데이터는 동일합니다.
  • 파일 위치 및 기타 열린 파일 속성의 변경 사항은 두 디스크립터에 영향을 미칩니다.

파일 디스크립터 인덱스

  • 파일 디스크립터는 작은 정수로, 프로세스의 열린 파일에 대한 커널 테이블의 인덱스입니다.
  • 각 프로세스는 자체 파일 디스크립터 테이블을 가집니다.
  • 이 테이블의 항목은 전역 열린 파일 테이블을 가리킵니다.

전역 열린 파일 테이블

  • 파일의 현재 위치, 파일 시스템 및 디스크 위치(또는 장치 상태), 열린 파일의 권한 및 모드를 유지 관리합니다.
  • 이 테이블의 메타데이터는 최소한의 오버헤드로 열린 파일에 접근할 수 있게 합니다.

파일 디스크립터 관리

  • 사용하지 않는 파일 디스크립터는 닫는 것이 좋은 습관입니다.
    • 예: 읽기 끝(arr[0])에서 쓰기 작업이 필요하지 않으면 close(arr[1])을 호출하여 닫습니다.
    • 반대로, 쓰기 끝(arr[1])에서 읽기 작업이 필요하지 않으면 close(arr[0])을 호출합니다.

파일 디스크립터 복사 및 리다이렉션

파일 디스크립터 복사

  • 파일 디스크립터는 명시적으로 복사될 수 있습니다.
  • 이는 동일한 열린 파일 테이블 항목을 가리키는 두 파일 디스크립터를 생성합니다.
  • 프로세스가 생성될 때 자동으로 생성되는 세 개의 표준 파일 디스크립터가 있습니다:
    • stdin (표준 입력): 파일 디스크립터 0
    • stdout (표준 출력): 파일 디스크립터 1
    • stderr (표준 오류): 파일 디스크립터 2

dupdup2

  • dup() 시스템 호출은 파일 디스크립터를 복사합니다.
  • dup2() 시스템 호출은 대상 파일 디스크립터를 지정할 수 있습니다.
#include <unistd.h>

int dup(int fd);
int dup2(int oldfd, int newfd);

표준 출력 리다이렉션

단계 1: 파일 열기

  • stdout이 리다이렉션될 파일을 엽니다.
int fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) {
    perror("open");
    exit(1);
}

단계 2: 파일 디스크립터 복사

  • dup2()를 사용하여 stdout을 새로운 파일 디스크립터로 복사합니다.
dup2(fd, 1);

전체 과정

  1. 파일을 엽니다.
  2. dup2()를 사용하여 파일 디스크립터를 복사합니다.
  3. 프로그램을 실행하여 출력이 파일로 리다이렉션되도록 합니다.

버퍼 용량과 데드락

버퍼 용량

  • 파이프의 버퍼는 유한한 용량을 가집니다.
  • 단일 프로세스에서 파이프 크기보다 큰 데이터를 읽거나 쓸 수 없습니다.
    • 파이프의 버퍼가 가득 차면 쓰기 작업이 블록됩니다.
    • 파이프의 버퍼가 비어 있으면 읽기 작업이 블록됩니다.

데드락 방지

  • 단일 프로세스에서 파이프 크기보다 큰 데이터를 읽고 쓰려고 하면 데드락이 발생할 수 있습니다.
  • fork를 사용해 다른 프로세스와 데이터를 주고받으면 데드락을 피할 수 있습니다.
    • 부모와 자식 프로세스가 동시에 실행되어, 하나의 프로세스가 쓰는 동안 다른 프로세스가 읽을 수 있습니다.

파일 디스크립터

파일 디스크립터 테이블

  • 각 프로세스는 "파일 디스크립터 테이블"을 가집니다.
    • 이 테이블에는 열린 파일 디스크립터의 인덱스가 기록되어 있습니다.
    • 프로세스는 파일에 직접 접근하지 않고, 시스템 내부에서 관리되는 테이블을 통해 파일에 접근합니다.

fork와 파일 디스크립터

  • 프로세스가 fork를 호출하면, 원래 프로세스의 파일 디스크립터 테이블이 복사됩니다.
    • 새로운 자식 프로세스는 부모 프로세스의 파일 디스크립터를 공유하게 됩니다.
    • 예를 들어, 부모 프로세스가 읽기 작업을 수행 중일 때, 자식 프로세스도 동일한 파일 디스크립터를 통해 읽기 작업을 수행할 수 있습니다.

코드 예제

파이프 생성 및 사용 예제

#include <stdio.h>
#include <unistd.h>

int main() {
    int pipefd[2];
    char buffer[30];
    
    if (pipe(pipefd) == -1) {
        perror("pipe");
        return 1;
    }
    
    if (fork() == 0) {
        // 자식 프로세스
        close(pipefd[1]); // 쓰기 끝을 닫음
        read(pipefd[0], buffer, sizeof(buffer));
        printf("자식 프로세스에서 읽음: %s\n", buffer);
        close(pipefd[0]);
    } else {
        // 부모 프로세스
        close(pipefd[0]); // 읽기 끝을 닫음
        write(pipefd[1], "Hello, World!", 13);
        close(pipefd[1]);
    }
    
    return 0;
}

0개의 댓글