6 분 소요


IPC를 공부할 때 정말 어려웠는데🤯🤯🤯🤯 taegyunwoo.github 이 분이 작성하신 포스팅이 정말 많은 도움이 되었다. 한 번 보고오시길😼


▶ IPC란?

  • IPC (Inter-Process Communication)란?
    • 동일한 시스템에서 실행되는 두 개 이상의 프로세스 간에 데이터를 교환을 의미한다.


  • 특징

    • 컴퓨터 시스템에서 프로그램은 독립적인 공간을 가진다.
      • 즉, OS는 메모리 공간에 대해서 다른 프로세스가 접근하지 못하도록 외부 접근을 차단한다.
    • 이렇게 프로세스는 원래 독립적이지만, 상황에 따라서 프로세스끼리 협력해야하는 경우가 발생한다.
    • 이럴 때 프로세스간 자원과 데이터를 공유할 수 있어야 하는데, 커널 영역에서 IPC라는 내부 프로세스간 통신을 제공하게 되고, 프로세스는 커널이 제공하는 IPC설비를 이용해서 프로세스간 통신을 할 수 있게 된다.


    • 따라서, 프로세스는 독립적이고 협력적이라는 특징을 가지고 있다.




▶ IPC 모델

종류 Message passing(메세지 전달) Shared memory(공유 메모리)
방식 커널을 통해 메세지를 전달(메세지 송신, 메세지 수신)하는 방식으로 자원이나 데이터를 주고 받는다. 프로세스끼리 특정 공통의 메모리 영역인 세그먼트를 공유하며 자원이나 데이터를 공유하는 방식이다.
장점 ① 커널을 이용하기 때문에 구현이 비교적 쉽다.
② 보내는 쪽과 받는 쪽 사이의 동기화 문제나 충돌 상황을 처리하는 것이 상대적으로 간단해진다.
커널 의존도가 낮기 때문에 속도가 빠르고 통신이 자유롭다.
단점 시스템 콜이 필요하며 이로 인해 커널 오버헤드가 발생해 속도가 느릴 수 있다. 자원과 데이터를 공유하기 때문에 동기화 문제가 발생한다.
종류 Pipe, Signal, Message Queueing, Socket 등 Semaphores 등



▷ Shared memory

  • 공유 메모리 원리
    • 공유 메모리를 사용하는 프로세스들은 공유 메모리 영역인 세그먼트를 구축해야 한다.



▷ Message passing

① 동기화에 따른 분류

  • 전역 변수를 사용하는 통신 방식의 문제는 언제 데이터를 보낼지 데이터를 받는 쪽에서 모른다는 것이다. 그러므로 데이터를 받는 쪽에서 반복적으로 전역 변수의 값을 점검하여 언제 데이터를 보냈는지 확인해야한다.
  • 이처럼 상태 변화를 살펴보기 위해 반복문을 무한 실행하며 기다리는 것을 바쁜 대기라고 한다.
  • 바쁜 대기 문제를 해결하기 위해서는 데이터가 도착했음을 알려주는 동기화(Synchronization)를 사용해야 한다. (ex. 메세지 도착 알림은 동기화의 예시다.)
  • 따라서 프로세스 통신은 동기화 기능이 있느냐 없느냐에 따라 아래와 같이 나뉜다.

    분류 동기화 통신 (대기가 있는 통신) 비동기화 통신 (대기가 없는 통신)
    예시 파이프, 소켓 전역변수,파일


동기화는 봉쇄(Blocking)와 비봉쇄(Non-blocking) 방식이 있다.

  • 봉쇄(Blocking)을 통해, 동기(Sync)를 할 수 있다.
  • 하나의 프로세스나 스레드가 어떤 조건을 충족할 때까지 다른 프로세스나 스레드가 실행되지 않고 대기하는 방식이다.

    • 장점 : 프로세스 간의 통신이 완전히 이루어 질 때까지 다른 작업 진행하지 않으므로 데이터 일관성과 순서를 보장하는 데 유용하다.
    • 단점 : 프로세스 간의 통신이 완전히 이루어질때까지 해당 프로세스가 일시중단되므로 효율성이 저하될 수 있다.

      종류 봉쇄형 송신 봉쇄형 수신
      설명 sender는 보낸 메시지가 수신될 때까지 대기한다.
      receiver는 메시지가 도착할 때까지 대기한다.


  • 비봉쇄형 (= Non-blocking)

    • 비봉쇄(Unblocking)을 통해, 비동기(async)를 할 수 있다.

    • 한 프로세스나 스레드가 대기 상태에 들어가지 않고 계속해서 다른 작업을 수행할 수 있도록 하는 방식이다.

      종류 비봉쇄형 송신 비봉쇄형 수신
      설명 sender는 메시지를 보내고, 할일을 계속한다. receiver는 ‘유효한 메시지를 받거나’,
      ‘Null 메시지라도 받아서’ 작업을 계속한다.


  • 랑데뷰 현상
    • 송신 프로세스가 메시지 보내고 수신 프로세스가 수신되고 처리될 때까지 대기하며 두 프로세스는 특정 시점(메시지 교환 지점)에서 서로를 기다리게 되는 시점을 말한다.
      • 즉, send와 receive가 동시에 Block인 상태를 뜻한다.
    • 한 번에 하나씩만 주고받을 수 있으므로 성능이 좋지 않다.


② 통신방향에 따른 분류

  • 프로세스는 데이터를 주거나 받으면 통신을 한다.
  • 동기화에 따른 분류 외에 데이터가 전송되는 방향에 따라 통신을 분류할 수 있다.

    분류 양방향 단양방향 단방향
    방식 데이터를 동시에 양쪽 방향으로 전송할 수 있는 구조로, 일반적인 통신은 모두 양방향 통신이다. 데이터를 양쪽 방향으로 전송할 수 있지만 동시 전송은 불가능하고 특정 시점에 한쪽 방향으로만 전송할 수 있는 구조이다. 모스 신호처럼 한쪽 방향으로만 데이터를 전송할 수 있는 구조이다.
    예시 소켓 무전기 파이프, 전역 변수




▶ Message passing 종류

▷ Mach

  • Mach 시스템
    • 커널을 통해 Massage Passing을 한다. (커널을 통과)
    • Mach는 Microkernels이다! ( 더 알고싶으면 Microkernels을 클릭! ) msg_send() : 메시지를 메일박스에 보낸다. msg_receive() : 메일박스에서 메시지를 수신한다. mag_rpc() : 원격 프로시저 콜 port_allocate() : 새로운 메일박스 생성, 메일박스 메세지 큐를 위한 공간 할당



▷ Socket

  • Socket(소켓)
    • 여러 컴퓨터가 네트워크로 연결되어 있을 때 프로세스는 소켓을 이용하여 데이터를 주고받는다. 이처럼 소켓을 이용하는 프로세스 간 통신을 네트워킹이라고 한다.
    • 네트워킹 상에서의 통신은 원격 프로시저 호출이나 소켓을 이용한다.
    • 소켓은 동기화를 지원하므로 바쁜 대기를 하지 않아도 되고, 하나만 사용해도 양방향 통신이 가능하다.


  • 소켓의 구성요소
    • ip
    • port
      • 1024번 이하 포트는 주로 사용되는 메인 포트이다
      • 개발, 테스트 용으로 포트를 사용할 때는 1024번 이상 포트를 사용해야 한다.


  • 돼지코가 소켓임!


  • Loopback 주소
    • 자기 자신으로 돌아오는 주소이다.
    • localhost
    • 127.0.0.1



▷ RPC

  • 원격 프로시저 콜(RPC, Remote Procedure Call)

    • 내가 아닌 원격지(remote)에 존재하는 프로시저를 호출하는 방법이다.
    • 프로시저 : 특정한 로직을 처리하기만 하고 결과 값을 반환하지 않는 서브 프로그램


    • 즉, 다른 컴퓨터에 있는 함수를 호출한다.
    • 일반적으로 원격 프로시저 콜은 소켓을 이용해 구현한다.

    1. 클라이언트와 서버가 있다. (클라우드 컴퓨텅이랑 똑같은 구조)
    2. 클라이언트에서 ig라는 값을 k에 넣는 연산을 하고싶으면!! (간단해 보여도 무지하게 복잡한 연산임)
    3. 클라우드에 있는 서버한테 연산을 대신 시킨다.



▷ Pipe

  • Pipe(파이프)

    • 두 프로세스가 통신할 수 있게 하는 통로이다.
    • 하나의 프로세스가 파이프를 이용중 이라면 다른 프로세스는 접근할 수 없다.


    • 일반 파이프(Ordinary Pipes) (= 익명 파이프)

      • 부모, 자식 프로세스 간의 통신시 사용하는 파이프이다. (생산자-소비자 형태)
      • 단방향 통신만 가능하며 양방향 통신을 위해 두 개의 파이프를 사용해야 한다.
      • 값을 쓰거나 읽어야 하기 때문에 2bit가 필요하다.
      • 프로세스들이 통신을 마치고 종료하면 일반 파이프는 없어진다.


    • 지명 파이프(Named Pipe) (= FIFO)

      • 양방향 통신이 가능해 부모-자식 관계여야만 사용할 수 있다는 제한이 존재하지 않는다.
      • 관련 없는 프로세스 간에도 통신을 가능하게 한다.
      • 프로세스 통신이 종료되어도 보통 파일처럼 존재한다.




▶ 임계 구역

  • 임계 구역
    • 공유 자원 접근 순서에 따라 실행 결과가 달라지는 프로그램의 영역을 말한다.
    • 임계구역에서는 프로세스가 동시에 작업하면 안 된다.
    • ex. 주방에서 가스레인지는 공유가 가능한 자원이지만, 믹서기를 사용할 때는 순서를 지켜야 하므로 공유가 불가능한 자원이므로 임계구역이다.


▷ 생산자-소비자 문제

임계구역과 관련된 전통적인 문제로 생산자 - 소비자 문제가 있다.

  • 협력 프로세스에서는 생산자와 소비자 역할을 하는 두 개의 프로세스가 존재한다.
  • 생산자 프로세스가 정보나 데이터를 생성, 이 정보는 소비자 프로세스에 의해 소비된다.


  • 생산자는 계속 수를 증가시켜가며 버퍼에 넣고 : input(buf)
  • 소비자는 생산자를 쫓아가며 물건을 소비한다 : output(buf)
  • 또한 버퍼가 비었는지 가득 찼는지 확인하기 위해 sum이라는 전역 변수를 사용한다. (sum에는 현재 버퍼에 있는 상품의 총 수 저장)


  • 생산자와 소비자는 독립적이기 때문에 생산자 코드와 소비자 코드가 동시에 실행되면 문제가 발생한다. 생산자와 소비자가 전역 변수 sum에 접근하는 타이밍을 서로 맞추지 않기 때문이다.


생산자-소비자 문제 예시

  • ① 생산자가 물건 하나를 buf 4에 저장sum=sum+1;하고 코드 실행을 하지 않은 상태이다.
  • ② 소비자가 물건 하나를 소비sum=sum-1;하고 코드 실행을 하지 않은 상태이다.
  • 이 상황에서 생산자와 코드를 거의 동시에 실행했을 때, 생산자와 소비자는 독립적이기 때문에 상대방이 sum을 바꾸려는 것을 모른 채 현재 상태인 sum=3을 읽어서 작업을 한다.
    • 따라서 ①, ② 순서로 실행됐을 때 sum=2가 되고, ②, ① 순서로 실행됐을 때 sum=4가 되는 것이다.




▶ 버퍼⭐

  • 버퍼의 종류

    종류 무한버퍼 유한 버퍼
    특징 ① 제한이 없는 크기를 갖는 버퍼
    ② 크기에 대한 제약이 없으므로 생산자가 계속해서 데이터를 생성하고, 소비자가 필요할 때마다 데이터를 가져갈 수 있다.
    ① 고정된 크기를 갖는 버퍼
    ② 사전에 정해진 크기만큼만 데이터를 저장, 만약 버퍼가 가득 차거나 비어있으면, 생산자나 소비자 프로세스는 대기 상태에 들어간다.
    장점 손실이 안된다 딜레이를 줄일 수 있다.
    단점 쌓인 데이터가 많을 시 속도가 느려진다. packet 손실이 발생할 수 있다.


▷ 유한 버퍼

  • 유한 버퍼는 공유 메모리 해결책이다!


유한 버퍼 소스코드⭐⭐⭐⭐

#define BUFFER_SIZE 4
typedef struct {
	//...
} item;

item buffer[BUFFER_SIZE]; // 사이즈 4인 배열 선언
int in = 0; // 버퍼에서 다음으로 올라갈 프로세스 (쓸 곳의 index)
int out = 0; // 다음에 실행될 프로세스 (읽을 곳의 index)


  • 버퍼 상태

    • in == out : buffer empty
    • ((in+1)%BUFFER_SIZE) == out : buffer full


▷ 유한버퍼 : 생산자

아래 코드는 버퍼에 생산자가 공유할 데이터를 생산하는 예시 코드이다.

item next_produced; // 다음 생성할 원소 저장
while (true) {
	whlie(((in + 1) % BUFFER_SIZE == out) {// buffer full인 경우, 새로운 원소 저장할 수 없다면 대기 상태
		/* do nothing */
    }
	buffer[in] = next_produced; // 비어 있는 곳 변수(in)에 item 삽입(생산)
	in = (in + 1) % BUFFER_SIZE; // 변수 in이 다음 비어 있는 원소를 가리키도록 한다.
}
  • ((in+1)%BUFFER_SIZE)==out : 해당 조건은 원형 큐 형태인 버퍼가 full 상태인지 판단한다.
  • while 문을 통해, 버퍼가 꽉 차있으면 공유할 데이터를 삽입하지 않도록 한다.


▷ 유한버퍼: 소비자

  • 아래 코드는 버퍼에 소비자가 공유할 데이터를 소비하는 예시 코드이다.
 item next_consumed;
  while (true) {
  	while (in==out) { // 공백 상태일 때
  		; /* do nothing */

  	next_consumed = buffer[out]; // 첫 번째 원소 가져옴
  	out = (out+1) % BUFFER_SIZE; // 변수 out이 다음 원소로 인덱스 이동
}


▷ 환영 버퍼(원형 버퍼) 예시

  • 사용자는 최대 BUFFER_SIZE-1개 만큼의 버퍼를 사용할 수 있다.
  • 그 이유는 마지막 공간까지 데이터가 채워진다면, full 상태와 empty 상태를 구분할 수 없기 때문이다.
    • (in+1)%4 == 0이므로 buffer가 full 상태가 되어 더 이상 입력을 받지 않음


  • 환영 버퍼 예시로 정말 그런지 확인해보자!
    • (in+1)%4==0 이므로 in ==out인 상태이다, 따라서 full과 empty 구분이 안된다.




📎참조

  • 성결대학교 강영명 교수님 운영체제 (2023)
  • 쉽게 배우는 운영체제 - 한빛 아카데미
  • https://taegyunwoo.github.io/os/OS_Process2
  • https://y-oni.tistory.com/77
  • https://velog.io/@yanghl98/OS%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-IPC%EB%9E%80
  • https://pwnkidh8n.tistory.com/160

태그: ,

카테고리:

업데이트:

댓글남기기