1. IPC (Inter Process Communication)
pipe를 알기위해서는 먼저 IPC라는 개념을 알아야한다.
IPC는 Inter Process Communication 으로 process들 간에 데이터 및 정보를 주고 받기 위한 Mechanism을 이야기한다.
Kernel에서 IPC를 위한 도구를 제공하며 System Call의 형태로 Process에게 제공된다.
IPC에는 크게 Shared memory와 Message Passing이라는 두 가지 모델이 있다.
1. Shared Memory
- Process의 특정 메모리 영역을 공유한다.
- 공유한 메모리 영역에 읽기/쓰기를 통하여 통신을 수행한다.
- 공유 메모리가 설정되면(kernel에게 할당 받음), 응용 프로그램 레벨에서 통신 기능이 제공된다(kernel 관여 X).
- read, write 방식
2. Message Passing
- Process간 메모리 공유 없이 동작한다.
- kernel을 통한 메시지 통신 기능을 제공한다.
- 구현 예시로는 pipe, message queue, socket 이 있다.
- send, receive 방식
2. Pipe
그렇다면 다시 본론으로 돌아와 pipe가 무엇인지 알아보자.
Pipe란 하나의 Process가 다른 Process로 데이터를 직접 전달하는 방법으로, pipe를 사용할 때는 기본적으로 fork() 시스템 콜이 발생한다.
데이터는 한 방향으로만 이동하며 프로세스 간 1대1 의사소통만이 가능하다.
예를 들어 Process A와 Process B가 있다고 했을 때 양방향 통신을 하기 위해서는 pipe A는 A->B, pipe B는 B->A로 데이터를 전달하는 pipe가 필요하다(Half Duplex).
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#define MAXLINE 4096
int main(void)
{
int n, fd[2];
pid_t pid;
char line[MAXLINE];
if (pipe(fd) < 0) {
perror("pipe error");
exit(-1);
}
if ( (pid = fork()) < 0) {
perror("fork error");
exit(-1);
} else if (pid > 0) {
/* parent */
close(fd[0]);
write(fd[1], "hello, world.\n", 14);
waitpid(pid, NULL, 0);
} else {
/* child */
close(fd[1]);
n = read(fd[0], line, MAXLINE);
write(STDOUT_FILENO, line, n);
}
fflush(stdout);
exit(0);
}
- fd[2]는 file descriptor를 담는 배열로 0은 read(input)용 파일 디스크립터, 1은 write(output)용 파일 디스크립터를 나타낸다.
- 만약 pid가 0이상이면 (== parent인 경우) input은 close 하고, "hello, world."를 output으로 보낸다.
- pid가 0인 child의 경우 fd[1]을 close하고(output X), 값을 읽어 n에 대입한다.
- write() 함수를 통해 n바이트만큼 표준 출력하면 'hello, world'가 출력된다
3. ls | grep .txt
이는 ls 프로세스의 output을 grep 프로세스의 input으로 전달한 후 결과를 출력하는 명령어이다.
ls는 parent 프로세스가 되며, grep는 child 프로세스가 된다.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#define MAXLINE 4096
int main(void)
{
int n, fd[2];
pid_t pid;
char line[MAXLINE];
if (pipe(fd) < 0) {
perror("pipe error");
exit(-1);
}
if ( (pid = fork()) < 0) {
perror("fork error");
exit(-1);
} else if (pid > 0) {
/* parent */
close(fd[0]);
dup2(fd[1], STDOUT_FILENO);
close(fd[1]);
//ls 실행
execl( "/bin/ls", "/bin/ls", NULL);
} else {
/* child */
close(fd[1]);
dup2(fd[0], STDIN_FILENO);
close(fd[0]);
//grep 실행
execl("/usr/bin/grep", "/usr/bin/grep", ".txt", NULL);
}
fflush(stdout);
exit(0);
}
1) pid > 0 ( ls 실행 )
- 가장 먼저 dup2() 함수를 실행하면 fd[1](파이프의 쓰기) 파일을 복제하여 STDOUT_FILENO 번호에 할당한다.
- STDOUT_FILEN에 쓰는 데이터는 파이프의 쓰기인 fd[1]로 전송이 되므로 부모 프로세스에서 실행되는 ls가 출력하는 데이터는 파이프의 쓰기 끝으로 전달된다.
2) pid == 0 ( grep 실행 )
- 자식 프로세스에서는 dup2(fd[0], STDIN_FILENO)를 사용하여, 파이프의 읽기 끝을 표준 입력으로 지정한다.
- 이 결과 파이프로 전달되는 데이터는 grep의 입력으로 들어며 해당 명령으로 처리한다.
3) 결과 화면
프로그램 결과와 ls | grep .txt 결과 동일한 것을 확인할 수 있다.