わかりやすくQEMUを説明してみる(第4回):アドレス変換、Soft-MMU、システムモード、ユーザモード
はじめに
前回(第3回)ではQEMUにおけるペリフェラルの扱いについて説明しました。
今回(第4回)ではQEMUにおける以下について説明します。
- アドレス変換
- Soft-MMU
- システムモード
- ユーザモード
このあたりから少し話がややこしくなり、わかりやすく説明できるのかちょっと不安なのですが、できるだけやわかりすくを目指して説明していきたいと思います。
アドレス変換って?
そもそもQEMUにおけるアドレス変換とは何を指すのでしょうか。
QEMUにおけるアドレス変換で登場する「アドレス」には複数の種類が存在します。いきなり複雑な図になりますが図1にQEMUにおけるアドレス変換の様子を示します。なおこの図はQEMUにおけるSoft-MMU(MMU=Memory Management Unit)に関する図です。Soft-MMUはQEMUのシステムモードに相当します。またQEMUにはシステムモードの他にユーザモードというものがあります。Soft-MMU、システムモード、ユーザモードについては後ほど説明します。まずはアドレス変換の種類について説明するためにこの図の一部を使います。
QEMUにおいては以下の種類のアドレスが存在します。
- ARM CPU(ゲスト)での仮想アドレス(guest virtual address、QEMUソース内では略してgvaddr)
- ARM CPU(ゲスト)での物理アドレス(guest physical address、QEMUソース内では略してgpaddr)
- x86 CPU(ホスト)での仮想アドレス(host virtual address、QEMUソース内では略してhvaddr)
仮想アドレスは論理アドレスとも呼ばれます。個人的には論理アドレスの方がしっくりきますが、QEMUでは仮想アドレス(virtual address)という用語が使われているので、以降では仮想アドレスという用語を使うことにします。
さてここで仮想アドレスとは何かについて説明しようか迷いました。しかしここで仮想アドレスの説明を挟んでしまうと話の流れが悪くなるので仮想アドレスについては別の回で説明することにします。それにQEMUに関心がある方の中には仮想アドレスについてご存知の方も多いかと思います。
クロスコンパイル
それでは図1について少しずつ説明していきます。まず最初は図1内の①の部分についてです。ここではARM CPU用のCプログラムAをホスト上にてクロスコンパイルし、ARM CPU用の実行ファイル(バイナリファイル)を作っています。そしてこの実行ファイルの中には仮想アドレスgvaddr番地から値をloadする命令があるとします。そして図1の②の部分に示すように、このプグラムAはARM CPUから見た物理メモリ空間にあるRAM1の中に配置されるもとします。②では仮想アドレスgvaddr番地に対する物理アドレスgpaddr番地も示されています。
ホスト上でのメモリ確保
QEMUはまず最初にRAM1に対してホスト側でCの関数calloc()を使って実際にホスト上でメモリ領域を確保します。その様子を図1の③の部分に示します。その後、ARM CPU用実行ファイルを読み込み、読み込んだARM CP用プログラムA1をホスト上のRAM1内に配置します。そしてこのRAM1内のARM CPU用プグラムをTCG(Tiny Code Generator)を使ってx86 CPU用命令に変換しながらプグラムAをx86上で実行していきます。
ARM CPUのMMU(Memory Management Unit)
このときQEMUは、ARM CPU用プグラム内で参照されている仮想アドレスgvaddrが、ホストメモリ上のどのアドレス(正確にはどの仮想アドレス)か、つまり図1内の③にあるアドレスhvaddrを知る必要があります。
図1の④にも示してあるように、ARM CPU用プグラム内で参照される仮想アドレスはARM CPU内部のMMU(Memory Management Unit)と呼ばれる回路により物理アドレスに変換されます。QEMUではこのMMU回路もエミュレーションしています。このMMUはARM上で動くLinuxなどのOSにより制御されます。なおARM CPU上でLinuxなどのOSを使用しない場合、基本的にはMMUを使用しないこととなり、ARM CPUにおいて仮想アドレスという概念はなくなり物理アドレスのみ使用されます。
ARM CPU内部のMMUにより出力された物理アドレス(gpaddr)はあくまでもARM CPUから見た物理アドレスです。つまり図1の②にあるgpaddrです。QEMUはここからさらに図1の③にあるhvaddrを知る必要があります。
ページテーブル、TLB、Soft-MMU
QEMUではゲスト側の物理アドレスを元に作成したページテーブルとTLB(Translation Look aside Buffer)を使って、ゲスト物理アドレス→ホスト仮想アドレスの変換をしています。ページテーブルについては説明が複雑になるので機械があれば別の回に説明したいと思います。
ただ、図1の②と③をよく見て頂くと、ホスト側での仮想アドレスhvaddrは
hvaddr = gpaddr + 何らかの値
で計算できそうな気がしないでしょうか。上記の「何らかの値」を求めるのに使用するのがページテーブルです。そして「何らかの値」を計算したらTLB(Translation Look aside Buffer)に覚えておいて、再度アドレスgpaddrに対して変換する場合は、「何らかの値」を計算から求めることなくTLBに保存されている値をそのまま使います。
QEMUでは図1の④部分にある赤枠内をSoft-MMU(Memory Management Unit)と呼んでいます。
Soft-MMU内部のページテーブルを使用すると、ゲスト側の物理アドレスがMMIO(ペリフェラルアクセス)かどうかが分かります。なぜわかるかについては別の回で説明できればと思います。そしてMMIOの場合はぺリベラルのC関数(read/write関数)が呼ばれ、ペリフェラルのエミュレーションが可能となります。
QEMUのシステムモードとユーザモード
QEMUにはシステムモードとユーザモードがあります。システムモードとは先述したSodt-MMUを使うモードのことです。システムモードのQEMU実行コマンドは以下のように実行コマンドのsystemという文字列が含まれます。
- qemu-system-arm
- qemu-system-i386
- qemu-system-mips
ではユーザモードとは何を指すのでしょうか。実はユーザモードとはゲスト(ARM CPU用)OSとしてLinuxを使用していることが前提となっています。さらにSoft-MMUを使わないモードのことをユーザモードと呼んでいます。つまり以下の両方があてはる場合をユーザモードと呼んでいます。
- ゲストOSはLinux
- Soft-MMUは使用しない
ユーザモードでのアドレス変換の様子を図2に示します。
図2ではARM CPU用のCソースコードを、ARM CPU上でLinuxを使うことを前提としたクロスコンパラarm-linux-gnu-gccによりコンパイルしています。図1では、ゲストの仮想アドレスからホスト仮想アドレスに変換するためにSoft-MMUを使っていましたが、図2ではその変換をQEMUではしていません。ユーザモードではアドレス管理はホスト側のOS(例Linux)に任せています。
ユーザモードのQEMU実行コマンド名は以下のようにsystemという文字列がありません。
- qemu-arm
- qemu-i386
- qemu-mips
ここで余談になりますが1つ疑問が生じます。それは、アドレス管理をホスト側のOSに任せると、図3のようにゲスト側でのプログラムAの配置アドレスstart_gvaddrはすでにホスト側で使用されている場合があるのでは、という疑問です。
ユーザモードにおいてQEMUは、ゲスト用プログラムAをホストのメモリ上に配置するときC言語のmmap()関数を使って仮想メモリ空間に領域を確保します。mmap()関数には先頭アドレスとしてstart_gvaddrを渡します。このとき領域に空がなかった場合は、
mmapの戻り値 ≠ start_gvaddr
となり、空きが無かったことがわかります。この場合、QEMUはstart_gvaddrの値を少し増やし、再度mmap()の実行を試みます。mmap()が成功するまでこれを繰り返し、start_gvaddrにいくつ加算したかを覚えておきます。
ユーザモードではペリフェラルモデルは使えない
QEMUのユーザモードではSoft-MMUを使っていません。したがってSoft-MMUのページテーブルも使用していません。ページテーブルを使っていないとアクセスアドレスがMMIOかどうかの判定ができません。もちろんホスト側のOSもゲスト側のペリフェラルの情報など持っていないのでQEMUペリフェラルのCモデルを実行することはできません。
次回
今回(第4回)はQEMUにおけるアドレス変換、Soft-MMU、システムモード、ユーザモードについて説明しました。かなり分かりにくい説明になってしまいましたが、QEMUを利用するにあたっては詳細を理解できなくても問題はないかと思います。
次回(第5回)はQEMU-KVM(Kernel-based Virtual Machine)について説明します。これはゲスト CPUとホスト CPUが両方ともx86系の場合についての話です。例えばx86系のWindows(ホスト)上でx86系のLinux(ゲスト)を動かす場合です。ホストのマシンコードもゲストのマシンコードも両方ともx86系ならマシン語変換など不要では、という話がベースになっています。