Operating System

독립적인 프로세스들은 다른 프로세스의 실행에 영향을 주거나 받을 수 없다. 하지만 Cooperating 프로세스들은 다른 프로세스의 실행에 영향을 주거나 받을 수 있다. 여러 개의 프로세스/스레드(thread)가 동시에 작업을 진행하는 것이다.

이는

  • 정보 공유
  • 계산 속도 증가
  • 모듈성
  • 편리함

등의 이점을 준다.

Inter-Process Communication(IPC)

IPC는 프로세스들이 통신하고 그들의 행위를 동기화하기 위한 매커니즘이다. 두 가지 방법이 존재한다.

  • 공유된 변수(또는 메모리) 사용
  • 메시지 전달 사용

467414_b45778c5-0bb5-4feb-82c7-3f284f8e2da8_lg.png

POSIX Shared Memory(Producer)

#include <stdio.h>
#include <stlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <sys/stat.h>

int main()
{
  /* the size (in bytes) of shared memory object */
  const int SIZE 4096;
  /* name of the shared memory object */
  const char *name = "OS";
  /* strings written to shared memory */
  const char *message_0 = "Hello";
  const char *message_1 = "World!";

  /* shared memory file descriptor */
  int shm_fd;
  /* pointer to shared memory object */
  void *ptr;

  /* Create the shared memory object */
  shm_fd = shm_open(name, O_CREAT | O_RDRW, 0666);

  /* configure the size of the shared memory object */
  ftruncate(shm_fd, SIZE);

  /* memory map the shared memory object */
  ptr = mmap(0, SIZE, PROT_WRITE, MAP_SHARED, shm_fd, 0);

  /* write to the shared memory object */
  sprintf(ptr, "%s", message_0);
  ptr += strlen(message_0);
  sprintf(ptr, "%s", message_1);
  ptr += strlen(message_1);

  return 0;
}

POSIX Shared Memory(Consumer)

#include <stdio.h>
#include <stlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <sys/stat.h>

int main()
{
  /* the size (in bytes) of shared memory object */
  const int SIZE 4096;
  /* name of the shared memory object */
  const char *name = "OS";
  /* strings written to shared memory */
  const char *message_0 = "Hello";
  const char *message_1 = "World!";

  /* shared memory file descriptor */
  int shm_fd;
  /* pointer to shared memory object */
  void *ptr;

  /* Create the shared memory object */
  shm_fd = shm_open(name, O_RDONLY, 0666);

  /* memory map the shared memory object */
  ptr = mmap(0, SIZE, PROT_READ, MAP_SHARED, shm_fd, 0);

  /* read from the shared memory object */
  printf("%s", (char*)ptr);

  /* remove the shared memory object */
  shm_unlink(name);

  return 0;
}

위와 같은 방식으로 공유된 메모리를 통해 프로세스끼리 통신할 수 있다. 사용된 함수는 모두 System call 함수이다.

Message Passing System

메시지 전달 방식은 프로세스들이 같은 주소의 공간을 공유하지 않고 통신한다. IPD (message passing) 도구는 두 가지의 오퍼레이션을 제공한다

  • send(message) - 고정된 크기의 메시지나 변수, 다양한 형태의 parameter
  • receive(message)

만약 P와 Q가 통신하길 원한다면, 다음과 같은 것이 필요하다.

  • 둘 사이의 통신 링크(논리적 링크)
  • send/receive를 통한 메시지 교환

메시지 전달 시스템을 디자인하는 데에는 다음과 같은 이슈들이 존재한다. 차례대로 하나씩 살펴보도록 하자.

  • 네이밍(Naming)
  • 동기화(Synchronization)
  • 버퍼링(Buffering)

Naming

  • Direct Communication: 프로세스들은 서로를 명확히 지정해야 한다.
    • send(P, message) - 프로세스 P로 메시지를 보낸다.
    • receive(Q, message) - 프로세스 Q로부터 온 메시지를 받는다.
  • Indirect Communication: 메시지는 특정 프로세스를 지정하지 않고 특정 사서함(포트)로 지정되고 전송된다.
    • 각각의 사서함은 고유한 아이디를 가진다.
    • 프로세스들은 그들의 사서함을 공유할 때만 통신할 수 있다.
    • send(A, message) - 사서함 A로 메시지를 보낸다.
    • receive(A, message) = 사서함 A로부터 메시지를 받는다.

Sychronization

메시지 전달은 blocking 또는 non-blocking일 수 있다.

  • Blocking은 동기적이다.

  • Non-blocking은 비동기적이다.

  • Blocking send: 메시지를 보내는 프로세스는 메시지가 메시지를 받는 프로세스나 사서함에 보내지기 전까지 동작하지 않는다.

  • Non-blokcing send: 메시지를 보내는 프로세스는 메시지를 보내고 오퍼레이션을 다시 시작한다.
    Blocking receive: 메시지가 유효하기 전까지 동작하지 않는다.

  • Non-blocking receive: 유효한 메시지든 아니든 계속 동작한다.

Buffering

메시지 큐가 링크에 붙어 있다. 일종의 데이터 보관함이다. 세 가지 방법 중 하나로 구현된다.

  • Zero capacity: 0 messages, 보내는 프로세스는 받는 프로세스를 기다려야만 한다. (동기화) 이 경우 버퍼는 필요하지 않다.
  • Bounded capacity: n messages, 보내는 프로세스는 링크가 꽉 찬 경우 기다려야 한다.
  • Unbounded capacity: 무한한 길이, 보내는 프로세스는 기다리지 않는다. 하드웨어에 제한이 존재하므로 현실적으로는 불가능하다.

IPC in UNIX

Inter-Process Communication은 여러 프로세스들이 그들 사이에서 통신하기 위한 매커니즘이다. 다른 프로세스들은 다른 주소 공간에서 동작한다. 따라서 운영체제는 통신하기 위한 매커니즘을 제공해야 하는데, 이것이 IPC이다.

UNIX에서의 IPC 타입들은 다음과 같다. system V IPC는 전통적 IPC를 사용할 수 있다.

  • 전통적 UNIX IPCs: signal, pipe, socket
  • System V IPCs: message queue, semaphore, shared memory

Pipe

파이프(pipe)는 한 프로세스를 다른 프로세스로 연결하는 단방향의 byte 스트림이다.

  • 한 통로의 끝에서 작성된 데이터는 다른 끝에서 읽힌다.
  • 논리적 관점에서, 파이프는 글자들의 FIFO 큐와 유사하다.

pipe.gif

  • 구조화되지 않은 통신: 파이프에 있는 데이터의 송신자, 수신자나 크기는 알 수 없다.
  • 파이프에 접근하는 것은 file descriptor에/로부터 작성함으로써/읽음으로써 가능하다.

Pipe Used by Commands

파이프 매커니즘의 현재 사용 중 하나는 커맨드들이 연결될 때 커맨드 라인 인터프리터의 수단으로써 수행되는 것이다.

iopipe02b.png

Ordinary (Anonymous) pipe

프로세스에 의해 생성된, 그리고 연관된 descriptor 사이에서의 전달은 parend-child(조상과 자손) 사이에서만 가능하다.
혹은 파이프의 생성자를 공통된 조상으로 하는 프로세스들 사이에서의 통신만 제한적으로 가능하다.

anonymous pipe의 생성

  • int pipe(int filesdes[2]);
    • filesdes[0]: read descriptor, filesdes[1]: write descriptor
    • filesdes는 desriptor의 배열이다.

parent_child.jpg

Use of Pipe - Example

3_22_PipeFileDescriptors.jpg

#include <stdio.h>
#include <unistd.h>

int main(void){
  /* fd[0] is for read, fd[1] is for write */
  inr n, fd[2], pid; char line[100];

  if(pipe(fd) < 0) exit(-1); // make pipe
  if((pid == fork()) < 0) exit(-1);
  else if(pid > 0) { /* parent */
    close(fd[0]);
    write(fd[1], "Hello, World\n", 12);
    wait(NULL) //wait child
  }
  else { /* child */
    close(fd[1]);
    n = read(fd[0], line, MAXLINE);
    write(STDOUT_FILENO, line, n);
  }
}

Named Pipe(FIFO)

anonymous 파이프의 제약이 named 파이프에서는 없다. 왜냐하면 그들의 엔트리들은 파일 시스템에 존재하기 때문이다. 커널 안의 메모리에 파일을 만든 개념이라고 생각할 수 있다.

이름을 가지고 파일처럼 다루어진다. (e.g., open, close, read, write)

  • mkfifo 또는 mknod로 생성된다.
  • C언어 함수에 의해 생성될 수도 있다.
    • int mkfifo(const char *path, mode_t mode);
    • path가 이름이 된다.
  • named 파이프에서 읽어오거나 쓰는 건 standard read() 그리고 write() 시스템 콜을 사용하면 된다.