Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
250 changes: 250 additions & 0 deletions OS/Ch05-CPU Scheduling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
# 5장 - CPU Scheduling

CPU 스케줄링은 다중 프로그램 운영체제의 기본이다. 운영체제는 CPU를 프로세스 간에 교환함으로서 컴퓨터를 보다 생산적으로 만든다.

스레드를 지원하는 운영체제에서는 실질적으로 운영체제는 프로세스가 아니라 커널 수준 스레드를 schedule 한다.

프로세스 스케줄링과 스레드 스케줄링 용어는 상호 교환적으로 사용된다.

- 다양한 CPU 스케줄링 알고리즘을 설명
- 스케줄링 기준에 따라 CPU 스케줄링 알고리즘을 평가
- 멀티프로세서 및 멀티코어 스케줄링과 관련된 문제를 설명
- 다양한 실시간 스케줄링 알고리즘 설명
- Windows, Linux 및 Solaris 운영 체제에서 사용되는 스케줄링 알고리즘에 관해 설명
- 모델링 및 시뮬레이션을 적용하여 CPU 스케줄링 알고리즘을 평가
- 여러 가지 CPU 스케줄링 알고리즘을 구현하는 프로그램을 설계

## 5.1 Basic Concepts

multiprogramming의 목적은 CPU 이용률을 최대화하기 위해 항상 실행 중인 프로세스를 가지게 하는 데 있다.

프로세스가 대기해야 할 경우, 운영체제는 CPU를 그 프로세스로부터 회수해 다른 프로세스에 할당한다. 이러한 패턴은 계속되며, 하나의 프로세스가 대기할 때마다 다른 프로세스가 CPU 사용을 양도받는다.

Scheduling은 기본적인 운영체제의 기능이다. 거의 모든 컴퓨터의 리소스는 사용 전에 schedule 되어있다. CPU 이용률을 최대화하는 것은 다중 프로세서 운영체제 설계의 핵심이 된다.

### 5.1.1 CPU-I/O Burst Cycle

![Figure 5.1 Altering sequence of CPU and I/O burst](https://user-images.githubusercontent.com/16266103/132116709-98da70ce-9a8b-4e0f-b853-ab9f2c93b760.png)

프로세스 실행은 CPU 실행과 I/O 대기의 사이클로 구성된다. 프로세스 실행은 CPU Burst로 시작된다. 뒤이어 I/O Burst가 발생한다. 결국 마지막 CPU Burst는 또 다른 I/O Burst가 뒤따르는 대신, 실행을 종료하기 위한 요청과 함께 끝난다.

입출력 중심의 프로그램은 짧은 CPU Burst를 많이 가질 것이다. CPU 지향 프로그램은 다수의 긴 CPU Burst를 가질 수 있다. CPU Burst들의 지속시간을 광범위하게 측정한 그래프는 CPU 스케줄링 알고리즘을 구현할 때 매우 중요하다.

### 5.1.2 CPU Scheduler

![Figure 5.2 Histogram of CPU-burst durations](https://user-images.githubusercontent.com/16266103/132116707-80989803-c290-4c5b-b3d1-d91c53497de5.png)

CPU가 유휴 상태가 될 때마다, 운영체제는 Ready Queue에 있는 프로세스 중에서 하나를 선택해서 실행해야 한다. 선택 절차는 CPU Scheduler에 의해 수행된다.

CPU Scheduler는 실행 준비가 되어 있는 메모리 내의 프로세스 중에서 선택하여, 이들 중 하나에게 CPU를 할당한다.

Ready Queue는 반드시 FIFO 방식의 큐가 아니어도 되고, 우선순위 큐, 트리 등으로 구현될 수 있다. 일반적으로 큐에 있는 레코드들은 프로세스의 PCBs(process control blocks)이다.

### 5.1.3 Preemptive and Nonpreemptive Scheduling

CPU-scheduling decisions는 다음의 네 가지 상황에서 발생할 수 있다.

1. 한 프로세스가 실행 상태에서 대기 상태로 전환될 때 (I/O 요청이나 자식 프로세스가 종료되기를 기다리기 위해 wait()를 호출)
2. 프로세스가 실행 상태에서 준비 완료 상태로 전환될 때 (인터럽트가 발생)
3. 프로세스가 대기 상태에서 준비 완료 상태로 전환될 때 (I/O의 완료)
4. 프로세스가 종료할 때

1, 4번의 경우, 스케줄링 측면에서의 선택이 불가(nonpreemptive)하다. 실행을 위해 새로운 프로세스(ready queue에 하나라도 존재할 경우)가 반드시 선택되어야 한다. 그러나 2, 3번은 선택의 여지가 있다.

상황 1, 4에서만 스케줄링이 발생할 경우, 우리는 이러한 스케줄링 방법을 비선점(nonpreemptive) 또는 협조적(cooperative)이라고 한다. 그렇지 않으면, 그것은 선점(preemptive)이라고 한다.

데이터가 다수의 프로세스에 의해 공유될 때 racing condition이 발생할 수 있다. 이럴 경우 mutex lock, monitor 등의 기법을 사용해서 racing condition을 피한다.

인터럽트는 어느 시점에서건 일어날 수 있고, 커널에 의해서 항상 무시될 수는 없기 때문에, 인터럽트에 의해서 영향을 받는 코드 부분은 반드시 동시 사용으로부터 보호되어야 한다.

### 5.1.4 Dispatcher

CPU 스케줄링 기능에 포함된 또 하나의 요소는 dispatcher 이다. dispatcher는 CPU 코어의 제어를 CPU 스케줄러가 선택한 프로세스에 주는 모듈이며, 다음과 같은 작업을 포함한다.

- 한 프로세스에서 다른 프로세스로 context switch
- 사용자 모드로 전환하는 일
- 프로그램을 다시 시작하기 위해 사용자 프로그램의 적절한 위치로 이동(jump) 하는 일

![Figure 5.3 The role of the dispatcher](https://user-images.githubusercontent.com/16266103/132116705-b47be689-8f65-4aea-be88-848dc0143c0d.png)

dispatcher는 모든 프로세스의 context switch시 호출되므로 가능한 한 빠르게 수행되어야 한다. dispatcher가 하나의 프로세스를 정지하고 다른 프로세스의 수행을 시작하는 데까지 소요되는 시간을 dispatch latency라고 한다.

context switch는 voluntary context switch와 nonvoluntary context switch로 나뉜다.

- voluntary context switch: 현재 사용 불가능한 자원을 요청했을 때 프로세스가 CPU 제어를 포기한 경우 발생
- nonvoluntary context switch: time slice가 만료되었거나 우선순위가 더 높은 프로세스에 의해 선점되는 경우와 같이 CPU를 빼앗겼을 때 발생

<details>
<summary> 질문(2021.09.19) </summary>

- 인터럽트에서 사용자모드?
- 사용자모드로 돌아갈 때 현재 사용 불가능한 자원을 요청했을 때 프로세스가 CPU 제어를 포기한 경우 발생
- 나를 입력하시오 i/o 기다리는 작업할 때 cpu를 사용해서 하지 않아도 됨
- cpu를 놓음 => 자식프로세스가 종료될 때까지 기다리거나 waiting 이런게 cpu 제어를 포기한 상태라고 함

- dispatcher가 어떻게 움직어야 하는가?
- context switch의 종류
- 스케쥴링 시간 주기에 의해서 강제적으로 contxt switch가 일어나는 경우와 스스로 cpu를 사용할 일이 없어져서 저게 되는경우가 voluntary context siwtch
- 선점 비선점의 기준
- 비선점은 제어권을 뺏을 수 있고 선점은 아니다
- voluntary와 non voluntary, preemtive의 정의는 뺏어서 들어갈 수 있는가가 기준

- 1, 4는 nonpreemtive 이고 2, 3은 preemptive 일 수도 있다
- 선점을 할 수 있는 케이스와 애초에 선점이 개입할 수 없는 케이스로 나눠놨다
- 선점을 허용하는 스케쥴링 기법, 허용하지 않는 스케쥴링 기법
- 스케쥴링 알고리즘 기법들을 하나하나 배우다보면 이해가 명확하다

- 5.1장 마지막에 volutary, non voluntary 설명하는 부분에서 맨 마지막에 context switch 횟수 보여주면서 이야기 하는 부분은 무엇을 의미하는가?
- 통게치를 보여주는 명령어가 있다로 끝임

</details>


## 5.2 Scheduling Criteria

CPU 스케줄링 알고리즘마다 특성이 다르다.
특정한 상황에서 사용할 알고리즘을 선택할 때는 다양한 알고리즘의 특징을 생각해보고 선택해야한다.

CPU 스케줄링 알고리즘을 비교하기 위해서 다양한 기준이 제안되었는데, 어떤 특성을 비교하느냐에 따라 어떤 알고리즘이 가장 좋은 것인지 결정하는데 많은 차이를 발생시킨다.

여러 CPU 스케줄링 알고리즘 사이에서 하나를 선택하기 위한 CPU 스케줄링 비교 기준은 다음과 같다.

- CPU utilization(CPU 사용률) : 우리는 CPU가 최대한 바쁘기를 원한다. 개념적으로 CPU 사용률은 `0% ~ 100%` 사이다.

실제 시스템에서는 `40%(lightly loaded system) ~ 90%(heavily loaded system)` 범위에 있어야 한다. (CPU 사용률은 Linux, macOS 및 UNIX 시스템에서 top 명령을 사용하여 얻을 수 있다.)

- Throughput(처리량) : **시간당 완료된 프로세스의 수**
- Turnaround time(처리 시간) : **프로세스 제출(submission) 시간부터 완료 시간까지의 간격이 처리시간**

> 처리시간 : 준비 대기열에서 대기하고 CPU에서 실행하고 I/O를 수행하는데 소요된 시간의 합

- Waiting time(대기 시간): 프로세스가 준비 큐에서 대기하면서 보낸 시간의 합
- Response time(응답 시간) : 하나의 Request를 제출한 후 첫 번째 Response가 나올 때까지의 시간이다.

Interactive System에서 처리시간이 최상의 기준이 아닐 수 있다.

보통 프로세스는 일부 결과값이 빠르게 올 수 있고, 이전 결과값이 사용자에게 출력되는 동안 새로운 응답이 올 수 있다. 따라서 측정은 요청부터 처음 응답이 올 때까지의 시간이다.

응답 시간이라고 하는 측정값은 응답을 출력하는데 걸리는 시간이 아닌 응답(Response)까지 걸리는 시간이다.

CPU 사용률과 처리량은 최대화하고 처리시간, 대기시간, 응답시간은 최소화하는 것이 바람직한 선택이다.

예를 들어 모든 사용자가 좋은 서비스를 받을 수 있도록 최대 응답 시간을 최소화할 수 있다.

Investigators는 대화형 시스템(예: PC 데스크톱 또는 랩톱 시스템)의 경우 평균 응답 시간을 최소화하는 것보다 응답 시간의 변동을 최소화하는 것이 더 중요하다고 제안했다.

합리적이고 예측가능한 응답시간을 가진 시스템은 평균적으로 더 빠르지만 매우 가변적인 시스템보다 더 바람직한 것으로 간주될 수 있다.

대부분의 알고리즘의 경우는 Trade-Off 임으로 본인의 Context에 맞춰서 선택하는 것이 가장 좋은 방법이다.

## 5.3 Scheduling Algorithms

CPU 스케줄링은 준비 대기열에 있는 프로세스 중에서 CPU의 코어에 할당할 프로세스를 결정하는 것이 중요하다.

대부분의 최신 CPU 아키텍처에는 Multiple processing 코어가 있지만, 여기서는 사용 가능한 Processing 코어가 하나뿐이라는 가정으로 설명되어있다. 즉, 단일 처리 코어가 있는 단일 CPU이므로 시스템은 한 번에 하나의 프로세스만 실행할 수 있다.

### 5.3.1 First-Come, First-Served Scheduling

가장 간단한 CPU 스케줄링 알고리즘은 **First-Come, First-Served Scheduling(fcfs)** 다.

이 방식에서는 먼저 CPU를 요청하는 프로세스가 먼저 CPU에 할당된다.

프로세스가 Ready Queue에 들어가면, PCB는 대기열의 꼬리에 연결된다. CPU가 사용 가능해지면 Queue의 선두에 있는 프로세스가 할당된다. 그 다음 실행 중인 프로세스가 대기열에서 제거된다.

단점으로 FCFS 정책은 평균 대기시간이 길다. CPU Burst의 길이가 0ms 되고나서 다음 프로세스를 지정해야한다.

![5 3-1](https://user-images.githubusercontent.com/24274424/135699106-b339ca85-b805-45a3-8fcb-d86986d6f7ec.png)

프로세스가 P1, P2, P3 순서로 도착하고 FCFS 순서대로 한다면, 다음과 같은 결과가 나온다.

![5 3-2](https://user-images.githubusercontent.com/24274424/135699104-9a8887e7-1b90-46f0-8b79-1887937beb47.png)

### 대기시간

- P1 : 0ms
- P2 : 24ms
- P3 : 27ms

따라서 평균 대기 시간은 `(0 + 24 + 27) / 3 = 17ms` 다. 그러나 프로세스가 P2, P3, P1 순서로 도착하면 결과는 다음 차트와 같이 나온다.

![5 3-3](https://user-images.githubusercontent.com/24274424/135699103-bc5ea102-a2eb-4fe7-81a7-5fd9c1929290.png)

평균 대기 시간은 `(6 + 0 + 3)/3 = 3ms` 다. 상당히 줄었다. 따라서 FCFS 정책에 따른 평균 대기 시간은 일반적으로 최소가 아니며, 프로세스의 CPU Burst 시간이 크게 다를 경우 결과가 크게 달라질 수 있다.

동적 상황에서 FCFS 스케줄링의 성능을 고려해보자.

하나의 CPU-bound process와 많은 I/O-bound processes가 있다고 가정해보자. 프로세스가 시스템을 중심으로 흐르기 때문에 다음과 같은 시나리오가 발생할 수 있다.

CPU-bound process는 CPU를 가져오고 유지한다.

이 시간 동안 다른 모든 프로세스는 I/O를 완료하고 준비 대기열로 이동하여 CPU를 기다린다. 프로세스가 준비 대기열에서 기다리는 동안 I/O 장치는 유휴 상태다.

결국 CPU-bound process는 CPU Burst를 끝내고 I/O 장치로 이동한다. 짧은 CPU Burst를 가지는 모든 I/O 바운드 프로세스는 빠르게 실행되고 I/O 대기열로 다시 이동한다. 이 시점에서 CPU는 유휴 상태다.

그런 다음 CPU 바인딩된 프로세스는 준비 대기열로 돌아가서 CPU를 할당받는다.

다시 말하면, 모든 I/O 프로세스는 CPU 바운드 프로세스가 완료될 때까지 준비 대기열에서 대기한다. 다른 모든 프로세스가 하나의 큰 프로세스가 CPU에서 나올 때까지 기다리기 때문에 호송 작업이 있다. 이로 인해 더 짧은 프로세스가 먼저 실행되도록 허용된 경우보다 CPU 및 장치 사용률이 낮아진다.

### 5.3.2 Shortest-Job-First Scheduling

CPU 스케줄링에 대한 다른 접근 방식은 SJF(Shortest-Job-First) 스케줄링 알고리즘이다. 이 알고리즘은 프로세스의 다음 CPU Burst 길이를 각 프로세스와 연관해서 결정한다.

CPU를 사용할 수 있는 경우 다음 CPU Burst가 가장 작은 프로세스를 할당된다. 프로세스의 다음 CPU Burst가 동일한 경우 FCFS 스케줄링을 사용하여 동점을 끊는다. 스케줄링은 전체 길이가 아니라 프로세스의 다음 CPU Burst 길이에 따라 달라지기 때문에 스케줄링 방법에 대한 더 적절한 용어는 **the shortest-next-CPU-burst algorithm** 다.

흔히 사용하는 용어가 SJF이므로 여기서도 이 용어를 사용한다.

SJF 스케줄링의 예로, CPU Burst의 길이가 ms 단위로 주어진 다음 프로세스를 보자.

![5 3-4](https://user-images.githubusercontent.com/24274424/135699102-9bd9009b-9c42-40a5-8103-f1561a9d5fc0.png)

SJF 스케줄링을 사용하여 Gantt 차트에 따라 프로세스를 스케줄링한다.

![5 3-5](https://user-images.githubusercontent.com/24274424/135699101-ccac06ad-d56d-4c7e-b78f-ca2752bfe3fc.png)

### 대기시간

- P1 : 3ms
- P2 : 16ms
- P3 : 9ms
- P4 : 0ms

따라서 평균 대기 시간은 `(3 + 16 + 9 + 0)/4 = 7ms` 다. 이에 비해 FCFS 스케줄링 체계를 사용하는 경우 평균 대기 시간은 **10.25ms**다.

SJF 스케줄링 알고리즘은 주어진 프로세스 세트에서 최소 평균 대기 시간을 제공한다는 점에서 최적임이 입증되었다. 짧은 프로세스를 긴 프로세스보다 앞으로 이동하면 긴 프로세스의 대기시간이 늘어나는 것보다 짧은 프로세스의 대기시간이 더 줄어든다. 결과적으로 평균 대기시간이 줄어든다.

SJF 알고리즘이 최적이지만 다음 CPU Burst의 길이를 알 수 있는 방법이 없기 때문에 CPU 스케줄링 수준에서는 구현할 수 없다. 이 문제에 대한 한 가지 접근 방식은 SJF 스케줄링을 근사화하는 것이다. 다음 CPU Burst의 길이는 알 수 없지만 그 값은 예측할 수 있다. 다음 CPU Burst의 길이는 이전 버스트와 비슷할 것으로 예상한다. **다음 CPU Burst 길이의 근사치를 계산하여 예측된 CPU Burst가 가장 짧은 프로세스를 선택** 할 수 있다.

<!-- 다음 CPU Burst는 일반적으로 이전 CPU Burst의 측정된 길이의 지수 평균으로 예측한다. 다음 공식으로 지수평균을 정의할 수 있다. tn을 n번째 CPU Burst의 길이라고 하고 τ를 다음 CPU Burst에 대한 예측 값이라고 한다.

![5 3-6](https://user-images.githubusercontent.com/24274424/135699100-ae88c4f0-508d-4617-89ff-a9676f157222.png)

그런 다음, α에 대해 0 ≤ α ≤ 1 정의된 tn의 값은 가장 최근 정보를 포함하고 τn은 과거기록을 저장한다. 매개변수 α는 예측에서 최근 및 과거 기록의 상대적 가중치를 제어한다. α = 0 이면 τn + 1 = τn이고 최근 이력은 영향을 미치지 않는다(현재 조건은 일시적인 것으로 가정). α = 1이면 τn+1 = tn이고 가장 최근의 CPU Burst만 중요합니다(기록은 오래되고 관련이 없는 것으로 가정됨). 더 일반적으로 α = 1/2이므로 최근 기록과 과거 기록이 동일한 가중치를 갖는다. 초기 τ0는 상수 또는 전체 시스템 평균으로 정의할 수 있다. 그림 5.4는 α = 1/2 및 τ0 = 10인 지수 평균을 보여준다.

![5 3-7](https://user-images.githubusercontent.com/24274424/135699099-75bde58f-63b3-4c36-ae12-5c5723f471a4.png)

지수 평균의 동작을 이해하기 위해 다음을 찾기 위해 τn을 대입하여 τn+1에 대한 공식을 확장할 수 있다.

![5 3-8](https://user-images.githubusercontent.com/24274424/135699098-70964679-a625-4001-b6b9-bd9e25bf5274.png)

일반적으로 α는 1보다 작다. 결과적으로 (1−α)도 1보다 작으며 각 연속 항은 이전보다 가중치가 적다. -->

SJF 알고리즘은 선점형 또는 비선점형이 될 수 있다. 선택은 이전 프로세스가 아직 실행 중인 동안 새 프로세스가 준비 대기열에 도착할 때 발생한다. 새로 도착한 프로세스의 다음 CPU Burst는 현재 실행 중인 프로세스의 남은 것보다 짧을 수 있다. 선점형 SJF 알고리즘은 현재 실행 중인 프로세스를 선점하며, 비선점형 SJF 알고리즘은 현재 실행 중인 프로세스가 완료하도록 한다. **선점형 SJF 스케줄링은 최단 잔여시간 우선 스케줄링이라고도 한다**.

예를 들어 CPU Burst의 길이가 ms로 지정된 다음 네 가지 프로세스를 보자.

![5 3-9](https://user-images.githubusercontent.com/24274424/135699095-92406bf2-b666-4f67-8edd-dac744000063.png)

프로세스가 표시된 시간에 준비 대기열에 도착하고 표시된 Burst 시간이 필요한 경우 결과 선점형 SJF 일정은 다음 Gantt 차트와 같다.

![5 3-10](https://user-images.githubusercontent.com/24274424/135699094-dfa0994d-45d0-4afe-9b1e-d5bee80a29d2.png)

### 대기시간

- P1 : 10ms - 1ms
- P2 : 0ms
- P3 : 17ms - 2ms
- P4 : 5ms - 3ms

평균 대기 시간은 `[(10−1) + (1−1) + (17−2) + (5−3)]/4 = 26/4 = 6.5ms`다.

비선점형 SJF 스케줄링을 사용하면 평균 대기 시간이 **7.75ms**가 된다.