VirtIO结构分析

扫码查看
VirtIO是一种共享内存的数据格式,可用于hypervisor做一种I/O半虚拟化解决方案,也可以用于多核之间的相互通信RPmsg的实现方案即RPmsg是一种基于virtIO消息传递的总线,RPmsg有属于自己的协议格式。本篇只简单讲讲virtIO的原理, 该原理方便日后进行Hypervisor开发或者RPmsg开发。VirtIO主要是由一堆管理结构组成,最外面的结构我这里叫做virtio,我们这里将一个传输队列叫做vring,一个vring只有一个方向,即要么是TX,要么是RX(通常在hypervisor或者rpmsg中的一个虚拟设备只有一组vring,即一个TX和一个RX)。一个vring拥有两个管理结构,即vring_avail和vring_used,当然它还有一个数据内存池叫vring_desc结构。这些结构注释如下(本篇重点放到virtio的另一种模式和linux的使用有点差异,但原理一样):点击(此处)折叠或打开struct vring_desc {   // virtIO的内存池描述    uint32_t addr;    // 指向当前buffer的物理地址    uint32_t padding; // 填充    uint32_t len;     // 当前buffer的长度大小    uint16_t flags;   // 当前buffer的操作标记    uint16_t next;    // 下一个};struct vring_avail {  // 指明vring里面的空闲的buffer描述    uint16_t flags;   // 标记位    uint16_t idx;     // 指明在vring有多少个可用buffer,每一个buffer是一个vring_desc描述    uint16_t ring[256];// 这里默认最多支持256个,这是一个头咬尾的环线数组,ring[]指向可用desc的idx。};struct vring_used { // 指明vring里面已经填充了数据的buffer描述    uint16_t flags; // 标记    uint16_t idx;   // 已填充buffer的总数量    struct vring_used_elem ring[256]; // 这是一个头咬尾的环形数组,ring[]指向填充desc的描述};struct vring_used_elem { // 指明vring里面一个可用的buffer描述    uint32_t id;         // 指向vring里面的desc结构的idx。    uint32_t len;        // len指向buffer的实际使用长度。};struct vring {   // 一个vring描述    uint32_t num;  // 指明这个vring有多少个buffer(真实数量)    struct vring_desc *desc; // 这是一个数组,指向buffer的具体地址描述    struct vring_avail *avail; // 这是一个指针,指向当前空闲buffer的idx    struct vring_used *used;   // 这是一个指针,指向当前已填充buffer的描述};struct virtio {  // 操作vring的结构体    struct vring vring; // 指向vring    uint16_t last_avail_idx; // avail环形数组的头,当该节点与vring_avil->idx相等时,表示无空闲    uint16_t last_used_idx; // 原理同last_avail_idx}为了完成virtIO的操作,会有如下几个操作函数,如下:点击(此处)折叠或打开// 向vring结构增加一个已填充bufferint32_t virtio_add_used_buf(virtio vq, int16_t head, int32_t len){    struct vring_used_elem *used;        // idx始终指向一个空闲节点,这里获取一个空闲节点,用来存储填充buffer信息    used = &vq->vring.used->ring[vq->vring.used->idx % vq->vring.num];    used->id = head; // 这个head就是vring_desc结构数组的索引号    used->len = len; // 这个len表示这个buffer实际消耗大小(buffer申请都是固定长度)    vq->vring.used->idx++; // 将idx+1指向一个空闲节点,以方便下次使用。    return 0;} // 向vring增加一个空闲buffervoid virtio_add_avail_buf(virtio vq, void *buf){    uint16_t avail;     // avail->idx始终指向一个空闲节点,用于存储空闲buffer的信息    avail = vq->vring.avail->idx % vq->vring.num;    vq->vring.avail->idx++; // 更改idx,使其指向新的空闲结点    vq->vring.desc[avail].addr = vtop(buf); // 将增加的buf的物理地址赋值给addr    vq->vring.desc[avail].len = RP_MSG_BUF_SIZE;     vq->vring.desc[avail].flags = 2; // buffer属性    vq->vring.avail->ring[avail] = avail; // 将desc的索引号存到ring[]里面}// 从vring里面获取一个已经填充了数据的buffervoid *virtio_get_used_buf(virtio vq){    uint16_t head;    void *buf = NULL;     // 这里通过last_used_idx来查找,当相等,即头咬到尾的时候,表示没有填充了数据的buffer    if (vq->last_used_idx != vq->vring.used->idx)    {        head = vq->vring.used->ring[vq->last_used_idx % vq->vring.num].id;        vq->last_used_idx++;        buf = ptov(vq->vring.desc[head].addr);    }    return buf;}// 从vring里面获取一个空闲buffer,用于数据填充,最后会添加到填充buffer数组里面int16_t virtio_get_avail_buf(virtio vq, void **buf, int32_t *len){    uint16_t head;    int16_t retVal; // 这里通过last_avail_idx 来查找,当相等,即头咬到尾的时候,表示没有空闲buffer,即buffer消耗尽    if (vq->last_avail_idx == vq->vring.avail->idx)    {        vq->vring.used->flags &= (uint16_t)~VRING_USED_F_NO_NOTIFY;        retVal = -1;    }    else    {  // 通过last_avail_idx获取到vring_desc的空闲所以head        head = vq->vring.avail->ring[vq->last_avail_idx % vq->vring.num];        vq->last_avail_idx++;// 由于Head将会被使用, 因此将last_avail_idx增加        *buf = ptov(vq->vring.desc[head].addr);// buf为虚拟地址        *len = (int32_t)vq->vring.desc[head].len;        retVal = (int16_t)head;    }    return retVal;} // 这个函数用于触发通知信息,在hypervisor中,这个是一个中断路由,rpmsg中是一个mailboxvoid virtio_kick(virtio vq){    if (0 == (vq->vring.avail->flags & VRING_AVAIL_F_NO_INTERRUPT))    {        //这里由具体情况实现,可以是gpio,也可以是中断,也可以是其它ipc通信,如ti的mailbox    }}通常,初始化这些vring,一般调用两次,一次用于初始化TX的vring,一个是初始化RX的vring。另外初始化Tx vring的时候,还附带为这个vring增加空闲buffer,即向该vring的vring_desc中添加工作使用的内存地址(Linux当中,不管是TX ring还是RX ring,他们的vring_desc内存都是由Linux设置提供)。而RX vring就不需要特别初始化desc(Linux当中,不管是TX ring还是RX ring,他们的vring_desc内存都是由Linux设置提供),因为这个desc应该由对端来分配。点击(此处)折叠或打开// 初始化vring结构static inline void vring_init(struct vring *vr, uint32_t num, void *p,                              uint32_t pagesize){    vr->num = num; // 当前vringd buffer数量    vr->desc = (struct vring_desc *) p;// 结构需要的指针    vr->avail = (struct vring_avail *)                    ((uintptr_t)p + (num * sizeof(struct vring_desc)));    // 这里让出vring_desc数组所需要的内存    vr->used = (struct vring_used *)(((uintptr_t)&vr->avail->ring[num] + pagesize-1)                & ~(pagesize - 1));} // 初始化buffer,即向vring_desc中添加空闲buffer。void virtio_prime(virtio *vq, uint32_t addr, uint32_t num){    uint32_t i;    uint32_t buf;    buf = addr;    for (i = 0; i        virtio_add_avail_buf(vq, (void *)(uintptr_t)buf);        buf += RP_MSG_BUF_SIZE;    }} // 通常调用如下{    struct virtio tx_vq, rx_vq;    // 初始化一个TX的vring,数量为tx_num, 结构体维护地址是tx_addr,对齐到tx_align    vring_init(&(tx_vq->vring), tx_num, (void*)(uintptr_t)tx_addr, tx_align);    // 初始化一个RX的vring,数量为rx_num, 结构体维护地址是rx_addr,对齐到rx_align    vring_init(&(rx_vq->vring), rx_num, (void*)(uintptr_t)rx_addr, rx_align);    // 为tx的vring 初始化实际状态数据buffer,数量和tx管理数量一致    virtio_prime(tx_vq, data_buf, tx_num);    // 发送一个virtl IO    // 从发送vring里面获取一个空闲buffer    virtio_get_avail_buf(tx_vq, (void **)&msg, &length);     // 这里对msg进行数据填充,在rpmsg中,填充的内存是rpmsg报文    ........    // 将这个已经填充了数据的buffer添加到使用队列中。    virtio_add_used_buf(tx_vq, token, bufsize);     // 触发一个远程通知(中断或者mailbox),通知对端来处理该buffer    virtio_kick(vq);}如下为Linux virtio结构:点击(此处)折叠或打开struct vring_desc_state {    void *data;                       /* Data for callback. */    struct vring_desc *indir_desc;    /* Indirect descriptor, if any. */};struct virtqueue {    struct list_head list;    void (*callback)(struct virtqueue *vq);    const char *name;    struct virtio_device *vdev;    unsigned int index;    unsigned int num_free;    void *priv;};struct vring_virtqueue {    struct virtqueue vq;    struct vring vring;    /* Can we use weak barriers? */    bool weak_barriers;    /* Other side has made a mess, don't try any more. */    bool broken;    /* Host supports indirect buffers */    bool indirect;    /* Host publishes avail event idx */    bool event;    /* Head of free buffer list. */    unsigned int free_head;    /* Number we've added since last sync. */    unsigned int num_added;    /* Last used index we've seen. */    u16 last_used_idx;    /* Last written value to avail->flags */    u16 avail_flags_shadow;    /* Last written value to avail->idx in guest byte order */    u16 avail_idx_shadow;    /* How to notify other side. FIXME: commonalize hcalls! */    bool (*notify)(struct virtqueue *vq);    /* DMA, allocation, and size information */    bool we_own_ring;    size_t queue_size_in_bytes;    dma_addr_t queue_dma_addr;    /* Per-descriptor state. */    struct vring_desc_state desc_state[];}// 注册驱动和设备,设备可以为mimo,pci, remoteproc,驱动可以为rpmsg,blk等int register_virtio_driver(struct virtio_driver *drv);int register_virtio_device(struct virtio_device *dev);linux中,每个virt_queue都通过list挂到virt_device下面,一个virt_queue对应一个virt_ring,一个virt_device可以对应多个virt_queue。通过register_virtio_driver注册驱动,register_virtio_device注册设备。什么是RPmsg?或者什么是hypervisor的virtual IO?也许会在我的其他博客看到分析。
10-08 15:43
查看更多