IPC(interProcess Communication)
: 프로세스 간 통신을 말한다.
종류
1. 프로세스 "내부" 데이터 통신
2. 프로세스 "간" 데이터 통신
3. 네트워크를 이용한 "원격" 데이터 통신
프로세스 내부 데이터 통신
= 프로세스 내부 스레드간 통신을 말한다,
전역변수를 이용한 통신방식
: from1to2, from2to1선언하고 이를 이용해서 통신한다,
• from1to2와 from2to1은 각각 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_loop1과 do_loop2는 각각 별도의 스레드로 실행됩니다.
• 두 함수 모두 뮤텍스를 이용해 공유 자원에 대한 접근을 제어합니다.
• 스레드 간에 뮤텍스를 사용함으로써 동시에 공유 자원에 접근하는 것을 방지하고, 공유 자원의 일관성을 유지할 수 있습니다.
'운영체제' 카테고리의 다른 글
동기화와 세마포어 (0) | 2024.10.10 |
---|---|
병행 프로세스와 병렬 프로세스 (1) | 2024.09.29 |
비동기 병렬프로세스 (1) | 2024.09.29 |
운영체제 프로세스 (1) | 2024.09.25 |
운영체제- (1) | 2024.09.24 |