파이프와 리다이렉션
프로세스 간 통신 (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
dup와 dup2

dup() 시스템 호출은 파일 디스크립터를 복사합니다.
dup2() 시스템 호출은 대상 파일 디스크립터를 지정할 수 있습니다.
#include <unistd.h>
int dup(int fd);
int dup2(int oldfd, int newfd);
표준 출력 리다이렉션
단계 1: 파일 열기
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);
전체 과정
- 파일을 엽니다.
dup2()를 사용하여 파일 디스크립터를 복사합니다.
- 프로그램을 실행하여 출력이 파일로 리다이렉션되도록 합니다.
버퍼 용량과 데드락
버퍼 용량
- 파이프의 버퍼는 유한한 용량을 가집니다.
- 단일 프로세스에서 파이프 크기보다 큰 데이터를 읽거나 쓸 수 없습니다.
- 파이프의 버퍼가 가득 차면 쓰기 작업이 블록됩니다.
- 파이프의 버퍼가 비어 있으면 읽기 작업이 블록됩니다.
데드락 방지
- 단일 프로세스에서 파이프 크기보다 큰 데이터를 읽고 쓰려고 하면 데드락이 발생할 수 있습니다.
- 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;
}