목적
- 프로세스의 통신 방법을 다양한 측면에서 실습한다.
실천 목표
- 파이프와 POSIX 공유 메모리를 사용하여 프로세스 간 통신을 수행하는 프로그램을 설계한다.
- 소켓과 원격 프로시저 호출을 사용하여 클라이언트-서버 통신을 설명한다.
- Linux 운영체제와 상호 작용하는 커널 모듈을 설계한다.
IPC 실습 4가지
- POSIX 공유 메모리
- Mach 운영체제의 메시지 전달 시스템
- Windows IPC (공유 메모리 사용)
- 파이프 (UNIX의 가장 오래된 IPC 기법)
IPC 실습 (1) POSIX 공유 메모리
- file과 shared memory 영역을 연관시키는 memory-mapped file를 통해 만들 수 있음
- Producer Process
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 32 33 34 35 36 37 38 39 40 41
#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() { /* 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 = "Shared Memory!\n"; /* shared memory file descriptor */ int shm_fd; /* pointer to shared memory object */ char *ptr; /* create the shared memory object */ shm_fd = shm_open(name, O_CREAT | O_RDWR, 0666); /* configure the size of the shared memory object */ ftruncate(shm_fd, SIZE); /* memory map the shared memory object */ ptr = (char *) mmap(0, SIZE, PROT_READ | 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; }
- Consumer Process
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 32 33
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <sys/shm.h> #include <sys/stat.h> #include <sys/mman.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"; /* shared memory file descriptor */ int shm_fd; /* pointer to shared memory object */ char *ptr; /* open the shared memory object */ shm_fd = shm_open(name, O_RDONLY, 0666); /* memory map the shared memory object */ ptr = (char *) 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; }
IPC 실습 (2) Mach 운영체제의 메시지 전달 시스템
- Mach 운영체제(커널)에서 사용하는 메시지 전달
- 프로세스와 유사하지만 다른 부분이 있는 태스크의 개념 사용
- 태스크는 프로세스보다 제어 스레드가 많고 관련 자원이 적음
- 모든 태스크 간 통신을 포함해서 Mach에서 대부분의 통신은 메시지로 수행
- 포트(Port)라고 하는 메일박스로 메시지를 주고 받음
- 각 포트에는 여러 송신자가 있을 수 있지만 수신자는 하나만 존재
IPC 실습 (3) Windows IPC (공유 메모리 사용)
- Windows는 다중 운영 환경 (또는 서브시스템)을 지원
- 응용 프로그램은 메시지 전달 기법을 통해 서브시스템과 통신
- 그러므로 응용 프로그램은 서브시스템 서버의 클라이언트로 간주 가능
- Windows의 메시지 전달 설비는 고급 로컬 프로시저 호출 설비(ALPC, Advanced Local Procedure Call Facilitiy)
- ALPC는 동일 기계상에 있는 두 프로세스 간의 통신에 사용, 표준 원격 프로시저 호출(RPC) 기법과 같음
- Windows는 두 프로세스 간 연결을 구축하고 유지하기 위해 연결 포트(Connection Port)와 통신 포트(Communication Port) 사용
IPC 실습 (4) 파이프 (UNIX의 가장 오래된 IPC 기법)
- 초창기 UNIX의 IPC 메커니즘 중 하나로, 두 프로세스가 통신하도록 하는 배관
- 단방향도 가능하고, 양방향도 가능한데, 양방향으로는 반이중(half-duplex)만 가능
- 부모-자식 관계의 프로세스에서만 사용 가능
- 네트워크 상에서는 사용하지 않음 (네트워크 상에서 사용하는 파이프 개념이 다음으로 배울 소켓임)
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
#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\n"; char read_msg[BUFFER_SIZE]; int fd[2]; pid_t pid; /* create the pipe */ if (pipe(fd) == -1) { fprintf(stderr, "Pipe Failed"); return 1; } /* fork a child process */ pid = fork(); if (pid < 0) { /* error occured */ fprintf(stderr, "Fork Failed"); return 1; } if (pid > 0) { /* parent process */ /* close the unused end of the pipe */ close(fd[READ_END]); /* write to the pipe */ write(fd[WRITE_END], write_msg, strlen(write_msg)+1); /* close the write end of the pipe */ close(fd[WRITE_END]); } else { /* child process */ /* close the unused end of the pipe */ close(fd[WRITE_END]); /* read from the pipe */ read(fd[READ_END], read_msg, BUFFER_SIZE); printf("Read %s", read_msg); /* close the read end of the pipe */ close(fd[READ_END]); } return 0; }
클라이언트 서버 환경에서 통신 2가지
- 소켓
- 원격 프로시저 호출 (RPC, Remote Procedure Call)
클라이언트 서버 환경에서 통신 (1) 소켓
- 소켓은 일종의 파이프인데, 네트워크에서 사용하기 위해 IP와 Port가 바인딩되어 있는 것
- 엄밀하게는, 커뮤니케이션을 위한 두 종단을 의미
- Data Server
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
package os.concepts; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; public class DateServer { public static void main(String[] args) throws IOException { ServerSocket serverSocket = null; int port = 7777; try { /* 서버 소켓 생성 후 7777번 포트와 결합 */ serverSocket = new ServerSocket(port); System.out.printf("%s 서버가 준비되었습니다.\n", getTime()); } catch (Exception e) { e.printStackTrace(); } while (true) { System.out.printf("%s 연결요청을 기다립니다.\n", getTime()); /* 서버 소켓은 클라이언트의 연결요청이 올 때까지 실행을 멈추고 기다림 */ /* 클라이언트의 연결요청이 들어오면 클라이언트 소켓과 통신할 새로운 소켓 생성 */ Socket socket = serverSocket.accept(); System.out.printf("%s %s:%d으로부터 연결요청이 들어왔습니다.\n", getTime(), socket.getInetAddress(), socket.getPort()); /* 소켓의 출력스트림을 얻음 */ OutputStream out = socket.getOutputStream(); PrintWriter pout = new PrintWriter(out, true); /* 원격 소켓에 데이터를 보냄 */ pout.printf("%s Message From Server\n", getTime()); /* 스트림과 소켓을 닫음 */ socket.close(); pout.close(); out.close(); } } private static String getTime() { DateFormat df = new SimpleDateFormat("[hh:mm:ss]"); return df.format(new Date()); } }
- Data Client
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
package os.concepts; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.ConnectException; import java.net.Socket; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; public class DateClient { public static void main(String[] args) { try { String serverIp = "127.0.0.1"; int serverPort = 7777; System.out.printf("%s:%d서버에 연결 중입니다.\n", serverIp, serverPort); /* 소켓을 생성해서 연결을 요청 */ Socket socket = new Socket(serverIp, serverPort); /* 소켓의 입력스트림을 얻음 */ InputStream in = socket.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(in)); /* 원격 소켓으로부터 받은 데이터 출력 */ System.out.printf("%s 서버로부터 받은 메시지\n", getTime()); String line = null; while ((line = br.readLine()) != null) System.out.println(line); /* 스트림과 소켓을 닫음 */ socket.close(); br.close(); in.close(); } catch (ConnectException ce) { ce.printStackTrace(); } catch (IOException ie) { ie.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } private static String getTime() { DateFormat df = new SimpleDateFormat("[hh:mm:ss]"); return df.format(new Date()); } }
클라이언트 서버 환경에서 통신 (2) 원격 프로시저 호출
- RPC, Remote Procedure Call
- 원격에 있는 함수를 호출하는 것
- 원격 서비스의 가장 보편적인 형태 중 하나
- 네트워크 시스템 상에 있는 프로세스들 간에 프로시저 콜을 추상화한 것
- RPC 시스템은 클라이언트에게는 서버의 stub을 제공하며 디테일은 숨기며 서버의 skeleton에 접근하도록 함
- Marshaling된 인수 또는 반환값을 주고 받으며 데이터를 교환할 수 있도록 함
참고
- 운영체제 공룡책 강의 | 주니온 | 인프런 https://www.inflearn.com/course/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EA%B3%B5%EB%A3%A1%EC%B1%85-%EC%A0%84%EA%B3%B5%EA%B0%95%EC%9D%98/dashboard
운영체제 제 10판 Abraham Silberschatz, Peter Baer Galvin, Greg Gagne 저/박민규 역 퍼스트북 2020년 02월 28일 - 운영체제 제 10판 솔루션 https://codex.cs.yale.edu/avi/os-book/OS10/practice-exercises/index-solu.html