CS 전공/OS

[운영체제] 7. 프로세스와 쓰레드의 동기화 (Synchronization)

Easyho.log 2024. 6. 23. 01:41

 1. IPC (Inter - Process Communication)

 일단 프로세스 간의 통신을 봐봅시다. 원래 원칙상으로는 프로세스끼리 통신은 힘들다. 프로세스끼리의 간섭을 없애기 위해 서로의 영역을 침범하지 않기 때문이다. 따라서, 쓰레드 간의 통신을 이용하여 프로세스끼리 통신을 한다. 하지만 프로세스끼리 통신은 필수이다. 당연하다. 우리가 컴퓨터 프로그램을 쓰면서 크롬만 켜지는 않을 것이다. 카카오톡도 켜고 동영상 강의도 틀고 op.gg도 켜고  아무튼 다양한 프로그램을 쓰기 때문이다. 또 어떤 컴퓨터 프로그램은 여러 프로세스로 구성이 되기에 프로세스간 통신은 필수이다. 그렇다면 프로세스의 종류는 뭐가 있을까?

 

1) IPC의 종류

1. 프로레스 내부 데이터 통신 (== 쓰레드)

- 하나의 프로세스 내에 2개 이상의 쓰레드가 존재하는 경우의 통신

- 프로세스 내부의 쓰레드는 전역 변수파일을 이용하여 데이터를 주고받는다.

 

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

- 같은 컴퓨터에 있는 여러 프로세스끼리 통신하는 경우 공용 파일 또는 운영체제가 제공하는 파이프를 사용하여 통신

- 주로 프로레스 간 통신은 이 방법이다.

 

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

- 여러 컴퓨터가 네트워크로 연결되어 있을 때 통신

- 소켓을 이용하여 데이터를 주고 받는다.

2) IPC 분류

1. 전송 방식에 따른 분류

- 단방향 통신

- 반이중 통신 (동시 전송 불가)

- 양방향 통신 (동시 전송 가능)

 

2. 수신 대기에 따른 분류

- 대기가 있는 통신 (~인터럽트) : 동기화를 지원하는 통신 방식

- 대기가 없는 통신 (~풀링) : 동기화를 지원하는 통신 방식 (Busy-Wait)

 

동기식, 비동기식

3) 파이프

파이프를 이용한 통신이 있다고 하였다. 이는 프로세스 간 통신이라고 하면 나오는 대표적인 통신 방식이다. 운영체제가 제공하는 동기화 통신 방식으로 파일 입출려과 같이 open() 함수로 pipe descriptor을 열고 작업을 한 후 close()로 마무리한다. 단방향 통신 방식이다.

 

- 파이프의 종류

1. 이름 없는 파이프 : 일반적인 파이프

2. 이름 있는 파이프 : FIFO라 불리는 특수 파일을 이용하며 서로 관련 없는 프로세스끼리의 통신에 사용된다.

 

이 둘의 차이점은 열려 있는 pipe에 대해서는 열 수 없지만 FIFO는 가능하다. 또한 FIFO는 파일 시스템 상에서 이미지를 가질 수 있다.

4) 소켓

소켓을 이용한 통신 방법은 일종의 네트워크 프로그래밍이다. 여러 컴퓨터에 있는 프로세스들끼리 통신하는 방법이다. 시스템에 있는 프로세스가 소켓을 바인딩한 후 소켓에 쓰기 연산을 하면 데이터가 전송되고, 읽기 연산을 하면 데이터를 받게 된다. 양방향 통신 방식이다.

소켓을 이용한 통신의 단계

5) 공유 메모리

- 커널이 관리되는 일정한 크기의 공유 메모리 공간을 통해 통신

- 사용하려는 프로세스 간 할당한 크기가 동일해야 사용 가능하다.

- 동기화가 중요

공유 메모리를 이용한 통신

6) 메시지 큐

- 구조체를 기반으로 작동하는 통신 방법으로 단위는 블록 단위이다. (파이프랑 비슷하지만 파이프는 stream 단위이다.)

- 동기화를 고려할 필요 없음

7) 메모리 맵

- 파일을 프로세스의 메모리에 일정 부분 맵필 키셔를 사용한다.

- 메모리를 공유하는 것에서 공유 메모리와 비슷하지만, 열린 파일을 메모리에 맵핑 시켜서 공유

- read(), write()와 시스템 콜을 사용할 때 발생하는 불필요한 복사를 방지한다.

 

그래서 정리를 하면 다음과 같다.

7가지 통신 방법

그러면 어떤 IPC를 써야할까?

1. IPC 쓰는 프로세스가 Parent-Child 관계이냐의 여부 -> fork()

2. IPC proc의 server/client가 하나냐 다수이냐의 여부

3. IPC로 주고 받을 데이터의 크기 (대용량일 경우  : shared memory가 적합)

4. IPC 처리 과정이 file descriptor 처리와 비슷하냐의 여부

5. IPC 프로그램이 쉽게 파일을 지정해서 쓸 수 있느냐의 여부

 

아무 생각 없이 제일 쓰기 편한 것 : 소켓


2. 공유 자원의 개념

1) 공유 자원 (공유 데이터)

- 하나를 두고 여럿이 쓰는 것

: 다중 프로그래밍에서는 CPU도 공유 자원이다.

- 여기서의 공유 자원은 여러 프로세스나 쓰레드가 공동으로 이용하는 변수, 메모리 파일 등이다.

: 공동으로 사용하기 때문에 누가 언제 데이터를 읽거나 쓰거나에 따라 결과가 달라질 수 있다. -> 왜?

명령어를 처리하는 중에 스케줄링이 걸리면 부득이하게 명령어가 분리가 된다.

2) 경쟁 상태 (Race Condition)

- 2개 이상의 프로세스가 공유 자원을 병행적으로 읽거나 쓰는 상황

- 경쟁 조건이 발생하면 공유 자원 접근 순서에 따라 실행 결과가 달라질 수 있다.

- 데이터가 오염된다.

3) 동기화 (Synchronization)

- 공유 자원에 대해 다수가 동시에 접근할 때 공유 데이터가 훼손되는 문제의 해결책이다.

- 공유 데이터를 접근하고자 하는 다수의 프로세스 또는 쓰레드가 충돌 없이 공유 데이터에 접근하기 위해 상호 협력한다.

  • 한 프로세스/쓰레드가 공유 데이터를 배타적 독점적으로 접근한다.
  • 차례대로 순서대로 접근한다. 

-> 이런 작업을 상호 배제 (Mutual Exclusion)이라 한다.

 

4) 상호 배제

- 임계 구역 : 공유 자원 접근 순서에 따라 실행 결과가 달라지는 프로그램의 영역이다.

-> 공유 자원에 접근하는 프로그램 코드 영역이다.

-> 임계 구역이 오직 한 프로세스만 배타적 독점적으로 사용하도록 하는 기술을 상호 배제라 한다. 이 임계 구역에는 프로세스가 하나만 들어갈 수 있고 나머지 프로세스는 임계 구역 밖에서 대기해야 한다.


3. 상호 배제 (Mutual Exclusion)

상호 배제의 목표는 임계 영역에는 오직 1개의 프로세스 및 쓰레드를 진입시키는 것이다. (Lock, Unlock)

일종의 문을 만든다고 생각하면 된다. 

1) 상호 배제를 포함하는 프로그램

1) 일반 코드 (non-critical code)

: 공유 데이터를 액세스하지 않는 코드

2) 임계 구역 진입 코드 (entry code)

: enterCS()를 통해 임계 구역에 들어가게 한다. 그 전에 임계 구역을 실행 중인 쓰레드가 있는지 검사한 후 들여보낸다.

3) 임계 구역 코드

4) 임계 구역 진출 코드

: exitCS()를 통해 임계 구역에서 나간다는 신호를 보내준다.

상호 배제

다음과 같은 조건을 만족해야 한다.

1) 상호 배제

2) 한정 대기 : 어떤 프로세스도 무한 대기가 없어야 한다.

3) 진행의 융통성 : 한 프로세스가 다른 프로세스의 진행을 방해해서는 안된다.

2) 상호 배제를 구현하는 방법

1. 인터럽트 서비스 금지

: 임계 구역 entry 코드에서 인터럽트 서비스를 금지하는 명령 실행

-> 장치로부터 인터럽트가 발생해도 CPU가 인터럽트를 무시함, 인터럽트 루틴을 실행하지 않는다.

-> 인터럽트를 금지한다는 것은 문맥 교환도 자연스럽게 금지된다는 것이다.

-> 하지만, 모든 인터럽트가 금지된다는 것은 전체 시스템 운영에 영향이 간다. 이로써 멀티코어 CPU에서는 일어날 수 없다.

-> 해결 방안으로 LOCK을 현재 액세스 하고 있는 메모리에만 국한시켜야 한다. (lock도 공유 자원이긴 함... 동기화 이슈가 있다)

 

2. 원자 명령

- lock 변수를 이용한 상호 베제의 실패 원인은 바로 entry 코드에 있다.

- lock 변수를 읽는 명령과 lock 변수에 1을 저장하는 2개의 명령 사이에 컨텍스트 스위칭이 될 때 문제가 발생한다.

- 이의 해결책은 원자 명령이다. 저 2개의 명령을 한 번에 처리하는 것이다.


4. Process / Thread Synchronization 기법

1) 동기화의 방식

1. Lock 방식 : 뮤텍스 (Mutex), 스핀락 (Spinlock)

- 상호 배제가 되도록 만들어진 락을 활용

- 뮤텍스는 동기화 대상이 오직 한 개일때 사용한다.

- 락을 소유한 쓰레드만이 임계 구역을 진입한다.

- 둘의 차이는 busy-wait (spinlock), sleep-wait (mutex)

2. wait - signal 방식 : 세마포어

- n개 자원을 사용하려는 m개 멀티쓰레드의 원활한 관리 : 카운터를 활용

- 자원을 소유하지 못한 쓰레드는 대기하고 자원을 다 사용한 쓰레드는 알린다.

2) Mutex (= 상호 배제)

- 잠김, 열림 중 한 상태를 가지는 락 변수를 이용하여 임계 구역에 진입시킨다. (sleep - waiting 기법)

- 구성 요소

1) 락 변수 : true/false 중 한 값

2) 대기 큐 : 락이 열리기를 기다리는 쓰레드 큐

3) 연산

- Lock 연산 : 락이 잠김 상태이면, 현재 쓰레드를 Block 상태로 만들고 대기 큐에 삽입한다.

- Unlock 연산 : 대기 큐에서 기다리는 쓰레드를 하나 깨운다.

3) Spinlock : Mutex에 반복문 (While) 추가

- 뮤텍스의 Non-Blocking 모델 (busy-waiting)

: 큐가 아닌 While Loop로 대기

- 멀티 코어에 적합

: 단일 코어 CPU에서 의미 없는 CPU 시간 낭비

- 락을 소유한 다른 쓰레드가 실행되어야 락이 풀린다.

- 임계 구역의 실행 시간이 짧은 경우에 효과적이다.

4) Mutex VS Spinlock

- 락이 잠기는 시간이 긴 경우 : 뮤텍스

- 단일 CPU를 가진 시스템 : 뮤텍스

- 사용자 응용 프로그램 : 뮤텍스, 커널 코드 : 스핀락

- 스핀락을 사용하면 기아 발생 가능성

5) 세마포어

: n개의 공유 자원을 다수 쓰레드가 공유하여 사용하도록 돕는 자원 관리 기법

 

- 구성 요소

1. 자원 : 여러 개

2. 대기 큐 : 자원을 할당받지 못한 쓰레드들이 대기하는 큐

3. counter 변수 : 사용 가능한 자원의 개수를 나타내는 정수형 전역 변수

4. P (wait), V (signal) 연산

 

busy-wait 세마포어와 sleep-wait 세마포어 2가지 종류가 있다.

 

** 이진 세마포어 **

- 1개의 자원에 대해 1개의 쓰레드만 액세스할 수 있도록 보호하는 기법이다. (뮤텍스..?)

6) 뮤텍스와 세마포어의 차이점

1. 가장 두드러지게 나타나는 차이점은 동기화 대상의 갯수이다. 세마포어는 뮤텍스와 다르게 동기화 대상이 여러개이다.

2. 세마포어는 뮤텍스가 되고 있지만, 뮤텍스는 세마포어가 될 수 없다.

3. 뮤텍스는 자원 소유가 가능하지만 세마포어는 불가하다.

4. 뮤텍스는 소유하고 있는 쓰레드만이 현재 뮤텍스를 해제할 수 없다.

7) 세마포어의 오사용

1. 프로세스가 세마포어를 사용하지 않고 임계구역으로 들어가버리면 임계구역을 보호할 수 없다.

2. P 연산을 두 번 사용하여 wake_up 신호가 발생하지 않은 경우로 프로세스 간의 동기화가 이루어지지 않아 세마포처 큐에서 대기하고 있는 프로세스들이 무한 대기에 빠진다.

8) 모니터 (Monitor)

 모니터는 공유 자원을 내부적으로 숨기고 공유 자원에 접근하기 위한 인터페이스만 제공함으로써 자원을 보호하고 프로세스 간에 동기화를 시킨다.

- 작동 원리

1. 임계 구역으로 지정된 자원에 접근하고자 하는 프로세스를 연산 없이 모니터에 작업 요청

2. 모니터는 요청받은 작업을 모니터 큐에 저장한 후 순서대로 처리하고 그 결과만 해당 프로세스에 알려준다.

 


5. 동기화 때문에 발생할 수 있는 문제

1) 우선순위 역전

  • 쓰레드의 동기화로 인해 높은 순위의 쓰레드가 낮은 쓰레드보다 늦게 스케쥴링되는 현상
  • 우선순위를 기반으로 스케줄링하는 실시간 시스템에서 쓰레드 동기화로 인해 발생
  • 높은 순위의 쓰레드가 늦게 실행되면 심각한 문제 발생 가능
  • 낮은 순위의 쓰레드가 길어지면 더욱 더 심각한 문제 발생 가능

그렇다면 이의 해결책은 뭘까? 바로, 우선순위 올림과 상속이다.


6. 생산자-소비자 문제

1) 생산자 - 소비자 문제

이 문제는 공유 버퍼를 사이에 두고 공유 버퍼에 데이터를 공급하는 생산자들과 데이터를 읽고 소비하는 소비자들이 공유 버퍼를 문제 없이 사용하도록 생산자와 소비자를 동기화시키는 문제이다. 

2) 이를 해결하는 방법 : 2개의 세마포어

읽기용 세마포어와 쓰기용 세마포어 2개를 이용한다.

읽기용 세마포어를 통해 비어 있는 버퍼 문제를 해결한다.

쓰기용 세마포어를 통해 꽉 차있는 버퍼 문제를 해결한다.

 

'CS 전공 > OS' 카테고리의 다른 글

[운영체제] 9. 메모리 관리  (0) 2024.06.23
[운영체제] 8. 교착상태  (0) 2024.06.23
[운영체제] 6. 스케줄링  (2) 2024.04.21
[운영체제] 5. 쓰레드  (1) 2024.04.20
[운영체제] 4. 프로세스 (2)  (2) 2024.04.18