pipe
1
2
3
#include <unistd.h>
int pipe(int fildes[2]);
크기가 2인 int 배열에 한 쪽에서는 데이터를 쓰고, 다른 한 쪽에서는 데이터를 읽을 수 있는 데이터 통신 파일 디스크립터를 만드는 함수이다.
첫 번째 원소인 fildes[0] 는 파이프의 read end 이고, 두 번째 원소인 fildes[1] 은 파이프의 write end 이다. 그래서 fildes[1] 에서 데이터를 입력하면 fildes[0] 에서 입력한 데이터를 읽을 수 있다.
반환값
- 성공 : 0
 - 오류 : -1
- 전역 변수 
errno에 발생한 에러 자동 설정 
 - 전역 변수 
 
예시 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int	main(void)
{
	int		pipe_fd[2];
	char	buf[6];
	if (pipe(pipe_fd) == -1)
	{
		printf("PIPE ERROR OCCURED\n");
		return (1);
	}
	printf("pipe_fd[0]: %d, pipe_fd[1]: %d\n", pipe_fd[0], pipe_fd[1]);
	write(pipe_fd[1], "HELLO\n", 6);
	read(pipe_fd[0], buf, 6);
	printf("%s", buf);
	return (0);
}
파이프를 생성해서 write end 에서 HELLO\n 라는 데이터를 입력하면 read end 에서 이를 읽어와서 출력하는 예시이다.
실행 결과는 다음과 같다.
1
2
pipe_fd[0]: 3, pipe_fd[1]: 4
HELLO
파이프를 생성하면 사용할 수 있는 가장 낮은 파일 디스크립터부터 순서대로 배열에 할당된다.
파이프가 동작하는 모습을 그림으로 표현하면 다음과 같다.

만약, read end 파이프를 먼저 닫고 데이터를 읽어오려고 하면 데이터를 읽어오지 못한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int	main(void)
{
	int		pipe_fd[2];
	char	buf[6];
	if (pipe(pipe_fd) == -1)
	{
		printf("PIPE ERROR OCCURED\n");
		return (1);
	}
	printf("pipe_fd[0]: %d, pipe_fd[1]: %d\n", pipe_fd[0], pipe_fd[1]);
	write(pipe_fd[1], "HELLO\n", 6);
	close(pipe_fd[0]); // 데이터를 읽기 전에 파이프를 닫음
	read(pipe_fd[0], buf, 6);
	printf("%s", buf);
	return (0);
}
실행 결과는 아래의 이미지와 같으며, 실행 환경에 따라 쓰레기값이 출력되거나 빈 문자열이 출력될 수 있다.

그림으로 표현하면 다음과 같다.

close 를 수행하면 파일 디스크립터가 가리키고 있던 대상은 해제되며 해당 파일이 가지고 있던 데이터는 더 이상 접근할 수 없게 된다.
파이프는 read end 또는 write end 중에 하나라도 닫히면 widowed(상대가 떠나버린) 상태가 된다.
구체적으로는 read end 가 먼저 닫히고 write 를 수행하면 프로세스는 SIGPIPE 시그널을 받게 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
void	print_hello(int signo)
{
	printf("SIGPIPE OCCURED %d\n", signo);
}
int	main(void)
{
	int		pipe_fd[2];
	char	*buf;
	signal(SIGPIPE, print_hello);	// SIGPIPE를 감지하기 위한 이벤트 핸들러
	if (pipe(pipe_fd) == -1)
	{
		printf("PIPE ERROR OCCURED\n");
		return (1);
	}
	printf("pipe_fd[0]: %d, pipe_fd[1]: %d\n", pipe_fd[0], pipe_fd[1]);
	close(pipe_fd[0]); // 데이터를 쓰기 전에 read end를 닫음
	write(pipe_fd[1], "HELLO\n", 6);
	read(pipe_fd[0], buf, 6);
	printf("%s", buf);
	return (0);
}
실행 결과는 다음과 같다.
1
2
3
pipe_fd[0]: 3, pipe_fd[1]: 4
SIGPIPE OCCURED 13
(null)
반대로 write end 를 먼저 닫고 read end 에 접근하면 프로세스는 EOF 를 받는다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int	main(void)
{
	int		pipe_fd[2];
	char	*buf;
	if (pipe(pipe_fd) == -1)
	{
		printf("PIPE ERROR OCCURED\n");
		return (1);
	}
	printf("pipe_fd[0]: %d, pipe_fd[1]: %d\n", pipe_fd[0], pipe_fd[1]);
	close(pipe_fd[1]);
	read(pipe_fd[0], buf, 6);
	printf("%s", buf);
	return (0);
}
실행 결과는 다음과 같다.
1
2
pipe_fd[0]: 3, pipe_fd[1]: 4
(null)
만약, write end 를 닫지 않은 채 read end 에 접근하면 프로세스는 파이프에 데이터가 들어올 때까지 계속 대기한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int	main(void)
{
	int		pipe_fd[2];
	char	*buf;
	if (pipe(pipe_fd) == -1)
	{
		printf("PIPE ERROR OCCURED\n");
		return (1);
	}
	printf("pipe_fd[0]: %d, pipe_fd[1]: %d\n", pipe_fd[0], pipe_fd[1]);
	read(pipe_fd[0], buf, 6);	// 파이프의 write end 에서 데이터가 들어오는 것을 계속 기다림
	printf("%s", buf);
	return (0);
}
이는 fork 를 수행했을 때도 동일하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int	main(void)
{
	int		pipe_fd[2];
	char	*buf;
	pid_t	pid;
	if (pipe(pipe_fd) == -1)
	{
		printf("PIPE ERROR OCCURED\n");
		return (1);
	}
	pid = fork();
	if (pid == 0) // 자식 프로세스
	{
		printf("I'm waiting data written.\n");
		read(pipe_fd[0], buf, 6);	// 파이프의 write end 에서 데이터가 들어오는 것을 계속 기다림
	}
	else // 부모 프로세스
	{
		waitpid(pid, NULL, 0);
		write(pipe_fd[1], "HELLO\n", 6);
		close(pipe_fd[1]);
	}
	return (0);
}
부모 프로세스는 자식 프로세스가 끝나기를 기다리고 있는데, 자식 프로세스에서는 파이프의 write end 로부터 데이터가 들어오기를 계속 기다리고 있다. 그래서 위의 코드를 실행하면 끝나지 않고 계속 대기하는 현상이 발생한다.

따라서 더 이상 사용하지 않는 파이프는 close 를 해주어야 무한 대기 현상 등을 방지할 수 있다.
참고 자료
- c언어 pipe 란 [티스토리]
 - C언어 파이프를 이용한 IPC 함수 pipe [바다야크]