Link Search Menu Expand Document

IPC

IPC (InterProcess Communication)

원칙적으로 프로세스는 다른 프로세스의 공간에 접근할 수 없다. 하지만 요즘은 성능을 높이기 위해 여러 프로세스를 만들어서 동시 실행하기 때문에 프로세스끼리 서로의 상태를 확인하고 데이터를 송수신할 필요가 있다.

fork() 시스템 콜

fork() 함수로 프로세스 자신을 복사해서 새로운 프로세스를 만들 수 있다. 이렇게 복사한 프로세스는 각각의 코어에서 병렬로 실행할 수 있다.

가볍게 생각해보기

  1. 1 ~ 10000까지 더하기

fork() 함수로 10개 프로세스를 만들어 각각 1~1000, 1001~2000 … 을 더하여 각각의 프로세스의 결과값을 합친다. 이 때 각 프로세스가 더한 값을 수집해야 하므로 프로세스간 통신이 필요하다.

  1. 웹 서버 만들기

웹 서버란 요청이 오면 HTML과 같은 정적 파일을 클라이언트에 제공한다. 새로운 사용자 요청이 올 때마다 fork() 함수로 새로운 프로세스를 만들고 각 사용자 요청에 즉시 대응할 수 있다. 이 때 프로세스의 제어 및 상태 정보 교환을 위하여 프로세스간 통신이 필요하다.

파일을 사용한 커뮤니케이션

간단히 다른 프로세스에 전달할 내용을 파일에 쓰고 다른 프로세스가 그 파일을 읽는다고 가정해보자. 이러한 방법은 실시간으로 데이터 전달이 어렵다는 문제점이 있다. 파일 입출력과 프로세스 실행을 동시에 할 수 없다. 또한, 파일을 사용한다는 것은 HDD 등의 보조 기억장치를 이용한다는 것인데 이러한 과정에서 오버헤드가 지나치게 크다.

실제 프로세스

프로세스간 공간은 완전히 분리되어 있어 사용자 모드에서는 커널 공간에 접근할 수 없다.

물리 메모리에서는 모든 프로세스가 커널 공간을 공유한다. 대부분의 IPC 기법은 이 커널 공간을 활용하여 이루어 진다.

다양한 IPC 기법

  1. file 사용
  2. Message Queue
  3. Shared Memory
  4. Pipe
  5. Signal
  6. Semaphore
  7. Socket

2번부터는 모두 커널 공간을 사용

다양한 IPC 기법

Pipe (파이프)

기본 파이프는 단방향 통신으로 fork()로 자식 프로세스를 만들었을 때 부모와 자식간의 통신을 담당한다.

Pipe

예제

char* msg = "Hello Child Process!";
int main()
{
	char buf[255];
	int fd[2], pid, nbytes;
	if (pipe(fd) < 0) // pipe(fd)로 파이프 생성
		exit(1);
	pid = fork(); // 이 함수 실행 다음 코드부터 부모/자식 프로세스로 나눠짐
	if (pid > 0) { // 부모 프로세스는 pid에 실제 프로세스 ID가 들어감
		write(fd[1], msg, MSGSIZE); //fd[1]에 씁니다.
		exit(0);
	}
	else { // 자식 프로세스는 pid가 0이 들어감
		nbytes = read(fd[0], buf, MSGSIZE); // fd[0]으로 읽음
		printf("%d %s\n", nbytes, buf);
		exit(0);
    }
    return 0;
}

메시지 큐 (message queue)

FIFO 정책으로 데이터를 전송한다. 메시지 큐는 파이프와 달리 부모/자식이 아니라 어느 프로세스 간에도 데이터 송수신이 가능하다.

예제

  • A 프로세스
msqid = msgget(key, msgflg) // key는 1234, msgflg는 옵션
msgsnd(msqid, &sbuf, buf_length, IPC_NOWAIT)
  • B 프로세스
msqid = msgget(key, msgflg) // key는 동일하게 1234로 해야 해당 큐의 msgid를 얻을 수 있음
msgrcv(msqid, &rbuf, MSGSZ, 1, 0)

공유 메모리 (shared memory)

kernel space에 메모리 공간을 만들어 해당 공간을 변수처럼 쓰는 방식이다. 공유메모리 key를 가지고 여러 프로세스가 접근 가능하다.

예제

  1. 공유 메모리 생성 및 공유 메모리 주소 얻기
shmid = shmget((key_t)1234, SIZE, IPC_CREAT|0666))
shmaddr = shmat(shmid, (void *)0, 0)
  1. 공유 메모리에 쓰기
strcpy((char *)shmaddr, "Linux Programming")
  1. 공유 메모리에서 읽기
printf("%s\n", (char *)shmaddr)

시그널 (signal)

  • UNIX에서 30년 이상 사용된 전통적인 기법
  • 커널 또는 프로세스에서 다른 프로세스에 어떤 이벤트가 발생되었는지 알려주는 기법
  • 프로세스 관련 코드에 관련 시그널 핸들러를 등록해서 해당 시그널 처리를 실행
    1. 시그널 무시
    2. 시그널 블록 (블록을 푸는 순간 프로세스에 해당 시그널 전달)
    3. 등록된 시그널 핸들러로 특정 동작 수행
    4. 등록된 시그널 핸들러가 없다면 커널에서 기본 동작 수행

주요 시그널

기본 동작

SIGKILL: 프로세스 강제 종료, 슈퍼관리자가 사용하는 시그널

SIGALARM: 알람 발생

SIGSTP: 프로세스 중단

SIGCONT: 멈춰진 프로세스 실행

SIGINT: 프로세스 인터럽트

SIGSEGV: 프로세스가 다른 메모리 영역을 침범

예제

  1. 시그널 핸들러 등록 및 핸들러 구현
static void signal_handler (int signo) {
	printf("Catch SIGINT!\n");
	exit (EXIT_SUCCESS);
}

int main (void) {
	if (signal (SIGINT, signal_handler) == SIG_ERR) {
		printf("Can't catch SIGINT!\n");
		exit (EXIT_FAILURE);
	}
    for (;;)
		pause();
	return 0;
}
  1. 시그널 핸들러 무시
int main (void) {
    if (signal (SIGINT, SIG_IGN) == SIG_ERR) {
		printf("Can't catch SIGINT!\n");
		exit (EXIT_FAILURE);
	}
	for (;;)
		pause();
	return 0;
}

시그널과 프로세스

PCB에 해당 프로세스가 블록 또는 처리해야 하는 시그널 관련 정보 관리

SignalPCB

소켓 (socket)

  • 기본적으로는 두 개의 다른 컴퓨터 간의 네트워크 기반 통신을 위한 기술
  • 하나의 컴퓨터 안에서 두 개의 프로세스간 통신 기법으로도 사용 가능