📀 운영체제
06. 프로세스간 통신의 실제: Chapter 3. Processes
락꿈사
2022. 5. 24. 12:21
3.7 IPC 시스템의 사례
- IPC 시스템의 두 가지 사례
- 공유 메모리를 위한 POSIX API
- 파이프
POSIX(Portable shared Memory for Unix) 공유 메모리
- mamory-mapped 파일을 사용하여 구현됨
- mamory-mapped 파일: 공유 메모리의 특정 영역을 파일과 연관시킴
- 사용하는 코드
- fd = shm_open(name, O_CREAT | O_RDWR, 0666);
- name : 공유 메모리의 객체 이름 지정
- O_CREAT | O_RDWR: 객체가 존재하지 않으면 생성되고 객체는 읽기와 쓰기가 가능한 상태로 열리는 것을 나타냄
- 0666: 공유 메모리 객체에 파일-접근 허가권 부여
- ftruncate(fd, 4096)
- ftruncate: 객체의 크기를 바이트 단위로 설정
- 4096: 객체의 크기를 4096 바이트로 설정
- (char *) mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
- mmap: 공유 메모리 객체를 포함하는 mamory-mapped 파일을 구축하고, 공유 메모리 객체에 접근할 때 사용될 mamory-mapped 파일의 포인터를 반환함
- fd = shm_open(name, O_CREAT | O_RDWR, 0666);
- 전체 코드
// Producer
#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;
}
// Consumer
#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);
printf("%s", (char *)ptr);
shm_unlink(name);
return 0;
}
파이프
- 파이프는 두 프로세스가 통신할 수 있게 하는 전달자로써 동작
- 파이프를 구현하기 위해 고려해야 할 4가지 문제
- 파이프가 단방향 통신 또는 양방향 통신을 허용하는가?
- 양방향 통신이 허용된다면 반이중 통신인가, 전이중 통신인가?
- 반이중 통신: 한번에 한 반향 전송만 가능
- 전이중 통신: 동시에 양방향 데이터 전송 가능
- 통신하는 두 프로세스 간에 부모-자식과 같은 특정 관계가 존재해야 하는가?
- 파이프는 네트워크를 통해 통신이 가능한가, 아니면 동일한 기계 안에서 존재하는 두 프로세스끼리만 통신할 수 있는가?
- 일반 파이프Ordinary pipes
- 일반 파이프는 생산자-소비자 형태로 두 프로세스 간의 통신을 허용함
- 생산자는 파이프의 한 종단(쓰기 종단)에 쓰고, 소비자는 다른 종단(읽기 종단)에서 읽음
- 결과적으로 일반 파이프는 한쪽으로만 데이터를 전송할 수 있으며 오직 단방향 통신만을 가능하게 함
- 만일 양방향 통신이 필요하다면 각각 다른 방향으로 데이터를 전송할 수 있는 두개의 파이프를 사용해야 함
- 일반 파이프는 파이프를 생성한 프로세스 이외에는 접근할 수 없음
- 따라서 부모 프로세스가 파이프를 생성하고 fork()로 생성한 자식 프로세스와 통신하기 위해 사용함
- 부모가 파이프의 쓰기 종단(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 WRTIE_END 1
int main(){
char write_msg[BUFFER_SIZE] = "Hello Pipes!";
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[WRTIE_END], write_msg, strlen(write_msg)+1);
close(fd[WRTIE_END]);
}
else{
close(fd[WRTIE_END]);
read(fd[READ_END], read_msg, BUFFER_SIZE);
printf("read %s", read_msg);
close(fd[READ_END]);
}
return 0;
}
- 지명 파이프 Named Pipes
- 지명 파이프는 통신이 양방향 통신이 가능하며 부모-자식 관계도 필요하지 않음
- 여러 프로세스들이 이를 사용하여 통신할 수 있음
3.8 클라이언트 서버 환경에서 통신
- 클라이언트 서버 시스템 통신에서 사용할 수 있음
- 두 가지 통신 전략
- 소켓
- 원격 프로시저 호출(RPCs)
소켓
- 소켓은 통신의 end-point를 뜻함
- 두 프로세스가 네트워크 상에서 통신하려면 양 프로세스마다 하나씩, 총 두개의 소켓이 필요함
- 각 소켓은 IP와 포트번호 두 가지를 접합 해서 구별함
- 클라이언트 프로세스가 연결을 요청하면 호스트 컴퓨터가 포트 번호를 부여함 (이 번호는 1024보다 큰 임의의 정수가 됨)
- 위 그림에서는 IP가 148.86.5.20인 호스트 X가 포트 1625를 이용하여 IP가 161.25.19.8 이고 포트가 80인 웹서버와 통신하고 있음.
- 이때 소켓은 148.86.5.20:1625 / 161.25.19.8:80 이 두 개로 이루어짐
- Java는 3가지 종류의 소켓을 제공함
- TCP(연결 기반) 소켓
- Socket 클래스로 구현
- UDP(비연결 기반) 소켓
- DatagramSocket 클래스로 구현
- Multicast 소켓
- MulicastSocket 클래스로 구현
- TCP(연결 기반) 소켓
- 사용하는 코드
- new ServerSocket(6013): 포트 6013을 사용하여 listen 한다는 것을 지정함
- sock.accept(): listen 하게 됨
- 서버는 accept() 메소드에서 클라이언트가 연결을 요청할 때 까지 봉쇄됨
- 연결이 요청이 들어오면 accept()는 클라이언트와 통신하기 위해 사용할 수 있는 소켓을 반환함
- new Socket("127.0.0.1", 6013) : IP 주소 127.0.0.1에 있는 포트 6013의 서버와 연결 요청
- 전체 코드
package com.os_study;
import java.net.*;
import java.io.*;
public class DateServer {
public static void main(String[] args) {
try{
ServerSocket socket = new ServerSocket(6013);
while(true){
Socket client = socket.accept();
PrintWriter pout = new PrintWriter(client.getOutputStream(), true);
pout.println(new java.util.Date().toString());
client.close();
}
} catch (IOException e) {
System.out.println(e);
}
}
}
package com.os_study;
import java.net.Socket;
import java.io.*;
public class DataClient {
public static void main(String[] args) {
try {
Socket socket = new Socket("127.0.0.1", 6013);
InputStream in = socket.getInputStream();
BufferedReader bin = new BufferedReader(new InputStreamReader(in));
String line;
while((line = bin.readLine())!=null)
System.out.println(line);
socket.close();
} catch (Exception e) {
System.out.println(e);
}
}
}
원격 프로시저 호출
- RPC: 네트워크로 연결된 두 시스템 사이의 통신을 사용하기 위하여 프로시저 호출을 추상화하는 패러다임
- IPC 방식과 달리 RPC 통신에서 전달되는 메시지는 구조화 되어 있음
- RPC 시스템은 클라이언트 쪽에 Stub을 제공하여 통신을 하는 데 필요한 자세한 사항을 숨겨 줌
- 원격 프로시저 호출 과정
- 클라이언트가 원격 프로시저를 호출
- RPC는 그에 대응하는 stub을 호출하고 원격 프로시저가 필요로 하는 매개변수를 건네줌
- 그러면 stub이 원격 서버의 포트를 찾고 매개변수를 mashalling함
- parameter mashalling: 클라이언트와 서버 기기의 데이터 표현 방식의 차이 문제를 해결함
- ex) 최상위 비트를 먼저 저장하는 big-endian 방식 vs 최하위 비트를 먼저 저장하는 little-endial 방식
- 그후 sub은 메시지 전달 기법을 사용하여 서버에게 메시지를 전송함
번외 - 부모와 자식이 모두 읽고 쓰기 할 수 있는 ordinary_pipe
- 코드
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#define BUFFER_SIZE 25
#define READ_END 0
#define WRTIE_END 1
int main(){
char parent_write_msg[BUFFER_SIZE] = "Hello Pipes!";
char parent_read_msg[BUFFER_SIZE];
char child_write_msg[BUFFER_SIZE] = "No";
char child_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){
write(fd[WRTIE_END], parent_write_msg, strlen(parent_write_msg)+1);
wait(NULL);
read(fd[READ_END], parent_read_msg , BUFFER_SIZE);
printf("Parent read from Child: %s\n", parent_read_msg);
close(fd[READ_END]);
close(fd[WRTIE_END]);
}
else{
read(fd[READ_END], child_read_msg, BUFFER_SIZE);
printf("Child read from Parent: %s\n", child_read_msg);
write(fd[WRTIE_END], child_write_msg, strlen(child_write_msg)+1);
}
return 0;
}