System Programming 16. Interprocess Communication

Layfort·2023년 11월 16일
0

System Programming

목록 보기
3/5

Interprocess Communication(IPC)

1. Concepts of Interprocess Communication (IPC)

  • 떄로는 process 사이의 협업이나 데이터 전송이 필요한 경우가 있다.
    • e.g. 한 프로그램의 결과가 다른 프로그램의 입력이 되거나(| in shell command)
    • e.g. 2개 이상의 프로그램이 같은 memory를 공유한다던지...

1-1. Two module for IPC

1-1-1. Message passing

  • Kernel 내부에 Buffer를 위치시켜 해당 공간에 읽고 씀을 통해서 data를 전송한다.

  • Kernel에 buffer를 위치시키는 만큼 안전하지만, 느리고 많은 데이터를 전송하기 힘들다.

  • Window에는 CreateMailSlot이라는 함수를 사용하는데 linux는 못찾았다... message queue가 그나마 가장 유사한 가능을 포함하고 있기는 하지만, message box라고 할 수 있을지

1-1-2. Shared memory

  • 2개 이상의 process가 특정 영역의 memory를 공유하고, 이 memory를 통해서 data를 전송한다.

  • kernel을 안거치기 때문에 빠르지만, 사용자가 race condition을 비롯한 각종 error에 대해서 책임을 져야한다.(사실 이건 굉장히 어려운 주제이기도 하다... OS 13-16 참조)

  • linux에서는 주로 mmap을 이용하여 구현한다.

2. Producer-Consumer Paradigm

  • Producer는 Buffer에 data를 채우고, consumer는 buffer에서 data를 꺼내 처리한다.

    • 운영체제 ch 14.에서도 다루었던 주제
  • 가운데 위치하는 저 buffer의 크기에 따라서 unbounded-buffer, bounded-buffer로 나뉜다.

  • Producer와 Consumer사이의 Concurrency 문제는 정말 많은 이야기를 할 수 있는 주제이지만... OS에서 다루도록 하겠다.

2-1. Bounded-Buffer

  • Bounded Buffer의 가장 일반적인 implementation은 이런 원형 queue를 사용하는 것이다.
typedef struct {} item;
item buffer[BUFFER_SIZE];
int in = 0;
int out = 0;

int inc(int v) { return (v+1) % BUFFER_SIZE; }
int empty() { return in==out; }
int full() { return inc(in) == out; }

void enqueue(item item) {
	while (full()); buffer[in]=item; in=inc(in);
}

item dequeue() {
	while (empty()); item=buffer[out]; out=inc(out); return item;
}

3. Message Passing

3-1. Direct Communication

3-2. Indirect Communication

4. Client-Server Communication

4-1. Pipe

  • data를 주고 받을 수 있는 링크, fifo 파일
    • 전혀 어렵게 생각할 것이 없다... buffer 그 자체기 때문에...
    • 단지 이 data에 어떤 식으로 접근을 허용하는지에 대해서 차이가 있을 뿐이다.

4-1-1. Ordinary Pipe

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

int main() {
    int pipe_fd[2];						// create ordinary pipe
    pid_t pid;
    char message[] = "Hello, Pipe!";

    if (pipe(pipe_fd) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }

    pid = fork();

    if (pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) {  // Child process
        close(pipe_fd[1]);  // Close write end in the child
        char buffer[100];
        read(pipe_fd[0], buffer, sizeof(buffer));
        printf("Child received: %s\n", buffer);
        close(pipe_fd[0]);
    } else {  // Parent process
        close(pipe_fd[0]);  // Close read end in the parent
        write(pipe_fd[1], message, sizeof(message));
        close(pipe_fd[1]);
    }

    return 0;
}
  • pipe()를 통해서 kernel 상에 buffer를 만든다.
    • 이 buffer는 main-memory상에 위치하게 된다.
    • 정규 file은 아니지만... 어찌되었든 file descriptor를 이용해서 접근 가능하기 떄문에 virtual file이라고 부르는 거 같다.
    • pipe(int fd[]) -> fd[0]: read, fd[1]: write)
      • 일반적인 file descriptor 처럼 사용하면 된다.
  • 특징
    • 단방향
    • 당연하겠지만, 이 pipe는 parent-child 관계를 가지는 process에서만 사용이 가능하다...

4-1-2. Named Pipe

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

#define FIFO_FILE "/tmp/myfifo"

int main() {
    mkfifo(FIFO_FILE, 0666);	// create name pipe

    pid_t pid = fork();

    if (pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) {  // Child process
        int fd = open(FIFO_FILE, O_RDONLY);		// link pipe -> output entry
        char buffer[100];
        read(fd, buffer, sizeof(buffer));		// receive message to pipe
        printf("Child received: %s\n", buffer);
        close(fd);
    } else {  // Parent process
        int fd = open(FIFO_FILE, O_WRONLY);		// link pipe -> input entry
        char message[] = "Hello, FIFO!";
        write(fd, message, sizeof(message));	// send message to pipe
        close(fd);
        unlink(FIFO_FILE);  // Remove the FIFO file
    }

    return 0;
}
  • 임시 fifo 파일을 하나 만들고, 이 파일을 공유하는 방식으로 이루어지는 IPC

  • 오히려 pipe보다 직관적인 이해는 더 쉬울 듯... 파일 이름만 알면 진짜 file descriptor 처럼 사용할 수 있으니까...

4-2. Socket

  • 솔직히 중요한거 같지 않다.

5. IPC Examples

#define READ 0
#define WRITE 1

int main(int argc, char *argv[])
{
  int pipefd[2];						
  pid_t pid;
  
  if (pipe(pipefd) < 0) {			// pipe 생성
    printf("Cannot create pipe.\n");
    exit(EXIT_FAILURE);
  }
  
  pid = fork();
  if (pid > 0) parent(pipefd)		// parent 동작
  else if (pid == 0) child(pipefd);	// child 동작
  else printf("Cannot fork.\n");
  
  // parent/child do not return
  return EXIT_FAILURE;
}

void child(int pfd[2])
{
  // close unused read end and redirect 
  // standard out to write end of pipe
  close(pfd[READ]);					// 읽기 쪽 pipe는 닫는다 
  dup2(pfd[WRITE], STDOUT_FILENO);	// STDOUT을 pipe의 쓰기 fd와 연결
  									// 사실 이후에 write pipe는 닫아버려도 무방하다.
  
  // arguments for execv call
  char *argv[] = {
    "/bin/ls",
    "-l",
    "/etc",
    NULL,
  };
  
  // exec does not return on success
  execv(argv[0], argv);				// 이제 여기서 실행한 결과가 stdout으로 출력되고, 그게 pipe를 타고 이동해서 parent에 전해지겠지
  // exec failed
  exit(EXIT_FAILURE);
}

void parent(int pfd[2])
{
  // close unused write end
  close(pfd[WRITE]);				// 마찬가지로 쓰기 쪽 입구는 닫아버린다.
  
	// read from pipe
  int even = 1;
  char c;
  while (read(pfd[READ], &c, 1) > 0) {
    // every even character: upper case
    // every odd character: lower case
    if (even) c = toupper(c);
    else c = tolower(c);
    even = !even;
    
    write(STDOUT_FILENO, &c, sizeof(c));	// pipe로 전해져 오는 data를 읽어들인다!
  }
  
  exit(EXIT_SUCCESS);
}
profile
물리와 컴퓨터

0개의 댓글

관련 채용 정보