KVMにおけるcpuid命令の取り扱い
前提
cpuid命令の取り扱い
cpuid命令はKVM内で処理されます.
具体的には arch/x86/kvm/vmx.c:handle_cpuid() => arch/x86/kvm/cpuid.c:kvm_emulate_cpuid() => arch/x86/kvm/cpuid.c:kvm_cpuid() => arch/x86/kvm/cpuid.c:kvm_find_cpuid_entry()
ここで,kvm_find_cpuid_entry()
で対応するcpuidのエントリがあるか探し,あれば
それを返します.
このcpuidのエントリはKVM_SET_CPUID
あるいはKVM_SET_CPUID2
ioctlによって登録します.
KVM_SET_CPUID2
はデータを連結リストの形で受け取るので,一回のioctlで複数の
cpuidエントリが登録できます.
また,KVM_GET_SUPPORTED_CPUID
iotclにより,KVMが実際にサポートするcpuidを取得できます.
QEMUでの取り扱い
target/i386/kvm.c:kvm_arch_init_vcpu()内でcpuidの設定をしています.
例えば,cpu->expose_kvm
のとき (-cpu kvm=on
のとき) 0x40000000にのクエリに対して"KVMKVMKVM"を返すように設定しています.
rustのIteratorの実装
関連するトレイト
Iterator
libcore/iter/traits/itetarot.rs
pub trait Iterator { type Item; fn next(&mut self) -> Option<Self::Item>; ... }
- イテレータの関数のためのトレイト
next()
さえ実装すれば,あとはデフォルト定義が存在
IntoIterator
libcore/iter/traits/collect.rs
pub trait IntoIterator { type Item; type IntoIter: Iterator<Item=Self::Item>; fn into_iter(self) -> Self::IntoIter; } impl<I: Iterator> IntoIterator for I { type Item = I::Item; type IntoIter = I; fn into_iter(self) -> I { self } }
IntoIterator
はイテレータを得るためのトレイトfor _ in x
としたとき,x
に対してinto_iter()
が呼ばれるIterator
にはIntoIterator
が実装されている (単純に自分自身を返すだけ)
Slice ([T]
)
iter()
, iter_mut()
libcore/slice/mod.rs
impl<T> [T] { ... #[stable(feature = "rust1", since = "1.0.0")] #[inline] pub fn iter(&self) -> Iter<T> { unsafe { let ptr = self.as_ptr(); assume(!ptr.is_null()); let end = if mem::size_of::<T>() == 0 { (ptr as *const u8).wrapping_add(self.len()) as *const T } else { ptr.add(self.len()) }; Iter { ptr, end, _marker: marker::PhantomData } } } ... }
- sliceの
iter()
はIter<T>
を返すiter_mut()
も同様
Iter<T>
の定義は以下の通り
libcore/slice/mod.rs
pub struct Iter<'a, T: 'a> { ptr: *const T, end: *const T, // If T is a ZST, this is actually ptr+len. This encoding is picked so that // ptr == end is a quick test for the Iterator being empty, that works // for both ZST and non-ZST. _marker: marker::PhantomData<&'a T>, } macro_rules! iterator { ... impl<'a, T> Iterator for $name<'a, T> { type Item = $elem; #[inline] fn next(&mut self) -> Option<$elem> { // could be implemented with slices, but this avoids bounds checks unsafe { assume(!self.ptr.is_null()); if mem::size_of::<T>() != 0 { assume(!self.end.is_null()); } if is_empty!(self) { None } else { Some(& $( $mut_ )* *self.post_inc_start(1)) } } } ... } ... } iterator!{struct Iter -> *const T, &'a T, const, {/* no mut */}, {...
Iter<T>
に対してIterator
が実装されている- 各要素の参照を返す
iter()
とiter_mut()
で実装を共通化するためマクロで定義されている
into_iter()
iter()
,iter_mut()
を呼ぶ形で&'a [T]
,&'a mut [T]
に実装let x = [1,2,3];
のとき,for loopにおいては,for _ in x.iter()
もfor _ in &x
も同じ意味- 前者の場合,
x.iter()
はIterator
を実装するIter<T>
を返し,それに対してIntoIteration::into_iter()
が呼ばれる- イテレータ自身は
IntoIteration
を実装するので問題ない
- イテレータ自身は
- 後者の場合は
&[T]
に対してIntoIteration::into_iter()
が呼ばれる.
- 前者の場合,
[T]
に対してはinto_iter()
の実装なし (そもそもsliceで実際に使えるのはshared (&[T]
) かmutable (&mut [T]
) の形のみ)
libcore/slice/mod.rs
#[stable(feature = "rust1", since = "1.0.0")] impl<'a, T> IntoIterator for &'a [T] { type Item = &'a T; type IntoIter = Iter<'a, T>; fn into_iter(self) -> Iter<'a, T> { self.iter() } } #[stable(feature = "rust1", since = "1.0.0")] impl<'a, T> IntoIterator for &'a mut [T] { type Item = &'a mut T; type IntoIter = IterMut<'a, T>; fn into_iter(self) -> IterMut<'a, T> { self.iter_mut() } }
Array ([T; N]
)
iter()
, iter_mut()
- coerceによってsliceの
iter()
,iter_mut()
が呼ばれる
into_iter()
- sliceと同様に,
iter()
,iter_mut()
を呼ぶ形で&'a [T; N]
,&'a mut [T; N]
に実装[T; N]
に対する実装はない
libcore/array.rs
#[stable(feature = "rust1", since = "1.0.0")] impl<'a, T> IntoIterator for &'a [T; $N] { type Item = &'a T; type IntoIter = Iter<'a, T>; fn into_iter(self) -> Iter<'a, T> { self.iter() } } #[stable(feature = "rust1", since = "1.0.0")] impl<'a, T> IntoIterator for &'a mut [T; $N] { type Item = &'a mut T; type IntoIter = IterMut<'a, T>; fn into_iter(self) -> IterMut<'a, T> { self.iter_mut() } }
Vec (Vec<T>
)
iter()
, iter_mut()
- deref coerceによってsliceの
iter()
,iter_mut()
が呼ばれる
into_iter()
src/liballoc/vec.rs
impl<T> IntoIterator for Vec<T> { type Item = T; type IntoIter = IntoIter<T>; #[inline] fn into_iter(mut self) -> IntoIter<T> { unsafe { let begin = self.as_mut_ptr(); assume(!begin.is_null()); let end = if mem::size_of::<T>() == 0 { arith_offset(begin as *const i8, self.len() as isize) as *const T } else { begin.add(self.len()) as *const T }; let cap = self.buf.cap(); mem::forget(self); IntoIter { buf: NonNull::new_unchecked(begin), phantom: PhantomData, cap, ptr: begin, end, } } } } #[stable(feature = "rust1", since = "1.0.0")] impl<'a, T> IntoIterator for &'a Vec<T> { type Item = &'a T; type IntoIter = slice::Iter<'a, T>; fn into_iter(self) -> slice::Iter<'a, T> { self.iter() } } #[stable(feature = "rust1", since = "1.0.0")] impl<'a, T> IntoIterator for &'a mut Vec<T> { type Item = &'a mut T; type IntoIter = slice::IterMut<'a, T>; fn into_iter(self) -> slice::IterMut<'a, T> { self.iter_mut() } }
&'a Vec<T>
,&'a mut Vec<T>
の場合はiter()
,iter_mut()
を呼ぶVec<T>
の場合はIntoIter<T>
を返すIntoIter<T>
は以下の様に定義- これにより,
Vec<T>
では要素そのものをに対してイテレーションすることが可能 (vec自身は消費される)
src/liballoc/vec.rs
#[stable(feature = "rust1", since = "1.0.0")] pub struct IntoIter<T> { buf: NonNull<T>, phantom: PhantomData<T>, cap: usize, ptr: *const T, end: *const T, } impl<T> Iterator for IntoIter<T> { type Item = T; #[inline] fn next(&mut self) -> Option<T> { unsafe { if self.ptr as *const _ == self.end { None } else { if mem::size_of::<T>() == 0 { // purposefully don't use 'ptr.offset' because for // vectors with 0-size elements this would return the // same pointer. self.ptr = arith_offset(self.ptr as *const i8, 1) as *mut T; // Make up a value of this ZST. Some(mem::zeroed()) } else { let old = self.ptr; self.ptr = self.ptr.offset(1); Some(ptr::read(old)) } } } } ... }
Arrayに[T; N]
のIntoIterator
が実装されていない理由
- 前述の通り,rustではArrayの要素そのものをiterationすることが不可能
- 一方で,Vecであればそれは可能
- ドキュメントには"There is no way to move elements out of an array."と書いてある
- しかしながら,原理的にはmove可能な気がする
- 実装されていない理由
- redditでの議論 "Iterating over array?" https://www.reddit.com/r/rust/comments/7lhhj8/iterating_over_array/
- iteration中にpanicしたときどう対処するかが問題
- 現状rustはconst generics(定数のパラメタライズ)がない(ので実装が冗長になりがち)
- const generics待ち? https://github.com/rust-lang/rust/pull/32871
Vec<T>
の場合は,panicした場合メモリリークを許容する形で実装
- redditでの議論 "Iterating over array?" https://www.reddit.com/r/rust/comments/7lhhj8/iterating_over_array/
まとめ
- trait/struct
- 一般にstdでのcollectionでは
- 現状Arrayに関しては
T
に対してイテレーションはできない
rustのArcについてその2
スライスからのArcの作成
Arcのソースをみていて気づきましたが,impl<T: Clone> From<&[T]> for Arc<[T]>
などが実装されており,CopyあるいはCloneが実装されているスライスからArcを作成することができます.(RCも同様です)
例えばこれを使うと,Arc<str>
(=Arc<[u8]>
)が作れます.
Arc<[T]>
はfat pointerとなり,構造的には以下の様になります.
x86_64環境なら最初の8byteがデータへのポインタ,そのあと8byteがデータのサイズです.
作成例
let s: Arc<str> = Arc::from("str"); println!("{:?}", as_raw_bytes(&s)); println!("{:?}", as_raw_bytes(&*s));
[64, 10, 247, 79, 223, 85, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0] [115, 116, 114]
Arc<String>
だと文字列にアクセスするまで二回ポインタを辿らないといけませんが(前回の記事参照),これなら一回で済みます.
BoxからのArcの作成
impl<T: ?Sized> From<Box<T>> for Arc<T>
も実装されています.
内部動作としてはBoxの中身をArc側にmemcpyして,box側のメモリを解放することになります.
こちらの場合,CopyあるいはCloneが実装されていなくてもArcが作成できます.T: ?Sized
なのでArc<[T]>
が作れます.
struct A(i32); let s: Box<[A]> = Box::new([A(0),A(1),A(2)]); let s: Arc<[A]> = Arc::from(s); println!("{:?}", as_raw_bytes(&s)); println!("{:?}", as_raw_bytes(&*s));
[224, 11, 247, 79, 223, 85, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0] [0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0]
まぁこの場合そもそも普通に Arc::new([A(0), A(1), A(2)])
すればいいんですが.
Arc<str>
をString
から作成したい場合は,String::into_boxed_str()
を使ってBox<str>
にしてからArc
にできます.
let s: Arc<str> = Arc::from(format!("str").into_boxed_str()); println!("{:?}", as_raw_bytes(&s)); println!("{:?}", as_raw_bytes(&*s));
[192, 11, 247, 79, 223, 85, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0] [115, 116, 114]
VecからのArcの作成
impl<T> From<Vec<T>> for Arc<[T]>
もあります.
let s: Arc<[i32]> = Arc::from(vec![0,1,2]); println!("{:?}", as_raw_bytes(&s)); println!("{:?}", as_raw_bytes(&*s));
[16, 12, 247, 79, 223, 85, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0] [0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0]
こちらも動作的にはArcが新規に割り当てたメモリ領域にVecのデータを全部移すことになります.
servo_arc
servoにはservo_arcという,Arcの派生実装が含まれています.
以下の様な特徴があります.
- weakカウントがなくてstrongカウントのみ
- dynamically-sized type (DST)のサポート
DSTに関しては以前記事を書きました.servo_arc
を使うと,以下の様なことができます.
#[derive(Debug)] struct Header { a: u32, b: u32, c: u32, }; let header = Header { a: 1, b: 2, c: 3 }; let data: Vec<u32> = vec![4, 5, 6]; let a = servo_arc::Arc::from_header_and_iter(header, data.into_iter()); println!("{:?}", &a); println!("{:?}", as_raw_bytes(&a)); println!("{:?}", as_raw_bytes(&*a));
HeaderSlice { header: Header { a: 1, b: 2, c: 3 }, slice: [4, 5, 6] } [240, 44, 64, 90, 168, 127, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0] [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 6, 0, 0, 0]
Arc::from_header_and_iter()
はArc<HeaderSlice<H, [T]>>
を作成します.HeaderSliceは以下の様に定義されます.
pub struct HeaderSlice<H, T: ?Sized> { /// The fixed-sized data. pub header: H, /// The dynamically-sized data. pub slice: T, }
Arc<HeaderSlice<H, [T]>>
は以下の様な構造になります.
これの何が嬉しいのかというと,zero-length arrayを受け取るCの関数をFFIで呼ぶ時なんかに便利かなと思います.servoではどう使われてるんでしょう(未確認)
rustのMutexの内部構造
前回に引き続き,今度はMutexの話です.
Raph Levien氏の図を引用すると,Mutexは以下のような構造になっています.
また,データ構造としては以下のように定義されています(一部抜粋).
pub struct Mutex<T: ?Sized> { inner: Box<sys::Mutex>, poison: poison::Flag, data: UnsafeCell<T>, }
ここから,以下のことが分かります.
Mutex
自体はデータをヒープに置かない- mutex本体(
sys::Mutex
)は,ヒープに置かれる
sys::Mutex
は,*nix環境ではpthread_mutex_t
です.
したがって,Mutexのlock/unlockはpthread_mutex_lock()
およびpthread_mutex_unlock()
に対応しています.
mutex本体をヒープに置くのは,OSによっては(pthreadが?)mutex本体のアドレスが変わらないことを前提としているからだそうです(このあたりの実装は未確認).
ヒープに置いておかないと,Mutexをmoveしたときmutex本体のアドレスが変わってしまいます.
ちなみに,RwLock
もMutex
とほぼ同じ構造をしています.
https://github.com/rust-lang/rust/blob/1.32.0/src/libstd/sync/rwlock.rs
pub struct RwLock<T: ?Sized> { inner: Box<sys::RWLock>, poison: poison::Flag, data: UnsafeCell<T>, }
Mutexを利用するときはマルチスレッドで何かしたい場合なので,実際に利用する場合は,Arcと組み合わせてArc<Mutex<T>>
のような形で利用されることが多いと思います.
parking_lot
さて,上のMutexの図には"parking_lot
を使ってみたら?"と書いてあります.
parking_lot
はスピンロックを使ったmutexを提供します.
以下のような感じでMutex
が定義されます(一部抜粋).
// lock_api/src/mutex.rc pub struct Mutex<R: RawMutex, T: ?Sized> { raw: R, data: UnsafeCell<T>, } // src/mutex.rc type Mutex<T> = lock_api::Mutex<RawMutex, T>; // src/raw_mutex.rs pub struct RawMutex { state: AtomicU8, } unsafe impl RawMutexTrait for RawMutex { ... #[inline] fn lock(&self) { if self .state .compare_exchange_weak(0, LOCKED_BIT, Ordering::Acquire, Ordering::Relaxed) .is_err() { self.lock_slow(None); } unsafe { deadlock::acquire_resource(self as *const _ as usize) }; } ... }
stdのMutexと比較すると,以下の特徴があります.
- pthreadではなくて(adaptive)スピンロックを利用する
- mutex本体をヒープに割り当てる必要がない
- poisonフィールドもない
- このフィールドは,ロックを保持したスレッドがpanicしたかどうか検出するためのもの
parking_lot
の場合,panicしたらlockは解放されるようになっている
さて,こんなparking_lot
ですが,ちょうど今parking_lot
をlibstdに取り込もうという動きがあるようです.
- https://internals.rust-lang.org/t/standard-library-synchronization-primitives-and-undefined-behavior/8439
- https://github.com/rust-lang/rust/pull/56409
深く議論は追っていませんが,しばらくするとstdに追加されてるかもしれません(というか,OS nativeなmutexの代わりにバックエンドとして利用されることになりそう?).
rustのArcの内部構造
rustのスマートポインタの構成については,Raph Levien氏の以下の図が参考になります.
今回はここで特にArcについて見ていきたいと思います.
Arcは以下のように定義されています(一部抜粋).
pub struct Arc<T: ?Sized> { ptr: NonNull<ArcInner<T>>, phantom: PhantomData<T>, } struct ArcInner<T: ?Sized> { strong: atomic::AtomicUsize, weak: atomic::AtomicUsize, data: T, } impl<T: ?Sized> Deref for Arc<T> { type Target = T; #[inline] fn deref(&self) -> &T { &self.inner().data } }
具体例
具体例としてArc<String>
で確認してみます.
Raph Levien氏の図を参考に書くと,Arc<String>
の構造は以下のようになります.ちなみにString
自体はVec<u8>
です.
実際にメモリ上の表現を確認してみます(playgroundへのリンク).
as_raw_bytes()
で与えられたリファレンスを[u8]のスライスとして解釈してprintします.
use std::sync::Arc; fn as_raw_bytes<'a, T: ?Sized>(x: &'a T) -> &'a [u8] { unsafe { std::slice::from_raw_parts(x as *const T as *const u8, std::mem::size_of_val(x)) } } fn main() { let s: Arc<String> = Arc::new(format!("str")); println!("[1] {:?}", as_raw_bytes(&s)); println!("[2] {:?}", as_raw_bytes(&*s)); println!("[3] {:?}", as_raw_bytes(&**s)); }
実行結果
[1] [96, 90, 157, 11, 128, 85, 0, 0] [2] [64, 90, 157, 11, 128, 85, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0] [3] [115, 116, 114]
[1]がArc
が保持するArcInner
へのポインタのアドレスです.
[2]は,ArcInner
の中身...ではなくてArc
のDerefによって,ArcInnerのdata部分が表示されています.
これは今回の場合String
すなわちVec<u8>
です.Vec<u8>
は最初に要素先頭へのポインタ,その次が要素の長さ(len
),最後が容量(cap
)です.
[3]はStringが保持する文字列("str")です.
さて,ArcInnder
のstrong countおよびweak countを確認するにはどうしたら良いでしょうか.
ArcInner
へのリファレンスが作れれば良いのですが,ArcInner
自体はpublicでないのでそれはできません.
そこで必殺技としてtransmute_copy
を使って無理やりArcのポインタをu8の配列に変換させて表示させてみます.
{ let s = s.clone(); let arc_inner = unsafe { std::mem::transmute_copy::< Arc<String>, &[u8; std::mem::size_of::<atomic::AtomicUsize>() * 2 + std::mem::size_of::<String>()], >(&s) }; println!("{:?}", &arc_inner[..]); println!("strong={}, weak={}", Arc::strong_count(&s), Arc::weak_count(&s)); }
[2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 64, 90, 157, 11, 128, 85, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0] strong=2, weak=0
これでstrong countおよびweak countが確認できました.
(ArcInnerのweak countは1ですが,Arc::weak_count()
はweak countから1引いた数を返すのでこれで合っています)
ちなみにtransmute_copy
ではなくtransumute
を使うと,Arcのデストラクタが呼ばれなくなるのでメモリリークします.
into_raw
とfrom_raw
Arc
にはinto_raw()
とfrom_raw()
というメソッドがあります.
Arc::into_raw()
はArcが保持するTを指すraw pointerを返します.
また,Arc::from_raw()
はraw pointerからArcを作成します.
ここで,Arc::from_raw()
で渡すraw pointerは,Arc::into_raw()
で得られたものでなければならないとドキュメントに書いてあります.
というのもArc::from_raw()
は与えられたraw pointerからstrong countとweak countのサイズ分だけのオフセットを引いたアドレスを使ってArcを作成するからです.
https://github.com/rust-lang/rust/blob/1.32.0/src/liballoc/sync.rs#L407
pub unsafe fn from_raw(ptr: *const T) -> Self { // Align the unsized value to the end of the ArcInner. // Because it is ?Sized, it will always be the last field in memory. let align = align_of_val(&*ptr); let layout = Layout::new::<ArcInner<()>>(); let offset = (layout.size() + layout.padding_needed_for(align)) as isize; // Reverse the offset to find the original ArcInner. let fake_ptr = ptr as *mut ArcInner<T>; let arc_ptr = set_data_ptr(fake_ptr, (ptr as *mut u8).offset(-offset)); Arc { ptr: NonNull::new_unchecked(arc_ptr), phantom: PhantomData, } }
逆に言えばこういうデータ構造を最初にメモリに作成すればArc::from_raw()
からArcが作成できるわけですね.
おそらくバージョン・環境依存の動作だと思いますし,需要は謎ですが...
一年以上振りにrustを触っていますが,スマートポインタの内部構造で少し混乱したので書いてみました.この辺り,コンパイラがderef coercionやauto dereferenceなど勝手にやってくれる分,少々分かりにくい気がします. といってもrustでは基本的にポインタ演算はできませんし,コンパイラに型チェックまかしておけば困ることはほぼないのですが. よく設計されているなとつくずく思います.
QEMU/KVM上のゲストのハイパーコールをQEMU側に渡す方法
前回説明したように,KVMでは,ゲストのハイパーコール(Intel CPUの場合はVMCALL命令,AMDの場合はVMMCALL命令)はKVM側で処理され,ioctl側に戻ることなくゲストに戻ります.
QEMU/KVMにおいて,独自にハイパーコールを追加してQEMU側で処理するには修正が必要です.
対象環境
方法
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h index fbda5a917c5b..fba797631a3f 100644 --- a/arch/x86/include/asm/kvm_host.h +++ b/arch/x86/include/asm/kvm_host.h @@ -78,6 +78,7 @@ #define KVM_REQ_HV_STIMER KVM_ARCH_REQ(22) #define KVM_REQ_LOAD_EOI_EXITMAP KVM_ARCH_REQ(23) #define KVM_REQ_GET_VMCS12_PAGES KVM_ARCH_REQ(24) +#define KVM_REQ_HYPERCALL KVM_ARCH_REQ(25) #define CR0_RESERVED_BITS \ (~(unsigned long)(X86_CR0_PE | X86_CR0_MP | X86_CR0_EM | X86_CR0_TS \ diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c index f049ecfac7bb..2accaba0e75a 100644 --- a/arch/x86/kvm/x86.c +++ b/arch/x86/kvm/x86.c @@ -7002,6 +7002,10 @@ int kvm_emulate_hypercall(struct kvm_vcpu *vcpu) ret = kvm_pv_send_ipi(vcpu->kvm, a0, a1, a2, a3, op_64_bit); break; #endif + case KVM_HC_MY_HYPERCALL: + kvm_make_request(KVM_REQ_HYPERCALL, vcpu); + ret = 0; + break; default: ret = -KVM_ENOSYS; break; @@ -7624,6 +7628,11 @@ static int vcpu_enter_guest(struct kvm_vcpu *vcpu) r = 0; goto out; } + if (kvm_check_request(KVM_REQ_HYPERCALL, vcpu)) { + vcpu->run->exit_reason = KVM_EXIT_MY_HYPERCALL; + r = 0; + goto out; + } /* * KVM_REQ_HV_STIMER has to be processed after diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h index 2b7a652c9fa4..1c2707d5b70c 100644 --- a/include/uapi/linux/kvm.h +++ b/include/uapi/linux/kvm.h @@ -235,6 +235,7 @@ struct kvm_hyperv_exit { #define KVM_EXIT_S390_STSI 25 #define KVM_EXIT_IOAPIC_EOI 26 #define KVM_EXIT_HYPERV 27 +#define KVM_EXIT_MY_HYPERCALL 28 /* For KVM_EXIT_INTERNAL_ERROR */ /* Emulate instruction failed. */ diff --git a/include/uapi/linux/kvm_para.h b/include/uapi/linux/kvm_para.h index 6c0ce49931e5..a6a0417ee029 100644 --- a/include/uapi/linux/kvm_para.h +++ b/include/uapi/linux/kvm_para.h @@ -28,6 +28,7 @@ #define KVM_HC_MIPS_CONSOLE_OUTPUT 8 #define KVM_HC_CLOCK_PAIRING 9 #define KVM_HC_SEND_IPI 10 +#define KVM_HC_MY_HYPERCALL 11 /* * hypercalls use architecture specific
diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h index f11a7eb49c..0b44980eda 100644 --- a/linux-headers/linux/kvm.h +++ b/linux-headers/linux/kvm.h @@ -235,6 +235,7 @@ struct kvm_hyperv_exit { #define KVM_EXIT_S390_STSI 25 #define KVM_EXIT_IOAPIC_EOI 26 #define KVM_EXIT_HYPERV 27 +#define KVM_EXIT_MY_HYPERCALL 28 /* For KVM_EXIT_INTERNAL_ERROR */ /* Emulate instruction failed. */ diff --git a/target/i386/kvm.c b/target/i386/kvm.c index b2401d13ea..685c3ac58e 100644 --- a/target/i386/kvm.c +++ b/target/i386/kvm.c @@ -3622,6 +3622,21 @@ int kvm_arch_handle_exit(CPUState *cs, struct kvm_run *run) ioapic_eoi_broadcast(run->eoi.vector); ret = 0; break; + case KVM_EXIT_MY_HYPERCALL: + ret = kvm_getput_regs(cpu, 0); + if (ret < 0) { + return ret; + } + fprintf(stderr, "KVM: KVM_EXIT_MY_HYPERCALL %d, %ld, %ld, %ld, %ld, %ld\n", + run->exit_reason, + cpu->env.regs[R_EAX], + cpu->env.regs[R_EBX], + cpu->env.regs[R_ECX], + cpu->env.regs[R_EDX], + cpu->env.regs[R_ESI]); + cpu->env.regs[R_EAX] = 0; + ret = kvm_getput_regs(cpu, 1); + break; default: fprintf(stderr, "KVM: unknown exit reason %d\n", run->exit_reason); ret = -1;
解説
QEMU側からkvm_vcpu_ioctl(cpu, KVM_RUN, 0)
すると,いろいろあってKVMのvcpu_enter_guest()
にきます.
この中のkvm_x86_ops->run(vcpu)
でVMENTRY, kvm_x86_ops->handle_exit(vcpu)
でVMEIXT後の処理がおこなわれます.
ちなみにVMENTRYやVMEXITが関数ポインタになっているのはIntelとAMD双方に対応するためです.
VMCALL命令でVMEXITした場合は,handle_vmcall()
からkvm_emulate_hypercall()
が呼ばれ,ここでハイパーコールが処理されます.
vcpu_enter_guest()
のコメントに書いてある通り,kvm_emulate_hypercall()
の戻り値で1以外の値を返せばioctl側に制御が戻ります.そうしてもいいのですが,するとQEMU側で命令エミュレーション(RIPを進める処理)をおこなう必要があります.
今回はその代わりにKVMのVCPU request
という機能を使っています.
これはもともとはカーネルのスレッドがvCPU側に何かリクエストをするために使用する機能のようです.
ハイパーコール処理の中で,自分が定義したハイパーコール番号だった場合,kvm_make_request()
でリクエストの登録し,あとはこれまで通り命令のエミュレーション自体はKVMにまかせます.このままだとまたKVMはゲストにVMENTRYしますが,vcpu_enter_guest()
の先頭の方でkvm_check_request()
し,もし対応するリクエストが存在したらexit_reasonをその旨に書き換えてユーザランド側に戻るようにしています.
QEMUはioctlから戻ってきたら,kvm_arch_handle_exit()
(accel/kvm/kvm-all.c => target/i386/kvm.c) でVMEXIT後の処理をするので,ここにハイパーコール用の処理を追加しています.
QEMUからはkvm_getput_regs()
を使ってゲストのレジスタを取得することが可能です.
QEMUコードリーディングメモ
主にイベントループとKVM周り.
QEMU Main Loop
- QEMUの主処理はGLibを使ったイベントループ.
- ゲストのvCPUは別のスレッドで実行(後述)
vl.c:main() => vl.c:main_loop() => util/main-loop.c:main_loop_wait() => loop { slirp_pollfds_fill(); os_host_main_loop_wait() => g_main_context_acquire(context) glib_pollfds_fill() => g_main_context_prepare() g_main_context_query() qemu_mutex_unlock_iothread() qemu_poll_ns() => g_poll() qemu_mutex_lock_iothread() glib_pollfds_poll() => g_main_cotext_disapatch() g_main_context_release(context) slirp_pollfds_poll() }
- QEMUはiothreadに関してlockを持っている
- イベントのpoll中はこのiothreadのlockを外す
- イベントループ処理の流れを理解するにはGLibの動作を知る必要がある.
GLibメモ
GSource
- http://maemo.org/api_refs/4.0/glib/glib-The-Main-Event-Loop.html#GSource
- GLibにおけるイベントを表現
- file descriptors
- file
- pipe
- sockets
- ...
- timeout event
- file descriptors
- sourceの主要な関数
prepare()
poll()
の前に呼ばれる- sourceが(pollすることなく)準備完了であればtrueを返す
- 例
- timeout eventでtimeoutしてたらtrue
- file descriptorの場合は,通常はpollするのでfalse
- 例
check()
- poll()後に呼ばれる
- dispatchすることができるならtrueを返す
dispatch()
- イベントに対応付けられたcallback関数を実行
- 各sourceにはpriorityが存在
基本的なイベント処理の流れ
g_source_attach()
でcontextに対してsourceを追加- メインループ
g_main_context_prepare(context, &max_priority)
- コンテキストが持つ各GSourceのprepare()を呼ぶ
- ここで,max_priorityにprepare()がtrueの中から最大のpriorityが設定される.
g_main_context_query(context, max_priority, ..., fds)
- max_priorityを持つsourceのfile descriptorを取得
- この関数によって実際にpoll()するfdを選択する
g_poll()
- poll
g_main_context_check()
- dispatch可能なfdをチェック
g_main_cotext_disapatch()
- コールバック関数を呼ぶ
I/O handler
- 仮想デバイスを処理するためのハンドラ (GSource)
- 初期化
util/main-loop.c:qemu_init_main_loop()
iohandler_init() => util/async.c:aio_context_new()
g_source_attach()
でiohandlerをdefault contextのsourceに追加
- コールバック関数の登録
util/iohandler.c:qemu_set_fd_handler()
KVM 初期化
- 全体の初期化
accel/kvm/kvm_all.c:kvm_init()
vl.c:main() => accel/accel.c:configure_accelerator() => accel_init_machine() => acc->init_machine() = accel/kvm/kvm_all.c:kvm_init()
- マシン初期化時に,vCPUごとにスレッドが作成される.
target/i386/cpu.c:x86_cpu_realizefn() => cpus.c:qemu_init_vcpu() => qemu_kvm_start_vcpu() qemu_kvm_start_vcpu() => qemu_thread_create(..., qemu_kvm_cpu_thread_fn, ...)
KVM vCPU event loop
cpus.c:qemu_kvm_cpu_thread_fn => qemu_mutex_lock_iothread() loop { accel/kvm/kvm-all.c:kvm_cpu_exec() => qemu_mutex_unlock_iothread() do { kvm_vmrun_ioctl(cpu, KVM_RUN, 0) ret = handle_vmexit() }while(ret == 0); qemu_mutex_lock_iothread() qemu_wait_io_event() } qemu_mutex_unlock_iothread()
- 各vCPUは
iothread
のロックを持つ KVM_RUN
実行中はiothread
のロックを外す
KVMRUN
- ※ これはLinux側の処理
virt/kvm/kvm_main.c:kvm_vcpu_ioctl() => arch/x86/kvm/x86.c:kvm_arch_vcpu_ioctl_run() => vcpu_run() => loop { ret = { vcpu_enter_guest() => kvm_x86_ops->run(vcpu) = arch/x86/kvm/vmx.c:vmx_vcpu_run() => VMLAUNCH kvm_x86_ops->handle_exit(vcpu) = arch/x86/kvm/vmx.c:vmx_handle_exit() => kvm_vmx_exit_handlers[exit_reason](vcpu) } if (ret >= 0) break }
- VMCALLの処理
arch/x86/kvm/vmx.c:handle_vmcall() => arch/x86/kvm/x86.c:kvm_emulate_hypercall() => handle_vm_call or kvm_skip_emulated_instruction()
続く?