Notice
Recent Posts
Recent Comments
05-21 07:17
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

Byeol Lo

3.7 Examples of IPC Systems 본문

OS/OS Design

3.7 Examples of IPC Systems

알 수 없는 사용자 2024. 4. 13. 16:16

 이 섹션에서는 네 가지 다른 IPC 시스템을 탐색한다. 먼저 메모리를 위한 POSIX API를 보자.

 

3.7.1 POSIX Shared Memory

 POSIX 시스템에서는 공유 메모리와 메시지 전달을 포함하여 여러 IPC 메커니즘이 제공된다. 여기서는 공유 메모리에 대한 POSIX API를 탐색한다. POSIX 공유 메모리는 메모리 매핑된 파일을 사용하여 구성된다. 이는 공유 메모리 영역을 파일과 연관시킨다. 프로세스는 먼저 shm_open() 시스템 호출을 사용하여 공유 메모리 개체를 생성해야 한다.

fd = shm_open(name, O_CREAT | O_RDWR, 0666);

 첫 매개변수는 공유 메모리 개체의 이름, 공유 메모리에 액세스하려는 프로세스는 이 이름으로 개체를 참조해야 한다. 그 다음 매개변수는 공유 메모리 개체가 아직 존재하지 않는 경우(O_CREAT)에 개체를 생성하고, 개체가 읽기 및 쓰기 용으로 열려 있음을 지정한다(O_RDWR). 마지막 매개변수는 공유 메모리 개체의 파일 액세스 권한을 설정한다. shm_open() 함수 호출이 성공하면 공유 메모리 개체에 대한 정수 파일 디스크립터를 반환한다.

 객체가 설정되면, ftruncate()함수를 사용하여 객체의 크기를 바이트 단위로 구성한다. 다음 호출인 ftruncate(fd, 4096);은 객체의 크기를4096 바이트로 설정한다. 마지막으로 mmap() 함수는 공유 메모리 개체를 포함하는 메모리 매핑 파일에 대한 포인터를 반환한다.

ftruncate(fd, 4096);

 이제 이를 생산자-소비자 모델을 사용하여, 생산자는 공유 메모리 개체를 설정하고, 공유 메모리에 쓰며, 소비자는 공유 메모리에서 읽는다.

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

#include <sys.mman.h>

int main()
{
  const int SIZE = 4096;
  const char *name = "OS";
  const char *message_0 = "Hello";
  const char *message_1 = "World!";
  
  int fd;
  char *ptr;
  
  fd = shm_open (name, O_CREAT | O_RDWR, 0666);
  ftruncate (fd, SIZE);
  
  ptr = (char *) mmap (0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
  
  sprintf (ptr, "%s", message_0);
  ptr += strlen (message_0);
  sprintf (ptr, "%s", message_1);
  ptr += strlen (message_1);
  
  return 0;
}

 위는 생산자는 OS라는 이름의 공유 메모리 객체를 생성하고, Hello 와 World! 를 공유 메모리에 기록한다. 이 프로그램은 지정된 크기의 공유 메모리 개체를 메모리에 매핑하고, 객체에 쓰기를 허용한다. MAP_SHARED 플래그는 공유 메모리 객체에 대한 변경 사항이 해당 객체를 공유하는 모든 프로세스에게 표시됨을 지정한다. 공유 메모리 객체에 쓰기 위해 sprintf() 함수를 호출하고 포인터 ptr에 서식이 있는 문자열을 기록하는 것에 주목하라. 각 쓰기 후에는 쓰여진 바이트 수만큼 포인터를 증가시켜야 한다.

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

#include <sys/mman.h>

int main()
{
  const int SIZE = 4096;
  const char *name = "OS";
  int fd;
  char *ptr;
  
  fd = shm_open(name, O_RDONLY, 0666);
  ptr = (char *) mmap (0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
  
  printf ("%s", (char *) ptr);
  shm_unlink(name);
  
  return 0;
}

 소비자는 또한 shm_unlink() 함수를 호출하여 공유 메모리 세그먼트에 접근한 후 해당 세그먼트를 제거한다.

 

3.7.2 Mach Message Passing

 Message Passing 모델에서, Mach 의 운영체제를 고려할 수 있다. Mach 는 분산시스템을 위해 특별히 설계된 운영체제였지만, macOS, iOS 운영체제에 포함되어서 데스크톱 및 모바일 시스템에도 적합하다는 것이 증명되었다. Mach 커널은 여러 작업을 생성하고 제거하는게 가능하고, 이 작업은 프로세스와 유사하지만, 여러 제어 스레드와 적은 수의 관련 리소스를 가진다. Mach 에서 대부분의 통신은 메시지를 통해 수행된다. 메시지는 Mach 에서 포트라고 불리는 메일 박스로 송수신한다. 포트는 크기가 유한하고 단방향이다. 양방향 통신을 위해 메시지가 한 포트로 송신하고, 다른 포트로 수신을 한다.

 포트는 크기가 유한하고 단방향이다. 양방향 통신을 위해 메시지가 한 포트로 보내지고, 다른 응답 포트로 응답이 보내진다. 각 포트는 여러 발신자를 가질 수 있지만, 하나의 수신자만 가질 수 있다. Mach는 작업, 스레드, 메모리, 프로세서와 같은 리소스를 나타내기 위해 포트를 사용하고, 메시지 전달은 이러한 시스템 리소스 및 서비스와 상호작용하기 위한 객체 지향적인 방식을 제공한다. 메시지 전달은 동일 호스트, 분산 시스템의 별도 호스트간의 모든 두 포트 간에 발생할 수 있다.

 각 포트와 관련된 것은 해당 포트와 상호작용하기 위해 필요한 기능을 식별하는 포트 권한의 모음이다. 포트로부터 메시지를 수신하려면 해당 포트에 대한 MACH_PORT_RIGHT_SEND 기능을 부여해야 한다. 포트 권한의 소유권은 작업 수준에서 이루어지고, 이는 동일한 작업에 속하는 모든 스레드가 동일한 포트 권한을 공유함을 의미한다. 따라서 동일한 작업에 속하는 두 스레드는 각 스레드에 연결된 개별 스레드 포트를 통해 메시지를 교환함으로써 쉽게 통신할 수 있다.

 작업이 생성될 때, 두 가지 특별한 포트인 작업 자체(task self port)와 알림 포트(notify port)도 생성된다. 커널은 작업 자체 포트(task self port)에 대한 수신 권한을 가지고 있어서 작업이 커널에게 메시지를 보낼 수 있다. 커널은 이벤트 발생에 대한 알림을 작업의 notify port로 보낼 수 있다.

 mach_port_allocate() 함수 호출은 새로운 포트를 생성하고, 해당 포트의 메시지 코에 대한 공간을 할당한다. 또한 포트의 권한을 식별한다. 각 포트 권한은 해당 포트에 대한 이름을 나타내며, 포트는 권한을 통해서만 액세스할 수 있다. 포트 이름은 단순한 정수 값이며 UNIX 파일 디스크립터와 매우 유사하게 작동한다.

mach_port_t port;
mach_port_allocate (
  mach_task_self(),
  MACH_PORT_RIGHT_RECEIVE,
  &port);

 각 작업은 또한 작업이 생성한 포트를 시스템 전체 부트스트랩 서버에 등록할 수 있는 부트스트랩 포트(bootstrap port)에 액세스할 수 있다. 한 번 포트가 부트스트랩 서버에 등록되면, 다른 작업은 이 레지스트리에서 포트를 찾아서 해당 포트로 메시지를 보내는 권한을 얻을 수 있다.

 각 포트와 관련된 큐는 크기가 유한하며 초기에는 비어있다. 메시지가 포트로 전송되면, 메시지는 큐로 복사된다. 모든 메시지는 신뢰성 있게 전달되며 동일한 우선 순위를 갖는다. Mach는 동일한 발신자로부터의 여러 메시지가 먼저 들어온 순서대로 큐에 들어가는 것을 보장하지만, 절대적인 순서를 보장하지는 않는다. 두 발신자로부터의 메시지가 어떤 순서로든 큐에 들어갈 수 있다. Mach 메시지에는 다음 두 가지 필드가 포함되어 있다.

  • 메시지에 대한 메타데이터를 포함하는 고정 크기의 메시지 헤더이다. 이는 메시지의 크기뿐만 아니라 송신 및 수신 포트와 같은 정보를 포함한다. 일반적으로, 송신 스레드는 응답을 기대하기 때문에 소스의 포트 이름이 수신 작업으로 전달되며, 이를 수신 작업이 응답을 보내는 데 "반환 주소"로 사용할 수 있다.
  • 데이터를 포함하는 가변 크기의 바디가 있다.

 메시지는 간단하거나 복잡할 수 있다. 간단한 메시지는 커널에서 해석되지 않는 보통의 구조화되지 않는 사용자 데이터를 포함한다. 복잡한 메시지의 경우에는 데이터를 포함하는 메모리 위치를 가르키는 포인터("out-of-line" 데이터라고 함)를 포함할 수 있고, 다른 작업에게 포트 권한을 전송하는데 사용될 수 있다. 큰 데이터 청크를 전달 할 때 out-of-line 데이터 포인트가 특히 유용하다. 간닪나 메시지의 경우 데이터를 메시지에 복사하고 패키징해야 하지만, out-of-line 데이터 전송은 데이터가 저장된 메모리 위치를 가리키는 포인터만 필요하다.

 mach_msg() 함수는 메시지를 보내고 받기 위한 표준 API이다. 함수의 매개변수 중 하나의 값인 MACH_SEND_MSG 또는 MACH_RCV_MSG는 전송 또는 수신 작업을 나타낸다. 이제 클라이언트 작업이 서버 작업에 간단한 메시지를 보낼 때 사용되는 방법을 보자. 클라이언트와 서버 작업에 각각 연결된 두 개의 포트가 있다고 가정하자. mach_msg() 함수 호출은 메시지 전달을 수행하기 위해 사용자 프로그램에 의해 호출된다. 그 다음 mach_msg()는 Mach 커널에 대한 시스템 호출인 mach_msg_trap() 함수를 호출한다. 커널 내에서 mach_msg_trap() 함수는 다음으로 실제 메시지 전달을 처리하는 mach_msg_overwrite_trap() 함수를 호출한다.

#include <mach/mach.h>

struct message {
  mach_msg_header_t header;
  int data;
};

mach_port_t client;
mach_port_t server;


/* CLIENT CODE */

struct message message;

message.header.msgh_size = sizeof (message);
message.header.msgh_remote_port = server;
message.header.msgh_local_port = client;

mach_msg(&message.header,
  MACH_SEND_MSG,
  sizeof(message),
  0,
  MACH_PORT_NULL,
  MACH_MSG_TIMEOUT_NONE,
  MACH_PORT_NULL
);


/* SERVER CODE */

struct message message;

mach_msg(&message.header,
  MACH_RCV_MSG,
  sizeof(message),
  0,
  MACH_PORT_NULL,
  MACH_MSG_TIMEOUT_NONE,
  MACH_PORT_NULL
);

 보내기 및 받기 작업 자체가 일단 유연한데, 포트로 메시지를 보낼 때 해당 큐가 가득 찰 수 있을 것이다. 큐가 가득차지 않은 경우 메시지가 큐로 복사되고, 보내는 작업이 계속된다. 하지만 포트의 큐가 가득찬 경우 보내는 측에서는 여러 옵션을 가진다.(mach_msg()의 매개변수)

  1. 큐에 공간이 생길 때까지 무기한 대기함.
  2. 최대 n ms 까지 대기함.
  3. 대기하지 않고 즉시 반환함.
  4. 메시지를 임시로 캐싱함.

 마지막 옵션은 서버 작업을 위한 것이다. 요청을 완료한 후에 서버작업은 서비스를 요청한 작업에게 일회성 응답을 보내야 할 수 있지만, 클라이언트의 응답 포트가 가득 차 있더라도 다른 서비스 요청을 계속해야 한다. 메시지 시스템의 주요 문제는 일반적으로 메시지를 보낸 사람의 포트에서 수신자의 포트로 메시지를 복사하는 데서 발생하는 성능저하이다. Mach 메시지 시스템은 가상 메모리 관리 기술을 사용하여 복사 작업을 피할려고 한다. 송신자 메시지를 포함하는 주소 공간을 수신자의 주소 공간에 매핑한다. 따라서 메시지가 실제로 복사되지 않으며, 송신자와 수신자가 동일한 메모리에 액세스한다. 이 메시지 관리 기술은 큰 성능 향상을 제공하지만, 시스템 내 메시지에만 작동한다.

 

3.7.3 Windows

 윈도우는 객체 기반 프로그래밍으로 모듈화 하여 기능을 증가시키고 새로운 기능을 구현하는데 필요한 시간을 줄이는 현대 설계의 대표적인 예이다. 윈도우는 다중 운영 환경 또는 서브 시스템에 대한 지원을 제공한다. 응용 프로그램은 이러한 서브 시스템과 메시지 전달 메커니즘을 통해 통신한다. 따라서 응용 프로그램은 서브 시스템의 서버의 클라이언트로 간주될 수 있다.

 윈도우의 메시지 전달 시설은 고급 로컬 프로시저 호출(Advanced Local Procedure Call, ALPC) 시설은 동일한 컴퓨터 상의 두 프로세스 간의 통신에 사용된다. 이는 일반적으로 사용되느 원격 프로시저 호출(RPC) 메커니즘과 유사하지만 윈도우에 최적화되어  있다. Mach와 마찬가지로 윈도우는 두 프로세스 간의 연결을 설정하고 유지하기 위해 포트 객체를 사용하며, 두 가지 유형의 포트가 있다(connection ports, communication ports.

 서버 프로세스는 모든 프로세스에서 볼 수 있는 연결 포트 객체를 게시한다. 클라이언트가 서브 시스템에서 서비스를 요청하려면 서버의 connection port 객체에 대한 핸들을 열고 해당 포트로 연결 요청을 보낸다. 그 후 서버는 채널을 생성하고 클라이언트에게 핸들을 반환하게 된다. 채널은 두 개의 개인 communication port로 구성된다. 하나는 클라이언트-서버 메시지 용이고, 다른 하나는 서버-클라이언트 메시지 용이다. 통신 채널은 클라이언트와 서버가 일반적으로 응답을 기대하는 시기에 요청을 수락할 수 있는 콜백 메커니즘을 지원한다. ALPC 채널이 생성될 때 세가지 메시지 전달 기술 중 하나가 선택된다.

  1. 작은 메시지(최대 256 바이트) 의 경우 포트의 메시지 큐가 중간 저장소로 사용되고, 메시지가 한 프로세스에서 다른 프로세스로 복사된다.
  2. 큰 메시지는 채널과 관련된 공유 메모리 영역인 섹션 객체를 통해 전달되어야 한다.
  3. 데이터 양이 섹션 객체에 맞지 않을 경우, 서버 프로세스가 클라이언트의 주소 공간에 직접 읽고 쓸 수 있도록 API가 제공된다.

 클라이언트는 채널을 설정할 때 큰 메시지를 보내야 할 지 결정해야 한다. 클라이언트가 큰 메시지를 보내려고 하는 경우 섹션 객체를 생성하도록 요청한다. 마찬가지로, 서버가 응답이 큰 경우 섹션 객체를 생성한다. 섹션 객체를 사용하도록 설정하기 위해 섹션 객체에 대한 포인터와 크기 정보를 포함하는 작은 메시지가 전송된다. 이 방법은 위에 나열된 첫번째 방법보다 더 복잡하지만 데이터 복사를 피할 수 있다.

 Windows의 ALPC 시설은 Windows API의 일부가 아니므로 응용 프로그래머에게는 보이지 않는다. 대신, Windows API를 사용하는 응용 프로그램은 표준 원격 프로시저 호출을 호출한다. RPC가 동일한 시스템의 프로세스에서 호출될 때, RPC는 ALPC 프로시저 호출을 통해 간접적으로 처리된다. 또한 많은 커널 서비스가 클라이언트 프로세스와 통신하기 위해 ALPC를 사용한다.

 다음 절은 중요한 절이다.

 

3.7.4 Pipes

 파이프는 두 프로세스가 통신할 수 있도록 하는 도관 역할을 한다. 파이프는 초기 UNIX 시스템에서 처음으로 사용된 IPC 메커니즘 중 하나였다. 파이프는 일반적으로 프로세스가 서로 통신하는 더 간단한 방법 중 하나를 제공하지만, 일부 제한점도 있다. 구현할 때는 다음 4가지를 고려한다.

  1. 파이프가 양방향 혹은 단방향 통신인지?
  2. 양방향이면 반이중(half duplex)인지 전이중(full duplex)인지?
  3. 통신하는 프로세스 간에 parent-child 과 같은 관계가 존재하는지?
  4. 파이프는 네트워크 상에서 통신할 수 있는지 아니면 통신하는 프로세스가 동일한 컴퓨터에 있어야 하는지?

 

3.7.4.1 Ordinary Pipes

 일반 파이프는 표준 생산자-소비자 방식으로 두 프로세스가 통신할 수 있게 한다. 생산자는 팡프 한쪽 끝에 쓰고, 소비자는 다른 쪽 끝에서 읽는다. 결과적으로 일반 파이프는 단방향으로, 단방향 통신만 허용한다. 양방향 통신이 필요한 경우에, 각각의 파이프가 데이터를 다른 방향으로 전송하도록 두 개의 파이프 사용을 허용해야 한다.

 유닉스 시스템에서는 일반 파이프를 생성하기 위해 함수 pipe(int fd[]) 를 사용한다. 이 함수는 int fd[] 파일 디스크립터를 통해 접근되는 파이프를 생성한다. fd[0]는 파이프 끝에서 읽는 것이고, fd[1]은 파이프 끝에서 쓰는 것이다. 유닉스는 파이프를 특별한 종류의 파일로 취급한다. 파이프는 일반적인 read() 및 write() 시스템 호출을 사용하여 액세스 할 수 있다.

 일반 파이프는 생성한 프로세스의 외부에서 액세스 할 수 없다. 일반적으로 부모 프로세스가 파이프를 생성하고 fork()를 통해 생성한 자식 ㄹ프로세스와 통신하기 위해서 사용된다. 3.3.1에서 다시 생각해야할 점은 자식 프로세스가 부모로 부터 열린 파일을 상속한다는 것이다. 파이프는 특별한 종류의 파일이므로 자식 프로세스는 부모 프로세스로부터 파이프를 상속한다. 위 그림에서는 fd 배열의 파일 디스크립터와 부모 및 자식 프로세스간의 관계를 보여준다. 이를 통해서 부모가 파이프의 끝 fd[1] 에서 작성한 내용은 자식이 파이프의 끝인 fd[0]에서 읽을 수 있음을 보여준다.

#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#define BUFFER_SIZE 25
#define READ_END 0
#define WRITE_END 1

int main(void)
{
  char write_msg[BUFFER_SIZE] = "Greetings";
  char read_msg[BUFFER_SIZE];
  int fd[2];
  pid_t pid;
  
  if (pipe(fd) == -1) {
    fprintf (stderr, "Pipe failed");
    return 1;
  }
  
  pid = fork();
  
  if (pid < 0) {
    fprintf (stderr, "Fork Failed");
    return 1;
  }
  
  if (pid > 0) {
    close (fd[READ_END]);
    write (fd[WRITE_END], write_msg, strlen(write_msg) + 1);
  }
  else {
    close (fd[WRITE_END]);
    
    read (fd[READ_END], read_msg, BUFFER_SIZE);
    printf ("read %s", read_msg);
    
    close (fd[READ_END]);
  }
  
  return 0;
}

 

 위의 유닉스 프로그램에서 부모 프로세스는 파이프를 생성한 다음 fork() 호출을 통해 자식 프로세스를 생성하고, fork() 호출 이후에 파이프를 통해 흐르는 방식에 따라 달라진다. 중요한 점은 부모 프로세스와 자식 프로세스가 초기에 사용하지 않는 파이프 끝을 모두 닫는다는 것이다. 위의 코드는 이 작업이 필요하지 않지만, 파이프에서 읽는 프로세스가 쓰는 프로세스가 파이프의 끝을 닫으면 파일의 끝을 감지할 수 있도록 하는 중요한 단계이다(read()가 0을 반환).

 

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

#define BUFFER_SIZE 25

int main(VOID)
{
  HANDLE ReadHandle, WriteHandle;
  STARTUPINFO si;
  PROCESS_INFORMATION pi;
  char message[BUFFER_SIZE] = "Greetings";
  DWORD written;
  
  SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE};
  ZeroMemory (&pi, sizeof(pi));
  
  if (!CreatePipe(&ReadHandle, &WriteHandle, &sa, 0)) {
    fprintf(stderr, "Create Pipe Failed");
    return 1;
  }
  
  GetStartupInfo (&si);
  si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
  
  si.hStdInput = ReadHandle;
  si.dwFlags = STARTF_USESTDHANDLES;
  
  SetHandleInformation (WriteHandle, HANDLE_FLAG_INHERIT, 0);
  
  CreateProcess (NULL, "child.exe", NULL, NULL,
  TRUE,
  0, NULL, NULL, &si, &pi);
  
  CloseHandle(ReadHandle);
  
  if (!WriteFile (WriteHandle, message, BUFFER_SIZE, &writeen, NULL))
    fprint(stderr, "Error writing to pipe.");
  
  CloseHandle (WriteHandle);
  
  WaitForSingleObject(pi.hProess, INFINITE);
  CloseHandle(pi.hProcess);
  CloseHandle(pi.hThread);
  
  return 0;
}

 

 윈도우에서 일반 파이프를 익명 파이프(anonymous pipes)라고 하며, 유닉스와 유사하게 동작한다. 단방향이며 통신하는 프로세스 간의 parent-child 관계를 사용한다. 그리고 파이프로의 읽기 쓰기 는 일반적인 ReadFile() 및 WriteFile() 함수를 사용하여 수행할 수 있다. 파이프를 생성하기 위한 Windows API는 CreatePipe() 함수이고, 이 함수는 4개의 매개변수를 받는다. (1) reading, (2) writing, (3) 자식프로세스가 파이프의 핸들을 상속하도록 지정하는 STARTUPINFO 구조체의 객체, (4) 파이프의 크기(바이트 단위)를 지정할 수 있다.

 UNIX 시스템과는 다르게 Windows에서는 자식 프로세스가 부모가 생성한 성한 파이프를 자동으로 상속하지 않고, 프로그래머가 자식 프로세스가 상속할 속성을 명시해야 한다. 이를 위해 먼저 SECURITY_ATTRIBUTES 구조체를 초기화하여 핸들을 상속할 수 있도록 한 다음, 자식 프로세스의 표준 입력 또는 표준 출력 핸들을 파이프의 읽기 또는 쓰기 핸들로 리다이렉션한다. 자식 프로세스가 파이프에서 읽기 때문에 부모는 자식의 표준 입력을 파이프의 읽기 핸들로 리다이렉션해야 한다. 또한 파이프가 반이중(half duplex)이기 때문에 자식에게 파이프의 쓰기 끝을 상속하지 않도록 금지해야 한다. 프로세스를 생성할때, 다섯 번째 매개변수를 TRUE로 설정하여 자식 프로세스가 부모로부터 지정된 핸들을 상속하도록 지정한다. 파이프에 쓰기 전에 부모는 먼저 사용하지 않는 파이프의 읽기 끝을 닫는다. 이 프로그램은 GetStdHandle()을 호출하여 파이프의 읽기 핸들을 얻은 후 파이프에서 읽는다.

 일반 파이프는 UNIX 및 Windows 시스템 모두에서 통신하는 프로세스 간에 부모-자식 관계가 필요합니다. 이는 이러한 파이프가 동일한 컴퓨터 상의 프로세스 간 통신에만 사용될 수 있음을 의미합니다.

 

3.7.4.2 Named Pipes

 일반 파이프는 두 프로세스가 통신하는 동안에만 존재하는 간단한 메커니즘을 제공한다. 그러나 UNIX 및 Windows 시스템 모두에서 프로세스가 통신을 마치고 종료하면 일반 파이프가 존재하지 않게 된다.

 이에 비해 Named Pipe는 훨씬 강력한 통신 메커니즘을 제공한다. 통신은 양방향이 될 수 있으며, 부모-자식 관계가 필요하지 않는다. 한번 명명된 파이프가 설정되면 여러 프로세스가 통신에 사용할 수 있다. 실제로 일반적인 시나리오에서는 명명된 파이프에 여러 작성자가 있다. 게다가, 통신이 완료된 후에도 명명된 파이프는 계속해서 존재한다. UNIX 및 Windows 시스템 모두가 Named Pipe를 지원하지만 구현 세부 사항은 크게 다르다.

 UNIX 시스템에서 명명된 파이프는 FIFO(FIFOs)로 알려져 있다. 생성된 후에는 파일 시스템에서 일반적인 파일로 나타난다. FIFO는 mkfifo() 시스템 호출을 사용하여 생성되며, 일반 open(), read(), write(), close() 시스템 호출을 사용하여 조작된다. 파일 시스템에서 명시적으로 삭제되기 전까지 계속 존재한다. FIFO는 양방향 통신을 허용하지만 반 이중 통신만 허용된다. 데이터가 양방향으로 이동해야하는 경우 일반적으로 두 개의 FIFO가 사용된다. 또한 통신하는 프로세스는 동일한 컴퓨터에 있어야 한다. 컴퓨터 간 통신이 필요한 경우에는 소켓(Section 3.8.1)을 사용해야 한다.

 Windows 시스템에서 명명된 파이프는 UNIX와는 다르게 더 풍부한 통신 메커니즘을 제공한다. 전 이중 통신(full duplex)이 허용되며, 통신하는 프로세스는 동일한 컴퓨터 또는 다른 컴퓨터에 있을 수 있다. 또한 UNIX FIFO에서는 바이트 지향 데이터만 전송할 수 있지만, Windows 시스템에서는 바이트 또는 메시지 지향 데이터를 모두 허용한다. 명명된 파이프는 CreateNamedPipe() 함수를 사용하여 생성되며, 클라이언트는 ConnectNamedPipe()를 사용하여 명명된 파이프에 연결할 수 있다. 명명된 파이프를 통한 통신은 ReadFile() 및 WriteFile() 함수를 사용하여 수행할 수 있다.

'OS > OS Design' 카테고리의 다른 글

4.2 Multicore Programming  (0) 2024.04.13
4.1 Thread & Concurrency Overview  (0) 2024.04.13
3.6 IPC in Message-Passing Systems  (0) 2024.04.13
3.5 IPC in Shared-Memory Systems  (0) 2024.04.12
3.4 Interprocess Communication  (1) 2024.04.12
Comments