컴퓨터 구조 운영체제 네트워크 자료구조 알고리즘 데이터베이스 내 학습
os · 09

디스크 스케줄링

OS디스크스케줄링

디스크 스케줄링은 여러 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으로 정합니다(버전에 따라 다릅니다).

디스크 구조 — 플래터(원판)가 회전하고 헤드가 방사형으로 움직입니다. 트랙(동심원)→섹터(트랙 분할) 순으로 구성됩니다(클러스터는 보통 파일 시스템의 할당 단위입니다). 7200RPM HDD의 평균 회전 지연은 약 4.17ms입니다(한 바퀴 8.33ms의 절반).
LOOK / C-LOOK — SCAN/C-SCAN의 최적화 버전입니다. 디스크의 끝(0 또는 최대)까지 이동하지 않고, 마지막 요청 위치까지만 이동합니다. 불필요한 이동이 줄어듭니다.
SSTF vs SCAN — SSTF는 평균 탐색 시간이 짧지만 기아 가능성이 있습니다. SCAN은 약간 더 이동할 수 있지만 모든 요청이 한 번의 왕복 안에 처리됩니다.
SSD와 스케줄링 — SSD는 기계적 헤드가 없어 탐색 시간이 없습니다. SSD도 내부 동작에 따라 지연이 달라질 수 있습니다. Linux의 기본 스케줄러는 장치의 하드웨어 큐 수에 따라 정해집니다.

디스크 I/O 시간은 어디서 걸리나

HDD에서 데이터를 읽는 시간은 헤드를 원하는 트랙으로 옮기는 탐색 시간, 원판이 돌아 원하는 섹터가 헤드 아래로 오는 회전 지연, 실제 전송 시간의 합입니다. 이 중 기계적으로 움직이는 탐색과 회전이 큰 부분이라, 요청이 여러 개일 때 처리 순서를 바꿔 헤드 이동을 줄이는 것이 디스크 스케줄링입니다.

숫자로 확인하기

같은 요청, 다른 이동 거리 — 헤드가 53번 실린더에 있고 요청이 98, 183, 37, 122, 14, 124, 65, 67번(0~199번 범위)이라고 합시다. FCFS는 45 + 85 + 146 + 85 + 108 + 110 + 59 + 2 = 640칸을 움직입니다. SSTF는 65, 67, 37, 14, 98, 122, 124, 183 순서로 가까운 것부터 처리해 236칸입니다.
SCAN과 LOOK — 헤드가 0번 쪽으로 먼저 가는 SCAN은 53칸을 가서 0번에 닿고 다시 183번까지 183칸을 가서 모두 236칸입니다. 끝까지 가지 않고 요청이 있는 14번에서 되돌아가는 LOOK은 39 + 169 = 208칸입니다. 이 예에서는 SSTF(236)가 LOOK(208)보다 더 많이 움직여서, SSTF가 전체 최적이 아님을 볼 수 있습니다.
회전 지연 계산 — 7200RPM이면 한 바퀴가 60 ÷ 7200 = 약 8.33ms이고 평균 회전 지연은 절반인 약 4.17ms입니다. 15,000RPM이면 한 바퀴 4ms에 평균 2ms입니다.
랜덤 4KB 읽기의 시간(가정) — 평균 탐색 9ms, 회전 지연 4.17ms, 150MB/s 전송(4KB에 약 0.03ms)으로 가정하면 약 13.2ms이고 이 중 탐색이 약 68%입니다. 1초에 약 76번밖에 처리하지 못하는 셈이라 순서를 바꿔 탐색을 줄이는 것이 중요합니다.

자주 하는 오해

SSTF가 가장 좋은 알고리즘이다 — 매번 가까운 것을 고르는 탐욕적 방식이라 전체 이동 거리가 최소임을 보장하지 않고, 위 예에서 LOOK보다 더 움직였습니다. 먼 요청이 계속 밀리는 기아도 생깁니다.
SSD에서도 헤드 이동을 줄이려고 정렬한다 — SSD에는 헤드가 없어서 탐색 시간이 없으므로 헤드 이동을 줄이는 정렬의 이득이 작습니다.
SCAN은 항상 SSTF보다 이동 거리가 짧다 — 위 예에서는 같은 236칸입니다. 요청의 분포에 따라 달라집니다.
운영체제의 스케줄러가 디스크 접근 순서를 완전히 정한다 — 요청은 페이지 캐시와 미리 읽기 같은 위쪽 계층을 거쳐 만들어집니다.

면접에서 이렇게 물어요

면접 질문

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)를 보여 줍니다.
mq-deadline — 커널 문서는 mq-deadline이 요청을 섹터 순서의 묶음으로 처리하되 읽기·쓰기 요청마다 기한(read_expire, write_expire)을 두어 시작 시간을 지키려 하고, 읽기를 우선하되 쓰기가 무한정 밀리지 않게 한다고 설명합니다.
BFQ — 커널 문서는 BFQ가 프로세스별 예산(budget)으로 I/O 대역폭을 나누는 스케줄러라고 설명합니다.

함께 보면 좋은 토픽

파일 시스템 — 디스크 요청이 만들어지는 위쪽 계층을 봅니다.
가상 메모리 — 페이지 폴트가 디스크 I/O로 이어지는 과정을 봅니다.
CPU 스케줄링 — FCFS, 기아 같은 개념을 CPU에서 비교합니다.
그리디 — SSTF가 그리디 선택인 이유를 봅니다.

참고 자료

교재 — Silberschatz, Galvin, Gagne, "Operating System Concepts", 10th Edition, Chapter 11 "Mass-Storage Structure"

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 디스크 스케줄링의 단점은?

요청이 도착한 순서대로만 처리합니다.
FCFS는 디스크 요청을 들어온 순서대로 처리해 헤드가 불필요하게 왔다갔다 하며 이동 거리가 길어질 수 있습니다.

Q2

SSTF(Shortest Seek Time First)의 방식은?

'Shortest Seek Time', 탐색 시간이 가장 짧은 것부터 처리합니다.
SSTF는 현재 헤드 위치에서 가장 가까운 트랙 요청을 먼저 처리해 평균 탐색 시간을 줄이지만, 특정 요청이 계속 밀리는 기아(starvation) 문제가 생길 수 있습니다.

Q3

SCAN(엘리베이터) 알고리즘과 C-SCAN의 차이는?

'C'는 Circular(순환)를 의미합니다.
SCAN은 한쪽 끝까지 이동한 뒤 방향을 바꿔 반대쪽으로 이동하며 처리하고, C-SCAN은 끝에 도달하면 반대편 시작점으로 점프해 항상 같은 방향으로만 처리해 대기시간을 더 균등하게 만듭니다.