본문 바로가기

STUDY/linux

IPC in LINUX - Message Passing

IPC, Inter Process Communication

IPC는 프로세스 간의 정보를 주고 받는 매커니즘이다. 즉 프로세스 간의 동작을 동기화하여 통신을 한다. IPC에는 두가지 모델이 있다.

- message passing

- shared memory

 

자세한 내용은 process 글을 참고하면 좋다.

 

process concept

process가 무엇인가? process는 프로그램 실행 흐름의 가장 기본적인 단위이다. 다들 이렇게 설명하는데 사실 뭔 소린지 이해가 안갈 수 있다. 그럴때는 프로그램 실행을 위한 작업의 대상, 즉 os가 sc

josushell.tistory.com

 

LINUX, UNIX 에서 IPC는 크게 5가지이다.

 

- pipe

- FIFO

- message queue

- shared memory

- sockets (네트워크)

 

이 중에서 pipe, FIFO, message queue는 message passing 방식이다. 이에 대해 자세히 알아보자


Pipe

# include <unistd.h>

int pipe(int fd[2]);

 

함수 명세

return 성공 시 0, 실패 시 -1
description pipe를 생성한다.

 

파라미터

int fd[2] pipe 읽기, 쓰기를 진행할 file descriptor

fd[0]: read

fd[1]: write

 

 

pipes는 UNIX IPC 의 가장 오래된 형태이다. 또한 한쪽 방향으로만 데이터가 전달되는 half-duplex 방식이다.

fork() 를 통해 생성된 프로세스 간의 통신에 사용된다.

따라서 읽기, 쓰기 동작 두가지를 하기 위해서는 pipe 생성이 2번 필요하게 된다.

이때 파라미터로 넘겨주는 fd[2]는 file descriptor라고 생각하면 된다.

fd[0]: read 용도 file descriptor

fd[1]: write 용도 file descriptor 

 

Example

fork를 하게 되면 똑같이 parent, child process 모두 두개의 fd를 가진다. 따라서 fork를 하고 필요없는 fd는 close로 닫고 IPC를 진행한다. 이때 parent process에서 child process에게 data를 보내는 프로그램을 만들자.

 

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>

#define MAX 256

int main(int argc, char *argv[])
{
	int	fd[2];
	int	n;
	char	buf[MAX];
	pid_t	pid;

	if (pipe(fd) < 0)
		exit(1);
	
	if ((pid = fork()) < 0)
		exit(1);

	if (pid == 0)
	{
		close(fd[1]);
		printf("----- This is Child process ----- \n");

		// pipe에 data가 없는 경우 waiting (os가 동기화 지원)
		if ((n = read(fd[0], buf, MAX)) < 0)
			exit(1);

		printf("child process: Received data from parent\n");
		fflush(stdout);
		write(1, buf, n);
	}
	else
	{
		close(fd[0]);
		printf("----- This is Parent process -----\n");

		sleep(3);
		strcpy(buf, "This is data from parent\n");
		write(fd[1], buf, strlen(buf) + 1);
	}
	return 0;
}

 

child process 에서 read 를 통해서 Pipe에서 데이터를 읽을 때 read용 file descriptor인 fd[0]가 사용되었다.

 

그런데 이때 parent process에서 데이터를 보내지 않고 child process가 실행되면 어떻게 될까?

pipe와 같은 message passing 방식은 kernel을 거쳐 process 간 통신을 한다. 즉 os가 동기화를 맞춰준다.

따라서 fd[0]를 통해서 pipe에서 읽을 데이터가 없는 경우 os는 Process를 알아서 waiting 상태로 진입하게 한다.

 

결과는 다음과 같다. 

 

 

# include <unistd.h>

int dup(int oldfd);

 

함수 명세

return 성공 시 새로운 fd, 실패 시 -1
description fd를 복사한다.

 

파라미터

int oldfd 복사할 fd

 

file descriptor는 0번부터 시작한다. 이때 앞에서부터 비워진 fd를 복사해준다.

예를 들어 dup(0) 을 하게 되면 standard input 즉 표준 입력 버퍼를 복사하게 된 것이다.

즉 이렇게 dup(0)의 return 값으로 생긴 fd를 통해서 read를 하면 표준 입력에서 읽는 것, 즉 사용자 입력을 받는 것이 되는 것이다.

 

 # include <unistd.h>

int dup2(int oldfd, newfd)

이 함수는 dup와 똑같은 역할이다. 차이점은 이 함수는 복사할 fd를 지정가능하다는 것이다.

즉 oldfd 대신 newfd를 사용하겠다는 의미이다.

예를 들어 dup2(fd[1], 1) 은 fd[1] 대신 standard output 즉 표준 출력을 사용한다는 것이다.

 

Example

pipe와 dup 함수를 사용하여 shell command more를 만들어보자

more는 스크롤이 될 만큼 긴 길이를 전부 출력하지 않고 shell 화면에 보일만큼만 보여주고 나머지는 space or return으로 넘기면서 볼 수 있게 해주는 command이다.

 

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>

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

	if (pipe(fd) < 0)
		exit(1);

	if ((pid = fork()) < 0)
		exit(1);

	if (pid == 0)
	{
		close(1);
		close(fd[0]);

		if (dup(fd[1]) < 0) // dup2(fd[1], 1)
			exit(1);

		if (execlp("ls", "ls", "-al", NULL) < 0)
			exit(1);
	}
	else
	{
		close(0);
		close(fd[1]);

		if (dup2(fd[0], 0) < 0) // dup(fd[0])
			exit(1);

		if (execlp("more", "more", NULL) < 0)
			exit(1);
	}
	return 0;
}

 

execlp 함수는 환경변수에서 명령어를 찾아서 실행해주는 함수이다.

parent process는 fork를 통해서 child process를 생성한다. 이때 생성된 child process는 ls -al 명령어를 실행하고 이 명령어의 출력을 parent process에게 전달해야 한다.

이를 위해서는 표준 출력 대신에 pipe fd를 사용해야 한다. 따라서 close(1) 을 사용하여 표준 출력 fd를 닫아주고 dup(fd[1]) 을 실행하면

앞에서부터 비어있는 fd, 즉 1번 fd 대신에 pipe fd를 사용할 수 있게 되는 것이다.

parent process는 이를 받아서 more 명령어를 실행해준다. 이때도 마찬가지로 표준 입력이 아닌 pipe fd를 사용해야 하기 때문에

close(0)을 호출하여 표준 입력 fd를 닫아주고 dup2를 사용하여 pipe fd를 통해 복사를 해서 사용하게 동작하였다.

 


FIFO

# include <sys/types.h>

# include <sys/stat.h>

int mkfifo(char *pathname, mode_t mode);

 

함수 명세

return 성공 시 0, 실패 시 -1
description FIFO 파일을 생성한다.

 

파라미터

char *pathname FIFO 파일의 이름
mode_t mode FIFO 파일의 권한

 

FIFO는 Named Pipe이다. 즉 이름이 있다. 또한 양방향 통신이 가능한 full-duplex 방식이다.

이름이 있기 때문에, fork 관계가 아닌 서로 다른 두 프로세스 간의 통신에도 사용된다.

FIFO는 파일 시스템 상에서 파일로 생성된다. 하지만 데이터가 남는 것은 아니고 그냥 통신에만 사용되는 것이다.

파일로 생성되기 때문에 지울때에도 remove() 를 사용해서 지울 수 있다.

 

Example

FIFO도 파일로 생성되기 때문에 read, write를 통해서 통신을 할 수 있다.

 

#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>

#define MAX 256

int main(int argc, char *agrv[])
{
	int	fd;
	int	n;
	char	*buf;

	if (mkfifo("fifo", 0600) < 0)
		exit(1);

	fd = open("fifo", O_RDWR);
	if (fd < 0)
		exit(1);

	if ((n = read(fd, buf, MAX)) < 0)
		exit(1);
	printf("received data: %s\n", buf);

	close(fd);
	remove("fifo");

	return 0;
}

 

이때 중요한 점은 fifo를 사용한 방식은 message passing, 즉 os가 동기화를 맞춰준다는 것이다.

따라서 read() 를 통해서 데이터를 읽을 때, 읽을 데이터가 없는 경우 자동으로 process는 waiting 으로 진입한다.

'STUDY > linux' 카테고리의 다른 글

Synchronization in LINUX - POSIX Semaphore로 producer-consumer 해결하기  (0) 2022.04.13
IPC in LINUX - Shared Memory  (0) 2022.04.04
Files and Directories in LINUX  (0) 2022.03.31
SIGNAL in LINUX  (0) 2022.03.29
Process and Threads in LINUX  (0) 2022.03.28