diff --git "a/03-OperatingSystem/08-\353\246\254\353\210\205\354\212\244_\354\212\244\354\274\200\354\244\204\353\247\201/08_CFS.pdf" "b/03-OperatingSystem/08-\353\246\254\353\210\205\354\212\244_\354\212\244\354\274\200\354\244\204\353\247\201/08_CFS.pdf" new file mode 100644 index 0000000..f239fb0 Binary files /dev/null and "b/03-OperatingSystem/08-\353\246\254\353\210\205\354\212\244_\354\212\244\354\274\200\354\244\204\353\247\201/08_CFS.pdf" differ diff --git "a/03-OperatingSystem/08-\353\246\254\353\210\205\354\212\244_\354\212\244\354\274\200\354\244\204\353\247\201/cfs.md" "b/03-OperatingSystem/08-\353\246\254\353\210\205\354\212\244_\354\212\244\354\274\200\354\244\204\353\247\201/cfs.md" new file mode 100644 index 0000000..26db92c --- /dev/null +++ "b/03-OperatingSystem/08-\353\246\254\353\210\205\354\212\244_\354\212\244\354\274\200\354\244\204\353\247\201/cfs.md" @@ -0,0 +1,165 @@ +# CFS + +## CFS 이전의 시대 + +- **초기 리눅스 스케줄러 (O(n))**: + + - 리눅스 2.4 커널까지 사용된 스케줄러는 실행 대기 중인 모든 프로세스를 연결 리스트로 관리함 + - 다음에 실행할 프로세스를 찾기 위해 리스트 전체를 순회하며 우선순위를 계산했기 때문에, 프로세스 수($n$)가 늘어날수록 스케줄링 비용이 선형적으로 증가함 + - 이는 대규모 서버 환경에서 심각한 성능 저하를 초래함 + +- **O(1) 스케줄러 (리눅스 2.6.0 ~ 2.6.22)** + - 각 CPU마다 'Active'와 'Expired'라는 두 개의 우선순위 배열을 두어, 프로세스 선택 시간을 상수 시간($O(1)$)으로 단축함 + - 그러나 이 스케줄러는 공정성을 보장하고 대화형 프로세스의 응답성을 높이기 위해 복잡한 휴리스틱에 의존함 + - **휴리스틱의 문제** + - 스케줄러는 프로세스의 수면 시간을 측정하여 이것이 대화형 작업인지 배치 작업인지 '추측'하는 방식 + - 추측이 빗나갈 경우 대화형 프로세스가 기아 상태에 빠지거나 시스템이 버벅거리는 현상이 발생했음 + +## CFS (Completely Fair Scheduler) + +### 이상적인 멀티태스킹 CPU 모델 + +- 만약 $N$개의 실행 가능한 프로세스가 있다면, 무한한 병렬성을 가진 하드웨어는 각각의 프로세스에게 정확히 $1/N$의 물리적 CPU 파워를 동시에 제공어야 함 +- 100% 성능의 CPU에서 두 개의 프로세스가 실행된다면, 각각 50%의 속도로 끊김 없이 병렬 실행되어야 함 +- 하지만 실제 하드웨어는 한 순간에 하나의 프로세스만 실행할 수 있음 +- 따라서 CFS는 시간을 잘개 쪼개어 프로세스들을 번갈이 실행함으로써 병렬 실행의 환상을 만들어 냄 + +### 가상 런타임 (Virtual Runtime, `vruntime`) + +**정의** + +- 프로세스의 실제 실행 시간을 프로세스의 가중치(우선순위)로 정규화한 값 + +**계산** + +> $$vruntime += \text{actual\_runtime} \times \frac{\text{weight}_{0}}{\text{weight}_{task}}$$ + +- actual_runtime: 프로세스가 물리적으로 CPU를 점유한 시간(나노초 단위) +- weight_0: 기본 우선순위(Nice 0)를 가진 프로세스의 가중치 (일반적으로 1024) +- weight_task: 해당 프로세스의 실제 가중치 + +**수식의 의미** + +- **Nice 0 (기본 우선순위)** + - 가중치 비율이 1이므로, 실제 시간 1초가 흐르면 vruntime도 1초 증가함 +- **Nice -10 (고우선순위, 가중치가 큼)** + - 분모가 커지므로 실제 시간 1초가 흘러도 vruntime은 0.1초 정도만 증가함 + - vruntime이 천천히 증가하므로, CPU를 더 많이 사용할 수 있음 +- **Nice +10 (저우선순위, 가중치가 작음)** + - 실제 시간 1초 동안 vruntime은 10초나 증가함 + - vruntime이 빠르게 증가하므로, CPU를 조금만 사용할 수 있음 + +### 스케줄링 결정 로직 + +- 현재 실행 가능한 프로세스 중 `vruntime`이 가장 작은 프로세스를 선택하여 실행함 +- `vruntime`이 작다는 것은 이상적인 모델에 비해 CPU를 조금 할당받았다는 의미기에 공정성을 위해 CPU를 할당 받아야 함 +- 프로세스가 실행되면 `vruntime`이 증가하며 결국 다른 대기 중인 프로세스의 `vruntime`보다 커지게 되어 CPU를 반납해야 됨 +- 이 과정이 반복되면서 모든 프로세스의 `vruntime`은 균형을 맞추게 됨 + +## 레드-블랙 트리 (Red-Black Tree) + +**정의** + +- 시간 순서로 정렬된 트리 + +**사용 이유** + +- 스케줄러 환경에서 프로세스가 자발적으로 수면 상태로 가거나 종료되는 빈도가 매우 높아 삽입과 삭제 모두 안정적인 성능이 보장되어야 함 +- 자가 균형 이진 탐색 트리인 레드-블랙 트리는 삽입, 삭제, 탐색, 모든 연산에서 최악의 경우에서도 $O(\log n)$의 시간 복잡도를 보장함 + +**트리 구조** + +- **노드 (Node)** + - 실행 가능한 작업을 나타냄 +- **키 (Key)** + - `vruntime`을 기준으로 정렬함 +- **배치** + - 트리의 왼쪽으로 갈수록 `vruntime`이 작은 작업들이, 오른쪽으로 갈수록 `vruntime`이 큰 작업들이 배치됨 + +**동작 흐름** + +- 스케줄러는 `vruntime`이 최소인 트리의 가장 왼쪽 노드를 선택함 +- 선택된 작업이 실행되면 `vruntime`이 증가함 +- 작업이 선점되거나 타임 슬라이스를 소진하면, 갱신된 `vruntime`을 가지고 트리에 재삽입됨 +- `vruntime`이 커졌으므로, 이 작업은 트리의 오른쪽으로 이동하게 되고 새로운 가장 왼쪽 노드가 다음 실행 대상으로 선택됨 + +**최적화** + +- 리눅스 커널은 트리의 루트뿐만 아니라 가장 왼쪽 노드를 가리키는 포인터를 별도로 캐싱함 +- 그로 인해 다음에 실행될 프로세스를 선택하는 연산은 사실상 $O(1)$의 속도로 수행될 수 있음 + +## 커널 구현 분석 + +### 주요 구조체 + +- **task_struct** + - 리눅스에서 프로세스를 표현하는 구조체 +- **sched_entity** + - CFS가 스케줄링하는 단위 + - `vruntime`, 가중치, 레드-블랙 트리 노드 등이 정의되어 있음 +- **csf_rq** + - 각 CPU별 CFS 런큐 + - 레드-블랙 트리의 루트와 가장 왼쪽 노드 포인트를 관리함 + +### 스케줄링 실행 흐름 + +- **트리거** + - 타이머 인터럽트가 발생하거나, 프로세스가 I/O 대기를 위해 `sleep`을 호출하면 메인 스케줄러 함수인 `__schedule()`이 호출됨 +- **스케줄링 클래스 순회** + - `pick_next_task()` 함수는 우선순위가 높은 스케줄링 클래스부터 순회함 + - 리얼타임 클래스에 실행할 작업이 있는지 먼저 확인하고, 없다면 CFS 클래스의 함수를 호출합니다. +- **pick_next_task_fair() 호출** + - `cfs_rq`에서 `rb_leftmost`(가장 왼쪽 노드) 포인터를 참조하여 `vruntime`이 가장 작은 `sched_entity`를 가져옴 + - 가져온 엔티티가 `task_struct`로 변환되어 반환됨 +- **컨텍스트 스위치** + - CPU 레지스터가 저장되고 새로운 프로세스의 실행이 시작됨 + +### 그룹 스케줄링 (Group Scheduling) + +- CFS는 단순히 개별 프로세스뿐만 아니라 사용자나 컨테이너 단위의 공정성을 보장할 수 있음 +- 사용자 A가 99개의 프로세스를 실행하고 사용자 B가 1개의 프로세스를 실행할 때, 프로세스 단위로 공정한 경우 사용자 A가 CPU의 99%를 가져감 +- 그룹 스케줄링은 사용자 A와 B에게 각각 50% CPU를 할당하고, 사용자 A의 50% 내부에서 99개 프로세스가 다시 자원을 나눔 +- `sched_entity`는 계층적 구조를 가질 수 있으며, 런큐 안에 또 다른 런큐가 존재하는 형태가 됨 + +## CFS의 한계 + +- **공정성**에 집중하지만, 공정성이 **응답성**을 의미하지는 않음 +- 전체 CPU 사용량은 적더라도 필요할 때 즉시 CPU를 받아야 하는 경우에 문제가 발생함 + - CFS에서는 우선순위를 높일 수 있지만, 더 많은 CPU 시간을 달라는 요청이지 더 빨리 CPU를 달라는 요청과는 다름 +- **Bursty Task** + - 오랫동안 실행되지 않던 프로세스가 깨어나면 `vruntime`이 매우 작아 트리의 가장 왼쪽에 위치함 + - 즉시 실행은 보장하지만, 너무 오랫동안 실행권을 독점하여 다른 프로세스의 레이턴시를 급증시킬 수 있음 + - CFS는 이를 막기 위해 `vruntime`을 현재 최솟값 근처로 강제 조정하는 휴리스틱을 사용했지만 이는 공정성을 해치고 예측 불가능한 레이턴시 스파이크를 유발함 + +## EEVDF (Earliest Eligible Virtual Deadline First)의 등장 + +- **Lag(지연)** 과 **Deadline(마감 시간)** 이라는 두 가지 새로운 개념을 도입하여 CFS의 한계를 수학적으로 해결함 + +**Lag** + +- 각 프로세스가 받아야 할 이상적인 시간과 실제 받은 시간의 차이 +- $Lag > 0$ + - 받아야 할 시간보다 덜 받음 (CPU를 받을 자격이 있음, Eligible) +- $Lag < 0$ + - 몫보다 더 많이 씀 (당분간 실행 자격 없음) + +**Virtual Deadline** + +- 실행 자격이 있는 프로세스들 중에서 가상 마감 시간이 가장 빠른 프로세스를 선택함 +- 프로세스는 자신의 타임 슬라이스 크기를 통해 데드라인을 조절할 수 있음 + +**Latency Nice** + +- 전체 CPU 할당량은 그대로 유지하면서, 스케줄링 빈도를 높여 응답성을 극대화할 수 있음 +- **Latency Nice를 낮추면 (급함)** + - 스케줄러가 프로세스에게 할당하는 1회 실행 시간을 짧게 쪼갬 + - 할당량이 작으니 금방 처리해야 할 작업으로 인식되어 가상 마감 시간이 앞당겨짐 + - 프로세스가 **더 자주, 더 빨리** 선택됨 +- **Latency Nice를 높이면 (안 급함)** + - 실행 시간을 길게 뭉쳐서 제공함 (문맥 전환 오버헤드 감소) + - 마감 시간이 뒤로 밀리므로, 스케줄러가 천천리 처리함 + +## 출처 + +- https://developer.ibm.com/tutorials/l-completely-fair-scheduler/ +- https://opensource.com/article/19/2/fair-scheduling-linux diff --git "a/03-OperatingSystem/08-\353\246\254\353\210\205\354\212\244_\354\212\244\354\274\200\354\244\204\353\247\201/desktop.ini" "b/03-OperatingSystem/08-\353\246\254\353\210\205\354\212\244_\354\212\244\354\274\200\354\244\204\353\247\201/desktop.ini" new file mode 100644 index 0000000..70c1540 --- /dev/null +++ "b/03-OperatingSystem/08-\353\246\254\353\210\205\354\212\244_\354\212\244\354\274\200\354\244\204\353\247\201/desktop.ini" @@ -0,0 +1,2 @@ +[LocalizedFileNames] +08_CFS.pdf=@08_CFS.pdf,0