사전 블록 계층
개념

블록 계층

gabury1고친 사람 github-actions[bot]

블록 계층은 디스크로 가는 읽기와 쓰기 요청을 받아 정리한 뒤 장치에 넘기는 커널 안의 계층입니다. 디스크는 블록이라는 정해진 크기의 칸 단위로만 읽고 씁니다. 그래서 이 계층을 지나는 요청은 파일 이름이 아니라 디스크의 몇 번째 블록인지로 적혀 있습니다. 덕분에 파일 시스템은 디스크 종류를 몰라도 됩니다. 디스크를 여러 개 묶거나 캐시를 앞에 두는 기능도 이 계층에 끼워 넣습니다.

쉽고 빠른 이해

블록 계층은 디스크로 가는 요청이 모두 거쳐 가는 커널 안의 정리 창구입니다. 「디스크의 10번 블록부터 두 개를 읽어라」 같은 요청이 이곳을 지납니다.

이 계층이 없으면 파일 시스템마다 디스크 종류를 하나하나 알아야 합니다. 디스크 둘을 묶거나 캐시를 앞에 두는 기능도 파일 시스템마다 따로 만들어야 합니다.

  1. 파일 시스템이 파일 속 위치를 블록 번호로 바꿔 요청을 내려보냅니다
  2. 블록 계층이 이웃한 요청을 합치고 장치에 보낼 순서를 정합니다
  3. 디바이스 드라이버가 요청을 장치가 알아듣는 명령으로 바꿔 디스크에 보냅니다

대가는 이 계층이 파일을 모른다는 것입니다. 어느 블록이 어느 파일의 것인지 모르니 파일 단위로 판단하는 일은 못 합니다. 요청을 아주 빨리 끝내는 장치 앞에서는 이 계층의 대기열 하나가 오히려 병목이 되기도 합니다.

상세

큰 택배 물류센터를 떠올리면 됩니다. 여러 가게가 맡긴 상자가 한곳에 모입니다. 센터는 같은 동네로 가는 상자를 한 트럭에 모으고 도는 순서를 정합니다. 가게는 어느 트럭이 어느 길로 가는지 신경 쓰지 않습니다.

블록 계층은 운영체제 커널에서 파일 시스템과 디바이스 드라이버 사이에 놓인 계층입니다. 디바이스 드라이버는 장치를 직접 움직이는 커널 쪽 소프트웨어입니다. 블록 계층은 파일 시스템이 내려보낸 디스크 요청을 받아 합치고 줄 세운 뒤 드라이버에 넘깁니다. 영어 이름은 block layer 입니다.

리눅스 커널을 이야기할 때 특히 자주 나오는 이름입니다. 다른 운영체제에도 같은 일을 하는 계층이 있습니다.

예를 들어 프로그램이 로그 파일 끝에 몇 줄을 덧붙인다고 해 보겠습니다. 파일 시스템은 그 내용이 디스크의 몇 번째 블록에 들어갈지 정합니다. 그다음 「이 블록들에 이 내용을 써라」라는 요청을 블록 계층에 넘깁니다. 블록 계층은 그 요청을 잠시 대기열에 두었다가 드라이버를 거쳐 디스크로 보냅니다.

블록과 블록 장치

블록은 디스크를 읽고 쓰는 최소 단위입니다. 디스크는 한 바이트만 따로 읽지 못합니다. 한 글자가 필요해도 그 글자가 든 블록 하나를 통째로 읽습니다. 그래서 디스크의 공간은 번호 붙은 블록이 한 줄로 늘어선 모양으로 다룹니다.

블록 장치는 이렇게 블록 번호로 읽고 쓰는 장치를 말합니다. HDD(Hard Disk Drive, 하드 디스크)와 SSD(Solid State Drive, 솔리드 스테이트 드라이브)가 대표적입니다. 커널은 이런 장치를 전부 「번호 붙은 블록의 긴 줄」 하나로 봅니다. 블록 계층은 이 블록 장치로 가는 요청만 다룹니다.

키보드나 터미널처럼 바이트를 흘려보내는 장치는 문자 장치라고 합니다. 문자 장치로 가는 데이터는 블록 계층을 지나지 않습니다.

커널 안에서 놓인 위치

프로그램이 파일을 읽고 쓰는 요청은 커널 안에서 여러 계층을 차례로 지나 디스크에 닿습니다. 아래 그림은 그 가운데 블록 계층이 어디에 있는지를 보입니다.

flowchart TD
    P["프로그램"] -->|"시스템 콜 · 파일 이름과 파일 속 위치"| F
    subgraph K["커널"]
        F["파일 시스템"] -->|"몇 번째 블록부터 몇 개"| B["블록 계층"]
        B -->|"합치고 줄 세운 요청"| D["디바이스 드라이버"]
    end
    D -->|"장치가 알아듣는 명령"| H["디스크"]

프로그램은 시스템 콜로 커널에 파일 읽기나 쓰기를 부탁합니다. 시스템 콜은 프로그램이 커널에 일을 맡기는 정해진 창구입니다. 이때 프로그램이 넘기는 것은 파일 이름과 파일 안에서의 위치입니다.

파일 시스템은 그 위치가 디스크의 어느 블록인지 압니다. 파일 시스템이 블록 번호로 바꾼 요청을 블록 계층에 넘깁니다. 이 순간부터 요청에는 파일 이름이 없습니다.

디바이스 드라이버는 블록 계층이 정리한 요청을 받아 장치가 알아듣는 명령으로 바꿔 디스크에 보냅니다. 디스크가 일을 마치면 결과가 같은 길을 거슬러 올라갑니다.

이렇게 나눠 두었기 때문에 새 종류의 장치가 나오면 그 장치의 드라이버만 새로 만들면 됩니다. 파일 시스템은 손대지 않습니다.

페이지 캐시는 최근에 읽은 디스크 내용을 메모리에 남겨 두는 커널의 캐시입니다. 페이지 캐시는 블록 계층보다 위, 파일 시스템 쪽에 있습니다. 페이지 캐시에 사본이 있으면 요청은 블록 계층까지 내려오지도 않습니다. 사본이 없거나 디스크에 내려써야 할 때만 블록 계층을 지납니다.

요청 한 건에 적힌 내용

블록 계층이 받는 요청 한 건은 단순합니다. 아래 표가 요청 한 건에 적힌 것을 모두 보입니다.

적힌 것 뜻
장치 어느 블록 장치로 가는 요청인가
시작 블록 번호 몇 번째 블록부터인가
길이 블록 몇 개인가
방향 읽기인가 쓰기인가
메모리 읽은 내용을 담을 곳, 또는 쓸 내용이 있는 곳

표에는 파일 이름이 없습니다. 블록 계층은 이 요청이 어느 파일을 위한 것인지 모릅니다. 번호와 길이와 방향만 봅니다.

요청을 합치고 줄 세우기

블록 계층이 하는 일 가운데 가장 오래된 것은 요청을 모아 다듬는 일입니다. 요청이 들어온 순서 그대로 장치에 보내면 손해를 보는 경우가 많기 때문입니다.

첫째 손질은 합치기입니다. 10번 블록을 읽는 요청과 11번 블록을 읽는 요청이 따로 들어오면 블록 계층은 둘을 「10번부터 두 개」로 합칩니다. 장치에 명령을 보낼 때마다 드는 준비 시간이 있어서 명령 수가 줄면 전체 처리 시간도 줄어듭니다.

둘째 손질은 줄 세우기입니다. HDD 는 회전하는 원판 위에서 헤드라는 팔이 움직여 데이터를 읽습니다. 헤드가 먼 곳으로 옮겨 가는 시간이 읽는 시간보다 훨씬 깁니다. 그래서 블록 번호가 가까운 요청끼리 이어서 보내면 헤드가 덜 움직입니다.

아래 그림은 네 요청이 두 요청으로 줄어드는 모습입니다.

flowchart TD
    subgraph IN["들어온 순서"]
        A1["블록 70"] --> A2["블록 10"] --> A3["블록 71"] --> A4["블록 11"]
    end
    subgraph OUT["장치로 보내는 순서"]
        B1["블록 10부터 두 개"] --> B2["블록 70부터 두 개"]
    end
    IN -->|"이웃한 번호끼리 합치고 번호순으로 줄 세운다"| OUT

이 순서를 정하는 부품이 입출력 스케줄러입니다. 입출력 스케줄러는 대기열에 쌓인 요청 가운데 무엇을 먼저 보낼지 고릅니다.

가까운 번호만 먼저 보내면 먼 번호의 요청은 계속 뒤로 밀릴 수 있습니다. 그래서 요청마다 기한을 두어 너무 오래 밀리지 않게 하는 방식도 있습니다.

SSD 와 여러 대기열

SSD 는 헤드가 없습니다. 어느 블록이든 거의 같은 시간에 읽습니다. 그래서 번호순 줄 세우기로 얻는 것이 HDD 보다 훨씬 적습니다. SSD 에서는 입출력 스케줄러의 줄 세우기를 아예 끄는 설정도 흔히 고릅니다.

대신 SSD 는 여러 요청을 동시에 받아 한꺼번에 처리할 수 있습니다. 그런데 커널 쪽 대기열이 하나뿐이면 여러 CPU(Central Processing Unit, 중앙 처리 장치) 코어가 그 대기열 하나를 두고 차례를 기다립니다. 장치는 한가한데 대기열 앞에서 줄을 서는 셈이라 이 기다림이 병목이 됩니다.

그래서 요즘 커널은 CPU 코어마다 대기열을 따로 둡니다. 각 코어는 자기 대기열에만 요청을 넣으니 서로 기다리지 않습니다. 리눅스에서 이 구조의 이름은 blk-mq(block multi-queue, 블록 다중 대기열)입니다.

가상 블록 장치를 쌓는 방식

블록 계층이 하는 또 하나의 큰일은 장치를 겹쳐 쌓게 해 주는 것입니다. 이 소절은 파티션과 가상 블록 장치로 그 구조를 봅니다.

가상 블록 장치는 겉으로는 평범한 블록 장치입니다. 파일 시스템은 그 위에 만들어지고 평소처럼 블록 요청을 보냅니다. 가상 블록 장치는 요청을 받으면 블록 번호를 바꿔 다른 블록 장치로 다시 내려보냅니다.

flowchart TD
    F["파일 시스템"] -->|"가상 장치의 블록 번호"| Q1["블록 계층"]
    Q1 --> V["가상 블록 장치 · 번호를 바꾼다"]
    V -->|"실제 장치의 블록 번호"| Q2["블록 계층"]
    Q2 --> D1["디스크 A"]
    Q2 --> D2["디스크 B"]

그림에서 요청은 블록 계층을 두 번 지납니다. 가상 장치로 가는 요청으로 한 번, 실제 디스크로 가는 요청으로 또 한 번입니다. 가상 장치 위에 다른 가상 장치를 또 얹을 수도 있습니다.

가장 흔한 예는 파티션입니다. 파티션은 디스크 하나를 여러 구역으로 나눈 것입니다. 파티션의 0번 블록은 디스크 전체로 치면 그 파티션이 시작하는 블록입니다. 블록 계층은 파티션으로 온 요청에 그 시작 번호를 더해 디스크로 보냅니다.

아래 표가 대표적인 가상 블록 장치들입니다. 어느 장치의 몇 번 블록으로 보내느냐에 따라 하는 일이 갈립니다.

가상 블록 장치 무엇인가 요청을 받으면 하는 일
파티션 디스크 한 대를 나눈 구역 시작 번호를 더해 그 디스크로 보낸다
거울 복제 RAID RAID(Redundant Array of Independent Disks)는 디스크 여러 대를 하나처럼 묶는 방식이다. 거울 복제는 두 디스크에 같은 내용을 둔다 쓰기는 두 디스크에 똑같이 보낸다. 읽기는 둘 중 한 대로 보낸다
논리 볼륨 여러 디스크를 이어 붙여 큰 장치 하나처럼 쓰게 한 것이다. LVM(Logical Volume Manager, 논리 볼륨 관리자) 같은 도구가 만든다 번호를 보고 해당 디스크의 해당 블록으로 보낸다
캐시 장치 HDD 앞에 SSD 를 사본 보관용으로 둔 묶음 SSD 에 사본이 있으면 SSD 로 보낸다. 없으면 HDD 로 보낸다

표의 네 장치는 모두 같은 방식으로 돕니다. 요청을 받으면 어느 장치의 몇 번 블록으로 보낼지 정해 다시 내려보냅니다.

리눅스의 디바이스 매퍼는 이런 가상 블록 장치를 만드는 공용 틀입니다. 요청을 받아 아래로 내려보내는 뼈대는 틀이 맡습니다. 새 장치를 만드는 사람은 어디로 보낼지 정하는 규칙만 끼우면 됩니다.

dm-cache와 bcache는 둘 다 표의 캐시 장치입니다. dm-cache 는 디바이스 매퍼 위에 만들었습니다. bcache 는 디바이스 매퍼 없이 따로 만들었습니다.

파일을 모른다는 것

블록 계층의 장점과 대가는 한 뿌리에서 나옵니다. 이 계층은 블록 번호만 보고 파일은 모릅니다.

장점은 위에 무엇이 오든 상관없다는 것입니다. 어떤 파일 시스템이든 가상 블록 장치 위에 올라갈 수 있습니다. 디스크 묶기나 캐시 기능을 한 번 만들면 모든 파일 시스템이 함께 씁니다.

대가는 파일 단위의 판단을 못 한다는 것입니다. 캐시 장치는 어느 블록이 중요한 데이터베이스 파일의 것인지 모릅니다. 자주 읽히는 블록인지만 보고 사본을 둘지 정합니다.

파일을 지워도 블록 계층은 그 블록이 비었는지 모릅니다. 파일 시스템이 「이 블록들은 이제 안 쓴다」고 따로 알려 줘야 합니다.

SSD 는 이미 쓴 칸에 바로 덮어쓰지 못하고 먼저 지워야 합니다. 어느 칸이 비었는지 미리 알면 그 지우기를 미리 해 두어 새 쓰기를 빨리 받습니다. 이 알림을 SSD 에 전하는 명령이 TRIM입니다.

그래서 기능을 어디에 둘지 고를 때 이 성질이 기준이 됩니다. 파일 시스템을 가리지 않고 디스크 앞에 기능을 끼우려면 블록 계층에 둡니다. 파일 단위로 판단해야 하는 기능이면 파일 시스템이나 애플리케이션 쪽에 둡니다. ZFS(Zettabyte File System)처럼 디스크 묶기와 캐시를 파일 시스템이 스스로 하는 설계도 있습니다.

백엔드 개발자가 이 계층을 만나는 때

애플리케이션 코드가 블록 계층을 직접 부를 일은 거의 없습니다. 대신 디스크 응답이 늦다는 신호를 읽을 때 이 계층을 만납니다.

디스크 지연은 두 부분으로 나뉩니다. 요청이 블록 계층의 대기열에서 기다린 시간과 장치가 요청을 처리한 시간입니다. 대기열에 쌓인 요청 수를 큐 깊이라고 부릅니다. 리눅스의 iostat 같은 도구가 보여 주는 대기 시간과 큐 깊이는 이 계층에서 잰 값입니다.

데이터베이스가 커밋할 때 부르는 fsync도 이 계층을 지납니다. fsync 는 파일의 변경 내용을 디스크에 확실히 내려쓰라는 시스템 콜입니다. 파일 시스템은 필요한 블록 쓰기를 블록 계층에 내려보냅니다.

많은 장치는 받은 쓰기를 먼저 자체 메모리에 담아 두고 나중에 내려씁니다. 이 메모리가 쓰기 캐시입니다. fsync 때는 이 캐시를 비우라는 요청도 함께 보냅니다. 캐시에만 있는 내용은 전원이 나가면 사라지기 때문입니다.

그래서 커밋이 오래 걸린다면 그 시간의 일부는 이 계층에서 나옵니다. 요청이 대기열에서 기다린 시간과 장치가 쓰기 캐시를 비우는 시간입니다.

관련 항목

블록 계층 위에서 요청을 내려보내는 커널 부품

시스템 콜 · 가상 파일 시스템 · 파일 시스템 · 페이지 캐시 · 더티 페이지 · 다이렉트 입출력 · fsync

블록 계층 아래에서 요청을 받는 장치와 드라이버

디바이스 드라이버 · 블록 장치 · 디스크 · HDD · SSD · NVMe · SATA · SCSI · 플래시 메모리

블록 계층이 요청을 다루는 데 쓰는 부품

블록 · 섹터 · 요청 큐 · 입출력 스케줄러 · blk-mq · 입출력 요청 병합 · TRIM · 쓰기 캐시

블록 계층에 끼어드는 가상 블록 장치

가상 블록 장치 · 디바이스 매퍼 · dm-cache · bcache · LVM · RAID · 소프트웨어 RAID · 파티션 · 루프 장치

블록 계층이 속하는 상위 분류와 주제

커널 · 운영체제 · Linux · 스토리지 스택 · 디스크 입출력 · 캐싱

블록 계층의 처리를 재는 지표와 도구

iostat · 큐 깊이 · IOPS · 지연 · 처리량 · 탐색 시간

블록 계층과 맞세워지는 장치 갈래와 설계

문자 장치 · 장치 파일 · ZFS · 사용자 공간 드라이버

다른 이름: block layer · 블록 레이어 · block I-O layer · 블록 입출력 계층 · 블록 IO 계층