Theory
운영체제
IPC

IPC(Inter Process Communication)

프로세스들 사이에 서로 데이터를 주고받는 행위 또는 그에 대한 방법이나 경로를 뜻함.

IPC 방식

Shared Memory

프로세스 사이에 공유 메모리를 두고 데이터를 공유하는 방식.

Buffer는 Circular Buffer로 선언하는게 좋음.

###define BUFFER_SIZE 10 
 
typedef struct {
	...
} item;
 
item buffer[BUFFER_SIZE];
int in = 0;
int out = 0;

Producer-Consumer Problem

Producer는 정보를 생성하고 Consumer는 정보를 소비한다.

  • ex) Compiler(생산자) Assembler(Consumer)

Producer

item next_produced;
 
while(true) {
  // 버퍼가 꽉 차 있으면 기다린다.
  while(((in + 1) % BUFFER_SIZE) == out) {
  }
 
  buffer[in] = next_produced;
  in = (in + 1) % BUFFER_SIZE;
}

Consumer

item next_consumed;
 
while(true) {
  // 버퍼가 비어 있으면 기다린다.
  while (in == out) {
  }
  next_consumed = buffer[out];
  out = (out + 1) % BUFFER_SIZE;
}

Shared Memory 방식의 문제점

메모리 영역을 공유하게 되면 어플리케이션 개발자에 의해서 해당하는 부분이 개발되어야 한다는 문제점이 존재한다.

무한버퍼 vs 유한버퍼
  • 무한 버퍼 : 버퍼의 실질적인 한계가 없다.
  • 유한 버퍼 : 버퍼의 크기가 고정되어 있으며 버퍼가 차 있으면 생산자가 기다려야 하고 버퍼가 비어있으면 소비자가 대기해야 한다.

Message Passing

커널이 관리하는 message queue를 두고 거기에서 데이터를 공유하는 방식.

운영체제가 Message를 공유해주는 메모리를 알아서 관리해준다. (근본 개념은 Shared Memory를 크게 벗어나진 않음.)

  • Direct 방식과 Indirect 방식으로 나눌 수 있다.
  • 크게 Synchronous 방식과 Asynchronous 방식이 있다.
  • Automatic 방식과 Explicit Buffering 방식으로 나눌 수 있다.

Direct

정확하게 목적지와 출발지가 명시되야 한다. 즉, 1:1 연결에 사용한다.

명시적인 통신 링크가 있어야 한다.

Indirect

Communication Link 방식이라고도 부른다.

메시지를 port(mailbox)에 전송하고 port에서 꺼내서 쓴다.

두 개의 프로세스가 shared box(port)가 있을 때만 연결된다는 특징이 있다.

2개 이상의 프로세스가 통신하는데에도 문제가 없다.

OS가 Indirect 방식에서 제공하는 것
  • mailbox(port) 생성
  • mailbox 읽기 쓰기 권한을 process에 부여
  • 필요없어지면 mailbox 삭제

Synchronous(Blocking IO)

데이터를 받고 나서 다음 라인을 실행한다.

Asynchronous(Non-Blocking IO)

메세지가 들어오든 말든 다음 라인을 실행한다.

Client-Server System

Sockets

IP Addrees와 Port를 가지고 데이터를 공유하는 프로세스간 연결 방법이다.

Loopback TCP

같은 컴퓨터 내의 프로세스간 통신에 특화된 방식이다.

루프백 주소로 포트를 열고 닫아서 해당하는 포트로 소켓 통신을 해서 데이터를 주고받는 방식으로 간단하고 빠르게 구현 가능하며 높은 성능을 보여준다는 장점이 있지만 같은 컴퓨터 내 프로세스 간 통신에만 사용 가능하다는 단점이 있다.

RPCs (Remote Procedure Calls)

원격에 있는 함수를 내가 대신 호출한다.

IPC 예시

PIPE

  • 초기 UNIX 시스템에서 사용되던 IPC 방식이다.
  • 파일 I/O를 이용하여 데이터를 주고 받는다여
  • FIFO 구조를 갖는다.
  • 단방향 통신만 가능하다.
  • 양방향 통신을 해야하는 상황이라면 2 개의 파이프를 생성해야 한다.
    • 때문에 전이중 통신을 고려해야 한다면 파이프는 좋은 선택이 아니다.
  • 장점 : 구현이 간단하고, 오버헤드가 작고 높은 효율성을 갖는다.
  • 단점 : 반이중 통신을 해야하고, 동일 프로세스 그룹 내에서만 사용이 가능하며 보안 문제가 발생할 수 있다.

예시코드

  1. 파이프 생성
int n = write(pipefd[1], "Hello, world!", 13);
if (n == -1) {
  perror("write");
  return 1;
}
  1. 데이터 쓰기
int n = write(pipefd[1], "Hello, world!", 13);
if (n == -1) {
  perror("write");
  return 1;
}
  1. 데이터 읽기
char buf[100];
int n = read(pipefd[0], buf, 100);
if (n == -1) {
  perror("read");
  return 1;
}
 
printf("%s\n", buf);
  1. 파이프 닫기
close(pipefd[0]);
close(pipefd[1]);

시스템 V 메시지 큐

리눅스에서 사용되는 IPC 방식 중 하나이다.

  • 메시지 기반 통신: 프로세스 간에 메시지를 전달하여 통신합니다.
  • 다중 프로세스 지원: 여러 프로세스가 동일한 메시지 큐에 접근하고 데이터를 공유할 수 있습니다.
  • 유연성: 메시지의 크기와 형식을 자유롭게 정의할 수 있습니다.
  • 보안: 메시지 큐에 대한 접근 권한을 제어할 수 있습니다.

구현

  • 메시지 큐 생성: msgget() 함수를 사용하여 메시지 큐를 생성합니다.
  • 메시지 전송: msgsnd() 함수를 사용하여 메시지 큐에 메시지를 전송합니다.
  • 메시지 수신: msgrcv() 함수를 사용하여 메시지 큐에서 메시지를 수신합니다.
  • 메시지 큐 삭제: msgctl() 함수를 사용하여 메시지 큐를 삭제합니다.

예시코드

#include <sys/msg.h>
 
int main() {
  // 메시지 큐 생성
  key_t key = ftok(".", 'a');
  int msgid = msgget(key, 0666 | IPC_CREAT);
  if (msgid == -1) {
    perror("msgget");
    return 1;
  }
 
  // 메시지 전송
  struct msgbuf {
    long mtype;
    char mtext[100];
  } msg;
  msg.mtype = 1;
  strcpy(msg.mtext, "Hello, world!");
  if (msgsnd(msgid, &msg, sizeof(msg), 0) == -1) {
    perror("msgsnd");
    return 1;
  }
 
  // 메시지 수신
  struct msgbuf recv_msg;
  if (msgrcv(msgid, &recv_msg, sizeof(recv_msg), 1, 0) == -1) {
    perror("msgrcv");
    return 1;
  }
 
  // 메시지 큐 삭제
  msgctl(msgid, IPC_RMID, NULL);
 
  return 0;
}

OS Signal 기법

  • 유닉스에서 30년 이상 사용된 전통적인 기법이다.
  • 어떤 커널 또는 프로세스에게 어떤 이벤트가 발생되었는지를 알려주는 기법이다.
  • 프로세스 코드에 시그널 핸들러를 등록하여, 해당 시그널을 처리하는 방식으로 동작한다.

Reference

리눅스 로컬 프로세스 간의 통신은 TCP통신과 UDS통신중 어느게 성능이 좋을까? (opens in a new tab)

22. 프로세스 - IPC 기법(signal, socket) (opens in a new tab)