BitVisorのアドベントカレンダーってことですが,せっかくなので亜種であるTinyVisorの話を書こうと思います.
以下の流れで勧めていきます
- TinyVisorってなに?
- 本題
- 所感等
TinyVisorってなに?
概要
BitVisorはゲストを1つだけ持つことができ,それと引き換えに色々おもしろいことができます.(他の記事参照)
そのBitVisorに少し改良を加え,なんとTinyVisorは2つゲストが動きます!! すごい 面白い
基本的にはBitVisorと似たような特性(パススルー等)を持ちあわせつつ, もれなくもう一個ゲストが動くため,可能性が広がるわけです.
仕組み
先程ちらっとパススルーと言いましたが,これを達成しつつ,ゲストを2つ動かすために物理的にハードウェアを分割しています.
と こんな話をしつつ,早速こんなことを書くのも心が痛いのですが,詳しくは公式のホームページを読むと詳しくわかるかと思います笑
(全部書いていると時間がない..)
(https://ja.osdn.net/projects/tinyvisor/)
本題
私も現在勉強中ですが,ゲストを2つ動作させるためにBitVisorでは想定していなかったことを考慮する必要があります.
と言うのはVM0とVM1はそれぞれ独立して動作するため,メモリやデバイスへのアクセスを監視し,不正なアクセスがあると無効化したりしなくてはいけません.
例えば,VM0に割り当てたNICにVM1からアクセスできては困るわけです.
それに加えて,CPUとかメモリとかI/Oデバイスとかの分割に加えて,各VMの再起動等も独立してできます.
ということでBitVisorとTinyVisorを比較すると技術的に面白いことはたくさんあるのですが,
未だによくわからない
前にいじったけど忘れた
ということで,中でも記憶の新しいIOMMU周りの,特にdriver/iommu.c
の話に絞って書こうかなと思った次第です.
では前置きが長くなりましたが,ざっくり書いていきます.
(間違った解釈等ございましたら,ご意見ください.お願い致します.)
とりあえずこの辺から読んでいき,概要を追っていきたいと思います.
DRIVER_INIT(iommu_drvinit);
DRIVER_DEVINIT(iommu_devinit);
DRIVER_VMINIT(iommu_initvm);
DRIVER_START(iommu_enable);
読んで字のごとく,基本的にdriver/iommu.c
にはIOMMUの起動時の初期化及び初期設定が入っておりますので,以下の4つをスタートと見ていってもよいかと思います.
まあ上から読んでいっても問題ないと思います.(多分)
static void
iommu_drvinit(void)
ここではACPIのDMARテーブルを初期化したり,pci driverをlistに登録したりします.
static void
iommu_devinit(void)
{
struct dmar_drhd_u *drhd;
struct iommu *iommu;
if (dmar_drhd_exists()) {
for (drhd = dmar_get_next_drhd(NULL); drhd;
drhd = dmar_get_next_drhd(drhd)) {
iommu = new_iommu(drhd->address);
if (!iommu) {
continue;
}
drhd->iommu = iommu;
}
}
(略)
io_domain0 = create_domain(0);
if (io_domain0 == NULL) {
panic("Failed to create io domain 0");
}
LIST_FOREACH(iommu_list, iommu) {
setup_all_root_entry_to_domain(iommu, io_domain0);
}
msgregister ("vtddump", vtddump_msghandler);
}
DRHDはIOMMUのベースアドレスとかデバイスの情報等を持っている構造体です.なので序盤ではIOMMUを作成そのベースアドレスをセットしたりしています.
後半でio_domain0
と有りますが,IOMMUはdomain_idでどのVMにリソースを割り当てているかを管理することができるため,domain0を作っています.create_domain()
で次に出てくるcontext tableも作成しています.
引き続き```setup_all_root_entry_to_domain``を見ていきます.
static void
setup_all_root_entry_to_domain(struct iommu *iommu,
struct io_domain *dom)
{
int bus_no;
struct root_entry *root;
phys_t ctxtbl;
for (bus_no = 0; bus_no < 256; bus_no++) {
root = &iommu->root_entry[bus_no];
if (root_entry_present(*root)) {
printf("map_bus_to_domain: "
"root entry is already set. 0x02%x\n",
bus_no);
continue;
}
clear_root_entry(*root);
ctxtbl = dom->shared_ctxtbl[iommu->page_level - MIN_PAGE_LEVEL];
(略)
set_root_present(*root);
(略)
}
ここでrootというのが出てきましたが,IOMMUはroot tableにbusごとのエントリを持っていて,
その各エントリーはcontext tableのアドレス等を持っているので,先程作成したdomain0のcontext tableを登録します.
つまり,この段階ではdomain0としてすべてのデバイスを登録してしまいます.
ここから,VM0, VM1用にそれぞれに書き換えていきます.
static void
iommu_initvm(void)
{
struct io_domain *dom;
struct iommu *iommu;
if (vm_get_id() == 0) {
(略)
} else {
dom = create_domain(vm_get_id());
if (!dom) {
panic("Failed to create domain.");
}
}
map_all_guest_pages(dom);
map_assigned_devices_to_domain(dom);
(略)
}
このvm_get_id()
はVMのidを取得するもので,0のときはすでにdomainが作成されているため,map_all_guest_pages()
でゲストのアクセス可能なメモリ領域をマップしたあとmap_assigned_devices_to_domainでは特に何もしてません.(mmio周辺の設定はしてる)
1のときは先程の用にVM1用のdomainを作成し,メモリ領域をmapしてmap_assigned_devices_to_domainにそのdomainを渡しています.
static void
map_assigned_devices_to_domain(struct io_domain *dom)
{
struct pci_device *dev;
int bus_no;
for (dev = pci_next_assgined_pci_device(NULL); dev;
dev = pci_next_assgined_pci_device(dev)) {
map_mmio_resource(dom, dev);
if (vm_get_id() == 0) {
continue;
}
IOMMU_DBG("map bus 0x%x devfn 0x%x\n",
dev->bus_no, dev->devfn);
map_device_to_domain(dom, dev->bus_no, dev->devfn);
/*
* If assigned device is PCI-PCI bridge, Assign all
* devices behind it to a specified io domain.
*/
(略)
}
デバイスごとにdomainに登録していきます. map_device_to_domain -> map_device_to_domain_iommu
と進んでいきます.
map_device_to_domain_iommu(struct iommu *iommu, struct io_domain *dom,
u8 bus_no, u8 devfn)
{
struct context_entry *context;
spinlock_lock(&iommu->unit_lock);
context = get_context_entry(iommu, bus_no, devfn);
(略)
clear_context_entry(*context);
set_agaw(*context, iommu->page_level - MIN_PAGE_LEVEL);
set_asr(*context, get_iopt_phys(dom, iommu->page_level));
set_context_trans_type(*context, 0x0); /* means "ASR field
points to a multi-level
page-table" */
enable_fault_handling(*context);
set_context_domid(*context, dom->domain_id);
set_context_present(*context);
flush_cacheline_dw(iommu, context);
spinlock_unlock(&iommu->unit_lock);
}
ここでは該当するbus番号のcontext_entryを取得してきて,
(すでにdomain0として登録されている)そのエントリーの初期化,更新を行いdomain_1のものとします.
dom->domain_id
あたりを見るとわかりやすいでしょう.
これで終わりです.....(内容がうすry)
所感
内容はともかく,
ユーザが増えるきっかけになると嬉しいです.
(そもそBitVisorではないし,貴重なドキュメントの機会の1日を消費してしまった)