사전 KVM
구현체

KVM

gabury1고친 사람 github-actions[bot]

KVM 은 리눅스가 깔린 컴퓨터 한 대로 여러 대의 컴퓨터를 돌리게 해 줍니다. 그 일을 맡는 프로그램을 따로 깔지 않고 리눅스 커널 자신이 맡습니다. 그렇게 만든 컴퓨터 안의 코드는 한 줄씩 대신 실행해 주지 않고 진짜 프로세서에서 돌립니다.

쉽고 빠른 이해
  • 무슨 일을 하나 — 리눅스 커널 자신이 가상의 컴퓨터를 여러 대 만들어 돌리게 합니다. 클라우드에서 빌린 리눅스 서버 한 대는 대개 이렇게 만들어진 가상의 컴퓨터입니다.
  • 왜 있나 — 소프트웨어만으로 가상의 컴퓨터를 만들면 그 안의 명령을 하나씩 들여다보고 대신 실행해 줘야 합니다. 요즘 프로세서가 그 일을 거들어 주는데, 그 기능은 커널만 켤 수 있습니다. 그래서 커널이 직접 맡습니다.
  • 어떻게 도나
    1. 커널에 KVM 을 올리면 /dev/kvm 이라는 통로가 열립니다
    2. 프로그램이 그 통로로 가상의 컴퓨터와 그 안의 프로세서를 만들어 달라고 겁니다
    3. 「돌려라」라고 걸면 그 안의 코드가 진짜 프로세서에서 돕니다
  • 대가 — 프로세서에 가상화를 돕는 기능이 없으면 쓰지 못합니다. 디스크와 네트워크 카드를 흉내 내는 일은 KVM 이 안 하므로 그 일을 맡을 프로그램을 짝지어야 합니다.

상세

KVM(Kernel-based Virtual Machine, 커널 기반 가상 머신)은 리눅스 커널 안에서 도는 부품입니다. 커널에 이 부품을 올리면 그 커널이 곧 하이퍼바이저가 됩니다. 하이퍼바이저는 컴퓨터 한 대를 여러 대처럼 나눠 쓰게 해 주는 소프트웨어입니다. 그렇게 만들어진 컴퓨터 한 대를 가상 머신이라고 부릅니다.

서버실에서 모니터와 키보드 한 벌을 여러 컴퓨터가 돌려 쓰게 해 주는 KVM 스위치라는 장치도 있습니다. 이름만 같은 다른 물건입니다. 이 항목이 다루는 것은 리눅스 쪽입니다.

아래는 여섯 소절입니다. 앞의 둘은 밑바탕입니다. 이 일이 왜 커널 안으로 들어왔는지, 그렇게 만든 가상 머신이 리눅스에게 무엇으로 보이는지를 봅니다.

가운데 둘은 돌아가는 모습입니다. /dev/kvm 으로 어떻게 조립하는지, 게스트가 멈춰 설 때 무엇을 치르는지를 봅니다. 마지막 둘은 경계입니다. KVM 이 어디서 손을 떼는지, 언제 고를 물건인지를 봅니다.

커널 밖에서 하던 일을 커널이 맡은 까닭

가상 머신 안에 올라간 운영체제를 게스트라고 부릅니다. 게스트는 자기가 기계 전체를 쥐고 있다고 믿습니다. 그래서 다른 프로그램이 함부로 내면 안 되는 특권 명령을 거리낌 없이 냅니다. 특권 명령은 메모리 지도를 갈아 끼우거나 장치를 직접 만지는 것처럼 기계 전체에 영향을 주는 명령입니다.

하이퍼바이저는 그 명령을 가로채 대신 처리해 줘야 합니다. 예전에는 이 가로채기를 소프트웨어가 해냈습니다. 게스트의 명령을 미리 훑어 위험한 것을 안전한 것으로 바꿔치기하는 식이라, 만들기도 까다롭고 대신 처리하는 값도 컸습니다.

프로세서가 이 일을 거들자 사정이 달라졌습니다. 요즘 프로세서에는 게스트를 위한 실행 모드가 따로 있습니다. 이 모드를 게스트 모드라고 부릅니다. 이런 모드를 갖춘 프로세서의 기능을 통틀어 하드웨어 가상화 확장이라고 부릅니다.

게스트를 게스트 모드에 넣어 두면 특권 명령을 내도 기계가 망가지지 않습니다. 제어는 하이퍼바이저가 필요한 때에만 넘어옵니다.

flowchart TD
    subgraph OLD["예전 · 소프트웨어가 가로챈다"]
        O1["게스트의 명령"] --> O2["미리 훑어본다"]
        O2 --> O3["위험한 명령을 안전한 것으로 바꿔치기"]
        O3 --> O4["실행"]
    end
    subgraph NEW["지금 · 프로세서가 거든다"]
        N1["게스트의 명령"] --> N2["게스트 모드에서 그대로 실행"]
        N2 --> N3["필요한 때에만 하이퍼바이저로 제어가 넘어온다"]
    end

줄어든 것은 제어가 하이퍼바이저로 넘어오는 횟수입니다. 명령마다 넘어오던 것이 필요한 때에만 넘어오게 바뀌었습니다.

그 모드를 켜고 끄는 명령은 커널만 낼 수 있습니다. 게다가 커널에는 프로세서 시간을 나누는 스케줄러와 메모리를 나누는 가상 메모리 관리자가 이미 있습니다. 하이퍼바이저를 따로 만들면 이 둘을 한 벌 더 갖게 됩니다. KVM 은 그 둘을 새로 만들지 않고 커널의 것을 씁니다.

프로세스 하나로 도는 가상 머신

KVM 의 선택이 이 대목에서 갈립니다. 가상 머신 하나를 리눅스 프로세스 하나로 둡니다. 가상 머신에 프로세서를 넷 주면 그 프로세스 안에 스레드 넷이 생깁니다.

게스트가 보는 프로세서 한 개를 vCPU 라고 부릅니다. CPU(Central Processing Unit, 중앙처리장치) 앞에 가상을 뜻하는 v 를 붙인 이름입니다. 우리말로는 가상 프로세서라고 적습니다.

이 선택 덕에 따라오는 것이 있습니다. 가상 머신은 top 에 프로세스로 보입니다. 스케줄러는 다른 프로그램과 나란히 시간을 나눠 줍니다.

cgroup 으로 메모리 상한을 걸 수 있고, 끝내고 싶으면 프로세스를 끝내면 됩니다. 리눅스에서 쓰던 도구가 가상 머신에도 먹힙니다.

커널 밖에서 도는 프로그램의 영역을 사용자 공간이라고 부릅니다. 가상 머신은 그 영역에 놓입니다. KVM 은 커널 안에 있습니다.

flowchart TD
    subgraph U["사용자 공간"]
        subgraph VM1["가상 머신 하나 · 프로세스 하나"]
            T0["vCPU 0 · 스레드"]
            T1["vCPU 1 · 스레드"]
            T2["vCPU 2 · 스레드"]
            T3["vCPU 3 · 스레드"]
        end
        P2["또 다른 가상 머신 · 프로세스 하나"]
        P3["평범한 프로그램"]
    end
    subgraph K["리눅스 커널"]
        KV["KVM"]
        SC["스케줄러 · 가상 메모리 관리자"]
    end
    HW["하드웨어 · 가상화를 돕는 프로세서"]
    T0 -->|"「돌려라」를 건다"| KV
    T1 --> KV
    T2 --> KV
    T3 --> KV
    P2 -->|"「돌려라」를 건다"| KV
    P3 -->|"평범한 시스템 콜"| SC
    KV -->|"스레드로 시간을 받는다"| SC
    SC -->|"시간을 나눠 준다"| HW
    T0 -->|"게스트 코드는 곧장 여기서 돈다"| HW

가상 머신도 평범한 프로그램도 나란히 서서 같은 커널, 같은 스케줄러를 지나갑니다. 둘의 차이는 KVM 을 거치느냐 하나뿐입니다.

다만 게스트 코드 자체는 KVM 을 거쳐 한 줄씩 옮겨지지 않습니다. 「돌려라」를 한 번 걸면 그 스레드는 진짜 프로세서에서 곧장 돕니다. 그림에서 하드웨어로 바로 내려가는 선이 그것입니다.

층이 이렇게 놓이면 흔한 가르기 하나가 애매해집니다. 하이퍼바이저를 하드웨어 바로 위에 올라가는 타입 1 과 운영체제 위에 프로그램처럼 설치되는 타입 2 로 가르는 관습이 있습니다.

KVM 은 어느 쪽으로도 읽힙니다. 리눅스 위에서 돈다고 보면 타입 2 입니다. 커널 자신이 하이퍼바이저가 된다고 보면 타입 1 입니다.

/dev/kvm 으로 조립하는 세 계단

KVM 을 쓰는 쪽은 사용자 공간의 프로그램입니다. 이 프로그램은 커널 안의 함수를 직접 부르지 않습니다. /dev/kvm 을 파일처럼 엽니다. 이렇게 커널의 기능을 파일 모양으로 내주는 통로를 장치 파일이라고 합니다.

열면 파일 디스크립터가 하나 돌아옵니다. 열어 둔 것을 가리키는 번호입니다.

프로그램이 커널에게 일을 시킬 때 부르는 함수를 시스템 콜이라고 합니다. 파일을 읽는 것도 프로세스를 새로 만드는 것도 시스템 콜입니다. 그중 장치 파일의 디스크립터에 「이런 일을 해 달라」고 거는 것이 ioctl 입니다.

조립은 세 계단입니다. 먼저 /dev/kvm 에 가상 머신을 하나 만들어 달라고 겁니다. 그러면 그 가상 머신을 가리키는 디스크립터가 돌아옵니다.

거기에 다시 가상 프로세서를 만들어 달라고 걸면 가상 프로세서를 가리키는 디스크립터가 돌아옵니다. 마지막으로 그 디스크립터에 「돌려라」를 겁니다. 디스크립터 셋은 나란한 형제가 아니라 아래로 매달리는 3층입니다.

게스트가 쓸 메모리는 이 프로그램이 자기 것에서 떼어 줍니다. 자기가 잡아 둔 메모리 덩이 하나를 「이것이 게스트가 보는 메모리다」라고 등록합니다. 그래서 게스트의 메모리는 호스트 쪽에서 보면 어느 프로세스가 잡은 메모리일 뿐입니다.

sequenceDiagram
    participant A as 사용자 공간의 프로그램
    participant K as 커널의 KVM
    participant C as 프로세서
    A->>K: /dev/kvm 에 「가상 머신을 만들어 달라」
    K-->>A: 가상 머신 디스크립터
    A->>K: 그 디스크립터에 「가상 프로세서를 만들어 달라」
    K-->>A: 가상 프로세서 디스크립터
    A->>K: 그 디스크립터에 「돌려라」
    K->>C: 게스트 모드로 들어간다
    Note over C: 게스트 코드가 진짜 프로세서에서 돈다
    C-->>K: 게스트가 장치를 만졌다 · 게스트 모드에서 빠져나온다
    K-->>A: 이건 네가 처리해라

조립이 끝나면 마지막 한 줄이 되풀이됩니다. 「돌려라」를 걸고, 빠져나오면 처리하고, 다시 「돌려라」를 겁니다.

게스트가 멈춰 서는 순간

게스트가 도는 동안 KVM 은 아무 일도 하지 않습니다. 게스트 코드는 프로세서에서 직접 돕니다. 덧셈 하나를 위해 커널이 끼어들지 않습니다. 가상 머신 안의 계산이 밖과 비슷한 속도를 내는 까닭입니다.

게스트가 혼자 끝낼 수 없는 일을 만나면 프로세서가 게스트 모드에서 나와 KVM 에게 제어를 넘깁니다. 이것을 VM exit(Virtual Machine exit, 가상 머신 빠져나오기)라고 부릅니다.

빠져나온 뒤 갈 길은 둘입니다. 커널 안에서 끝낼 수 있는 일이면 KVM 이 처리하고 게스트 모드로 곧장 되돌아갑니다. 디스크나 네트워크 카드처럼 KVM 이 모르는 장치를 만진 것이면 사용자 공간의 프로그램에게 올려보냅니다.

stateDiagram-v2
    state "게스트 모드 · 게스트 코드가 진짜 프로세서에서 돈다" as G
    state "커널의 KVM" as K
    state "사용자 공간의 프로그램" as U
    [*] --> G
    G --> K: 혼자 끝낼 수 없는 일을 만난다 · 빠져나오기
    K --> G: 커널 안에서 끝냈다
    K --> U: KVM 이 모르는 장치다
    U --> G: 흉내 내 주고 다시 「돌려라」
    note right of G: 계산만 하는 동안은 이 고리를 한 바퀴도 안 돈다

그래서 KVM 위에서 성능을 말할 때 세는 것은 빠져나온 횟수입니다. 계산만 하는 작업은 거의 빠져나오지 않습니다. 장치를 쉬지 않고 만지는 작업은 빠져나오기가 쌓여 손해가 눈에 띕니다.

KVM 이 맡지 않는 몫

KVM 은 가상 머신을 통째로 굴려 주는 제품이 아닙니다. 프로세서와 메모리를 나누는 데까지만 합니다. 나머지는 다른 프로그램에게 넘깁니다.

디스크와 네트워크 카드와 화면을 흉내 내는 일은 사용자 공간이 합니다. 이 짝으로 오래 쓰인 것이 QEMU(Quick Emulator, 빨리 도는 에뮬레이터)입니다. QEMU 는 기계를 통째로 흉내 낼 수 있는 프로그램입니다. KVM 과 짝을 이룰 때는 프로세서 흉내를 KVM 에게 넘기고 장치 쪽만 맡습니다.

앞 절에서 /dev/kvm 에 명령을 걸던 사용자 공간의 프로그램이 대개 이 QEMU 입니다.

가상 머신을 정의하고 켜고 끄는 관리 계층도 KVM 밖입니다. 그 일은 libvirt 같은 도구의 몫입니다. 사람이 만지는 것은 대개 그쪽입니다.

사람이 만지는 층과 /dev/kvm 에 명령을 거는 층은 다릅니다. libvirt 는 QEMU 같은 프로그램을 대신 띄우고 내립니다. /dev/kvm 에 명령을 거는 것은 그 프로그램 쪽입니다.

리눅스 커널의 부품이라 리눅스에서만 돕니다. 프로세서에 가상화를 돕는 기능이 없거나 꺼져 있으면 /dev/kvm 이 열리지 않습니다. 빌려 쓰는 장비에서 가상 머신이 안 뜰 때 먼저 보는 곳이 이것입니다.

고르는 때와 피하는 때

게스트마다 다른 운영체제나 다른 커널이 필요하면 KVM 같은 하이퍼바이저를 씁니다. 리눅스 서버 안에서 윈도우를 돌리는 경우입니다. 서로 모르는 고객의 작업을 같은 기계에 함께 둘 때도 커널을 나눠 쓰는 컨테이너보다 두꺼운 벽이 필요합니다.

반대로 모두 같은 리눅스 커널로 충분하고 띄우고 지우기를 자주 한다면 컨테이너가 맞습니다. 가상 머신은 켤 때마다 운영체제를 한 벌씩 부팅합니다. 그 운영체제가 먹는 메모리도 따로 물어야 합니다.

리눅스가 아닌 기계에서는 선택지에 없습니다. 맥이나 윈도우에서 가상 머신을 돌릴 때는 그 운영체제가 가진 다른 하이퍼바이저를 씁니다.

flowchart TD
    A{"호스트가 리눅스인가"} -->|아니오| X["그 운영체제가 가진 다른 하이퍼바이저"]
    A -->|예| B{"게스트마다 다른 커널이나 다른 운영체제가 필요한가"}
    B -->|아니오| C["컨테이너"]
    B -->|예| D{"프로세서에 가상화를 돕는 기능이 있는가"}
    D -->|아니오| E["KVM 은 못 쓴다"]
    D -->|예| F["KVM"]

관련 항목

KVM 이 올라앉는 리눅스 커널 부품

커널 · 커널 모듈 · 시스템 콜 · ioctl · 프로세스 · 스레드 · 스케줄러 · cgroup · 파일 디스크립터 · 장치 파일 · 사용자 공간

KVM 이 기대는 프로세서의 가상화 기능

하드웨어 가상화 확장 · Intel VT-x · AMD-V · 특권 명령 · 트랩과 에뮬레이션 · 2단계 주소 변환 · 중첩 가상화 · VM exit

KVM 이 만들어 돌리는 대상

가상 머신 · 게스트 · 호스트 · vCPU · 가상 디바이스 · 가상 메모리

KVM 과 짝을 이뤄 장치를 흉내 내는 프로그램

QEMU · virtio · 에뮬레이터 · 반가상화 · SR-IOV

KVM 위에 얹혀 가상 머신을 관리하는 도구

libvirt · virsh · OpenStack · Proxmox VE · oVirt

KVM 과 같은 일을 하는 다른 하이퍼바이저

하이퍼바이저 · Xen · VMware ESXi · Hyper-V · VirtualBox · Firecracker · 타입 1 하이퍼바이저 · 타입 2 하이퍼바이저

KVM 대신 커널 하나로 떼어 놓는 방식

컨테이너 · 네임스페이스 · Docker · 격리 · 샌드박스

KVM 을 밑에 깔고 서버를 빌려주는 기반

가상화 · 클라우드 · IaaS · 라이브 마이그레이션 · 메모리 오버커밋 · 시끄러운 이웃

다른 이름: Kernel-based Virtual Machine · 커널 기반 가상 머신