Foundationপ্রথম নীতি থেকে
LEVEL 12লেসন ৪/২২কঠিন৫০ মিনিট

KVM ও QEMU — kernel-ই hypervisor, userspace-ই বাকি সব হার্ডওয়্যার

KVM and QEMU: The Linux Hypervisor Stack

KVM একটা kernel module যা শুধু CPU আর memory virtualization দেয় ioctl দিয়ে; QEMU একটা userspace প্রোগ্রাম যা সবসময় device emulation করে আর CPU execution-এর জন্য হয় নিজস্ব software translator নাহয় KVM ব্যবহার করে — দুইটা মিলে গঠন করে আজকের Linux hypervisor stack, আর `ioctl(KVM_RUN)`-ই তাদের মাঝের একমাত্র সেতু।

এই লেসন শেষে আপনি পারবেন

  • KVM ঠিক কী প্রদান করে (CPU ও memory virtualization primitive, ioctl-ভিত্তিক `/dev/kvm` interface) আর কী প্রদান করে না (device emulation) তা স্পষ্টভাবে আলাদা করে বলতে পারবেন
  • QEMU-শুধু (software TCG, cross-architecture emulation সম্ভব) বনাম QEMU+KVM (device emulation userspace-এ, CPU execution hardware-accelerated, same-architecture-only) — এই দুই মোডের পার্থক্য উদাহরণসহ ব্যাখ্যা করতে পারবেন
  • guest OS → QEMU → KVM kernel module → hardware — এই স্তরবিন্যাস trace করে libvirt ঠিক কোথায় বসে ও কী দায়িত্ব পালন করে তা বলতে পারবেন
  • `ioctl(vcpu_fd, KVM_RUN, ...)` কলটা ঠিক কখন VMX non-root mode-এ ঢোকে আর কোন ধরনের VM-exit-এ QEMU-তে ফিরে আসে তা ব্যাখ্যা করতে পারবেন, আর একটা device-I/O-ভারী workload-এ এর ফলে মোট কতগুলো userspace round-trip লাগে তা সংখ্যায় গণনা করতে পারবেন
  • পূর্ণ device emulation-এর VM-exit খরচ কেন paravirtualization-এর প্রয়োজন তৈরি করে, তা এই লেসনের হাতে-গোনা সংখ্যা দিয়ে যুক্তি দিতে পারবেন

আগে যা বোঝা থাকা দরকার

আগে এটা বুঝি

hardware-virtualization লেসনে আমরা দেখেছি VT-x কী দেয় — একটা নতুন CPU mode-জোড়া, VMX root আর VMX non-root, একটা VMCS (Virtual Machine Control Structure) যেটা সেই দুই জগতের মধ্যেকার চুক্তি লিখে রাখে, আর VM-entry/VM-exit নামের দুইটা hardware-level ট্রানজিশন যা সেই চুক্তি প্রয়োগ করে। কিন্তু পুরো আলোচনাটা একটা প্রশ্ন ইচ্ছাকৃতভাবে এড়িয়ে গিয়েছিল — ঠিক কে বসে সেই VMX root mode-এ? কার কোড VMCS তৈরি করে, vmlaunch চালায়, guest-এর জন্য memory মাপে?

এই module-এর curriculum-এর driving question মনে করুন — “docker run লিখলে kernel-এ ঠিক কোন কোন syscall হয়?” আজকের লেসন সেই একই প্রশ্নের একটা পুরনো, আরও fundamental সংস্করণ ধরবে ভার্চুয়াল মেশিনের জগতে: qemu-system-x86_64 (বা libvirt-এর মাধ্যমে সেটাই) চালালে kernel-এ ঠিক কোন ioctl হয়?

উত্তরটা দুইটা আলাদা প্রজেক্টের গল্প, যারা একসাথে মিলে আজকের বাস্তব উত্তর গঠন করে —

  • KVM (Kernel-based Virtual Machine) — Linux kernel-এর একটা module, যেটা hardware-virtualization লেসনের VMX root mode-এর কাজটা সরাসরি চলমান Linux kernel নিজেই করে ফেলে। কোনো আলাদা “hypervisor OS” নেই — আপনার রোজকার Linux kernel-ই, একটা module load করার পর, VMX root mode-এর বাসিন্দা হয়ে যায়।
  • QEMU — একটা সম্পূর্ণ, স্বাধীন, KVM-এর চেয়ে ৪ বছর পুরনো প্রজেক্ট, যেটা একটা সম্পূর্ণ কম্পিউটার software-এ emulate করতে পারে — CPU, disk, network card, graphics card, USB, BIOS — সব। QEMU একা, KVM ছাড়া, সম্পূর্ণ কার্যকর একটা emulator।

আজকের লেসনের কেন্দ্রীয় দাবি: এই দুইটা প্রজেক্ট, স্বাধীনভাবে ডিজাইন করা, একসাথে জোড়া লাগলে (QEMU KVM ব্যবহার করে CPU execution-এর জন্য) একটা চমৎকার division of labor তৈরি হয় — KVM CPU আর memory চালায়, QEMU বাকি সবকিছু চালায় — আর ঠিক মাঝখানে একটামাত্র syscall বসে থাকে যেটা এই পুরো সম্পর্কটার ভিত্তি: ioctl(vcpu_fd, KVM_RUN, ...)।

মূল ধারণা

KVM — Linux kernel-কে VMX root mode-এর বাসিন্দা বানানো

KVM আক্ষরিক অর্থেই একটা kernel module (kvm.ko প্লাস kvm-intel.ko বা kvm-amd.ko, vendor-নির্দিষ্ট)। এটা load হলে একটা character device তৈরি হয় — /dev/kvm — আর userspace-এর সাথে এর একমাত্র যোগাযোগের মাধ্যম হলো এই device-এর উপর ioctl() সিস্টেমকল। কোনো network protocol নেই, কোনো আলাদা daemon নেই (KVM নিজে কোনো process না) — শুধু একটা file descriptor আর তার উপর কয়েক ডজন সংজ্ঞায়িত ioctl command।

hardware-virtualization লেসনের ভাষায় বললে — KVM হলো সেই কোড যেটা VMCS তৈরি করে, তাতে guest-এর জন্য নিয়ম লেখে, আর vmlaunch/vmresume চালায়। KVM module load হওয়ার সাথে সাথেই চলমান Linux kernel VMX root mode গ্রহণ করে নেয় — kernel নিজেই এখন সেই “নিচের স্তর” যেটা আগের লেসনে বিমূর্তভাবে বর্ণনা করা হয়েছিল।

KVM কী প্রদান করে — আর কী করে না

এইটাই এই লেসনের সবচেয়ে গুরুত্বপূর্ণ বিভাজন-রেখা, তাই স্পষ্ট করে দুইটা তালিকায় লেখা যাক:

KVM যা প্রদান করে (শুধু এই দুইটা):

  1. CPU virtualization — VMCS তৈরি ও পরিচালনা, VM-entry/VM-exit ট্রানজিশন চালানো, guest register state সংরক্ষণ (hardware-virtualization লেসনের পুরো যন্ত্রপাতি, বাস্তবে কোড হয়ে)।
  2. Memory virtualization — EPT (Extended Page Table) সেটআপ, guest-physical-থেকে host-physical ঠিকানা অনুবাদের দ্বিতীয় স্তর (hardware-virtualization লেসনের EPT আলোচনার সরাসরি বাস্তবায়ন)।

KVM যা প্রদান করে না (এবং এটাই সবচেয়ে বেশি ভুল বোঝা হয়):

KVM কোনো ডিভাইস emulate করে না। কোনো ভার্চুয়াল disk controller নেই, কোনো ভার্চুয়াল network card নেই, কোনো ভার্চুয়াল graphics card নেই, কোনো BIOS/UEFI নেই। /dev/kvm একা খুলে একটা guest CPU রান করানো সম্ভব — কিন্তু সেই guest-এর কোনো disk-এ access নেই, কোনো keyboard/screen নেই, বুট করার মতো কোনো firmware নেই। এটা একটা primitive — একা অসম্পূর্ণ, কিন্তু একটা userspace প্রোগ্রামের (VMM — Virtual Machine Monitor) সাথে জোড়া লাগলেই সম্পূর্ণ VM হয়ে ওঠে।

KVM=শুধু CPU + memory virtualization primitive (ioctl দিয়ে)\text{KVM} = \text{শুধু CPU + memory virtualization primitive (ioctl দিয়ে)} Bootable, useful VM=KVM+একটা userspace VMM (device emulation, BIOS, VM lifecycle)\text{Bootable, useful VM} = \text{KVM} + \text{একটা userspace VMM (device emulation, BIOS, VM lifecycle)}

সবচেয়ে সাধারণ সেই userspace VMM হলো QEMU — এবং এখান থেকেই দ্বিতীয় প্রজেক্টের গল্প শুরু।

QEMU — একা, সম্পূর্ণ, KVM ছাড়াই কার্যকর একটা emulator

QEMU-কে (Quick EMUlator) বোঝার সবচেয়ে গুরুত্বপূর্ণ প্রথম ধাপ — QEMU KVM-এর আগে থেকে ছিল (Fabrice Bellard প্রথম প্রকাশ করেন ২০০৩ সালে, KVM Linux kernel-এ merge হয় ২০০৭-এ) আর QEMU KVM ছাড়াও সম্পূর্ণ কার্যকর। QEMU-র মূল ইঞ্জিন TCG (Tiny Code Generator) — একটা dynamic binary translator যা guest-এর instruction-কে (যে ISA-ই হোক) ছোট ছোট block-এ ভেঙে host-এর native instruction-এ রূপান্তর করে, তারপর সেই translated code চালায়। এটা সম্পূর্ণ software — কোনো hardware virtualization support লাগে না, তাই যেকোনো host-এ যেকোনো guest ISA emulate করা যায়।

QEMU সবসময়, প্রতিটা মোডে, একটা কাজ করে — সম্পূর্ণ device emulation: ভার্চুয়াল disk controller (IDE, AHCI, বা virtio-blk), ভার্চুয়াল network card (e1000, rtl8139, বা virtio-net), ভার্চুয়াল graphics (VGA, virtio-gpu), USB controller, আর BIOS/UEFI firmware (SeaBIOS/OVMF) লোড করা। এই সব কোড সবসময় QEMU-র নিজস্ব, userspace, সাধারণ C কোড — ide.c, e1000.c, vga.c-র মতো ফাইলে লেখা, স্বাভাবিক ফাংশন কল দিয়ে চলে।

দুই মোড — QEMU-শুধু বনাম QEMU+KVM

CPU execution-এর জন্য QEMU-র দুইটা সম্পূর্ণ ভিন্ন উপায় আছে, আর -accel (বা পুরনো -enable-kvm) ফ্ল্যাগ ঠিক করে কোনটা ব্যবহার হবে:

QEMU-শুধু (TCG)QEMU + KVM
CPU executionsoftware dynamic translation (TCG)সরাসরি hardware — ioctl(KVM_RUN) দিয়ে VMX non-root mode
গতিnative-এর তুলনায় বহুগুণ ধীর (প্রতিটা instruction translate)native-এর কাছাকাছি (guest code সরাসরি CPU-তে চলে)
Cross-architectureসম্ভব — guest ISA ≠ host ISA (যেমন ARM-on-x86)অসম্ভব — guest ISA অবশ্যই host ISA-র সমান হতে হবে
Device emulationQEMU-তেই, userspace কোডএকই QEMU কোড, অপরিবর্তিত
প্রয়োজনকিছুই না (যেকোনো host-এ চলে)/dev/kvm অ্যাক্সেস, host CPU-তে VT-x/AMD-V সক্রিয়

শেষ পর্যবেক্ষণটা সবচেয়ে গুরুত্বপূর্ণ — device model কোড দুই মোডেই হুবহু একই। ide.c-র ভেতরের কোড জানেই না CPU execution TCG দিয়ে হচ্ছে না KVM দিয়ে — সে শুধু একটা I/O access আসলে সেটা emulate করে। KVM যোগ করা মানে শুধু CPU execution engine বদলানো, device emulation architecture-এ কোনো পরিবর্তন নেই। এইটাই পরের অংশের ভিত্তি — কারণ device emulation যেভাবেই CPU চলুক, একই জায়গায়, একই খরচে ঘটে।

ভেতরে কী ঘটছে

ভেতরে কী ঘটছে — ঠিক কোন ioctl সিকোয়েন্স

QEMU যখন -enable-kvm-সহ একটা guest বুট করায়, নিচের ধাপগুলো (সরলীকৃত, কিন্তু ঠিক ক্রমে) ঘটে — প্রতিটাই /dev/kvm-এর উপর একটা ioctl() কল:

QEMU থেকে একটা running guest পর্যন্ত — ioctl সিকোয়েন্স
  1. open("/dev/kvm")একটা সাধারণ file descriptor — এখান থেকেই সব ioctl শুরু
  2. ioctl(KVM_CREATE_VM)একটা নতুন VM তৈরি — একটা নতুন vm_fd ফেরত দেয়, guest-এর জন্য একটা address space-এর প্রতিনিধিত্ব
  3. ioctl(vm_fd, KVM_SET_USER_MEMORY_REGION)QEMU নিজে mmap() করে একটা host memory অঞ্চল নেয় (guest RAM-এর জন্য), তারপর KVM-কে বলে দেয় — এই host ঠিকানাগুলোই guest-এর physical RAM; EPT সেটআপের কাঁচামাল
  4. ioctl(vm_fd, KVM_CREATE_VCPU)একটা vCPU তৈরি — নতুন vcpu_fd ফেরত দেয়; QEMU-র ভেতরে এর জন্য আলাদা একটা POSIX thread বরাদ্দ হয়
  5. ioctl(vcpu_fd, KVM_GET_VCPU_MMAP_SIZE) + mmap(vcpu_fd)kernel আর QEMU-র মধ্যে শেয়ার করা একটা struct kvm_run mmap হয় — exit reason, register data এখানে চলাচল করে, প্রতিবার copy করতে হয় না
  6. ioctl(vcpu_fd, KVM_SET_SREGS / KVM_SET_REGS)guest-এর প্রাথমিক register state সেট করা — segment register, instruction pointer, ইত্যাদি
  7. লুপ: ioctl(vcpu_fd, KVM_RUN)এই একটা কলই vmlaunch/vmresume চালায়, VMX non-root mode-এ প্রবেশ করে — guest code সরাসরি CPU-তে চলে, যতক্ষণ না KVM নিজে সামলাতে না-পারা একটা VM-exit ঘটে

লক্ষ্য করুন — শেষ ধাপ একটা লুপ, একবার না। প্রতিবার ioctl(KVM_RUN) কল ব্লক করে, guest CPU-তে সরাসরি চলে (kernel-এর ভেতরে vmlaunch/vmresume — এই সময় আর কোনো syscall হয় না, guest সরাসরি hardware-এ চলছে), যতক্ষণ পর্যন্ত না একটা VM-exit ঘটে যা KVM নিজে সামলাতে পারে না। তখন ioctl(KVM_RUN) কলটা ফেরত আসে — শেয়ার করা kvm_run struct-এ একটা exit_reason লেখা থাকে (KVM_EXIT_IO — port-mapped I/O, KVM_EXIT_MMIO — memory-mapped I/O, KVM_EXIT_HLT — guest hlt চালিয়েছে, ইত্যাদি)।

QEMU তখন exit_reason পরীক্ষা করে, নিজের device model কোড দিয়ে (যেমন ide.c-এর একটা ফাংশন) সেই I/O access emulate করে — port-এ লেখা মান ধরে রাখে, বা read-এর জন্য একটা মান বসিয়ে দেয় — তারপর আবার ioctl(vcpu_fd, KVM_RUN) কল করে। এই দ্বিতীয় কলটাই VM-entry — guest আবার VMX non-root mode-এ ঢোকে, ঠিক সেই instruction-এর পরে চালিয়ে যায় যেখানে থেমেছিল।

একটা VM-exit→QEMU device model handle করে (userspace)→পরের ioctl(KVM_RUN) (VM-entry)\text{একটা VM-exit} \to \text{QEMU device model handle করে (userspace)} \to \text{পরের } \texttt{ioctl(KVM\_RUN)} \text{ (VM-entry)}

এই exit → handle → re-entry চক্রটাই পুরো লেসনের যান্ত্রিক হৃদয়। একটা guest যত বেশি device access করে, তত বেশিবার এই চক্র ঘোরে — আর প্রতিটা চক্র একটা userspace ↔ kernel context switch-এর দাম বহন করে।

উদাহরণ

সংখ্যায় — একটা device access-ভারী বাস্তব দৃশ্য

এই খরচটা concrete করতে, একটা পুরনো কিন্তু এখনও ব্যাপকভাবে ব্যবহৃত উদাহরণ নেওয়া যাক — একটা unmodified guest OS, একটা emulated legacy ATA/IDE disk controller-এর PIO (Programmed I/O) মোডে একটা সেক্টর (৫১২ বাইট) পড়ছে। PIO মোড ইচ্ছাকৃতভাবে বেছে নেওয়া হলো কারণ এটা “পুরোপুরি বাস্তব হার্ডওয়্যার নকল করা”-র সবচেয়ে চরম উদাহরণ — কোনো batching নেই, কোনো shared ring buffer নেই, শুধু individual port-এ individual instruction।

ATA PIO প্রোটোকল অনুযায়ী একটা সেক্টর read-এ guest driver-কে যা করতে হয়:

ধাপInstructionসংখ্যা
Drive/head সিলেক্টOUT (drive/head register)১
Sector count সেটOUT১
LBA address (২৮-বিট, তিন byte)OUT × ৩৩
Command জারি (READ SECTORS)OUT১
Status poll (BSY=0, DRQ=1 না হওয়া পর্যন্ত)IN (status register)~৪
ডেটা পড়া — ১৬-বিট শব্দ প্রতি একবারIN (data port) × ২৫৬২৫৬
মোট২৬৬

৫১২ বাইট = ২৫৬টা ১৬-বিট শব্দ — আর ATA PIO মোডের ডিজাইনই এমন যে প্রতিটা শব্দের জন্য আলাদা IN instruction লাগে। VT-x-এ port-mapped I/O instruction (IN/OUT) ডিফল্টভাবে unconditionally trap হয় (I/O bitmap দিয়ে নির্বাচিতভাবে বাদ দেওয়া না থাকলে) — আর emulated legacy ATA controller-এর জন্য QEMU সেই bitmap trick ব্যবহার করে না, কারণ প্রতিটা access-ই তার device model-এর দরকার। ফলাফল: একটা মাত্র ৫১২-বাইট সেক্টর read ২৬৬টা আলাদা VM-exit ঘটায় — মানে ২৬৬টা আলাদা ioctl(KVM_RUN) রাউন্ড-ট্রিপ, শুধু একটা সেক্টর পড়তে।

ioctl(KVM_RUN) কলের সংখ্যা=Ndevice access+1\text{ioctl(KVM\_RUN) কলের সংখ্যা} = N_{\text{device access}} + 1

যেখানে +1 প্রথম VM-entry-র জন্য (কোনো আগের exit ছাড়াই)। এই সূত্রটাই পরের অংশে সরাসরি একটা চালানো Python মডেলে রূপ নেবে।

guest: OUT drive/head ──► VM-exit(KVM_EXIT_IO) ──► QEMU ide.c handle ──► ioctl(KVM_RUN) ──► VM-entry
guest: OUT sector count ─► VM-exit ───────────────► QEMU ide.c handle ──► ioctl(KVM_RUN) ──► VM-entry
guest: OUT LBA (×৩) ─────► VM-exit ×৩ ────────────► QEMU ide.c handle ──► ioctl(KVM_RUN) ×৩
guest: OUT command ──────► VM-exit ───────────────► QEMU ide.c handle ──► ioctl(KVM_RUN) ──► VM-entry
guest: IN status (×৪) ───► VM-exit ×৪ ────────────► QEMU ide.c handle ──► ioctl(KVM_RUN) ×৪
guest: IN data word ─────► VM-exit ───────────────► QEMU ide.c handle ──► ioctl(KVM_RUN) ──► VM-entry
   ...২৫৬ বার পুনরাবৃত্তি...
                                                          মোট: ২৬৬ রাউন্ড-ট্রিপ / সেক্টর
একটা সেক্টর read-এর ভেতরের exit/entry চক্র — প্রতিটা IN/OUT আলাদা একটা রাউন্ড-ট্রিপ।

ছবিতে

চার-স্তরের স্ট্যাক — আর libvirt কোথায় বসে

┌─────────────────────────────────────────────────────────────┐
│  Management layer (ঐচ্ছিক, কিন্তু বাস্তবে প্রায় সবসময়)         │
│  virt-manager / oVirt / OpenStack Nova / virsh                │
│                          │                                    │
│                          ▼                                    │
│               libvirtd (management API/daemon)                │
│      VM lifecycle-এর ঘোষণামূলক (declarative) XML সংজ্ঞা,        │
│      নিজে virtualize করে না — নিচের QEMU-কে সঠিক ফ্ল্যাগ দিয়ে   │
│      চালায় ও তদারকি করে                                        │
└──────────────────────────┬──────────────────────────────────┘
                            ▼
┌─────────────────────────────────────────────────────────────┐
│  GUEST OS  (unmodified — Linux, Windows, যা-ই হোক)             │
└──────────────────────────┬──────────────────────────────────┘
                            │  IN/OUT, MMIO access
                            ▼
┌─────────────────────────────────────────────────────────────┐
│  QEMU (userspace process)                                     │
│  • device emulation — disk, NIC, VGA, USB, BIOS/UEFI            │
│  • VM lifecycle — মেমরি বরাদ্দ, migration, snapshot            │
│  • CPU execution driver — TCG (software) নাহয় KVM ioctl        │
└──────────────────────────┬──────────────────────────────────┘
                            │  ioctl(KVM_RUN) / VM-exit ফেরত
                            ▼
┌─────────────────────────────────────────────────────────────┐
│  KVM  (Linux kernel module — /dev/kvm)                         │
│  • VMCS তৈরি ও পরিচালনা                                        │
│  • VM-entry/VM-exit ট্রানজিশন চালানো (vmlaunch/vmresume)        │
│  • EPT সেটআপ — memory virtualization                           │
│  ✕ device emulation নেই — ইচ্ছাকৃতভাবে                          │
└──────────────────────────┬──────────────────────────────────┘
                            │  VT-x / AMD-V hardware primitive
                            ▼
┌─────────────────────────────────────────────────────────────┐
│  HARDWARE — CPU (VMX root/non-root), EPT/NPT, physical RAM      │
└─────────────────────────────────────────────────────────────┘
Guest থেকে hardware পর্যন্ত — প্রতিটা স্তরের দায়িত্ব স্পষ্টভাবে আলাদা। Libvirt পুরো স্ট্যাকের উপরে বসে, নিজে virtualize করে না, শুধু পরিচালনা করে।

লক্ষ্য করুন — libvirt পুরো ছবিতে একটা বিশেষ ভূমিকায় বসে। এটা নিজে কোনো virtualization করে না — এটা একটা management API/daemon যা VM-কে একটা declarative XML হিসেবে বর্ণনা করে (কতগুলো vCPU, কত RAM, কোন disk image, কোন network), তারপর সেই বর্ণনা অনুযায়ী সঠিক qemu-system-x86_64 কমান্ড-লাইন ফ্ল্যাগ তৈরি করে QEMU-কে চালায় ও তদারকি করে। virt-manager (GUI), oVirt, আর OpenStack-এর Nova compute service — এরা প্রায় সবাই সরাসরি QEMU-র সাথে কথা না বলে libvirt-এর সাথে কথা বলে — libvirt-ই backend হিসেবে QEMU+KVM (বা Xen, বা LXC container) চালায়। এই লেসনের গভীরতায় libvirt না গিয়ে শুধু এর অবস্থানটা মনে রাখা যথেষ্ট — এটা orchestration স্তর, virtualization স্তর না।

নিজে চালিয়ে দেখুন

EXPERIMENT

raw KVM ioctl API দিয়ে একটা ন্যূনতম guest — আপনার নিজের Linux মেশিনে

Linux (x86-64), /dev/kvm অ্যাক্সেস-সহ (বেশিরভাগ modern desktop/server/cloud VM-এ থাকে — /dev/kvm-এ read/write permission লাগবে)· ২০ মিনিট

Guest machine code — মাত্র ১২ বাইট, x86-16 real mode-এ চলে:

0xba, 0xf8, 0x03,   /* mov $0x3f8, %dx      — serial port 0x3f8-এ ঠিকানা বসানো */
0x00, 0xd8,         /* add %bl, %al          — al = al + bl */
0x04, '0',          /* add $'0', %al         — al-কে ASCII digit বানানো */
0xee,                /* out %al, (%dx)       — port 0x3f8-এ byte লেখা → VM-exit! */
0xb0, '\n',          /* mov $'\n', %al */
0xee,                /* out %al, (%dx)       — আবার VM-exit */
0xf4                 /* hlt                   — শেষ, VM-exit (KVM_EXIT_HLT) */

Host program-এর register setup: rax=2, rbx=2, rip=0x1000 (যেখানে উপরের কোড লোড করা), আর rflags=0x2 (x86-এ reserved bit)। তাহলে add %bl, %al হিসাব করে al = 2 + 2 = 4, তারপর add $'0', %al সেটাকে ASCII '4'-তে রূপান্তর করে।

Run-লুপের কেন্দ্রীয় অংশ (ioctl(vcpu_fd, KVM_RUN)-এর পরে exit_reason অনুযায়ী branch):

for (;;) {
    ioctl(vcpu_fd, KVM_RUN, NULL);
    switch (run->exit_reason) {
    case KVM_EXIT_HLT:
        puts("KVM_EXIT_HLT");
        return 0;
    case KVM_EXIT_IO:
        if (run->io.direction == KVM_EXIT_IO_OUT &&
            run->io.size == 1 && run->io.port == 0x3f8 &&
            run->io.count == 1)
            putchar(*(((char *)run) + run->io.data_offset));
        else
            errx(1, "unhandled KVM_EXIT_IO");
        break;
    default:
        errx(1, "unhandled exit reason: %d", run->exit_reason);
    }
}

প্রত্যাশিত output (Triplett-এর প্রবন্ধ অনুযায়ী, real Linux + /dev/kvm-এ):

4
KVM_EXIT_HLT

তিনটা জিনিস লক্ষ করুন এই ছোট্ট প্রোগ্রামে — (১) KVM_CREATE_VM → KVM_SET_USER_MEMORY_REGION → KVM_CREATE_VCPU → KVM_SET_SREGS/KVM_SET_REGS → লুপে KVM_RUN — ঠিক hood অংশের সেই সিকোয়েন্স, কোনো সংক্ষিপ্তকরণ ছাড়াই; (২) out %al, (%dx) instruction-টা দুইবার চলেছে (একবার '4'-এর জন্য, একবার '\n'-এর জন্য) — প্রতিবার আলাদা একটা KVM_EXIT_IO, আলাদা একটা userspace round-trip, ঠিক আগের অংশের ATA PIO উদাহরণের একই প্যাটার্নের ক্ষুদ্রতম সম্ভাব্য সংস্করণ; (৩) hlt-এ থামা মানে guest নিজেই স্বেচ্ছায় CPU ফেরত দিচ্ছে — QEMU-র মতো একটা বাস্তব VMM এখানে guest-কে shutdown/reboot হিসেবে বিবেচনা করত।

এটা কী প্রমাণ করে

এই লেসনের পুরো hood অংশ — ioctl সিকোয়েন্স, KVM_EXIT_IO, VM-entry/VM-exit চক্র — কোনো abstraction না, প্রতিটা লাইন সরাসরি চালানো যায় এমন C কোড।

নিজে বানান

BUILD IT

ioctl(KVM_RUN) Round-Trip গণনাকারী — device-emulation বনাম paravirtual batching

Python · ●●●○○
  1. উপরের ATA PIO টেবিল থেকে প্রতি সেক্টরে VM-exit সংখ্যা গণনা করার একটা ফাংশন লিখুন — select head, sector count, LBA byte, command, status poll, আর সবচেয়ে বড় খরচ ২৫৬টা word-read
  2. একটা দ্বিতীয় ফাংশন লিখুন যা একটা কাল্পনিক ব্যাচ-করা (batched) ডিভাইস মডেল করে — N সেক্টর একবারে, একটামাত্র "kick"-এ পাঠানো (এটাই পরের লেসনের virtio-blk-এর আগাম ইঙ্গিত)
  3. দুইটা মডেলেই ioctl(KVM_RUN) কলের সংখ্যা (= VM-exit + ১) হিসাব করুন, ১টা, ১০০টা, ১০,০০০টা, ১০ লাখ সেক্টরের জন্য
  4. একটা প্রকাশিত, রক্ষণশীল order-of-magnitude সংখ্যা (~১২০০ ন্যানোসেকেন্ড/round-trip, VT-x exit+entry-র প্রায়) ব্যবহার করে মোট overhead সময় আনুমানিক করুন
  5. ১০,০০০ সেক্টরে দুই মডেলের অনুপাত বের করুন — এই একটা সংখ্যাই পরের লেসনের পুরো যুক্তির প্রথম প্রমাণ

এই স্ক্রিপ্টটা আসলেই চালানো হয়েছে (এই sandbox macOS, /dev/kvm নেই বলে সরাসরি KVM কল করা যায়নি — কিন্তু এই গণনাটা কোনো kernel access লাগে না, তাই পুরোপুরি verify-করা, real output):

# kvm_qemu_iotrip.py
# Model: কতগুলো userspace round-trip (VM-exit -> QEMU device model handle
# -> VM-entry, অর্থাৎ আরেকটা ioctl(KVM_RUN) কল) একটা device-I/O-ভারী
# workload-এর লাগে — legacy ATA/IDE PIO (পূর্ণ device emulation) বনাম
# virtio-blk (paravirtual, batched)।

import math

def ata_pio_exits_per_sector(lba_bytes=3, status_polls=4):
    select_drive_head = 1
    sector_count = 1
    lba_regs = lba_bytes
    command = 1
    polling = status_polls
    data_words = 512 // 2  # ২৫৬ — প্রতি ১৬-বিট শব্দে একটা IN
    return select_drive_head + sector_count + lba_regs + command + polling + data_words

def kvm_round_trips(n_sectors, exits_per_sector):
    n_exits = n_sectors * exits_per_sector
    ioctl_kvm_run_calls = n_exits + 1  # +১ প্রথম VM-entry-র জন্য
    return n_exits, ioctl_kvm_run_calls

def virtio_round_trips(n_sectors, sectors_per_batch):
    n_batches = math.ceil(n_sectors / sectors_per_batch)
    n_exits = n_batches  # এক "kick" (eventfd/MMIO doorbell) প্রতি ব্যাচে
    ioctl_kvm_run_calls = n_exits + 1
    return n_exits, ioctl_kvm_run_calls

EXIT_ENTRY_OVERHEAD_NS = 1200  # প্রকাশিত VT-x round-trip খরচের order-of-magnitude

if __name__ == "__main__":
    exits_per_sector = ata_pio_exits_per_sector()
    print(f"emulated ATA PIO: VM-exits per 512B sector = {exits_per_sector}")
    print()

    print("emulated ATA/IDE (PIO mode, full device emulation):")
    for n_sectors in (1, 100, 10_000, 1_000_000):
        n_exits, calls = kvm_round_trips(n_sectors, exits_per_sector)
        total_ms = n_exits * EXIT_ENTRY_OVERHEAD_NS / 1e6
        print(f"  sectors={n_sectors:>9,}  vm-exits={n_exits:>13,}  "
              f"ioctl(KVM_RUN) calls={calls:>13,}  overhead~{total_ms:>10.2f} ms")

    print()
    print("virtio-blk (paravirtual, batch of 128 sectors/request):")
    for n_sectors in (1, 100, 10_000, 1_000_000):
        n_exits, calls = virtio_round_trips(n_sectors, 128)
        total_ms = n_exits * EXIT_ENTRY_OVERHEAD_NS / 1e6
        print(f"  sectors={n_sectors:>9,}  vm-exits={n_exits:>13,}  "
              f"ioctl(KVM_RUN) calls={calls:>13,}  overhead~{total_ms:>10.4f} ms")

    print()
    n = 10_000
    ata_exits, _ = kvm_round_trips(n, exits_per_sector)
    vio_exits, _ = virtio_round_trips(n, 128)
    print(f"at {n:,} sectors: {ata_exits/vio_exits:.1f}x fewer VM-exits with virtio batching "
          f"({ata_exits:,} vs {vio_exits:,})")

প্রকৃত, চালানো output (Python 3.9, এই sandbox-এ হুবহু এই কমান্ডে পাওয়া গেছে — কোনো hand-trace না):

emulated ATA PIO: VM-exits per 512B sector = 266

emulated ATA/IDE (PIO mode, full device emulation):
  sectors=        1  vm-exits=          266  ioctl(KVM_RUN) calls=          267  overhead~      0.32 ms
  sectors=      100  vm-exits=       26,600  ioctl(KVM_RUN) calls=       26,601  overhead~     31.92 ms
  sectors=   10,000  vm-exits=    2,660,000  ioctl(KVM_RUN) calls=    2,660,001  overhead~   3192.00 ms
  sectors=1,000,000  vm-exits=  266,000,000  ioctl(KVM_RUN) calls=  266,000,001  overhead~ 319200.00 ms

virtio-blk (paravirtual, batch of 128 sectors/request):
  sectors=        1  vm-exits=            1  ioctl(KVM_RUN) calls=            2  overhead~    0.0012 ms
  sectors=      100  vm-exits=            1  ioctl(KVM_RUN) calls=            2  overhead~    0.0012 ms
  sectors=   10,000  vm-exits=           79  ioctl(KVM_RUN) calls=           80  overhead~    0.0948 ms
  sectors=1,000,000  vm-exits=        7,813  ioctl(KVM_RUN) calls=        7,814  overhead~    9.3756 ms

at 10,000 sectors: 33670.9x fewer VM-exits with virtio batching (2,660,000 vs 79)

সংখ্যাগুলো নিজেরাই কথা বলছে — মাত্র ১০,০০০ সেক্টর (৫ মেগাবাইটেরও কম!) পড়তে emulated ATA PIO মোডে ২৬ লাখেরও বেশি VM-exit লাগে, শুধু exit/entry overhead-এই আনুমানিক ৩.২ সেকেন্ড — অথচ একই ডেটা batched virtio মডেলে মাত্র ৭৯টা exit, ০.১ মিলিসেকেন্ডেরও কম overhead। ৩৩,৬৭০ গুণ পার্থক্য, শুধু “কীভাবে ডেটা রিকোয়েস্ট ব্যাচ করা হয়েছে” তার কারণে — CPU নিজে ঠিক একই গতিতে চলছে দুই ক্ষেত্রেই (KVM-accelerated), পার্থক্যটা সম্পূর্ণ device-interaction প্যাটার্নে। এই একটা সংখ্যাই পরের লেসনের (paravirtualization) পুরো justification।

(EXIT_ENTRY_OVERHEAD_NS = 1200 একটা রক্ষণশীল, প্রকাশিত-সাহিত্য-ভিত্তিক order-of-magnitude ধরে নেওয়া মান — বাস্তব সংখ্যা CPU generation, mitigation (Spectre/Meltdown patch) সক্রিয় কি না, ইত্যাদির উপর নির্ভর করে কয়েকশ থেকে কয়েক হাজার ন্যানোসেকেন্ডের মধ্যে ওঠানামা করে; এখানে গুরুত্বপূর্ণ অনুপাতটা — একটা ধ্রুবক গুণিতক পাল্টালেও ৩৩,৬৭০× পার্থক্যের মাত্রা (order of magnitude) পাল্টাবে না।)

নিজে বাড়ান:

  1. status_polls বাড়িয়ে/কমিয়ে দেখুন — বাস্তব ATA controller-এ polling loop-এর দৈর্ঘ্য ডিভাইসের গতির উপর নির্ভর করে; এটা মোট overhead-এ কতটা প্রভাব ফেলে?
  2. sectors_per_batch (virtio-র batch size) ১ থেকে ৫১২ পর্যন্ত বদলে একটা গ্রাফ কল্পনা করুন — batch size বাড়ানোর diminishing return কোথায় শুরু হয়?
  3. একটা তৃতীয় মডেল যোগ করুন — modern AHCI (native command queuing, PIO-র চেয়ে ভালো কিন্তু virtio-র চেয়ে খারাপ) কত exit/sector হতে পারে, ধরে নেওয়া অনুমানসহ যুক্তি দিন।

বাস্তব সিস্টেমে

বাস্তব জগতে KVM ও QEMU

  • Qumranet ও KVM-এর জন্ম (২০০৬-০৭)। KVM মূলত Qumranet নামের একটা ইসরায়েলি স্টার্টআপ ডেভেলপ করে, ২০০৬-এ প্রথম প্রকাশ, আর মাত্র কয়েক মাসের মধ্যেই Linux 2.6.20 (ফেব্রুয়ারি ২০০৭)-এ mainline kernel-এ merge হয় — একটা অস্বাভাবিক দ্রুত merge, যা দেখায় KVM-এর ডিজাইন (একটা ছোট kernel module, বিশাল আলাদা hypervisor কোডবেস না) কতটা elegant মনে হয়েছিল kernel maintainer-দের কাছে। Red Hat পরে ২০০৮-এ Qumranet কিনে নেয়; আজ KVM Red Hat-এর enterprise virtualization strategy-র কেন্দ্রে।

  • Firecracker (AWS, ২০১৮)। AWS Lambda আর Fargate-এর পেছনের microVM প্রযুক্তি। Firecracker QEMU ব্যবহার করে না — এটা সরাসরি /dev/kvm ioctl API-এর সাথে কথা বলে (Rust-এ লেখা), আর device emulation-এর জন্য শুধু কয়েকটা ন্যূনতম virtio ডিভাইস (block, net) সমর্থন করে — কোনো legacy ATA/VGA/USB emulation নেই। ফলাফল: একটা VM boot করতে ~১২৫ মিলিসেকেন্ডের কম, প্রতি VM-এর মেমরি overhead কয়েক মেগাবাইট মাত্র — QEMU-র পূর্ণাঙ্গ device model-এর তুলনায় বহুগুণ হালকা। এইটা এই লেসনের সবচেয়ে স্পষ্ট প্রমাণ যে QEMU আর KVM আলাদা, বদলযোগ্য — QEMU একমাত্র বিকল্প না, শুধু সবচেয়ে পরিচিত।

  • crosvm ও cloud-hypervisor। Google-এর crosvm (ChromeOS/Android-এর Crostini Linux container-এর জন্য) আর Intel/community-চালিত cloud-hypervisor প্রজেক্ট — দুইটাই Firecracker-এর মতো, Rust-এ লেখা, QEMU-র বদলে সরাসরি KVM API ব্যবহার করা VMM। এই পুরো ecosystem-টাই প্রমাণ করে “device emulation কে করবে” প্রশ্নের একাধিক বৈধ উত্তর আছে — QEMU সবচেয়ে পুরনো ও পূর্ণাঙ্গ, কিন্তু একমাত্র না।

  • libvirt + virt-manager — desktop/server virtualization-এর ডি-ফ্যাক্টো স্ট্যান্ডার্ড। Fedora/RHEL-ভিত্তিক Linux distribution-এ virt-manager GUI ব্যবহার করে VM তৈরি করলে, পর্দার আড়ালে libvirtd ঠিক এই লেসনের বর্ণিত qemu-system-x86_64 -enable-kvm ... কমান্ডই তৈরি করে চালায়।

  • OpenStack Nova। বড় প্রাইভেট ক্লাউড ডেপ্লয়মেন্টে (OpenStack-ভিত্তিক), Nova compute service প্রতিটা physical host-এ libvirt-এর সাথে কথা বলে VM schedule করে — এই লেসনের চার-স্তরের ছবিটাই, শুধু শত শত সার্ভার জুড়ে বিস্তৃত।

  • Android Emulator। Linux host-এ Android Studio-র emulator KVM acceleration ব্যবহার করে (আর macOS/Windows-এ যথাক্রমে HVF/WHPX — একই ধারণার Apple/Microsoft সংস্করণ) — QEMU-ভিত্তিক, ঠিক এই লেসনের QEMU+KVM মডেল, ডেভেলপারের ল্যাপটপে প্রতিদিন চলছে, প্রায়ই কেউ খেয়ালও করে না।

যে ভুলগুলো সবাই করে

“KVM নিজেই একটা সম্পূর্ণ hypervisor — 'KVM চালানো' মানে একটা কার্যকর VM পাওয়া।”

ভুল, আর এই লেসনের সবচেয়ে গুরুত্বপূর্ণ বিভাজন-রেখা এখানেই। KVM একা শুধু /dev/kvm device আর কিছু ioctl প্রদান করে — CPU আর memory virtualization primitive, ব্যস। কোনো disk নেই, কোনো BIOS নেই, কোনো network card নেই। একা /dev/kvm খুলে, KVM_CREATE_VM/KVM_CREATE_VCPU কল করে একটা vCPU চালানো সম্ভব (experiment অংশের ১২-বাইট প্রোগ্রামের মতো) — কিন্তু সেটা একটা “VM” না, এটা একটা ন্যূনতম sandbox যেখানে raw machine code চলে। একটা প্রকৃত, বুট-করা-যায় এমন VM লাগে একটা userspace VMM (QEMU, Firecracker, crosvm — যেকোনো একটা) যেটা device emulation আর firmware প্রদান করে।

“QEMU+KVM মানে QEMU সবসময় ধীর software emulation করছে, শুধু KVM 'সাহায্য' করে।”

উল্টো — যখন KVM ব্যবহৃত হয়, CPU instruction execution সম্পূর্ণ native hardware গতিতে চলে (VMX non-root mode-এ সরাসরি, hardware-virtualization লেসনের core promise)। QEMU-র TCG software translator তখন সম্পূর্ণ নিষ্ক্রিয় থাকে CPU execution-এর জন্য। শুধুমাত্র device I/O (disk, network, graphics access) QEMU-র userspace কোডে emulate হয় — আর এইটা KVM ব্যবহার করুক বা না করুক, উভয় মোডেই সমান খরচ। তাই “QEMU ধীর” একটা অতি-সরলীকরণ — QEMU+KVM-এর CPU-bound workload প্রায় native-এর কাছাকাছি গতিতে চলে; শুধু device-I/O-ভারী workload-এ (এই লেসনের ATA PIO উদাহরণের মতো) পার্থক্য প্রকট হয়।

“QEMU-র cross-architecture emulation (যেমন ARM binary x86-এ চালানো) ক্ষমতাটা আসলে KVM-এর কৃতিত্ব — KVM 'বিভিন্ন CPU নকল করতে' সাহায্য করে।”

সম্পূর্ণ উল্টো সম্পর্ক। Cross-architecture emulation একচেটিয়াভাবে QEMU-র TCG (software dynamic translation)-এর ক্ষমতা — KVM-এর কোনো ভূমিকা নেই এখানে, বরং KVM ব্যবহার করলে cross-architecture emulation অসম্ভব হয়ে যায়। কারণ সহজ: KVM guest instruction-কে সরাসরি host CPU-তে চালায় (VMX non-root mode-এ) — তাই guest ISA অবশ্যই host ISA-র সাথে হুবহু মিলতে হবে (x86-64 guest শুধু x86-64 host-এ KVM-accelerated হতে পারে)। ভিন্ন ISA emulate করতে হলে (ARM-on-x86) instruction-বাই-instruction software translation ছাড়া উপায় নেই — অর্থাৎ TCG, KVM ছাড়া।

“libvirt হলো hypervisor — 'libvirt VM চালায়' মানে libvirt নিজেই virtualization করছে।”

Libvirt একটা management layer — VM-কে একটা declarative XML হিসেবে বর্ণনা করার, তারপর সঠিক backend (QEMU+KVM, Xen, LXC container, VMware — একাধিক driver সমর্থন করে) চালানোর একটা প্রমিত API/daemon। এটা নিজে কোনো VMCS তৈরি করে না, কোনো ioctl(KVM_RUN) কল করে না — এই সবকিছুই এখনও QEMU+KVM স্তরে ঘটে, libvirt শুধু সেই কমান্ড-লাইন তৈরি করে ও তদারকি করে। virt-manager-এর GUI থেকে একটা VM “চালানো” মানে পর্দার আড়ালে হুবহু এই লেসনের বর্ণিত পুরো ioctl সিকোয়েন্স ঘটা — libvirt শুধু সেই প্রক্রিয়া শুরু করার সুবিধাজনক একটা স্তর।

বুঝেছেন কি না দেখুন

1

KVM ঠিক কী দুইটা জিনিস প্রদান করে, আর কোন তিনটা জিনিস প্রদান করে না?

স্মরণ

প্রদান করে: (১) CPU virtualization (VMCS তৈরি ও পরিচালনা, VM-entry/VM-exit ট্রানজিশন), (২) memory virtualization (EPT সেটআপ)। প্রদান করে না: disk controller emulation, network card emulation, graphics/BIOS emulation — এই সব device emulation সম্পূর্ণভাবে userspace VMM-এর (সাধারণত QEMU) দায়িত্ব।

2

একজন সহকর্মী প্রস্তাব দিচ্ছেন — “disk I/O দ্রুত করতে KVM-এর ভেতরে সরাসরি একটা virtual disk controller emulate করার কোড যোগ করা উচিত, তাহলে আর QEMU পর্যন্ত VM-exit যেতে হবে না।” এই প্রস্তাবের ডিজাইন-ঝুঁকিটা কী?

যুক্তি

KVM একটা kernel module — এতে device emulation কোড যোগ করা মানে সেই কোড kernel space-এ চলবে, যেখানে একটা বাগ পুরো সিস্টেম crash (kernel panic) করতে পারে, নিরাপত্তা-ঝুঁকিও অনেক বেশি (kernel-এ privilege escalation)। QEMU-তে একই বাগ শুধু সেই একটা QEMU process crash করবে — বাকি host system অক্ষত থাকবে। এটাই মূল কারণ কেন KVM ইচ্ছাকৃতভাবে ন্যূনতম রাখা হয়েছে — device emulation-এর জটিল, বাগ-প্রবণ কোড userspace-এ (QEMU) রাখাই নিরাপদ ডিজাইন, পারফরম্যান্সের সামান্য খরচ সত্ত্বেও। (বাস্তবে কিছু performance-critical, ছোট device সত্যিই in-kernel accelerate করা হয় — যেমন ঐচ্ছিক in-kernel APIC/PIT — কিন্তু এগুলো ছোট, ভালোভাবে-অডিটেড ব্যতিক্রম, নিয়ম না।)

3

Build অংশের মডেল অনুযায়ী, একটা প্রোগ্রাম emulated ATA PIO ডিস্ক থেকে ৫০০টা সেক্টর পড়ে। মোট কতগুলো VM-exit ঘটবে, আর কতগুলো ioctl(KVM_RUN) কল লাগবে? (ধরুন প্রতি সেক্টরে ২৬৬টা exit।)

প্রয়োগ

VM-exit = ৫০০ × ২৬৬ = ১৩৩,০০০টা। ioctl(KVM_RUN) কল = ১৩৩,০০০ + ১ (প্রথম VM-entry-র জন্য) = ১৩৩,০০১টা। শুধু exit/entry overhead-এর হিসাবে (~১২০০ ন্যানোসেকেন্ড/round-trip ধরে): ১৩৩,০০০ × ১২০০ ns ≈ ১৫৯.৬ মিলিসেকেন্ড — শুধু context-switch overhead-এই, প্রকৃত ডেটা transfer বাদ দিয়ে।

4

কেন QEMU+TCG দিয়ে একটা ARM binary x86 হোস্টে চালানো সম্ভব, কিন্তু QEMU+KVM দিয়ে অসম্ভব? এক বাক্যে যান্ত্রিক কারণ দিন।

যুক্তি

KVM guest instruction সরাসরি host CPU-তে চালায় (VMX non-root mode-এ hardware execution) — host CPU হার্ডওয়্যারগতভাবে শুধু তার নিজের ISA (x86-64) বোঝে, তাই guest ISA-ও x86-64 হতে হবে। TCG এর বদলে software-এ প্রতিটা instruction পড়ে-বুঝে-অনুবাদ করে — তাই guest ISA যা-ই হোক (ARM, RISC-V, MIPS), host-এর জন্য এটা শুধু ডেটা, hardware execution-এর প্রশ্নই আসে না।

5

Experiment অংশের ১২-বাইট guest প্রোগ্রামে ঠিক কয়টা VM-exit ঘটবে, আর কেন?

প্রয়োগ

দুইটা KVM_EXIT_IO (দুইটা out %al, (%dx) instruction — একটা '4'-এর জন্য, একটা '\n'-এর জন্য) প্লাস একটা KVM_EXIT_HLT (hlt instruction-এ) — মোট তিনটা VM-exit। প্রতিটা out instruction port-mapped I/O access, VT-x-এ unconditionally trap হয় (কোনো I/O bitmap override না থাকলে); hlt-ও সবসময় trap হয় কারণ CPU-কে idle করার সিদ্ধান্ত VMM-এর হাতে থাকা দরকার (একটা VM অনির্দিষ্টকাল CPU আটকে রাখতে পারবে না)।

6

আপনি একটা নতুন serverless platform বানাচ্ছেন যেখানে হাজার হাজার ছোট, স্বল্পস্থায়ী (কয়েক মিলিসেকেন্ড থেকে কয়েক সেকেন্ড) VM প্রতি সেকেন্ডে বুট করতে হবে। পূর্ণাঙ্গ QEMU (legacy ATA/VGA/USB device model-সহ) নাকি একটা Firecracker-ধাঁচের ন্যূনতম VMM (শুধু virtio-block/net, সরাসরি KVM ioctl) — কোনটা বাছবেন, আর এই লেসনের কোন দুইটা সংখ্যা/উদাহরণ আপনার সিদ্ধান্তকে সরাসরি সমর্থন করে?

ডিজাইন

Firecracker-ধাঁচের ন্যূনতম VMM বাছাই স্পষ্টভাবে সঠিক এখানে। কারণ: (১) boot দ্রুত হওয়া জরুরি — QEMU-র পূর্ণাঙ্গ device model (BIOS probe, legacy device enumeration) initialization overhead যোগ করে যা serverless-এর মিলিসেকেন্ড-স্কেল বাজেটে সহ্য করা কঠিন, যেখানে Firecracker-এর ~১২৫ ms boot time (realworld অংশ) স্পষ্টভাবে এই দাবি সমর্থন করে। (২) memory density — হাজার হাজার concurrent VM-এ প্রতিটার per-VM overhead গুরুত্বপূর্ণ; QEMU-র ভারী device model কোড প্রতিটা instance-এ বাড়তি মেমরি নেয়, Firecracker-এর ন্যূনতম footprint সেটা এড়ায়। (৩) build অংশের সংখ্যা সরাসরি প্রাসঙ্গিক — যদি এই VM-গুলো কোনো I/O করে, legacy device emulation-এর ২৬৬-exit/sector খরচ বনাম virtio-এর batched, কম-exit পথ — ৩৩,৬৭০× পার্থক্যটা সরাসরি latency budget-এ প্রভাব ফেলবে। Legacy ATA/VGA/USB সমর্থনের দরকারই নেই যখন guest নিজেই নিয়ন্ত্রিত (custom কার্নেল/rootfs), তাই সেই জটিলতা ফেলে দেওয়াই যুক্তিসঙ্গত।

এরপর কী

এরপর কী

আজ আমরা দেখলাম KVM+QEMU একটা unmodified guest OS-কে সম্পূর্ণ বিভ্রান্ত করে — guest ভাবে সে আসল ATA disk, আসল network card-এর সাথে কথা বলছে, কোনো cooperation ছাড়াই, শুধু faithful device emulation-এর জোরে। কিন্তু build অংশের সংখ্যা নির্মমভাবে স্পষ্ট — এই বিশ্বস্ততার দাম বিশাল। প্রতিটা disk read, প্রতিটা network packet, VM-exit সীমানা পার হয়ে userspace-এ যায়, প্রতিবারই একটা context-switch-এর খরচ বহন করে।

পরের লেসনে (paravirtualization) প্রশ্নটা উল্টে যাবে — যদি guest OS নিজে রাজি হয় সহযোগিতা করতে, প্রতারিত হওয়ার বদলে? যদি একটা ভার্চুয়াল disk controller “আসল ATA-র মতো” নকল করার চেষ্টা না করে, বরং একটা সম্পূর্ণ নতুন, virtualization-এর জন্যই ডিজাইন করা প্রোটোকল ব্যবহার করে — একটা shared ring buffer, batch করা রিকোয়েস্ট, একটা মাত্র “kick” প্রতি ব্যাচে? আজকের build অংশের virtio মডেলটাই সেই প্রশ্নের প্রথম ঝলক — পরের লেসনে সেটাই কেন্দ্রীয় বিষয় হয়ে পূর্ণ ব্যাখ্যা পাবে, virtio-র ring buffer গঠন, front-end/back-end driver split, আর ঠিক কীভাবে “guest agrees to cooperate” একটা বাস্তব, কোড-স্তরের চুক্তিতে রূপ নেয় তা-সহ।

আরও পড়ুন

  • kvm: the Linux Virtual Machine Monitor — Avi Kivity, Yaniv Kamay, Dor Laor, Uri Lublin, Anthony Liguori — Ottawa Linux Symposium (OLS) 2007, Vol. 1 · KVM-এর মূল paper — Qumranet টিম ব্যাখ্যা করছে কেন Linux kernel-কেই hypervisor বানানো একটা আলাদা VMM লেখার চেয়ে ভালো সিদ্ধান্ত।
  • QEMU, a Fast and Portable Dynamic Translator — Fabrice Bellard — USENIX Annual Technical Conference (FREENIX Track), 2005 · QEMU-র মূল paper — TCG (Tiny Code Generator)-এর ভিত্তি, cross-architecture dynamic binary translation-এর নকশা।
  • Using the KVM API — Josh Triplett — LWN.net, সেপ্টেম্বর ২৯, ২০১৫ · raw ioctl API দিয়ে একটা ন্যূনতম guest চালানোর হাতে-কলমে গাইড — এই লেসনের experiment অংশের ভিত্তি।
  • QEMU Documentation — System Emulation, Invocation, Accelerators — QEMU Project · device model, accelerator (KVM/TCG/HVF) নির্বাচন, আর `-enable-kvm`-এর মতো অপশনের প্রামাণ্য ডকুমেন্টেশন।