読者です 読者をやめる 読者になる 読者になる

撤退

参加報告を記します.

LKMのページを userspace にマップする。

LKMのページを userspace にマップする。

LKM のページを userspace にマップできると構造体を共有できていろいろと便利。 このやり方はKVMで使われていて、それを参考にして簡略化したものを実装してみた。 百聞は一見に如かずということでデモのコード。

github.com

前置き

Linux Kernelに組み込まれているKVMではqemuをフロントエンドとして用いている。

Documentation/virtual/kvm/api.txt を読むと、

5. The kvm_run structure
------------------------

Application code obtains a pointer to the kvm_run structure by
mmap()ing a vcpu fd.  From that point, application code can control
execution by changing fields in kvm_run prior to calling the KVM_RUN
ioctl, and obtain information about the reason KVM_RUN returned by
looking up structure members.

と書いてある。qemuKVM_RUN を実行すると VM-Exit が発生した時、構造体 struct kvm_run に値が入って返ってくるということが分かる。 一方 KVMソースコードを見ると、 struct kvm_run のメンバである exit_reason そのまま値を入れていたりもする。

このように struct kvm_runqemuApplication code obtains a pointer to the kvm_run structure by mmap()ing a vcpu fd. という方法を取ることでKVMstruct kvm_run と共有されている。

本題

qemukvm_init_vcpu では

int kvm_init_vcpu(CPUState *cpu)
{
    // omit

    ret = kvm_get_vcpu(s, kvm_arch_vcpu_id(cpu));

    // omit

    cpu->kvm_fd = ret;

    // omit

    mmap_size = kvm_ioctl(s, KVM_GET_VCPU_MMAP_SIZE, 0);

    // omit

    cpu->kvm_run = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED,
                        cpu->kvm_fd, 0);
    // omit
}

というように struct kvm_run をマップしている。 マップする側(user application)はこれだけでよいが、マップされる側(Kernel module)はもう少し面倒くさい。

KVM_CREATE_VCPU が発行されると、KVM側では

static int kvm_vm_ioctl_create_vcpu(struct kvm *kvm, u32 id)
{
  int r;
  struct kvm_vcpu *vcpu;

  // omit

  kvm_get_kvm(kvm);
  r = create_vcpu_fd(vcpu);
  if (r < 0) {
    kvm_put_kvm(kvm);
    goto unlock_vcpu_destroy;
  }

  // omit

  return r;

  // omit (Error handling with goto)
}

が実行される。create_vcpu_fd では以下のような設定の anonymous inode が作られ、そのファイルディスクリプタが返ってくる。 このファイルディスクリプタを使ってqemuから mmap ができるという話である。

// fault のハンドラ
// プロセス空間で例外が発生して正当なアクセスの場合新しいページフレームを割り当てる。
static int kvm_vcpu_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
{
  struct kvm_vcpu *vcpu = vma->vm_file->private_data;
  struct page *page;

  if (vmf->pgoff == 0)
    page = virt_to_page(vcpu->run);
#ifdef CONFIG_X86
  else if (vmf->pgoff == KVM_PIO_PAGE_OFFSET)
    page = virt_to_page(vcpu->arch.pio_data);
#endif
#ifdef KVM_COALESCED_MMIO_PAGE_OFFSET
  else if (vmf->pgoff == KVM_COALESCED_MMIO_PAGE_OFFSET)
    page = virt_to_page(vcpu->kvm->coalesced_mmio_ring);
#endif
  else
    return kvm_arch_vcpu_fault(vcpu, vmf);
  get_page(page);
  vmf->page = page;
  return 0;
}

// vm_operations_struct に fault のハンドラを登録する
static const struct vm_operations_struct kvm_vcpu_vm_ops = {
  .fault = kvm_vcpu_fault,
};

// fault のハンドラを登録する
static int kvm_vcpu_mmap(struct file *file, struct vm_area_struct *vma)
{
  vma->vm_ops = &kvm_vcpu_vm_ops;
  return 0;
}

// file_operations の設定
static struct file_operations kvm_vcpu_fops = {
  .release        = kvm_vcpu_release,
  .unlocked_ioctl = kvm_vcpu_ioctl,
#ifdef CONFIG_KVM_COMPAT
  .compat_ioctl   = kvm_vcpu_compat_ioctl,
#endif
  .mmap           = kvm_vcpu_mmap,
  .llseek   = noop_llseek,
};

static int create_vcpu_fd(struct kvm_vcpu *vcpu)
{
  return anon_inode_getfd("kvm-vcpu", &kvm_vcpu_fops, vcpu, O_RDWR | O_CLOEXEC);
}

kvm_run 自体は kvm_vcpu_init にて page が割り当てられている。

int kvm_vcpu_init(struct kvm_vcpu *vcpu, struct kvm *kvm, unsigned id)
{
  struct page *page;
  int r;

  // omit

  page = alloc_page(GFP_KERNEL | __GFP_ZERO);
  if (!page) {
    r = -ENOMEM;
    goto fail;
  }
  vcpu->run = page_address(page);

  // omit
}
EXPORT_SYMBOL_GPL(kvm_vcpu_init);