운영체제

IPC, 사례연구

chaeit1904 2024. 10. 12. 12:40

IPC(interProcess Communication)

: 프로세스 간 통신을 말한다.

 

종류

 

1. 프로세스 "내부" 데이터 통신

2. 프로세스 "간" 데이터 통신

3. 네트워크를 이용한 "원격" 데이터 통신


프로세스 내부 데이터 통신

= 프로세스 내부 스레드간 통신을 말한다,

전역변수를 이용한 통신방식

: from1to2, from2to1선언하고 이를 이용해서 통신한다,

 

from1to2from2to1은 각각 5와 2로 설정된 전역 변수입니다.

thread1() 함수에서는 from1to2 변수를 참조하여 data1을 계산합니다 (from1to2 + 3).

thread2() 함수에서는 from2to1 변수를 참조하여 data2를 계산합니다 (from2to1 + 5).

이 두 스레드는 서로 다른 전역 변수를 참조하면서 동시에 실행된다. 이를 통해 전역 데이터를 이용한 통신이 이루어진다.


프로세스 간 데이터 통신

= 파일공유를 이용한 통신방식

파일의 입출력을 이용해 통신하는 방식이다,

 

write(fd, "Test", 5);는 파일에 "Test" 문자열을 기록한다.

read(fd, buf, 5);는 파일에서 데이터를 읽어와 buf에 저장한다.

두 개의 프로세스 (Process 1, Process 2)가 존재.

Process 1은 com.txt 파일에 데이터를 쓰고, Process 2는 그 데이터를 읽는다.

 

위와 같은 방식으로 파일 공유를 통해 통신한다.

 

(a) : 공유메모리를 이용한 통신방법

:  커널을 통해 공유메모리에 대한 공간을 확보하고 A, B가 각각 공유메모리에

데이터를 입력하고 이를 읽어 통신하는 방법이다.

 

(b) : 메세지 큐를 이용한 통신방법

: A가 메세지 큐에 메세지를 입력하면 B는큐에 입력되어 있는 메세지를 읽어내는 구조로

통신하는 방식이다.

 

 

파이프 : 

두 프로세스 간의 데이터를 송수신하기 위한 특수 파일이다, 파일은 아니지만 파일의 성질을 
가진 특수 파일이다.

 

A와 B가 파이프를 통해 write하고 이를 read해서 통신한다,

반대의 경우도 마찬가지로 파이프로 데이터를 입출력하여 통신한다.

 

FIFO: First in First out, 먼저 기록된 데이터가 먼저 읽힌다. 따라서 송수신된 데이터는 그 순서대로 

수신된다.


네트워크를 이용한 원격 데이터 통신

= 네트워크를 이용해 프로세스간 데이터 전송

소켓 통신으로 통신하고 있다. 

A, B가 소켓을 이용해 데이터를 주고받는다.

A는 소켓에 write하여 데이터를 전송하고

B는 전달받은 데이터를 소켓에서 read하여 데이터를 수신한다.

 

바인딩 : 각 프로세스는 자신의 소켓을 네트워크 상의 ip와 포트에 바인딩하여 데이터를 주고받는다.


클라이언트 측 (왼쪽):

 

1. socket(): 소켓을 생성하여 네트워크 상에서의 통신 준비를 합니다.

2. connect(): 서버에 연결을 시도합니다.

3. send(): 데이터를 서버로 보냅니다.

4. recv(): 서버로부터 데이터를 수신합니다.

5. close(): 연결을 종료합니다.

 

1. socket(): 소켓을 생성하여 통신을 준비합니다.

2. bind(): 소켓을 특정 IP 주소와 포트에 바인딩합니다.

3. listen(): 클라이언트의 연결 요청을 기다립니다.

4. accept(): 클라이언트의 연결 요청을 받아들입니다.

5. recv(): 클라이언트로부터 데이터를 수신합니다.

6. send(): 클라이언트로 데이터를 전송합니다.

7. close(): 연결을 종료합니다.


프로세스간 원격 통신과정에서의 예외 사항

3가지 예외 상황

1. 프로세스 종료

2. 메세지 상실

3. 훼손 메시지


1. 프로세스 종료

P -> Q로 데이터를 전송하는 상황 -> Q가 전송

하지만 P는 상황을 모르기 때문에 계속해서 수신대기

또한 이미 종료된 Q에게 보내는 경우도 포함

 

해결방법 : 타이머

지정된 시간안에 응답이 오지 않으면 타임아웃 시키고

재전송 하도록함.

 

2. 메시지 상실

2가지 케이스가 존재한다.

 

P -> Q로 메시지 전송중 메세지 상실

P -> Q로 메시지 전송 후 응답메시지 전송 중 상실

 

3. 훼손 메세지

P -> Q에서 메세지가 전송 중 훼손되어 Q가 훼손된 메시지를 수신

따라서 에러가 발생하는지 확인하는 작업 필요


사례연구 - 병행 프로세스

1.

process를 fork를 이용해 child 프로세스를 만드는 작업

작업 : 부모 프로세스와 자식프로세스가 서로 각각 데이터를 버퍼에 추가

이 상황에서 서로 버퍼는 영향을 주지 않는다,

따라서 consumer 프로세스는 아무 데이터도 들어오지 않기 때문에

계속해서 대기한다.

위 작업의 실제 코드

위 코드를 통해 서로 버퍼에 입력하고 출력하지만

공유 버퍼를 이용하면서도 서로의 작업섹션에는 관여하지 않는다는 것을 할 수 있다.

메인 코드

두개의 프로세스를 동시 실행시킨다

 


사례연구2 - 멀티스레드 프로그래밍

먼저 실행화면을 보자

버퍼 in, out을 전역변수로 선언해서 사용하고 있다. 

2개의 스레드가 공통의 버퍼에서 데이터를 넣고 꺼내어 사용하는 것을 알 수 있다.

 

코드

프로듀서 프로세스는 3초마다 데이터 추가

컨슈머 프로세스는 9초마다 데이터를 꺼낸다.

 

즉 어느순간 버퍼가 꽉차는 순간이 오기 때문에 그때 producer : wait이라고 

출력하도록 작성되어 있다.

 

메인코드

스레드 1, 2를 각각 생성하고 역할을 부여한다

스레드가 종료되면 join 시스템콜함수를 이용해 작업을 종료한다,


사례연구 - 공유메모리 

이 공유메모리 사례연구는 특정메모리 영역을 두 개의 프로세스가 공유하는 것을 보여준다.

위 이미지는 producer 프로세스를 보여준다.

코드 설명

shared memory의 용량과 이름을 선언

메세지1, 2의 내용을 지정

shm_open: shm_open 함수는 이름을 기반으로 공유 메모리 객체를 생성하고,

이를 참조하는 파일 디스크립터를 반환. 여기서는 O_CREAT 플래그를 사용해 메모리가 없으면 새로 생성하고,

O_RDWR 플래그로 읽기/쓰기가 가능하도록 설정.

ftruncate: ftruncate 함수는 공유 메모리의 크기를 SIZE로 설정

mmap: mmap 함수는 공유 메모리를 해당 프로세스의 가상 주소 공간에 매핑.

MAP_SHARED 옵션을 통해 공유 메모리 내용이 다른 프로세스와 공유되도록 설정.

 

sprintf(ptr, "%s", message1): message1을 공유 메모리에 기록하고, 포인터 ptr을 해당 메시지 크기만큼 이동.

sprintf(ptr, "%s", message2): message2도 이어서 공유 메모리에 기록하고, 다시 포인터를 이동.

 

프로듀서는 공유 메모리에 데이터를 쓰는 역할을 한다. ptr 포인터가 공유 메모리로 매핑되어 데이터를 기록하는 것을 보여준다.


공유메모리 - consumer.c

SIZE: 공유 메모리의 크기를 4096 바이트로 설정.

name: 공유 메모리의 이름으로 “OS”라는 문자열을 사용.

fd: 파일 디스크립터로 공유 메모리 객체를 참조.

ptr: 공유 메모리 공간에 매핑된 포인터.

 

shm_open(name, O_RDONLY, 0666) 함수를 통해 이름이 “OS”인 공유 메모리 객체를 읽기 전용(O_RDONLY)으로 연다.

이때 생성된 파일 디스크립터를 fd에 저장.

mmap 함수는 공유 메모리를 소비자 프로세스의 가상 메모리 공간에 매핑. 이때 PROT_READ는 읽기 권한을, MAP_SHARED는 이 메모리가 다른 프로세스와 공유됨을 의미.

이 함수의 결과로 공유 메모리를 가리키는 포인터가 반환되고, 이를 ptr 변수에 저장.

shm_unlink(name)는 공유 메모리 객체를 시스템에서 삭제하는 함수. 이 명령은 더 이상 이 공유 메모리 객체가 필요하지 않을 때 호출.


사례연구 - 파이프

파이프: 두 개의 프로세스 간에 데이터를 주고받을 수 있도록 하는 통로

 

프로듀서 프로세스가 파이프를 통해 컨슈머로 데이터를 전송한다.

파이프는 일방성을 띄고 따라서 3을 생성해서 파이프를 통해 consumer로 전달


producer 프로세스를 fork를 이용해 child process 생성

pipes_child: 자식 프로세스에서 데이터를 쓰기 위한 파이프의 파일 디스크립터.

data: 1부터 19까지의 임의의 숫자를 생성하는 변수.

buff: 데이터를 저장할 버퍼로, 크기는 1024바이트로 정의됨.

while(1): 무한 루프를 통해 계속 데이터를 생성하고 전송.

data = (random() % 19) + 1: 1에서 19까지의 임의의 숫자를 생성.

memset(buff, 0, BUFF_SIZE): 버퍼를 0으로 초기화.

sprintf(buff, "%d", data): 생성된 숫자를 문자열 형식으로 버퍼에 저장.

write(pipes_child, buff, strlen(buff)): 파이프를 통해 버퍼의 데이터를 자식 프로세스로 보낸다.

printf: 전송한 데이터를 터미널에 출력.

sleep(2): 2초간 대기 후 다시 데이터를 생성하고 전송.

consumer프로세스

consumer(int pipes_parent):

pipes_parent: 부모 프로세스에서 데이터를 읽기 위한 파이프의 파일 디스크립터.

buff: 데이터를 읽어서 저장할 버퍼.

while(1): 무한 루프를 통해 계속 파이프로부터 데이터를 읽습니다.

memset(buff, 0, BUFF_SIZE): 버퍼를 0으로 초기화.

read(pipes_parent, buff, BUFF_SIZE): 파이프에서 데이터를 읽어 버퍼에 저장.

printf("consumer: producer에서 수신 : %s\n", buff): 수신한 데이터를 출력.

sleep(2): 2초간 대기 후 다시 데이터를 읽는다.


파이프 생성 코드

int ipc_pipe[2];:

ipc_pipe[0]: 파이프의 읽기용 파일 디스크립터.

ipc_pipe[1]: 파이프의 쓰기용 파일 디스크립터.

 

pipe(ipc_pipe):

파이프를 생성하여, 성공 시 ipc_pipe[0]에 읽기용 디스크립터, ipc_pipe[1]에 쓰기용 디스크립터가 할당.

파이프 생성에 실패하면 에러 메시지를 출력하고 프로그램을 종료.

 

pid = fork();:

fork() 함수는 부모 프로세스와 동일한 자식 프로세스를 생성.

부모 프로세스는 pid가 양수 값을 가지며, 자식 프로세스는 pid 값이 0.

 

if (pid == 0): 자식 프로세스는 Producer 역할을 수행.

producer(ipc_pipe[1]): Producer는 파이프의 쓰기 디스크립터 ipc_pipe[1]를 사용하여 데이터를 파이프에 쓴다.

else: 부모 프로세스는 Consumer 역할을 수행.

consumer(ipc_pipe[0]): Consumer는 파이프의 읽기 디스크립터 ipc_pipe[0]을 사용하여 데이터를 파이프에서 읽는다.

 

구조

**fork()**에 의해 자식 프로세스가 생성되면, 부모와 자식이 각각 파이프를 통해 데이터를 주고받는 역할을 수행하게 된다.

Producer(자식 프로세스)는 파이프에 데이터를 쓴다.

Consumer(부모 프로세스)는 파이프에서 데이터를 읽는다.

파이프의 ipc_pipe[1]은 쓰기 전용, ipc_pipe[0]은 읽기 전용 디스크립터로 사용.


사례연구 - 세마포어(상호배제)

임계영역 문제를 해결하기 위한 방법 -> 상호배제 -> 이를 구현하기 위한 것 : 세마포어

두 개의 스레드, 스레드1과 스레드2가 하나의 공유변수 : sharedresource++를 핸들링


 Mutex 초기화:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;:

뮤텍스를 선언하고 초기화합니다. 뮤텍스는 스레드 간의 상호 배제(Mutual Exclusion)를 보장하기 위해 사용됩니다.

**sharedresource**는 두 개의 스레드에서 공유되는 자원입니다.

 

함수 do_loop1 (첫 번째 스레드의 동작):

pthread_mutex_lock(&mutex);:

공유 자원에 접근하기 전에 뮤텍스 잠금을 설정합니다. 이렇게 하면 다른 스레드가 이 자원에 접근하지 못하게 합니다.

진입영역: 뮤텍스가 잠긴 상태에서 공유 자원에 접근하여 값을 출력하고, sharedresource++로 값을 증가시킵니다.

pthread_mutex_unlock(&mutex);:

공유 자원의 접근이 끝나면 뮤텍스 잠금을 해제합니다. 이렇게 하면 다른 스레드가 이 자원에 접근할 수 있게 됩니다.

출구영역: 뮤텍스를 해제한 후 1초간 대기합니다.

 

함수 do_loop2 (두 번째 스레드의 동작):

 

이 함수는 do_loop1과 비슷한 방식으로 동작합니다.

pthread_mutex_lock(&mutex);로 뮤텍스를 잠근 후, 공유 자원 sharedresource를 출력하고 값을 증가시킵니다.

이후 pthread_mutex_unlock(&mutex);로 잠금을 해제하고, 2초간 대기합니다.

 

멀티스레드의 동작 원리:

do_loop1do_loop2는 각각 별도의 스레드로 실행됩니다.

두 함수 모두 뮤텍스를 이용해 공유 자원에 대한 접근을 제어합니다.

스레드 간에 뮤텍스를 사용함으로써 동시에 공유 자원에 접근하는 것을 방지하고, 공유 자원의 일관성을 유지할 수 있습니다.


'운영체제' 카테고리의 다른 글

동기화와 세마포어  (0) 2024.10.10
병행 프로세스와 병렬 프로세스  (1) 2024.09.29
비동기 병렬프로세스  (1) 2024.09.29
운영체제 프로세스  (1) 2024.09.25
운영체제-  (1) 2024.09.24