使用virtio-serial实现guest OS与host的高效通信

Virtio简介


virtio是kvm/Linux中的io半虚拟化解决方案,其前端驱动运行在Guest OS的内核中,且已经被整合入Linux Kernel. 其后端驱动运行在qemu中负责接处理来自前端驱动的IO请求。由于virtio使用的是半虚拟化机制,使用virtio进行io可以达到几乎接近native的性能。目前virtio已经成为kvm
具体介绍可以查看:
http://www.ibm.com/developerworks/cn/linux/l-virtio/
http://www.ibm.com/developerworks/cn/linux/1402_caobb_virtio/
http://www.linux-kvm.org/page/Virtio/

Virtio-serial简介


virtio有一种有趣的应用:用于实现Guest与Host的高效通信。可以通过在Guest中虚拟出一个虚拟串口,并将该串口通过qemu上的后端驱动,经由host上的IPC机制,暴露给host上的其它应用。这样可以方便的实现Guest与Host之间的高效通信。该方式相对基于网络通信方式来说,具有效率高(没有封包解包过程),占用资源少,安全性高的优点。
可以参照:
http://blog.csdn.net/hbsong75/article/details/9451929

Libvirt简介


libvirt是一套免费、开源的支持Linux下主流虚拟化工具的C函数库,其旨在为包括Xen在内的各种虚拟化工具提供一套方便、可靠的API。当前主流Linux平台上默认的虚拟化管理工具virt-manager,virt-install等均基于libvirt开发而成。
libvirt 库是一种实现 Linux 虚拟化功能的 Linux API,它支持各种虚拟机监控程序,包括 Xen 和 KVM,以及 QEMU 和用于其他操作系统的一些虚拟产品。他对各种不同的hypervisor提供了较为统一的管理方式以及API。
libvirt使用xml来对各个域进行描述,具体到当前的目标,我们可以通过在xml中添加virtio-serial的方式来在guest中创建虚拟串口。此外,libvirt还支持由qemu的命令行参数生成相应的xml,因此由现有qemu虚拟机创建一个libvirt域是非常方便的。一有了描述域的xml之后,便可以通过libvirt的命令行管理工具virsh来创建,启动,停止该域。
详情可见libvirt官网:
http://libvirt.org/

AF_UNIX socket简介


通过libvirt的channel机制可以将guest里面的串口映射到host中的一个AF_UNIX socket文件中。
AF_UNIX类型的socket实质上是一种本机的IPC机制,其工作方式为直接将一个进程的用户空间数据copy到另一个进程的空间中,是一种效率很高的IPC。
可以参考:
https://en.wikipedia.org/wiki/Unix_domain_socket
http://man7.org/linux/man-pages/man7/unix.7.html

具体实现


环境安装

源码编译qemu(典型的./configure;make;make install过程,附带解决一些确实的依赖,都是routine),编译时按需打开相应支持,并在qemu上安装好一个ubuntu 14.04 LTS。
安装libvirt:
从git获取源码 git clone git://libvirt.org/libvirt.git
执行./autogen。若出现错误,则根据错误提示安装缺失的相应依赖(印象中有perl的XML::XML_Parser,libdevmap-dev, libxml2-dev等等,依赖较多,需要些耐心,不过都是些routine)
然后便是常规的make -jnproc; sudo make install

创建libvirt域

工作目录为/home/ubuntu/vms/,ubuntu_img是硬盘镜像文件

把qemu的参数写到一个文件里面
echo “/usr/local/bin/qemu-system-x86_64 -m 512 –drive file=/home/ubuntu/vms/ubuntu_img,format=raw,index=0,media=disk –boot order=d –enable-kvm” > qemu.args

从该文件创建xml
virsh domxml-from-native qemu-argv qemu.args > vm1.xml

修改域的名字
vm1

修改图形显示为vnc方式(不知道为什么libvirt认不出type=’gtk’, 所以只好用vnc了)

并添加两个channel,将virtio-serial映射为host上的两个AF_UNIX socket文件, vm.ctl和vm.data

<channel type='unix'>
<source mode='bind' path='/home/ubuntu/vms/vm.ctl'/>
<target type='virtio' address='virtio-serial' port='0'/>
</channel>

<channel type='unix'>
<source mode='bind' path='/home/ubuntu/vms/vm.data' />
<target type='virtio' address='virtio-serial' port='1' />
</channel>

<controller type='virtio-serial' index='0' ports='16' />

创建并启动vm1
virsh create vm1.xml

实现通信

用remmina(或者其它vnc客户端)连上去之后,可以发现在guest的/dev 下有如下两个文件
vport0p1 vport0p2
可以直接通过文件io来读写,我们尝试一下往vport0p1里面写字符串,看主机能否收到
这是客户端的代码,运行时要记得加sudo,因为该程序直接读写了设备文件

#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
     int fd0;
     char *greetings = "Hi there!\n";

     fd0 = open("/dev/vport0p1", O_RDWR);
     for(;;) {
         write(fd0, greetings, strlen(greetings) + 1);
         sleep(1);                                                                     //一秒写一次
     }

     return 0;
}

这是host端的代码,用来通过UNIX domain socket接收从guest中发来的消息,这里我们尝试一下读vport0p1对应的vm.ctl

#include <unistd.h>
#include <sys/types.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    int sock;
    struct sockaddr_un addr;      // AF_UNIX的地址结构是sockaddr_un
    char buffer[512];

    sock = socket(AF_UNIX, SOCK_STREAM, 0);  //创建一个 UNIX domain socket
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, "vm.ctl");

    if(-1 == connect(sock, (struct sockaddr *)&addr, sizeof(addr)))
        perror("aha:");

    while(read(sock, buffer, 512)) {
        printf("data: %s\n", buffer);
        getchar();
    }

    return 0;
}

运行之后,敲击一次回车可以看到一条信息,说明信道畅通。