IOThread を追加しても、すべてのデバイス操作が並列実行される保証はありません。プレコピーを何回実行しても、ダーティページが減少する保証はありません。QEMU と KVM の協調動作には、実行コンテキスト、デバイス状態、移行整合性 を同時に管理する必要があります。完全なバックエンドは他のプロセスで実行されている場合もあります。
参照目標 読書位置
マシンと実行の構成 スレッド、作成、実行ループ
通知とデータの役割分担 eventfd
収束、切り替え、障害特定 移行関連セクション
スレッドはデバイスに固定で 1 対 1 対応するわけではない
vCPU スレッドは KVM_RUN に入り、メインイベントループは管理と対応するデバイスイベントを処理します。IOThread が設定されたデバイスは独立したイベントループを使用できます。すべての virtio デバイスが自動的にスレッドを取得するわけではなく、スレッドプール、バックエンド、デバイス実装も I/O に参加します。IOThreads
マルチスレッドは並列実行を可能にしますが、状態の同期と実行コンテキストの制約をもたらします。QEMU のグローバルロック、デバイスロック、AioContext などのメカニズムは特定の状態を担当しており、「IOThread を追加する」だけですべてのデバイス操作が並列化され、ロックフリーになるとは限りません。
作成から実行可能なマシンへ
段階 ユーザー空間の役割 KVM の提供機能
インターフェース検出 API と必要な機能の照会 システム fd、拡張照会
マシンの作成 マシンレイアウトとバックアップの選択 VM オブジェクトとメモリ登録インターフェース
CPU 初期化 アーキテクチャに必要なレジスタ、機能、起動状態の設定 vCPU オブジェクト、アーキテクチャ ioctl
デバイス構築 デバイスモデルとバックエンドの設定 オプションのカーネルデバイス、irqfd/ioeventfd など
実行と終了処理 ユーザー空間で完了する必要がある操作の処理 ゲスト実行とカーネルで完了可能な終了
CREATE_VM、RAM の登録、CREATE_VCPU の呼び出しだけでは、オペレーティングシステムの起動はできません。CPU モード、起動コードまたはファームウェア、割り込みコントローラ、デバイスなどの設定が不足しているためです。チュートリアルの簡略化されたフローは、「完全な起動器」と区別する必要があります。
mmap によってバックアップを作成した後、物理ページは必要に応じて割り当てられる可能性があります。MAP_NORESERVE は、その後のメモリが常に十分であることを保証するものではありません。memslot はゲストアドレスのマッピングを表現しますが、VMM は依然としてバックアップのライフサイクルを維持する必要があり、マッピングの変更や解除は KVM と調整する必要があります。KVM API
実行ループの 2 層の戻り値
ハードウェアの終了は、KVM によって処理された後、ゲストに直接再入される場合があります。KVM_RUN が返って初めて VMM の終了処理に入ります。エラーが返された場合は、まず errno を処理し、成功して返された後に run->exit_reason を読み取る必要があります。すべての ioctl の戻り値を正常なデバイスリクエストとして解釈してはいけません。
完了が必要な MMIO/PIO の読み書きの場合、VMM がデータを埋め戻してから再度 KVM に入ってから、対応するゲスト操作が完了します。移行前にレジスタのみを保存し、未完了の終了を無視すると、状態の境界が失われる可能性があります。QEMU にはアーキテクチャとデバイスの協調ロジックが既に存在しており、エラー処理を欠いた数行の擬似 C 関数をその実際のソースコードとして扱うのは適切ではありません。
eventfd は通知の迂回を減らすだけで、すべてのデータを担うわけではない
ioeventfd は、一致するゲスト I/O 書き込みを eventfd 通知に変換できます。irqfd は、対応する eventfd シグナルをゲスト割り込み注入に関連付けることができます。要求データは依然として virtqueue またはデバイスプロトコルで記述されたバッファ内にあり、eventfd は完全な要求内容そのものではありません。
カーネル vhost バックエンドはこれにより、QEMU ユーザー空間でのデータ要求処理の往復を減らすことができますが、初期化、機能のネゴシエーション、デバイス状態、移行制御には依然として VMM が必要です。ユーザー空間バックエンドには、共有メモリとプロセス間プロトコルの別のセットがあります。
プレコピー移行と収束
プレコピー移行:ソース側の実行を維持し、最後に一斉に切り替える
ターゲットと互換状態の準備
マシンタイプ、CPU 機能、デバイス、ストレージパス
1
メモリの複製と変更の追跡
後のラウンドでまだダーティになっているページを送信
2
ソース側を停止し、最終状態転送を完了
残りのダーティページ、CPU、デバイス、および進行中の操作の調整
3
ターゲットでの実行再開
切り替え結果を確認し、2 つのインスタンスが同時に外部に書き込まないようにする
4
ダーティページレートはラウンドごとに減少する保証はありません。停止時間はワークロードと移行戦略に依存します。
ソース VM が実行中にメモリを継続的に変更するため、移行マネージャーは、このラウンド以降もダーティになるページを追跡する必要があります。書き込み保護による追跡が一種の方法ですが、ハードウェアのダーティビット、ダーティビットマップ/リングなども関与する可能性があります。すべてのメカニズムを「最初に全 RAM を読み込み専用にする」と記述することはできません。
ダーティになる速度が転送が処理できる速度に近づいたり超えたりする場合、プレコピーを繰り返しても収束しない可能性があります。新しいラウンドのダーティページ量が前のラウンドより少なくなる保証はありません。帯域幅、圧縮、ワークロードのスロットリング、切り替え戦略などが結果に影響します。移行フレームワーク
教育条件 レート
有効な複製能力 1 GiB/s
ワークロードが広範なページを継続的に再ダーティ化 約 1.5 GiB/s
この場合、「数回コピーすれば自然にゼロになる」のを待つことはできません。実際の収束は、同じページへの繰り返し書き込み、ページ粒度、ウィンドウにも影響され、2 つのレートをそのまま正確な予測式として扱うことはできません。
8 GiB から最終停止まで:ラウンドごとに計算
複製する必要があるメモリを M=8 GiB、有効な送信レートを B=2 GiB/s、各ラウンドで新たに生成され、次のラウンドで送信する必要がある異なるダーティページを D=0.5 GiB/s の速度で増加すると仮定します。レートは固定、圧縮なし、ページ粒度の切り捨てや追加の転送オーバーヘッドは考慮しないものとします。これはラウンドごとに集約されたモデルであり、D は異なるダーティページに換算されたデータ量を表し、アプリケーションが実行するストアの元のバイト数ではありません。
このラウンドでの送信量を R とすると、所要時間は R/B です。この間に発生する新しいラウンドの作業量は min(M, D×R/B) です。M の上限に達していない場合、収縮率は D/B=1/4 です。
実行中のラウンド このラウンドの送信 GiB このラウンドの所要時間 s 次のラウンドで送信予定 GiB
1 8 4 2
2 2 1 0.5
3 0.5 0.25 0.125
4 0.125 0.0625 0.03125
固定の CPU/デバイス切り替えオーバーヘッドを 40 ms とした場合、第 3 ラウンド直後にソース側を停止すると、0.125/2 s + 40 ms = 102.5 ms が必要です。第 4 ラウンドをさらに実行すると、残りは 0.03125 GiB、つまり 32 MiB となり、予想される停止時間は 15.625 + 40 = 55.625 ms となり、ようやくこの例の 60 ms の予算内に収まります。
from fractions import Fraction as F
def precopy ( memory , bandwidth , dirty_rate , rounds ) :
assert memory > 0 and bandwidth > 0 and dirty_rate >= 0 and rounds >= 0
remaining = F ( memory )
rows = [ ]
for _ in range ( rounds ) :
seconds = remaining / bandwidth
new_dirty = min ( F ( memory ) , dirty_rate * seconds )
rows . append ( ( remaining , seconds , new_dirty ) )
remaining = new_dirty
return rows , remaining
rows , remaining = precopy ( F ( 8 ) , F ( 2 ) , F ( 1 , 2 ) , 4 )
assert [ r [ 2 ] for r in rows ] == [ F ( 2 ) , F ( 1 , 2 ) , F ( 1 , 8 ) , F ( 1 , 32 ) ]
fixed_stop = F ( 40 , 1000 )
assert rows [ 2 ] [ 2 ] / 2 + fixed_stop == F ( 1025 , 10000 )
assert remaining / 2 + fixed_stop == F ( 55625 , 1000000 ) <= F ( 60 , 1000 )
for dirty in ( F ( 2 ) , F ( 3 ) ) :
unstable , left = precopy ( F ( 8 ) , F ( 2 ) , dirty , 4 )
assert left == 8 and all ( row [ 2 ] == 8 for row in unstable )
assert precopy ( F ( 8 ) , F ( 2 ) , F ( 0 ) , 1 ) [ 1 ] == 0
D≥B の場合、このモデルではラウンドごとに再び 8 GiB 全体が得られ、ラウンド数を増やしても最終的な停止時間が短縮されません。実際のワークロードではホットページの集合は非常に小さい場合があります。同じページが 1 万回書き込まれていても、特定の統計ウィンドウでは 1 回しか送信する必要がない場合があります。したがって、異なるダーティページ量、ウィンドウ、実際の帯域幅を組み合わせる必要があります。メモリの書き込み帯域幅のカウントを直接 D に代入することはできません。QEMU の dirty-limit などのメカニズムは、ダーティワークロードを制限することで収束を助けますが、その代償としてソース側のワークロードに影響が出ます。QEMU Dirty limit
複製後にダーティビットをクリアすると、新しく発生した書き込みが消去される可能性がある
現在値が 0 で、ダーティビットが既にセットされている 1 ページを考慮します。移行マネージャーが 0 をターゲットにコピーした後、ゲストがソースページを 1 に変更し、ダーティビットは 1 のままです。移行マネージャーが「このページのコピーが完了した」ことを示すために無条件でダーティビットをクリアすると、新しい書き込みの証拠も一緒に消去されます。次のラウンドではダーティページが見えず、値 1 も補送されません。
誤ったクリアタイミング:ターゲットは 0 のまま、新しい書き込みはダーティとしてマークされない
ソースページ 0 をコピー
ターゲットは 0 を取得
ゲストが 1 に書き込み
ソースページのダーティビットは 1 のまま
移行マネージャーがダーティビットをクリア
新しい書き込みの証拠が消去
次のラウンドでこのページをスキップ
ターゲットは値 1 を見逃す
次のラウンドの追跡を確立してから、現在のラウンドのページを読み取って送信する必要がある
図は単一ページの論理的競合を示している。実際の処理では、再保護、ダーティビット収集、DMA の調整も必要。
正しいプロトコルでは、明確な追跡境界を確立し、このラウンドで処理される古いダーティ状態と、その後の新しい変更を分離する必要があります。以下では、ダーティ集合の原子交換を使用してこの境界を表現します。現在のラウンドの集合を取り出して次のラウンドの追跡を有効にし、その後コピーします。コピー中に発生した変更は新しい集合に入ります。これは実際の KVM のダーティビット、書き込み保護の復元、メモリバリア、DMA 追跡を実装したものではありません。
class Memory :
def __init__ ( self ) :
self . pages = { 0 : 0 }
self . dirty = { 0 }
def write ( self , page , value ) :
self . pages [ page ] = value
self . dirty . add ( page )
def take_epoch ( self ) :
old , self . dirty = self . dirty , set ( )
return old
bad = Memory ( )
target = { 0 : bad . pages [ 0 ] }
bad . write ( 0 , 1 )
bad . dirty . clear ( )
assert not bad . dirty and target != bad . pages
source = Memory ( )
target = { }
for page in source . take_epoch ( ) :
target [ page ] = source . pages [ page ]
source . write ( 0 , 1 )
assert source . dirty == { 0 } and target [ 0 ] == 0
for page in source . take_epoch ( ) :
target [ page ] = source . pages [ page ]
assert target == source . pages and not source . dirty
for value in range ( 2 , 102 ) :
source . write ( 0 , value )
assert len ( source . dirty ) == 1
実際のシステムには、KVM、QEMU RAM 会計、移行送信などの多層の追跡があり、モデル内の take_epoch() を任意の clear ioctl に直接置き換えることはできません。カーネルによるページ再保護、ハードウェアダーティビットの収集、送信順序は、「読み取り後の変更が失われない」ことを共同で保証する必要があります。コピー中にページの内容が変化する可能性もあります。最終的にソース側を停止し、残りのダーティ状態を再同期して、最終的な整合性のある切り替えの境界を形成します。
次に、有効な複製帯域幅 B とダーティページ発生レート D を変更し、プレコピーをどのラウンドで終了するかを選択します。残りの集合と停止時の複製時間を比較し、D/B が 1 に近づいたときになぜさらに縮小しにくくなるかを観察します。
M n + 1 = min ( M , B D M n ) , T stop ≈ B M n + 1 + T fixed
M は総メモリ、M_n は第 n ラウンドで複製する必要があるデータです。このモデルは、一定の帯域幅と異なるページの有効なダーティ化レートを仮定し、総メモリで上限を設定します。同じページへの繰り返し書き込みは、書き込みごとのバイト数で無限に累積することはできません。
図を準備しています 再読み込み
さらに数ラウンドコピーすれば、残りのダーティデータは必ず減るのか? このラウンドのコピーには Mᵢ/B の時間が必要で、その間に新しいダーティデータは概ね D×Mᵢ/B 発生する。D/B<1 の場合は減少するが、1 に近づいたり超えたりすると、プレコピーを続けても収束する保証はない。
{"id":"migration-dirty-rate","title":"さらに数ラウンドコピーすれば、残りのダーティデータは必ず減るのか?","summary":"このラウンドのコピーには Mᵢ/B の時間が必要で、その間に新しいダーティデータは概ね D×Mᵢ/B 発生する。D/B<1 の場合は減少するが、1 に近づいたり超えたりすると、プレコピーを続けても収束する保証はない。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"bandwidth\" data-i18n=\"bandwidth\"></label><output for=\"bandwidth\" id=\"bandwidth-value\"></output><input id=\"bandwidth\" type=\"range\" min=\"100\" max=\"1000\" value=\"500\" step=\"10\"></div><div class=\"control\"><label for=\"dirty\" data-i18n=\"dirty\"></label><output for=\"dirty\" id=\"dirty-value\"></output><input id=\"dirty\" type=\"range\" min=\"0\" max=\"1200\" value=\"250\" step=\"10\"></div><p class=\"caption\" data-i18n=\"rounds\"></p><div class=\"rounds\"><button data-round=\"0\"><span>1</span><div><i id=\"round-0\"></i></div><strong id=\"size-0\"></strong></button><button data-round=\"1\"><span>2</span><div><i id=\"round-1\"></i></div><strong id=\"size-1\"></strong></button><button data-round=\"2\"><span>3</span><div><i id=\"round-2\"></i></div><strong id=\"size-2\"></strong></button><button data-round=\"3\"><span>4</span><div><i id=\"round-3\"></i></div><strong id=\"size-3\"></strong></button><button data-round=\"4\"><span>5</span><div><i id=\"round-4\"></i></div><strong id=\"size-4\"></strong></button><button data-round=\"5\"><span>6</span><div><i id=\"round-5\"></i></div><strong id=\"size-5\"></strong></button><button data-round=\"6\"><span>7</span><div><i id=\"round-6\"></i></div><strong id=\"size-6\"></strong></button><button data-round=\"7\"><span>8</span><div><i id=\"round-7\"></i></div><strong id=\"size-7\"></strong></button></div><div class=\"metrics\"><div><span data-i18n=\"remaining\"></span><strong id=\"remaining\"></strong></div><div><span data-i18n=\"downtime\"></span><strong id=\"downtime\"></strong></div></div><div class=\"formula\" id=\"formula\"></div><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"shrinking\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"stalled\"></p></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n.rounds button{display:grid;grid-template-columns:20px minmax(0,1fr) 62px;align-items:center;gap:8px;width:100%;padding:10px 8px;margin:5px 0;font-size:12px}.rounds button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.rounds button>div{height:18px;background:var(--surface)}.rounds i{display:block;height:100%;background:var(--accent);transition:width .2s}.rounds strong{font:11px var(--viz-font-ui);text-align:right}.formula{min-height:6em;white-space:pre-line}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}let chosen=2;function draw(){const b=+$('#bandwidth').value,d=+$('#dirty').value,ratio=d/b,sizes=[512];for(let i=0;i<8;i++)sizes.push(Math.min(512,ratio*sizes[i]));set('bandwidth-value',b+' MB/s');set('dirty-value',d+' MB/s');for(let i=0;i<8;i++){$('#round-'+i).style.width=sizes[i]/512*100+'%';set('size-'+i,viz.number(sizes[i],1)+' MB');document.querySelector('[data-round=\"'+i+'\"]').setAttribute('aria-current',String(i===chosen));}set('remaining',viz.number(sizes[chosen+1],2));set('downtime',viz.number(sizes[chosen+1]/b*1000+20,1));set('formula','D/B = '+viz.number(ratio,2)+'\\nM'+(chosen+2)+' = min(512, '+d+' × '+viz.number(sizes[chosen],2)+' / '+b+')\\nstop = '+viz.number(sizes[chosen+1],2)+' / '+b+' s + 20 ms');set('explanation',t(ratio<1?'shrinking':'stalled'));}$('#bandwidth').oninput=$('#dirty').oninput=draw;document.querySelectorAll('[data-round]').forEach(e=>e.onclick=()=>{chosen=+e.dataset.round;draw();});draw();","audio":false,"strings":{"bandwidth":"有効複製 B / MB/s","dirty":"書き込み D / MB/s","downtime":"推定停止時間 / ms","heading":"さらに数ラウンドコピーすれば、残りのダーティデータは必ず減るのか?","intro":"有効帯域幅 B と書き込みレート D を調整し、8 ラウンドの複製対象量を比較する。特定のラウンドを直接クリックして、そのラウンド終了後に VM を停止するために必要な最終複製時間を確認する。","ratio":"D/B","remaining":"選択したラウンド終了後の残り / MB","reset":"リセット","rounds":"ラウンドをクリック:棒の長さはそのラウンド開始時の複製対象量","scope":"メモリ M=512 MB(10 進数)、有効帯域幅には複製オーバーヘッドが含まれる。均一な一意のダーティバイトで近似し、Mᵢ₊₁=min(M,D×Mᵢ/B)。ラウンドの選択は、そのラウンド完了後に停止することを意味する。停止時間=残り/B+固定デバイス後処理 20 ms。圧縮、重複ダーティページ、実際の追跡コスト、その他の移行フェーズは省略しており、実際の VM 停止時間を予測するものではない。","scopeLabel":"モデルの範囲と規約","shrinking":"各ラウンドで残りの一部を消去できるが、1 に近づくほど収束は遅くなる。","stalled":"書き込みが複製に追いついた。ストリップはラウンドごとに短縮されない。このモデルでは、無限のラウンド数によって最終的な複製量を小さくすることはできない。"}}
最後の瞬間に整合性が必要なのは RAM だけではない
切り替え時の状態 一緒に調整する理由
vCPU レジスタ、仮想割り込み、タイマー 正しい実行位置と処理待ちイベントの復元
デバイスキュー、バックエンド、残りの I/O 未完了操作の境界の保持
CPU 機能、マシンタイプ、デバイスバージョン ターゲットがソースの状態を復元できること
ストレージとネットワークアクセス権 システムレベルの切り替えを完了し、両端への書き込みを避ける
したがって、移行の整合性は「RAM のコピーが完了した」だけでは判断できません。
postcopy は、ターゲットでの復元後に不足しているページを必要に応じて取得するため、停止時間とページフォルト待機のトレードオフを変え、切り替え後のソース/ターゲットの障害復旧をより複雑にします。それはプレコピーの「最後のラウンドを速くする」ものではなく、単一の downtime 数値だけを比較すべきではありません。
移行成功前後では、外部への書き込み権をどのインスタンスが持っているかを明確にし、分裂実行を避ける必要があります。パsthrough デバイスの移行可否は、デバイス状態と DMA 追跡能力に依存します。メモリとデバイスの仮想化 を参照してください。
問題を観察する際は、まず所在する層を見つける
CPU 使用率が高い場合、ゲストの有効な実行、頻繁な終了、ホストのスケジューリング待機を区別する。I/O 遅延の場合、ゲストのキューイング、通知、QEMU/バックエンドの実行、実際のストレージネットワークを区別する。移行が停止している場合、ダーティページレート、有効帯域幅、デバイスブロックを参照する。
/dev/kvm の可用性検出については、KVM アーキテクチャ を参照してください。さらに詳細なソースコードは、QEMU の accel/kvm/kvm-all.c、Linux の virt/kvm、および対応する arch ディレクトリから入手できます。バージョン固有のパスは、実際の checkout に準拠する必要があります。