실제 프로세스간 통신

Koo·2023년 10월 16일
post-thumbnail

IPC 시스템의 예시

Shared Memory: POSIX Shared Memory

  • POSIX: Portable Operating System Interface
  • 유닉스 계열 운영체제의 표준화를 시도
  • memory-mapped 파일을 사용하여 버퍼를 사용
fd = shm_open(name, O_CREAT | ORDWR, 0666);
ftruncate(fd, 4096); // 4096 byte씩 읽고 쓰게 됨
mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); // memory-mapped 파일을 shared memory에 mapping
mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);

fd로 지정된 파일에서 offset을 시작으로 length byte만큼 start 주소로 대응시킴. prot는 원하는 메모리를 설정하는 인자, flags는 대응된 객체의 타입, 대응 옵션, 대응된 페이지 복사본에 대한 수정이 해당 프로세스에서만 보일 것인지, 다른 참조 프로세스와 공유할 것인지를 지정

prot 인자

  • PROT_EXEC : 페이지 실행 가능
  • PROT_READ : 페이지 읽기 가능
  • PROT_WRITE : 페이지 쓰기 가능
  • PROT_NONE : 페이지 접근 불가능

flags

  • MAP_FIXED : 지정된 주소 이외의 다른 주소를 선택하지 않음. 지정된 주소가 사용 불가능하면 mmap이 실패함. MAP_FIXED를 지정하면, start는 페이지 크기의 배수여야 한다. (잘 사용x)
  • MAP_SHARED : 대응된 객체를 다른 모든 프로세스와 공유
  • MAP_PRIVATE : 개별적인 copy-on-write 대응을 만듬. 다른 프로세스와 대응 영역을 공유하지 않음

librt(Realtime Extensions 라이브러리)
shared memory에서 shm_open과 mmap을 사용하기 위해 -lrt 옵션을 추가해주어야함

Producer

shared memory에 쓰는 역할 수행

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

int main() {
	const int SIZE = 4096; // the size of shared memory
    const char *name = "OS"; // the name of shared memory
    const char *message_0 = "Hello, ";
    const char *message_1 = "Shared Memory!\n";
    
    int shm_fd; // the file descriptor of shared memory
    char *ptr; // pointer to shared memory
    
    /* create the shared memory object */
    shm_fd = shm_open(name, O_CREAT | O_RDWR, 0666);
    
    /* configure the size of the shared memory 
       실제 공유되고 있는 memory size로, shm_fd의 크기 조절*/
    ftruncate(shm_fd, SIZE);
    
    /* map the shared memory object */
    ptr = (char*)mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
    
    /* write to the shared memory */
    sprintf(ptr, "%s", mesage, 0); // Hello, 
    ptr += strlen(message_0); // 문장을 write한 후, pointer를 이동시킴
    sprintf(ptr, "%s", message_1); // Shared Memory!
    ptr += strlen(message_1);
    
    return 0;
}

$ gcc -o producer producer.c -lrt

Consumer

shared memory를 읽는 역할

#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; // the size of shared memory
    const char *name = "OS"; // the name of shared memory
    
    int shm_fd;
    char *ptr;
    
    /* create the shared memory object */
    shm_fd = shm_open(name, O_RDWR, 0666);
    
    /* map the shared memory object */
    ptr = (char*)mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
    
    /* read from the shared memory object */
    printf("%s", (char*)ptr);
    
    /* remove the shared memory */
    shm_unlink(name);
    
    return 0;
}

$ gcc -o consumer consumer.c -lrt

Message Passing: Pipes

  • 전통적으로 유닉스는 파이프를 많이 사용

  • shared memory는 사용자가 일일이 shm_open, mmap, shm_unlink 등의 작업을 모두 수행해야 함

  • 파이프는 2개의 프로세스가 단순히 communication하는 도구로 사용

  • 파이프를 구현할 때 고려할 4가지

    • 동시에 송수신 여부: unidirectional vs. bidirectional -> unidirectional
    • 통신 방향: half-duplex vs. full-duplex
    • 관계(relationship): parent-child 구조를 가짐
    • network를 통해 통신 가능 여부: 사용하지 않음, network에서는 socket 사용
  • Ordinary Pipe: parent-child 관계를 가지며, 파이프를 만든 프로세스 외에는 접근 불가

  • Named Pipe: parent-child 관계를 갖지 않음. 여러 개 가질 수 있음

Ordinary Pipe에서 parent와 child는 각각 2개의 file description을 가짐(각각 송수신). 각각의 pipe는 one-way communication만 가능함(unidirectional). 양방향 통신을 하기 위해서는 one-way pipe를 2개 사용.

pipe(int fd[])
fd[0]: the read end of the pipe
fd[1]: the write end

Pipe example using fork

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

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

int main() {
	char write_msg[BUFFER_SIZE] = "Greetings";
    char read_msg[BUFFER_SIZE];
    int fd[2]; // file descriptor의 size=2, READ_END, WRITE_END
    pid_t pid;
    
    /* create the pipe */
    pipe(fd);
    
    pid = fork();
    
    if (pid > 0) { // parent process, read할 필요 없음
    	close(fd[READ_END]);
        /* write to the pipe */
        write(fd[WRITE_END], write_msg, strlen(write_msg) + 1);
        close(fd[WRITE_END]);
	} else { // child process, write할 필요 없음
    	close(fd[WRITE_END]);
        read(fd[READ_END], read_msg, BUFFER_SIZE);
        printf("read %s\n", read_msg);
        close(fd[READ_END]);
	}
    
    return 0;
}

Client-server system

Socket

  • 통신을 위한 양종단을 의미함
  • IP address: endpoint인 컴퓨터를 특징할 수 있어야 함
  • Port: pipe를 특정함
  • socket: IP와 port를 묶어 통신을 위한 end point로 사용함
  • RPC (Remote Procedure Call): 네트워크 내에 있는 컴퓨터 프로세스들 간에 원격 호출을 추상화, 원격에 있는 함수를 호출하여 사용함
    • Java에서는 RMI로 사용
    • client는 server에 있는 함수를 사용하기 위해 stub을 사용
profile
스터디를 해보자

0개의 댓글