안녕하세요. 회사와 함께 성장하고 싶은 KOSE입니다.

 

이번 포스팅은 운영체제의 세마포어에 대한 개념을 정리하고자 합니다.

 

1. 세마포어란

  • 프로세스 간에 시그널을 주고받기 위해 사용되는 정수 값으로, 세마포어는 3가지 원자적인 연산을 지원합니다.
    initialize, decrement, increment
  • decrement 연산은 프로세스를 블록 시킬 수 있습니다. 반면 increment 연산은 블록되었던 프로세스를 깨울 수 있습니다.
  • 복잡한 프로세스 간 상호작용 속에서 세마포어 s를 통해 시그널을 전송하기 위해 프로세스는 semSignal(s)라는 프리미티브를 수행합니다.
  • 반면, 세마포어 s를 통해 시그널을 수신하기 위해 프로세스는 semWait(s)라는 프리미티브를 수행합니다.
  • 종류는 범용 세마포어, 이진 세마포어 등이 있습니다.

 

 

 

2. 세마포어 연산

  • 세마포어 초기화: 세마포어는 음이 아닌값으로 초기화됩니다.
  • semWait 연산: 세마포어 값을 감소시킵니다. 만일 값이 음수가 되면, semWait를 호출한 프로세스는 블록됩니다. 음수가 아니면 프로세스는 계속 수행됩니다.
  • semSignal 연산: 세마포어 값을 증가시킵니다. 만약 값이 양수가 아니면 semWait 연산에 의해 블록된 프로세스를 깨웁니다.

 

 

3. 세마포어의 사용 예

세마포어는 여러 프로세스 또는 스레드가 공유 리소스에 동시 접근하는 것을 제어하는데 사용됩니다. 

  • 공유 메모리 : 여러 프로세스가 동시에 메모리에 접근할 때, 서로 읽기 쓰기 작업이 충돌하지 않도록 보장하기 위해 사용됩니다.
  • 파일 시스템: 여러 프로세스가 동시에 파일이나 디렉토리에 접근할 때, 데이터 일관성을 유지하기 위해 사용됩니다.
  • 프로세스간 통신: 여러 프로세스가 메세지를 전달하거나 데이터를 주고받을 때, 동기화를 위해 사용됩니다.
  • 데이터베이스 시스템: 여러 사용자나 애플리케이션에서 동시에 데이터베이스에 접근할 때, 트랜잭션의 원자성과 일관성을 보장하기 위해 사용됩니다.

 

 

4. 세마포어 프리미티브

 

< 범용 세마포어 프리미티브 >

 

public class OsExample {

    class GeneralSemaphore {
        int count;
        Queue<Pid> queue;
    }

    class Pid {
        public Pid() {}
    }


    void semWait(GeneralSemaphore s) {
        s.count--;
        if (s.count < 0) {
            /* 요청한 프로세스를 s.queue에 연결 */
            /* 요청한 프로세스를 블록 상태로 전이시킴 */
        }
    }

    void semSignal(GeneralSemaphore s) {
        s.count++;

        if (s.count <= 0) {
            /* s.queue에 연결되어 있는 프로세스를 큐에서 제거 */
            /* 프로세스 상태를 실행 가능으로 전이시키고 redy list에 연결 */
        }
    }
}

 

 

< 이진 세마포어 프리미티브 >

 

public class BinarySemaphoreEx {

    class BinarySemaphore {
        Value value;
        Queue<Pid> queue;
    }

    class Pid {
        public Pid() {}
    }

    void semWaitB(BinarySemaphore s) {
        if (s.value == ONE) s.value = ZERO;
        else {
            /* 요청한 프로세스를 s.queue에 연결  */
            /* 요청한 프로세스를 블록 상태로 전이 */
        }
    }

    void semSignalB(BinarySemaphore s) {
        if (s.queue.isEmpty()) s.value = ONE;
        else {
            /* s.queue에서 프로세스 p를 제거  */
            /* 프로세스의 p의 상태를 실행 가능으로 전이, ready list에 연결 */
        }
    }
}

 

 

 

5. 세마포어 정책

 

범용 세마포어와 이진 세마포어 모두 세마포어에서 블록된 프로세스들을 관리하기 위해 큐를 사용합니다.

세마포어는 큐를 적용하는 정책에 따라 두 가지로 분리할 수 있습니다

 

강성 세마포어

프로세스들이 세마포어를 사용할 때, 먼저 semWait를 호출한 프로세스가 먼저 semSignal을 호출할 수 있는 순서로 실행됩니다. 

즉, 강력한 선입선출을 유지하여 많은 OS에서 강성 세마포어를 주로 사용하고 있습니다.

 

<강성 세마포어 실행 순서>

  • 프로세스 D 실행 s = 1 (S는 D가 실행되면 +1 )
  • D는 준비 큐 대기
  • A프로세스, B프로세스, D 준비 상태
  • A 프로세스가 스케줄링 -> A가 semWait() 호출 -> s 소비 -> s = 0 -> A 실행
  • B 프로세스 semWait() 호출 -> s <= 0 이므로 s = -1한 후, B 블록 큐 이동
  • A 프로세스 타임아웃으로 준비 큐 대기
  • D 프로세스 semSignal() 호출로 s++, 이어서 블록 큐에서 프로세스 B 호출
  • B가 실행 후, 종료
  • C가 준비 큐에서 스케줄링 -> semWait()를 호출 -> s = 0이므로  s--, C는 블록
  • A가 준비 큐에서 스케줄링 -> semWait()를 호출 -> s = -1이므로 s--. A는 블록
  • D가 준비 큐에서 스케줄링 -> semSignal()를 호출 -> s++, s-= -1, 블록된 'C' 실행

 

약성 세마포어

약성 세마포어는 큐를 사용하거나 혹은 다른 매커니즘을 사용할 수 있습니다. 만약 적용되는 프로세스가 실행 순서에 대한 요구사항이 없다면 다른 동기화 매커니즘을 사용하여 실행 순서를 관리할 수 있습니다. 따라서, 약성 세마포어는 '기아' 상태가 발생할 수 있습니다.

 

 

 

6. 상호 배제

 

세마포어를 사용할 경우, 공유 자원에 접근하려는 n개의 프로세스를 상호 배제할 수 있습니다.

프로세스가 공유 자원을 접근하려는 코드 부분이 임계영역으로 정의되고, 긱 프로세스는 임계영역에 들어가기 전 semWait를 호출합니다. s의 값의 범위에 따라 해당 프로세스의 블록이 결정됩니다.

만약 s가 양수라면 임계영역 내부로 입장하고, 임계 영역에서 나오며 s를 다시 증가킵니다.

따라서, s.count는 다음과 같이 정리할 수 있습니다.

  • s.count >= 0 : s.count는 현재 임계영역에 블록됨이 없이 진입할 수 있는 프로세스의 수를 나타냅니다.
  • s.count < 0: s.count의 절대값은 s.queue에 블록되어 있는 프로세스의 수를 나타냅니다.

 

 

운영체제는 공부해도 시간이 지나면 헷갈리고 잊어버리게 되는 것 같습니다.

꾸준히 정리하며, 공부를 이어나가도록 하겠습니다.

 

 

감사합니다!

 

- 자료 출처 : 운영체제 8판 내부구조 및 설게원리

'OS' 카테고리의 다른 글

[OS] 단일처리기 스케줄링  (0) 2023.04.25
[OS] 가상메모리(상)  (0) 2023.04.25
[OS] 메모리 관리  (0) 2023.04.19
[OS] 상호 배제를 위한 모니터  (0) 2023.04.19

+ Recent posts