Ivshmem实现分析与性能测试

本文深入探讨了Ivshmem,一种虚拟机内部共享内存的PCI设备。分析了Ivshmem的概念,包括PCI BARS、共享内存服务者、设备寄存器和中断模式。详细介绍了Ivshmem的原理与实现,包括数据结构、共享内存体建立、中断处理流程。此外,还进行了性能测试,包括非中断模式和中断模式的调测,以及性能比较,展示了Ivshmem在大数据量传输时的优越性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Ivshmem实现分析与性能测试

欢迎转载,转载请参见文章末尾处要求

Ivshmem实现分析

Ivshmem是虚拟机内部共享内存的pci设备。虚拟机之间实现内存共享是把内存映射成guest内的pci设备来实现的。

从代码分析和实际验证,guest与guest之间可以实现中断与非中断2种模式下的通信, host与guest之间只支持非中断模式的通信。

Ivshmem概念

PCI BARS

BAR是PCI配置空间中从0x10 到 0x24的6个register,用来定义PCI需要的配置空间大小以及配置PCI设备占用的地址空间,X86中地址空间分为MEM和IO两类,因此PCI 的BAR在bit0来表示该设备是映射到memory还是IO,bar的bit0是只读的,bit1保留位,bit2 中0表示32位地址空间,1表示64位地址空间,其余的bit用来表示设备需要占用的地址空间大小与设备起始地址。

ivshmem设备支持3个PCI基地址寄存器。BAR0是1kbyte 的MMIO区域,支持寄存器。根据计算可以得到,设备当前支持3个32bits寄存器(下文介绍),还有一个寄存器为每个guest都有,最多支持253个guest(一共256*4=1kbyte),实际默认为16。

BAR1用于MSI-X,BAR2用来从host中映射共享内存体。BAR2的大小通过命令行指定,必须是2的次方。

 

共享内存服务者

共享内存server是在host上运行的一个应用程序,每启动一个vm,server会指派给vm一个id号,并且将vm的id号和分配的eventfd文件描述符一起发给qemu进程。Id号在收发数据时用来标识vm,guests之间通过eventfd来通知中断。每个guest都在与自己id所绑定的eventfd上侦听,并且使用其它eventfd来向其它guest发送中断。

共享内存服务者代码在nahanni的ivshmem_server.c,后文给出分析。

 

Ivshmem设备寄存器

Ivshmem设备共有4种类型的寄存器,寄存器用于guest之间共享内存的同步,mask和status在pin中断下使用,msi下不使用:

/*registers for the Inter-VM shared memory device */

enumivshmem_registers {

    INTRMASK = 0,

    INTRSTATUS = 4,

    IVPOSITION = 8,

    DOORBELL = 12,

};

Mask寄存器:

与中断状态按位与,如果非0则触发一个中断。因此可以通过设置mask的第一bit为0来屏蔽中断。

Status寄存器:

当中断发生(pin中断下doorbell被设置),目前qemu驱动所实现的寄存器被设置为1。由于status代码只会设1,所以mask也只有第一个bit会有左右,笔者理解可通过修改驱动代码实现status含义的多元化。

IVPosition寄存器:

IVPosition是只读的,报告了guest id号码。Guest id是非负整数。id只会在设备就绪是被设置。如果设备没有准备好,IVPosition返回-1。应用程序必须确保他们有有效的id后才开始使用共享内存。

Doorbell寄存器:

通过写自己的doorbell寄存器可以向其它guest发送中断。为了向其它guest发送中断,需要向其它guest的doorbell写入值,doorbell寄存器为32bit,分为2个16bit域。高16bit是guest id是接收中断的guestid,低16bit是所触发的中断向量。一个指定guest id的doorbell在mmio区域的偏移等于:

guest_id * 32 + Doorbell

写入doorbell的语义取决于设备是使用msi还是pin的中断,下文介绍。

 

Ivshmem的中断模式

非中断模式直接把虚拟pci设备当做一个共享内存进行操作,中断模式则会操作虚拟pci的寄存器进行通信,数据的传输都会触发一次虚拟pci中断并触发中断回调,使接收方显式感知到数据的到来,而不是一直阻塞在read。

ivshmem中断模式分为Pin-based 中断和msi中断:

MSI的全称是Message Signaled Interrupt。MSI出现在PCI 2.2和PCIe的规范中,是一种内部中断信号机制。传统的中断都有专门的中断pin,当中断信号产生时,中断PIN电平产生变化(一般是拉低)。INTx就是传统的外部中断触发机制,它使用专门的通道来产生控制信息。然而PCIe并没有多根独立的中断PIN,于是使用特殊的信号来模拟中断PIN的置位和复位。MSI允许设备向一段指定的MMIO地址空间写一小段数据,然后chipset以此产生相应的中断给CPU。

从电气机械的角度,MSI减少了对interrupt pin个数的需求,增加了中断号的数量,传统的PCI中断只允许每个device拥有4个中断,并且由于这些中断都是共享的,大部分device都只有一个中断,MSI允许每个device有1,2,4,8,16甚至32个中断。

使用MSI也有一点点性能上的优势。使用传统的PIN中断,当中断到来时,程序去读内存获取数据时有可能会产生冲突。其原因device的数据主要通过DMA来传输,而在PIN中断到达时,DMA传输还未能完成,此时cpu不能获取到数据,只能空转。而MSI不会存在这个问题,因为MSI都是发生在DMA传输完成之后的。

由于具有了如上特性,ivshmem在执行pin中断时,则写入低16位的值(就是1)触发对目标guest的中断。如果使用Msi,则ivshmem设备支持多msi向量。低16bit写入值为0到Guest所支持的最大向量。低16位写入的值就是目标guest上将会触发的msi向量。Msi向量在vm启动时配置。Mis不设置status位,因此除了中断自身,所有信息都通过共享内存区域通信。由于设备支持多msi向量,这样可以使用不同的向量来表示不同的事件。这些向量的含义由用户来定。

 

原理与实现

数据结构
原理分析
共享内存体建立

host上Linux内核可以通过将tmpfs挂载到/dev/shm,从而通过/dev/shm来提供共享内存作为bar2映射的共享内存体。

mount tmpfs /dev/shm -t tmpfs -osize=32m

也可通过shm_open+ftruncate创建一个共享内存文件/tmp/nahanni。

 

eventfd字符设备建立

在中断模式下,qemu启动前会启动nahanni的ivshmem_server进程,该进程守候等待qemu的连接,待socket连接建立后,通过socket指派给每个vm一个id号(posn变量),并且将id号同eventfd文件描述符一起发给qemu进程(一个efd表示一个中断向量,msi下有多个efd)。

ivshmem_server启动方式如下:

./ivshmem_server -m 64 -p/tmp/nahanni &

其中-m所带参数为总共享内存大小单位(M),-p代表共享内存体,-n代码msi模式中断向量个数。

在qemu一端通过–chardev socket建立socket连接,并通过-deviceivshmem建立共享内存设备。

./qemu-system-x86_64  -hda mg -L /pc-bios/ --smp 4 –chardev socket,path=/tmp/nahanni,id=nahanni-device ivshmem,chardev=nahanni,size=32m,msi=off -serial telnet:0.0.0.0:4000,server,nowait,nodelay-enable-kvm&

 

Server端通过select侦听一个qemu上socket的连接。 Qemu端启动时需要设置-chardevsocket,path=/tmp/nahanni,id=nahanni,通过该设置qemu通过查找chardev注册类型register_types会调用qemu_chr_open_socket->unix_connect_opts,实现与server之间建立socket连接,server的add_new_guest会指派给每个vm一个id号,并且将id号同一系列eventfd文件描述符一起发给qemu进程,qemu间通过高效率的eventfd方式通信。

voidadd_new_guest(server_state_t * s) {

 

    struct sockaddr_un remote;

    socklen_t t = sizeof(remote);

    long i, j;

    int vm_sock;

    long new_posn;

    long neg1 = -1;

//等待qemu-chardev socket,path=/tmp/nahanni,id=nahanni连接

    vm_sock = accept(s->conn_socket, (structsockaddr *)&remote, &t);// qemu 启动时查找chardev注册类型register_types调用qemu_chr_open_socket->unix_connect_opts实现server建立socket连接

 

    if ( vm_sock == -1 ) {

        perror("accept");

        exit(1);

    }

 

    new_posn = s->total_count;

//new_posn==s->nr_allocated_vms代表一个新的posn的加入

    if (new_posn == s->nr_allocated_vms) {

        printf("increasing vmslots\n");

        s->nr_allocated_vms = s->nr_allocated_vms* 2;

        if (s->nr_allocated_vms < 16)

            s->nr_allocated_vms = 16;

                   //server_state_t分配新new_posn信息结构

        s->live_vms =realloc(s->live_vms,

                    s->nr_allocated_vms *sizeof(vmguest_t));

 

        if (s->live_vms == NULL) {

            fprintf(stderr, "reallocfailed - quitting\n");

            exit(-1);

        }

    }

//新结构的posn

    s->live_vms[new_posn].posn = new_posn;

    printf("[NC] Live_vms[%ld]\n",new_posn);

    s->live_vms[new_posn].efd = (int *) malloc(sizeof(int));

         //分配新结构的新结构的msi_vectorsefd,对于通用pci模式msi_vectors=0

    for (i = 0; i < s->msi_vectors; i++){

        s->live_vms[ne

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值