KVMにおけるcpuid命令の取り扱い

前提

x86/Intelの話です.AMDでも多分同様.

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"を返すように設定しています.

本来この領域はcpuid的にはreserved領域です.KVMはゲストにKVMの存在を伝えるために利用しています.

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()が呼ばれる
      • 後者の場合は&[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が実装されていない理由

まとめ

  • trait/struct
    • Iterationトレイト
      • next()を実装することでイテレータとして利用できるようになる
    • IntoIteratorトレイト
      • イテレータが欲しい時にtrait boundとして利用する
      • for loopはIntoIteratorが実装されているものを受け取るようになっている
    • Iter
      • sliceの&T, &mut Tに対するIterationのために存在
    • IntoIter
      • vecのTに対するIterationのために存在
  • 一般にstdでのcollectionでは
  • 現状Arrayに関してはTに対してイテレーションはできない

rustのArcについてその2

前回

以下で利用したplaygroundのリンク

スライスからのArcの作成

Arcのソースをみていて気づきましたが,impl<T: Clone> From<&[T]> for Arc<[T]>などが実装されており,CopyあるいはCloneが実装されているスライスからArcを作成することができます.(RCも同様です)

例えばこれを使うと,Arc<str> (=Arc<[u8]>)が作れます.

Arc<[T]>はfat pointerとなり,構造的には以下の様になります.

f:id:mm_i:20190214233307p:plain
Arc<T>

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]>>は以下の様な構造になります.

f:id:mm_i:20190214234050p:plain
Arc<HeaderSlice<H, [T]>>

これの何が嬉しいのかというと,zero-length arrayを受け取るCの関数をFFIで呼ぶ時なんかに便利かなと思います.servoではどう使われてるんでしょう(未確認)

rustのMutexの内部構造

前回に引き続き,今度はMutexの話です.

Raph Levien氏の図を引用すると,Mutexは以下のような構造になっています.

f:id:mm_i:20190213182929p:plain
Raph Levien, Copyright 2017 Google Inc., released under Creative Commons BY

また,データ構造としては以下のように定義されています(一部抜粋).

pub struct Mutex<T: ?Sized> {
    inner: Box<sys::Mutex>,
    poison: poison::Flag,
    data: UnsafeCell<T>,
}

ここから,以下のことが分かります.

  1. Mutex自体はデータをヒープに置かない
  2. 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本体のアドレスが変わってしまいます.

ちなみに,RwLockMutexとほぼ同じ構造をしています.

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に取り込もうという動きがあるようです.

深く議論は追っていませんが,しばらくすると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>です

f:id:mm_i:20190212220649p:plain
Arc<String>

実際にメモリ上の表現を確認してみます(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_rawfrom_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側で処理するには修正が必要です.

対象環境

方法

KVM

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

QEMU

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)すると,いろいろあってKVMvcpu_enter_guest()にきます. この中のkvm_x86_ops->run(vcpu)でVMENTRY, kvm_x86_ops->handle_exit(vcpu)でVMEIXT後の処理がおこなわれます. ちなみにVMENTRYやVMEXITが関数ポインタになっているのはIntelAMD双方に対応するためです.

VMCALL命令でVMEXITした場合は,handle_vmcall()からkvm_emulate_hypercall()が呼ばれ,ここでハイパーコールが処理されます. vcpu_enter_guest()のコメントに書いてある通り,kvm_emulate_hypercall()の戻り値で1以外の値を返せばioctl側に制御が戻ります.そうしてもいいのですが,するとQEMU側で命令エミュレーション(RIPを進める処理)をおこなう必要があります.

今回はその代わりにKVMVCPU 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
    • 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()
  • VMCALLはKVM内で処理され,ioctl側に制御が戻らない
  • VMCALLでioctl側に制御を渡すには,KVMの修正が必要

続く?