본문 바로가기
Computer Science

프로세스간 커뮤니케이션 (IPC)

by 개발자 데이빗 2021. 12. 28.

IPC (Inter Process Communication)

IPC는 프로세스간 커뮤니케이션을 하기 위해 사용되는 기법

원칙적으로 프로세스는 다른 프로세스의 공간을 접근할 수 없다. (프로세스의 데이터/코드가 바뀔 수 있어 위험하기 때문)

 

프로세스간 통신이 필요한 이유

성능을 높이기 위해 여러 프로세스를 만들어서 동시에 실행하기 때문에 이 때 프로세스간 상태 확인 및 데이터 송수신이 필요하다.

fork() 시스템콜 함수로 자신을 복사해서 새로운 프로세스를 만들 수 있다. (부모 프로세스, 자식 프로세스)

 

예를 들어 1~10000까지 더하는 프로그램을 실행할 때 fork()함수로 10개 프로세스를 만들어 각각 1000씩을 더한 후에

각각 더한 값을 모두 합하면 10배 빠르게 동작이 가능하다.

단 각각 더한 값을 수집해야 하므로 프로세스간 통신이 필요하다.

 

웹서버 예시

새로운 사용자 요청이 올 때마다, fork() 함수로 새로운 프로세스를 만들고 각 사용자 요청에 즉시 대응

cpu 병렬처리가 가능하다면 더 빠른 처리가 가능하다.

 

프로세스는 다른 프로세스의 공간을 접근할 수 없지만 저장매체를 공유할 수 있다. (file)

file을 사용하면 실시간으로 직접 원하는 프로세스에 데이터 전달이 어려움

(A프로세스가 저장매체에 데이터를 업데이트해도 B프로세스에 데이터가 업데이트 된 사실을 전달하기 힘들다)

저장매체의 데이터를 읽기 위한 시간이 많이 소요된다.

커널공간 사용 - 자세한 내용은 시스템 프로그래밍에서 다룬다.

 

pipe (파이프) 

기본 파이프는 단방향 통신이기 때문에 부모 프로세스에서 write 한 데이터를 자식 프로세스에서 read 할 수는 있지만 반대는 불가능 하다.

fork() 시스템콜로 자식 프로세스를 만들었을 때, 부모와 자식간의 통신이 가능하다.

 

파이프 코드예시

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(); // 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 정책으로 데이터를 전송한다.

부모 자식 관계 없이 key값을 동일하게 하면 해당 큐의 msgid를 얻어 데이터를 공유할 수 있다. 

어느 프로세스던지 공유가 가능하다.

양방향 통신이 가능하다.

fork() 시스템콜이 필요 없다.

 

메세지큐 코드 예시

// A 프로세스
msqid = msgget(key, msgflg) // key는 1234, msgflg는 옵션
msgsnd(msqid, &sbuf, buf_length, IPC_NOWIAT)

// B 프로세스
msqid = msgget(key, msgflg) // key를 동일하게 1234로 해야 해당 큐의 msqid를 얻을 수 있다.
msgrcv(msgiq, &rbuf, MSGSZ, 1, 0)

Shared Memory (공유 메모리) 코드 예시 및 특징

노골적으로 커널 공간에 메모리 공간을 만들고 해당 공간을 변수처럼 쓰는 방식

공유 메모리 key를 가지고 주소를 얻어내서 여러 프로세스가 접근 가능하다.

// 공유 메모리 생성 및 공유 메모리 주소 얻기
shmic = shmget((key_t)1234, SIZE, IPC_CREAT|0666)
shmaddr = shmat(shmid, (void *)0, 0)

// 공유 메모리에 쓰기
strcpy((char *)shmaddr, "Hello World")

// 공유 메모리에서 읽기
printf("%s\n", (char *)shmaddr)

 

 

Signal과 Socket은 IPC 이외에도 다양한 용도로 사용된다.

Signal (시그널)

유닉스에서 30년 이상 사용된 전통적인 기법이다.

커널 또는 프로세스에서 다른 프로세스에 어떤 이벤트가 발생되었는지를 알려주는 기법

프로세스 관련 코드에 관련 시그널 핸들러를 등록해서, 해당 시그널 처리 실행

  • 시그널 무시
  • 시그널 블록
  • 등록된 시그널 핸들러로 특정 동작 수행
  • 등록된 시그널 핸들러가 없다면 커널에서 기본 동작 수행

 

주요 시그널

  • SKGKILL 프로세스를 죽인다. (슈퍼 관리자가 사용하는 시그널, 어떤 경우든 프로세스가 죽는다)
  • SIGALARM: 알람 발생
  • SIGSTP 프로세스를 멈춘다 (ctrl + z)
  • SIGCONT 멈춰진 프로세스를 실행한다.
  • SIGINT 프로세스에 인터럽트를 보내서 프로세스를 죽인다 (ctrl + c)
  • SIGSEGV 프로세스가 다른 메모리 영역을 침범했다

특정 signal은 특별 동작을 취하도록 정의할 수 있다

SIGUSR의 경우 기본동작이 없지만 특별한 동작을 정의하여 사용할 수 있다.

 

시그널 핸들러 등록 및 핸들러 구현 코드 예시

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;
}

 

시그널 무시 코드 예시

int main (void) {
	if (signal (SIGINT, SIG_IGN) == SIG_ERR) {
		printf("Can't catch SIGINT!\n");
		exit (EXIT_FAILTURE);
	}
	for (;;)
		pause();
	return 0;
}

 

시그널과 프로세스

PCB에 해당 프로세스가 블록 또는 처리애햐하는 시그널 관련 정보를 관리한다.

 

Socket (소켓)

소켓은 네트워크 통신을 위한 기술

기본적으로는 클라이언트와 서버등 두 개의 다른 컴퓨터간의 네트워크 기반 통신을 위한 기술이다.

소켓을 하나의 컴퓨터 안에서 두 개의 프로세스간의 통신 기법으로 사용 가능하다

커널모드의 Network Device Driver 계층을 통해 통신

 

기타 IPC 기법

  • Semaphore

추가 지식 - 가상메모리에서 자세하게 다룸

프로세스 주소는 0 ~ 4GB

3~ 4GB = 운영체제 코드

0~ 3GB = 실제 작성된 프로그램 코드

이는 가상메모리라고 하며 실제 물리 메모리에는 극히 일부분만 적재된다.

운영체제의 경우에는 실제 물리 메모리에 적재될때는 한번 적재되고 같은 운영체제 코드를 공유한다.

즉 커널공간은 프로세스간에 공유가 된다.

 

정리

여러 프로세스 동시 실행을 통한 성능 개선, 복잡한 프로그램을 위해 프로세스간 통신이 필요하다.

프로세스간 공간은 완전 분리 되어있다.

프로세스간 통신을 위한 특별한 기법 - IPC

대부분의 IPC 기법은 커널 공간을 활용하는 것이다 - 커널 공간은 프로세스간 공유가 된다.

'Computer Science' 카테고리의 다른 글

가상 메모리  (0) 2022.01.01
스레드 (Thread)  (0) 2021.12.29
프로세스의 구조와 컨텍스트 스위칭  (0) 2021.12.27
인터럽트  (0) 2021.12.26
프로세스 스케줄링 알고리즘  (0) 2021.12.25

댓글