이 글에서는 리눅스 시스템 콜인 fork()와 pipe()의 실제 동작을 시연하는 C 프로그램을 다룹니다. 두 개의 프로세스를 생성하여 협력적으로 문자열을 연결(concatenate)하는 프로그램을 만들어 보겠습니다. 첫 번째 프로세스는 사용자 입력을 받아 파이프를 통해 두 번째 프로세스로 전달하고, 두 번째 프로세스는 전달받은 문자열을 미리 정의된 문자열과 연결한 뒤 결과를 다시 되돌려줍니다.
본격적인 예제에 앞서 fork()와 pipe()의 기본 개념을 간단히 복습해 보겠습니다.
fork()
fork()는 새로운 자식 프로세스(child process)를 생성하는 시스템 콜입니다. 생성된 자식 프로세스는 부모와 별개의 새로운 PID(프로세스 ID)와 PPID(부모 프로세스 ID)를 갖게 됩니다.
pipe()
pipe()는 프로세스 간 통신(Inter-Process Communication, IPC)을 위해 사용되는 유닉스·리눅스 시스템 콜입니다. 파이프는 단방향(one-way) 통신 채널로, 한쪽 끝(fd[1])에 데이터를 쓰면 다른 쪽 끝(fd[0])에서 읽을 수 있습니다.
예제로 이해하기
입력
Learn programming Predefined string: at tutorialspoint
출력
Learn programming at tutorialspoint
동작 설명
P1이 문자열 "learn programming"을 입력받습니다.
P1은 파이프를 통해 이 문자열을 P2로 전송합니다.
P2는 전달받은 문자열을 미리 정의된 문자열과 연결한 후 다시 P1으로 보내고, P1이 최종 결과를 화면에 출력합니다.
fork()의 반환값 이해하기
프로그램에서는 fork() 함수를 호출하여 P1과 P2라는 두 개의 프로세스를 생성합니다. fork()의 반환값은 다음 세 가지 경우로 구분되며, 각 값은 프로그램의 상태를 나타냅니다.
- 반환값 < 0 : 프로세스 생성 실패
- 반환값 = 0 : 자식 프로세스가 실행 중
- 반환값 > 0 : 부모 프로세스에게 반환되는 자식 프로세스의 PID, 즉 부모 프로세스가 실행 중
파이프는 단방향 통신만 지원하기 때문에, P1 → P2 방향의 통신용 파이프 하나와 P2 → P1 방향의 통신용 파이프 하나, 총 두 개의 파이프를 생성해야 합니다.
fork()와 pipe()를 시연하는 C 프로그램
코드
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<string.h>
#include<sys/wait.h>
int main(){
int p12[2];
int p21[2];
char fixed_str[] = " at tutorialspoint";
char input_str[100];
pid_t P;
if (pipe(p12)==-1 || pipe(p21)==-1 ){
fprintf(stderr, "Filed to create pipe" );
return 1;
}
scanf("%s", input_str);
P = fork();
if (P < 0){
fprintf(stderr, "fork Failed" );
return 1;
}
else if (P > 0){
char concat_str[100];
close(p12[0]);
write(p12[1], input_str, strlen(input_str)+1);
close(p12[1]);
wait(NULL);
close(p21[1]);
read(p21[0], concat_str, 100);
printf("Concatenated string %s\n", concat_str);
close(p21[0]);
}
else{
close(p12[1]);
char concat_str[100];
read(p12[0], concat_str, 100);
int k = strlen(concat_str);
int i;
for (i=0; i<strlen(fixed_str); i++)
concat_str[k++] = fixed_str[i];
concat_str[k] = '\0';
close(p12[0]);
close(p21[0]);
write(p21[1], concat_str, strlen(concat_str)+1);
close(p21[1]);
exit(0);
}
}
실행 결과
Concatenated string Learn at tutorialspoint
코드 핵심 포인트 정리
- 파이프 배열 : p12[0]은 읽기 끝, p12[1]은 쓰기 끝을 의미합니다. p21도 마찬가지 구조입니다.
- 파일 디스크립터 관리 : 각 프로세스는 사용하지 않는 파이프 끝을 즉시 close()로 닫아야 합니다. 이를 닫지 않으면 read()가 데이터의 끝을 감지하지 못해 무한 대기할 수 있습니다.
- 동기화 : 부모 프로세스는 wait(NULL)을 호출하여 자식 프로세스가 작업을 마칠 때까지 기다린 후 결과를 읽습니다.
- 문자열 연결 : 자식 프로세스는 strlen()으로 현재 문자열 길이를 구한 뒤, 고정 문자열을 한 글자씩 이어 붙이고 마지막에 널 종료 문자('\0')를 추가합니다.
이처럼 fork()와 pipe()를 조합하면 여러 프로세스가 데이터를 주고받으며 협업하는 구조를 손쉽게 구현할 수 있습니다. 이는 셸(shell)의 명령어 파이프라인이나 멀티프로세스 기반 애플리케이션의 기초가 되는 핵심 개념이므로, 직접 코드를 컴파일하고 실행해 보며 동작을 확인해 보시기 바랍니다.