리눅스 파이프

Ho Kim·2021년 5월 12일
0

파이프란?

파이프는 앞선 명령어의 결과를 뒤로 넘겨주기 위해 사용해 왔습니다.

ls | grap result.txt

이 작업을 코드를 통해 하려면 어떻게 해야하는 걸까요?

파이프의 이전 작업과 이후 작업이 동일 프로세스에서 일어나면 간단하게 처리할 수 있지만, 우리는 다른 프로세스에서 일어나는 작업을 연결해야 합니다.

프로세스는 메모리를 독립적으로 사용하기 때문에 프로세스끼리 데이터를 교환할 수 없습니다.
하지만, pipe()라는 함수를 통하면 부모 프로세스의 데이터를 자식 프로세스로 전달 할 수 있습니다.

그렇다면 파이프는 어떤 식으로 동작하는 걸까요?
동작 원리는 간단합니다.

첫번째 프로세스는 어떠한 데이터를 가지고 값을 만들어서 출력합니다.
두번째 프로세스는 출력된 값을 읽고, 작업을 한 뒤에 그 결과를 다시 출력합니다.
세번째 프로세스부터는 두번째 파이프의 일을 반복합니다.

파일을 읽고 쓰는 법만 안다면 금방 구현할 수 있습니다.
첫번째 프로세스에서 결과값으로 result.txt 파일을 만들고, 두번째 프로세스부터 result.txt 파일을 읽어서 작업을 수행한 뒤에 result.txt 파일을 새로 쓰는 일을 반복하면 됩니다.

하지만 '파일을 쓴다'는 것은 메모리에 접근하는 일이므로 시간이 오래걸립니다. 따라서 파일을 만들지 않고 읽고 써야합니다.
이 일을 해주는 것이 바로 파이프입니다.

pipe

파이프에 크기 2짜리 int 배열을 넣어줍니다.

int fd[2];

pipe(fd)

그러면 파이프는 fd[0]에 읽기용, fd[1]에 쓰기용 파일 디스크립터를 넣어 반환합니다.
fd[1]에 쓴 값을 fd[0]을 통해 읽을 수 있도록 둘은 연결되어 있습니다.

fork();

그리고 자식 프로세스를 만듭니다.

이제 부모프로세스와 자식프로세스는 같은 파일 디스크립터를 공유합니다!

기본적인 내용은 여기에서 끝입니다.
fd값을 지정해주어야하는 저수준의 입출력인 read/write만 사용할거라면 말입니다.

그러나 우리가 흔히 사용하는 함수중에는 기본 입출력을 바탕으로 하고 있는 것들이 많습니다. printf만 해도 값을 무조건 1로 출력하게 되어있습니다. 그 함수들을 사용하기 위해서는 파이프로 만든 fd를 기본입출력으로 연결해 주어야 합니다.

여기서 dup2 라는 새로운 함수가 필요합니다.
파이프에 대한 얘기를 마저 진행하기 전에 dup 함수에 대해 알아봅시다.

기본 파일디스크립터

STDIN  : 키보드 입력 (파일 디스트립터값 = 0(기본입력))
STDOUT : 콘솔창 출력 (파일 디스트립터값 = 1(기본출력))
STDERR : 콘솔창 오류출력 (파일 디스트립터값 = 2(기본에러))

dup

  1. dup
int dup(int fd);

dup는 fd가 연 파일을 가리키는 새로운 파일 디스크립터를 반환합니다.
같은 파일 테이블 엔트리를 참조하므로 첫번째 fd로 값을 읽고 나면 두번째 fd는 그 뒤부터 값을 읽어오게 됩니다.

  1. dup2
int dup2(int fd1, int fd2);

dup2는 dup과 같은 일을 하지만 새로운 파일 디스크립터의 값을 fd2로 지정합니다
즉, fd2가 fd1의 파일 테이블 엔트리를 참조하게 만듭니다. 기존의 fd2의 디스크립터는 자동으로 닫히게 됩니다.

Q) fd로 result.txt라는 파일을 열고 다음 코드를 실행하면 어떻게 될까요?

dup2(fd,STDOUT_FILENO);

A) STDOUT을 가리키고 있던 파일 디스크립터 1이 result.txt를 참조하게 됩니다.

printf는 STDOUT에 값을 쓰게 되어있습니다. 이렇게 파일 디스크립터를 변경한 뒤에 printf()를 해보면 출력한 값이 result.txt에 쓰이는 것을 확인 할 수 있습니다.


STDIN의 경우도 크게 다르지 않습니다.
dup2(fd,0);

STDIN을 가리키고 있던 파일 디스크립터 0이 result.txt를 참조하게 됩니다.

이렇게 되면 키보드 입력 대신 result.txt 안의 값들이 기본 입력으로 들어오게 됩니다.

pipe + dup

sort라는 명령어는 sort 명령어 입력 후 EOF가 들어올때까지 표준입력을 받아 그 값들을 정렬시킵니다.
부모 프로세스에서 정해진 값들을 printf로 출력하고, 자식 프로세스가 출력된 값들을 받아 sort로 정렬시킬 수 있도록 해봅시다.

int main(int argc, char *argv[]) 
{
  int fd[2];
  pipe(fd);
  pid_t pid = fork();

  if (pid == 0) {
    dup2(fds[0], 0);
    close(fds[0]);
    close(fds[1]);
    char *argv[] = {(char *)"sort", NULL};
    if (execvp(argv[0], argv) < 0)
    	exit(0);
  } 

  close(fd[0]);
  dup2(fd[1], 1);
  const char *data[] = {"b", "c", "a", NULL};
  int i = -1;
  while(data[++i])
    printf("%s\n", data[i]); 
  close(fds[1]); 
  //표준입력을 닫아 입력EOF의 역할을 대신함

  int status;
  wait(&status);
  printf("pipe end!");
  return 0;
}

1개의 댓글

comment-user-thumbnail
2022년 5월 27일

clear, thanks

답글 달기