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?也许会在我的其他博客看到分析。