Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1025795
  • 博文数量: 26
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 437
  • 用 户 组: 普通用户
  • 注册时间: 2019-09-08 12:19
个人简介

关于个人介绍,既然你诚心诚意的看了 我就大发慈悲的告诉你 为了防止世界被破坏 为了维护世界的和平 贯彻爱与真实的邪恶 可爱又迷人的反派角色 我们是穿梭在银河的火箭队 白洞,白色的明天在等着我们

文章分类

分类: C/C++

2019-12-11 14:57:08

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的使用有点差异,但原理一样):

点击(此处)折叠或打开

  1. struct vring_desc {   // virtIO的内存池描述
  2.     uint32_t addr;    // 指向当前buffer的物理地址
  3.     uint32_t padding; // 填充
  4.     uint32_t len;     // 当前buffer的长度大小
  5.     uint16_t flags;   // 当前buffer的操作标记
  6.     uint16_t next;    // 下一个
  7. };

  8. struct vring_avail {  // 指明vring里面的空闲的buffer描述
  9.     uint16_t flags;   // 标记位
  10.     uint16_t idx;     // 指明在vring有多少个可用buffer,每一个buffer是一个vring_desc描述
  11.     uint16_t ring[256];// 这里默认最多支持256个,这是一个头咬尾的环线数组,ring[]指向可用desc的idx。
  12. };

  13. struct vring_used { // 指明vring里面已经填充了数据的buffer描述
  14.     uint16_t flags; // 标记
  15.     uint16_t idx;   // 已填充buffer的总数量
  16.     struct vring_used_elem ring[256]; // 这是一个头咬尾的环形数组,ring[]指向填充desc的描述
  17. };

  18. struct vring_used_elem { // 指明vring里面一个可用的buffer描述
  19.     uint32_t id;         // 指向vring里面的desc结构的idx。
  20.     uint32_t len;        // len指向buffer的实际使用长度。
  21. };

  22. struct vring {   // 一个vring描述
  23.     uint32_t num;  // 指明这个vring有多少个buffer(真实数量)
  24.     struct vring_desc *desc; // 这是一个数组,指向buffer的具体地址描述
  25.     struct vring_avail *avail; // 这是一个指针,指向当前空闲buffer的idx
  26.     struct vring_used *used;   // 这是一个指针,指向当前已填充buffer的描述
  27. };

  28. struct virtio {  // 操作vring的结构体
  29.     struct vring vring; // 指向vring
  30.     uint16_t last_avail_idx; // avail环形数组的头,当该节点与vring_avil->idx相等时,表示无空闲
  31.     uint16_t last_used_idx; // 原理同last_avail_idx
  32. }

为了完成virtIO的操作,会有如下几个操作函数,如下:
点击(此处)折叠或打开

  1. // 向vring结构增加一个已填充buffer
  2. int32_t virtio_add_used_buf(virtio vq, int16_t head, int32_t len)
  3. {
  4.     struct vring_used_elem *used;
  5.     
  6.     // idx始终指向一个空闲节点,这里获取一个空闲节点,用来存储填充buffer信息
  7.     used = &vq->vring.used->ring[vq->vring.used->idx % vq->vring.num];
  8.     used->id = head; // 这个head就是vring_desc结构数组的索引号
  9.     used->len = len; // 这个len表示这个buffer实际消耗大小(buffer申请都是固定长度)

  10.     vq->vring.used->idx++; // 将idx+1指向一个空闲节点,以方便下次使用。

  11.     return 0;
  12. }
  13.  
  14. // 向vring增加一个空闲buffer
  15. void virtio_add_avail_buf(virtio vq, void *buf)
  16. {
  17.     uint16_t avail;
  18.  
  19.     // avail->idx始终指向一个空闲节点,用于存储空闲buffer的信息
  20.     avail = vq->vring.avail->idx % vq->vring.num;
  21.     vq->vring.avail->idx++; // 更改idx,使其指向新的空闲结点

  22.     vq->vring.desc[avail].addr = vtop(buf); // 将增加的buf的物理地址赋值给addr
  23.     vq->vring.desc[avail].len = RP_MSG_BUF_SIZE
  24.     vq->vring.desc[avail].flags = 2; // buffer属性
  25.     vq->vring.avail->ring[avail] = avail; // 将desc的索引号存到ring[]里面
  26. }

  27. // 从vring里面获取一个已经填充了数据的buffer
  28. void *virtio_get_used_buf(virtio vq)
  29. {
  30.     uint16_t head;
  31.     void *buf = NULL;
  32.  
  33.     // 这里通过last_used_idx来查找,当相等,即头咬到尾的时候,表示没有填充了数据的buffer
  34.     if (vq->last_used_idx != vq->vring.used->idx)
  35.     {
  36.         head = vq->vring.used->ring[vq->last_used_idx % vq->vring.num].id;
  37.         vq->last_used_idx++;

  38.         buf = ptov(vq->vring.desc[head].addr);
  39.     }

  40.     return buf;
  41. }

  42. // 从vring里面获取一个空闲buffer,用于数据填充,最后会添加到填充buffer数组里面
  43. int16_t virtio_get_avail_buf(virtio vq, void **buf, int32_t *len)
  44. {
  45.     uint16_t head;
  46.     int16_t retVal;
  47.  
  48. // 这里通过last_avail_idx 来查找,当相等,即头咬到尾的时候,表示没有空闲buffer,即buffer消耗尽
  49.     if (vq->last_avail_idx == vq->vring.avail->idx)
  50.     {
  51.         vq->vring.used->flags &= (uint16_t)~VRING_USED_F_NO_NOTIFY;
  52.         retVal = -1;
  53.     }
  54.     else
  55.     {  // 通过last_avail_idx获取到vring_desc的空闲所以head
  56.         head = vq->vring.avail->ring[vq->last_avail_idx % vq->vring.num];
  57.         vq->last_avail_idx++;// 由于Head将会被使用, 因此将last_avail_idx增加

  58.         *buf = ptov(vq->vring.desc[head].addr);// buf为虚拟地址
  59.         *len = (int32_t)vq->vring.desc[head].len;
  60.         retVal = (int16_t)head;
  61.     }

  62.     return retVal;
  63. }
  64.  
  65. // 这个函数用于触发通知信息,在hypervisor中,这个是一个中断路由,rpmsg中是一个mailbox
  66. void virtio_kick(virtio vq)
  67. {
  68.     if (0 == (vq->vring.avail->flags & VRING_AVAIL_F_NO_INTERRUPT))
  69.     {
  70.         //这里由具体情况实现,可以是gpio,也可以是中断,也可以是其它ipc通信,如ti的mailbox
  71.     }
  72. }

通常,初始化这些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应该由对端来分配。
点击(此处)折叠或打开

  1. // 初始化vring结构
  2. static inline void vring_init(struct vring *vr, uint32_t num, void *p,
  3.                               uint32_t pagesize)
  4. {
  5.     vr->num = num; // 当前vringd buffer数量
  6.     vr->desc = (struct vring_desc *) p;// 结构需要的指针
  7.     vr->avail = (struct vring_avail *)
  8.                     ((uintptr_t)p + (num * sizeof(struct vring_desc)));
  9.     // 这里让出vring_desc数组所需要的内存
  10.     vr->used = (struct vring_used *)(((uintptr_t)&vr->avail->ring[num] + pagesize-1)
  11.                 & ~(pagesize - 1));
  12. }
  13.  
  14. // 初始化buffer,即向vring_desc中添加空闲buffer。
  15. void virtio_prime(virtio *vq, uint32_t addr, uint32_t num)
  16. {
  17.     uint32_t i;
  18.     uint32_t buf;

  19.     buf = addr;
  20.     for (i = 0; i < num; i++) {
  21.         virtio_add_avail_buf(vq, (void *)(uintptr_t)buf);
  22.         buf += RP_MSG_BUF_SIZE;
  23.     }
  24. }
  25.  
  26. // 通常调用如下
  27. {
  28.     struct virtio tx_vq, rx_vq;

  29.     // 初始化一个TX的vring,数量为tx_num, 结构体维护地址是tx_addr,对齐到tx_align
  30.     vring_init(&(tx_vq->vring), tx_num, (void*)(uintptr_t)tx_addr, tx_align);

  31.     // 初始化一个RX的vring,数量为rx_num, 结构体维护地址是rx_addr,对齐到rx_align
  32.     vring_init(&(rx_vq->vring), rx_num, (void*)(uintptr_t)rx_addr, rx_align);

  33.     // 为tx的vring 初始化实际状态数据buffer,数量和tx管理数量一致
  34.     virtio_prime(tx_vq, data_buf, tx_num);

  35.     // 发送一个virtl IO
  36.     // 从发送vring里面获取一个空闲buffer
  37.     virtio_get_avail_buf(tx_vq, (void **)&msg, &length);
  38.  
  39.     // 这里对msg进行数据填充,在rpmsg中,填充的内存是rpmsg报文
  40.     ........
  41.     // 将这个已经填充了数据的buffer添加到使用队列中。
  42.     virtio_add_used_buf(tx_vq, token, bufsize);
  43.  
  44.     // 触发一个远程通知(中断或者mailbox),通知对端来处理该buffer
  45.     virtio_kick(vq);
  46. }


如下为Linux virtio结构:
点击(此处)折叠或打开

  1. struct vring_desc_state {
  2.     void *data;                       /* Data for callback. */
  3.     struct vring_desc *indir_desc;    /* Indirect descriptor, if any. */
  4. };

  5. struct virtqueue {
  6.     struct list_head list;
  7.     void (*callback)(struct virtqueue *vq);
  8.     const char *name;
  9.     struct virtio_device *vdev;
  10.     unsigned int index;
  11.     unsigned int num_free;
  12.     void *priv;
  13. };

  14. struct vring_virtqueue {
  15.     struct virtqueue vq;
  16.     struct vring vring;
  17.     /* Can we use weak barriers? */
  18.     bool weak_barriers;
  19.     /* Other side has made a mess, don't try any more. */
  20.     bool broken;
  21.     /* Host supports indirect buffers */
  22.     bool indirect;
  23.     /* Host publishes avail event idx */
  24.     bool event;
  25.     /* Head of free buffer list. */
  26.     unsigned int free_head;
  27.     /* Number we've added since last sync. */
  28.     unsigned int num_added;
  29.     /* Last used index we've seen. */
  30.     u16 last_used_idx;
  31.     /* Last written value to avail->flags */
  32.     u16 avail_flags_shadow;
  33.     /* Last written value to avail->idx in guest byte order */
  34.     u16 avail_idx_shadow;
  35.     /* How to notify other side. FIXME: commonalize hcalls! */
  36.     bool (*notify)(struct virtqueue *vq);

  37.     /* DMA, allocation, and size information */
  38.     bool we_own_ring;
  39.     size_t queue_size_in_bytes;
  40.     dma_addr_t queue_dma_addr;

  41.     /* Per-descriptor state. */
  42.     struct vring_desc_state desc_state[];
  43. }

  44. // 注册驱动和设备,设备可以为mimo,pci, remoteproc,驱动可以为rpmsg,blk等
  45. int register_virtio_driver(struct virtio_driver *drv);
  46. 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?也许会在我的其他博客看到分析。

阅读(3666) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

BugMan2019-12-13 14:46:01

Linux的virtio的使用和这里说的不太一样, Linux是从used中取空闲buffer, 把buffer处理后给avali, 客户机从avali拿待处理buffer,然后把处理好的buffer填入used.


对于客户机来说avali里能拿到两种buffer,一个是linux的发送buffer(含有发送数据), 一个是linux读取buffer,这个buffer没有数据,由客户机填充数据。