디스크 스케줄링
디스크 스케줄링은 여러 I/O 요청이 들어왔을 때 디스크 헤드의 이동 순서를 결정해 총 이동 거리(탐색 시간)를 최소화하는 OS 기법입니다. HDD에서는 기계적으로 움직이는 탐색과 회전 지연이 I/O 시간의 큰 부분을 차지하기 때문에 스케줄링이 중요합니다.
핵심 개념
FCFS (First-Come, First-Served)
요청이 들어온 순서대로 처리합니다. 구현이 가장 단순하지만 헤드가 불필요하게 왔다갔다 해 총 이동 거리가 길어지기 쉽습니다. 공정하지만 비효율적입니다.
핵심 개념
SSTF (Shortest Seek Time First)
현재 헤드 위치에서 가장 가까운 트랙을 먼저 처리합니다. 총 이동 거리는 크게 줄어들지만 헤드에서 멀리 있는 요청은 기아(Starvation)가 발생할 수 있습니다.
핵심 개념
SCAN (엘리베이터 알고리즘)
헤드가 한쪽 끝까지 이동하면서 요청을 처리하고, 끝에 도달하면 방향을 바꿔 반대쪽으로 이동합니다. SSTF와 달리 기아가 생기지 않으며 실제 엘리베이터와 유사합니다.
핵심 개념
C-SCAN (Circular SCAN)
SCAN의 변형으로 한쪽 끝에 도달하면 반대 방향으로 돌아오는 대신, 처음(트랙 0)으로 바로 돌아가 같은 방향으로만 처리합니다. 대기 시간이 더 균등합니다.
핵심 개념
탐색 시간 (Seek Time)
헤드를 원하는 트랙으로 이동시키는 시간입니다. 전체 디스크 I/O 시간(탐색+회전지연+전송) 중 큰 비중을 차지합니다. SSD는 기계적 헤드가 없어 탐색 시간이 없습니다.
핵심 개념
현대 디스크 스케줄링
Linux의 멀티큐 블록 계층은 none, mq-deadline, kyber, BFQ 스케줄러를 제공합니다(CFQ와 옛 Deadline은 커널 5.0에서 제거되었습니다). 커널 소스는 하드웨어 큐가 하나인 장치의 기본 스케줄러를 mq-deadline, 여러 개인 장치(NVMe 등)의 기본을 none으로 정합니다(버전에 따라 다릅니다).
디스크 I/O 시간은 어디서 걸리나
HDD에서 데이터를 읽는 시간은 헤드를 원하는 트랙으로 옮기는 탐색 시간, 원판이 돌아 원하는 섹터가 헤드 아래로 오는 회전 지연, 실제 전송 시간의 합입니다. 이 중 기계적으로 움직이는 탐색과 회전이 큰 부분이라, 요청이 여러 개일 때 처리 순서를 바꿔 헤드 이동을 줄이는 것이 디스크 스케줄링입니다.
숫자로 확인하기
자주 하는 오해
면접에서 이렇게 물어요
면접 질문
FCFS, SSTF, SCAN, C-SCAN의 차이는 무엇인가요?
FCFS는 도착 순서이고 이동이 많습니다. SSTF는 가까운 요청을 먼저 처리해 이동은 줄지만 기아가 생깁니다. SCAN은 한 방향으로 쓸고 가며 처리하고 끝에서 방향을 바꾸며, C-SCAN은 끝에 닿으면 처음으로 돌아가 한 방향으로만 처리해 대기 시간이 더 고릅니다.
면접 질문
LOOK은 SCAN과 무엇이 다른가요?
SCAN은 디스크 끝까지 가지만 LOOK은 해당 방향에서 마지막 요청까지만 가고 돌아옵니다. 불필요한 이동이 줄어듭니다.
면접 질문
SSD에도 디스크 스케줄링이 필요한가요?
탐색 시간이 없어서 헤드 이동을 줄이는 정렬은 의미가 작습니다. 다만 요청 사이의 공정성이나 지연 상한을 관리하려고 간단한 스케줄러를 둘 수 있습니다.
면접 질문
디스크 I/O 시간은 무엇으로 구성되나요?
탐색 시간, 회전 지연, 전송 시간의 합입니다. HDD에서는 앞의 두 가지가 대부분입니다.
실제 시스템에서는
/sys/block/장치/queue/scheduler에서 현재 I/O 스케줄러를 보고 바꿀 수 있다고 설명합니다. lsblk -d -o NAME,ROTA의 ROTA 열은 커널이 보고하는 회전식 여부(queue/rotational)를 보여 줍니다.
함께 보면 좋은 토픽
참고 자료
C — FCFS / SSTF / SCAN / C-SCAN 구현
#include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_TRACK 199 #define N 8 int requests[N] = {98, 183, 37, 122, 14, 124, 65, 67}; int head = 53; /* ── FCFS ─────────────────────────────────────── */ int fcfs() { int cur = head, dist = 0; for (int i = 0; i < N; i++) { dist += abs(requests[i] - cur); cur = requests[i]; } return dist; } /* ── SSTF ─────────────────────────────────────── */ int sstf() { int rem[N], n = N, cur = head, dist = 0; memcpy(rem, requests, sizeof(requests)); while (n > 0) { int minD = 99999, idx = 0; for (int i = 0; i < n; i++) { int d = abs(rem[i] - cur); if (d < minD) { minD = d; idx = i; } } dist += minD; cur = rem[idx]; rem[idx] = rem[--n]; // 처리된 요청 제거 } return dist; } /* ── SCAN ─────────────────────────────────────── */ int scan() { int sorted[N], cur = head, dist = 0; memcpy(sorted, requests, sizeof(requests)); // 오름차순 정렬 for (int i = 0; i < N-1; i++) for (int j = i+1; j < N; j++) if (sorted[j] < sorted[i]) { int tmp = sorted[i]; sorted[i] = sorted[j]; sorted[j] = tmp; } // 위 방향: 헤드 이상 요청을 오름차순으로 처리 for (int i = 0; i < N; i++) if (sorted[i] >= head) { dist += abs(sorted[i] - cur); cur = sorted[i]; } // 디스크 끝(MAX_TRACK)까지 이동 후 방향 전환 dist += abs(MAX_TRACK - cur); cur = MAX_TRACK; // 아래 방향: 헤드 미만 요청을 내림차순으로 처리 for (int i = N-1; i >= 0; i--) if (sorted[i] < head) { dist += abs(sorted[i] - cur); cur = sorted[i]; } return dist; } int main() { printf("FCFS 총 이동: %d 트랙\n", fcfs()); printf("SSTF 총 이동: %d 트랙\n", sstf()); printf("SCAN 총 이동: %d 트랙\n", scan()); return 0; }
SSTF는 매 단계마다 남은 요청 중 현재 헤드에서 가장 가까운 트랙을 선택합니다. 그리디 알고리즘이라 전역 최적이 보장되지 않으며, 극단적인 경우 먼 트랙이 계속 처리되지 못하는 기아(Starvation)가 발생합니다.
SCAN은 정렬 후 현재 헤드 위치 기준으로 이상·이하를 나눠 처리합니다. 헤드가 한쪽 끝까지 이동하므로 모든 요청이 반드시 처리됩니다.
실제 Linux I/O 스케줄러는 이 알고리즘들을 기반으로 하지만, 우선순위·데드라인·공정성을 함께 고려합니다.
HDD에는 BFQ, SSD에는 none(no-op) 또는 mq-deadline이 권장됩니다.
Java — SSTF 구현 (PriorityQueue 활용)
import java.util.*; public class DiskScheduler { static final int HEAD = 53; static final int[] REQ = {98, 183, 37, 122, 14, 124, 65, 67}; /** SSTF: 현재 위치 기준 가장 가까운 트랙을 우선 처리 */ static int sstf(int head, int[] requests) { List<Integer> rem = new ArrayList<>(); for (int r : requests) rem.add(r); int cur = head, total = 0; System.out.printf("HEAD: %d%n", cur); while (!rem.isEmpty()) { // 가장 가까운 트랙 탐색 int finalCur = cur; rem.sort(Comparator.comparingInt(t -> Math.abs(t - finalCur))); int next = rem.remove(0); int dist = Math.abs(next - cur); total += dist; System.out.printf("%d → %d (거리: %d, 누적: %d)%n", cur, next, dist, total); cur = next; } return total; } /** C-SCAN: 한 방향으로만, 끝에서 0으로 점프 */ static int cscan(int head, int[] requests) { int[] sorted = requests.clone(); Arrays.sort(sorted); List<Integer> order = new ArrayList<>(); // head 이상: 오름차순 for (int t : sorted) if (t >= head) order.add(t); // 끝(199)까지 이동 후 0으로 점프 order.add(199); order.add(0); // head 미만: 오름차순 for (int t : sorted) if (t < head) order.add(t); int cur = head, total = 0; for (int next : order) { total += Math.abs(next - cur); cur = next; } return total; } public static void main(String[] args) { System.out.println("=== SSTF ==="); int d1 = sstf(HEAD, REQ); System.out.println("총 이동: " + d1 + " 트랙\n"); System.out.println("=== C-SCAN ==="); int d2 = cscan(HEAD, REQ); System.out.println("총 이동: " + d2 + " 트랙"); } }
SSTF에서 매 반복마다 리스트를 현재 위치 기준으로 정렬해 첫 번째 요소가 항상 최단 거리 트랙이 되도록 합니다. Java의 Comparator.comparingInt()를 사용하면 람다로 간결하게 표현할 수 있습니다.
C-SCAN은 요청을 정렬한 뒤 현재 헤드 이상·이하로 나눠 처리합니다. 끝(199)까지 이동 후 0으로 점프하는 부분을 명시적으로 리스트에 추가합니다.
실제 HDD 드라이버는 I/O 큐를 배열이 아닌 레드-블랙 트리나 링크드 리스트로 관리해 O(log n) 삽입·삭제를 보장합니다.
Q1
FCFS 디스크 스케줄링의 단점은?
Q2
SSTF(Shortest Seek Time First)의 방식은?
Q3
SCAN(엘리베이터) 알고리즘과 C-SCAN의 차이는?