개요
운영체제 상에서 실행 중인 프로세스 간에 정보를 주고받는 것을 Inter Process Communication(IPC)라고 한다. 프로세스는 자신에게 할당된 메모리 내의 정보만 접근할 수 있고, 이를 벗어나서 접근할 경우, C언어를 배우는 사람이라면 누구나 한 번쯤은 봤을 Segmentation Fault 등의 오류가 발생하게 된다. 이는 안전성을 위해 운영체제에서 자기 프로세스의 메모리만 접근하도록 강제하고 있다는 것이다. 서로 다른 프로그램(즉, 서로 다른 프로세스)의 데이터를 공유하려면 결국 다른 프로세스의 메모리를 접근할 필요가 발생한다. 따라서 이 때는 IPC라는 것을 사용하게 된다.
방식
Regular File
일반적인 파일을 이용하는 방식이다. 고정된 최대 크기를 가지며, 동기화의 문제가 있다.
Pipes
Pipe를 통해서 연결된 프로세스 사이에서 통신하기 위한 방식이다. Data의 송수신에 있어서 파일 크기의 제약이 없는 장점이 있다. 또한 자동적으로 Synchronization이 이루어진다. 파이프는 Unnamed Pipe와 Named Pipe로 구분된다. 파이프는 유닉스 초창기부터 IPC를 위해서 이용된 방식이다. 파이프를 쉽게 생각하자면 하나의 큰 파일로 이루어진 버퍼라고 생각하면 된다.
UNIX Shell에서 파이프는 | 로 표현된다.
- ls -alg | more
- pic parer.ms | tbl | eqn | ditroff -ms
System call
- int pipe(int fd[2]) return 0 if OK, -1 if ERROR
파이프는 두개의 file descriptor를 리턴한다. fd[0]은 파이프를 통해서 읽을 수 있는 값이며, fd[1]은 파이프를 통해서 write하는 값이다. fd[1]을 통해서 작성한 어떤 것이든 fd[0]을 통해서 접근할 수 있다. 즉 parent와 child는 pipe system call을 통해서 효과적으로 통신할 수 있게 된다. 만약 아무것도 없는데 읽을려고 하면 0을 리턴하고, read파이프가 종료됐는데 작성하려고 하면 SIGPIP Error을 호출하게 된다.
Unamed Pipe
Unamed파이프는 단방향이며 (Half duplex) 공통적인 조상을 가지는 프로세스끼리만 통신할 수 있다. 양방향 통신을 하고 싶으면 이런 익명 파이프를 2개 만들면 된다. 또한 반드시 상속을 통해서 사용할 수 있기 때문에, 일반적으로 parent와 child가 통신하기 위해서 사용된다. 프로세스는 반드시 parent으로부터 pipe를 상속받아서 사용하여야하며, 만약 부모 프로세스가 Pipe를 가지고 있을 경우, 자식 프로세스는 그 파이프를 상속받게 된다. 유닉스는 커넥션으로 파일디스크립터를 반환한다. 유닉스의 경우 명명된 파이프와의 가장 큰 차이점은 소멸 시점이다. 익명 파이프의 경우 프로세스가 종료되면 그 프로세스의 커넥션(핸들 혹은 파일 디스크립터)를 OS가 찾아서 다 죽여버린다.
Named Pipe
기본적으로 명명된 파이프는 익명 파이프에 이름을 붙여서 특정할 수 있게 만든다는 컨셉에서 시작한 것이다. 이름을 붙여서 특정을 하였기 때문에 파이프의 이름으로 공유할 수 있다. 유닉스의 경우, 익명 파이프와의 가장 큰 차이점은, 프로세스가 죽어도 명명된 파이프는 회수하지 않는 한 남아있다. 윈도우의 경우는 파이프의 기본이 명명된 파이프이고, 익명 파이프는 아무 이름이나 설정한 파이프라서, 모든 API는 기본적으로 명명된 파이프용 API이다. 즉 윈도우스는 별로 큰 차이가 존재하지 않는다.
예제
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define MAX_BUF 1024
#define READ 0
#define WRITE 1
int main(){
int fd[2];
pid_t pid;
char buf[MAX_BUF];
if(pipe(fd) < 0){
printf("pipe error\n");
exit(1);
}
if((pid=fork())<0){
printf("fork error\n");
exit(1);
}
printf("\n");
if(pid>0){ //parent process
close(fd[READ]);
strcpy(buf,"message from parent\n");
write(fd[WRITE],buf,strlen(buf));
}else{ //child process
close(fd[WRITE]);
read(fd[READ],buf,MAX_BUF);
printf("child got message : %s\n",buf);
}
exit(0);
}
Socket
소켓을 통해서도 IPC를 구현할 수 있다. 기본적인 네트워크통신을 위해서 사용되는 소켓이 아니라 내부 UNIX Internal한 목적을 위해서 사용되는 소켓을 통해서 IPC를 보내게 된다.
빠르면서도 강력한 IPC방법중 하나로, shared memory를 잡고 Lock을 통해서 메모리 region에 접근하는 방식을 통해서 IPC를 구현한다. 빠르지만 Multiprocess혹은 Multicore환경에서 큰 속도저하가 있는 것으로 알려져 있다.
Signal
시그널을 통해서 IPC를 구현하는 방식으로, 커널에서 제공하는 Signal handler를 통해서 정보를 전달한다.