9.파이프
9-1. 개요
Pipe는 두 프로세스 사이에서 한 방향으로 통신할 수 있도록 지원한다. 쉘에서 | 파이프를 의미한다. 쉘에서 파이프 기능은 한 명령의 표준 출력을 다음 명령에서 표준 입력으로 받아 수행하는 것을 의미한다.
grep pip test.c | more
앞에 있는 명령인 grep pipe test.c 의 표준 출력을 다음 명령인 more의 표준 입력으로 사용한다. 위 예를 실행하면 test.c에서 "pipe"라는 문자열이 위치한 행을 찾아 화면 단위로 출력(more)한다.
파이프는 이름없는 파이프(익명파이프, anonymous pipe)와 이름없는 파이프(named pipe)로 구분된다.
이름 없는 파이프 : pipe
특별한 수식없이 그냥 파이프라고 하면 이름없는 파이프(익명파이프)를 의미한다. 이름없는 파이프는 부모-자식 프로세스 간에 통신할 수 있게 해준다.
부모 프로세스에서 fork함수를 통해 자식 프로세스를 생성하고, 부모 프로세스와 자식 프로세스 간에 통신하는 것이다.
따라서 부모 프로세스-> 자식 프로세스 또는 자식-> 부모 프로세스 중 한 방향을 선택해야한다.
파이프를 이용해 양방향 통신을 원할 경우, 파이프를 두개 생성해야한다.
- 익명 파이프 함수
기능 | 함수원형 |
---|---|
간단한 파이프 생성 | FILE *popen(const char *command, const char *mode); int pclose(FILE *stream); |
복잡한 파이프 생성 | int pipe(int fildes[2]); |
9-2. 이름없는 파이프
파이프는 두 프로세스 간에 통신을 할 수 있도록 인터페이스를 제공한다.
아무 수식어 없이 그냥 파이프라고 하면 일반적으로 이름없는 파이프(anonymous pipe)를 의미한다.
이름 없는 파이프는 부모-자식 프로세스 간에 통신을 할 수 있게 해준다.
부모 프로세스에서 fork함수를 사용해 자식 프로세스를 생성하고, 부모프로세스와 자식프로세스간에 통신하는 것이다.
파이프는 기본적으로 단방향이다. 따라서 부모 프로세스가 출력한 내용을 자식 프로세스에서 읽을 것인지, 자식 프로세스가 출력한 내용을 부모 프로세스에서 읽을 것인지 둘 중 한 방향을 선택해야한다.
이는 수도관에 물을 보낼 때 양쪽에서 서로 보낼 수 없는 이치와 마찬가지다.
9-2-1. 간단한 파이프 생성
파이프를 만드는 가장 간단한 방법은 popen 함수를 사용하는 것이고 사용을 마친 파이프는 pclose함수를 사용해 닫는다.
- 파이프 생성 : popen(3)
#include <stdio.h>
FILE *popen(const char *command, const char *mode);
* command : 쉘 명령
* mode : 'r' 또는 'w'
popen함수는 다른 프로세스와 통신하기 위해 파이프를 생성한다. 이 함수는 내부적으로 fork함수를 실행해 자식 프로세스를 만들고, command에서 지정한 명령을 exec함수로 실행해 자식 프로세스가 수행하도록 한다.
자식 프로세스가 실행하는 exec함수는 다음과 같다.
execl("usr/bin/sh", "sh", "-c", command, (char *) 0);
popen함수는 자식 프로세스와 파이프를 만들고 mode의 값에 따라 표준 입출력을 연결한다. 리턴 값은 파일 포인터이다. 파일 입출력 함수에서 이 파일 포인터를 사용하면 파이프를 읽거나 쓸 수 있다. popen함수는 파이프 생성에 실패하면 널 포인터를 리턴한다.
- 파이프 닫기 : pclose(3)
#include <stdio.h>
int pclose(FILE *stream);
* stream : popen함수에서 리턴한 파일 포인터
* retrun : 자식 프로세스의 종료 상태(exit status) /실패시 -1
이 함수는 파일 입출력함수처럼 인자로 지정한 파이프를 닫는다.
이 함수는 관련된 waitpid함수를 수행하며 자식 프로세스들이 종료하기를 기다렸다가 리턴한다.
9-2-2. 복잡한 파이프 생성
popen함수를 사용해 생성하는 것은 간단하지만, 쉘을 실행해야하므로 비효율적이고 주고받을 수 있는 데이터도 제한적이다.
이 함수 대신에 pipe함수를 사용하면 과정이 약간 복잡하지만 좀더 효율적으로 파이프를 이용할 수 있다
- 파이프 만들기 : pipe(2)
#include <unistd.h>
int pipe(int fildes[2]);
* fildes: 파이프로 사용할 파일 기술자(2개)
pipe는 인자로 크기가 2인 정수형 배열을 받는다.
이 함수는 파일 기술자 2개를 저장한다.
첫번째 요소에는 읽기 전용으로 열고, 두번째 요소는 쓰기 전용으로 연다.
- pipe함수로 통신하는 과정
파이프를 생성하고 나면 일반적으로 fork함수를 호출해 자식 프로세스를 생성한다.
자식 프로세스는 부모 프로세스가 pipe함수로 생성한 파일 기술자들도 복사한다.
이 파이프를 이용해 한 프로세스에는 쓰기를 수행하고, 다른 프로세스에서는 읽기를 수행하면 통신이 된다.
이 과정은 다음과 같다.
pipe함수를 호출해 파일에 사용할 파일 기술자를 얻는다.
파이프도 파일의 일종이므로 파일(파이프)를 읽고 쓸 수 있는 파일 기술자가 필요한데, 이를 pipe함수가 생성해준다.fork함수를 수행해 자식프로세스를 생성한다.
이때 pipe함수에서 생성한 파일 기술자도 자식 프로세스로 복사된다.
같은 파일 기술자를 부모와 자식 프로세스가 모두 가지고 있다.파이프는 단방향 통신으므로 통신 방향을 결정해야한다.
부모 프로세스는 쓰기를 하고 자식 프로세스는 읽기를 해야한다고 했을 때,
각각의 파일 기술자에서 쓰지 않는 읽기 또는 쓰기의 파일기술자를 닫아야한다.
그래서 부모 프로세스가 쓴 내용을 자식 프로세스가 읽으면 된다.
만약 파이프의 쓰기 부분이 닫혀 있다면 파이프에서 읽으려고 할 때 0이나 EOF가 리턴한다.
파이프의 읽기 부분이 닫혀 있다면 파이프에 쓰려고 할 때 SIGPIPE 시그널이 발생한다.
9-2-3. 양방향 파이프의 활용
파이프는 기본적으로 단방향 통신을 수행한다.
따라서 양방향 통신을 하려면 파이프를 두개 생성하면 된다.
상수도와 하수도가 각각 별도의 파이프로 구성되어 있는 것과 같은 이치다.
9-3. 이름있는 파이프
이름 없는 파이프는 부모-자식 프로세스 간에서만 통신할 수 있다.
부모-자식 프로세스는 서로의 존재를 알고 있으므로 이름을 붙여 파이프를 구별할 필요가 없다.
하지만 부모-자식 프로세스 관계가 아닌 독립적인 프로세스들은 서로의 존재를 알 수 없으므로 파이프를 이용하려면 파이프명이 있어야한다.
이런 기능을 제공하는 파이프를 이름 있는 파이프 (named pipe)라고 한다.
이름 있는 파이프는 특수 파일의 한 종류로서 FIFO(First-In First-out)라고도 한다.
이름있는 파이프는 글자 그대로 이름이 붙은 파이프로 모든 프로세스가 이 파이프명을 이용해 통신할 수 있다.
FIFO로 통신하려면 우선 FIFO 특수 파일을 생성하고, 파일 입출력 함수를 사용하면 된다.
즉, 한 프로세스가 FIFO로 사용할 특수 파일을 생성하면, 이 파일의 이름을 알고 있는 다른 프로세스가 같은 FIFO를 이용해 통신할 수 있다.
9-3-1. 명령으로 FIFO 파일 생성하기
- FIFO, 특수 파일 생성 : mknode 명령
이 명령은 FIFO 파일 뿐아니라, 특수 파일도 생성하는 명령이다.
mknod 파일명 p
mknod 명령을 사용할 때는 FIFO 파일의 이름과 FIFO 파일을 생성하라는 의미인 p를 지정한다.
이 명령은 /usr/sbin 디렉토리에 위치하고 있다.
/etc/mknod 파일은 /usr/sbin/mknod 파일에 대한 심볼릭링크(symbolic link)이다.
파일 생성후 ls -l 명령으로 조회해보면 파일의 종류를 나타내는 문자가 p 임을 알 수 있다.
p는 FIFO 파일을 의미한다.
ls -f 명령으로 확인해보면 해당 파일 뒤에 '|'기호가 추가 되어있는데, 이 기호도 FIFO 파일임을 표시한다.
- FIFO 파일 생성 : mkfifo 명령
이 명령은 FIFO파일만 생성하는 명령이다.
/usr/bin/mkfifo [-m mode] path...
-m 옵션은 새로 생성되는 FIFO 파일의 접근 권한을 지정한다. 이 옵션을 생략하면 umask값에 따라 기본 권한을 설정한다.
예를 들어 mkfifo 명령으로 BIT_FIFO라는 FIFO파일을 생성할 경우 다음고 같이 하면된다. mkmod명령으로 생성한 파일과 같은 형태의 파일을 만들어준다.
#mkfifo -m 0644 BIT_FIFO
#ls -l BIT_FIFO
pwr-r--r-- 1 root other 0 2월 13일 12:28 BIT_FIFO
9-3-2. 함수로 FIFO 파일 생성하기
- 특수 파일 생성 : mknod(2)
#include <sys/stat.h>
int mknod(const char *path, mode_t mode, dev_t dev);
* path : 특수 파일을 생성할 경로
* mode : 특수 파일의 종류와 접근 권한 지정
mode 구조체에 지정하는 특수 파일의 종류
- S_IFIFO : FIFO특수파일
- S_IFCHR : 문자 장치 특수 파일
- S_IFDIR : 디렉토리
- S_IFBLK : 블록 장치 특수 파일
- S_IFREG : 일반 파일
* dev : 블록/문자 장치의 설정값
mode에 지정하는 접근 권한은 0777과 같이 숫자 모드를 직접 사용하는 것이 편하다. 세번째 인자인 dev는 생성하려는 특수 파일이 블록 장치 특수 파일이나 문자 장치 특수 파일 일 때만 의미가 있다.
- FIFO 파일 생성 : mkfifo(3)
이 함수는 path에 지정한 경로에 접근 권한을 지정해 FIFO 파일을 생성한다.
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *path, mode_t mode);
* path : FIFO 파일을 생성할 경로
* mode : 접근 권한 지정
9-3-3.FIFO로 데이터 주고 받기
명령이나 함수로 FIFO 파일을 생성하면 저수준 파일 입출력 함수로 이 파일을 읽거나 쓸 수 있다.
open함수로 FIFO 파일을 열때는 O_NONBLOCK 옵션의 영향을 받는다.
이 옵션을 설정하지 않으면 다른 프로세스가 읽기 위해 열 때까지 쓰기 위한 open함수는 블록 된다.
그러나 이 옵션이 설정되어 있으면 open함수는 즉시 리턴된다.
FIFO에서 데이터를 읽으려는 프로세스가 없는데 쓰기 위해 open함수를 호출하면 오류가 발생한다.
FIFO도 파이프와 마찬가지로 단방향 통신이다.
따라서 FIFO파일을 읽거나 쓰기 위해 열 경우 반드시 O_RDONLY(읽기전용)이나 O_WRONLY(쓰기전용)으로만 열어야 한다. 읽고 쓰기용(O_RDWR)로 열수 없다.