DPDK vhost-user之packed ring(六)

virtio1.1已經在新的kernel和dpdk pmd中陸續支持,但是網上關於這一塊的介紹卻比較少,唯一描述多一點的就是這個ppt:https://www.dpdk.org/wp-content/uploads/sites/35/2018/09/virtio-1.1_v4.pdf 。但是看ppt這東西總覺得還是不過癮的。只是模糊的大概理解,但要想看清其本質還是要看代碼。這篇文章主要是基於dpdk18.11中的vhost_user來分析virtio1.1具有哪些新特性,已經具體是如何工作的。

virtio1.1 關鍵的最大改動點就是引入了packed queue,也就是將virtio1.0中的desc ring,avail ring,used ring三個ring打包成一個desc ring了。向對應的,我們將virtio 1.0這種實現方式稱之爲split ring。我們以vm的接收處理邏輯(vhost_user的發送邏輯)爲例分析一下split 和packed方式的區別。

在virtio_dev_rx中有如下實現:

if (vq_is_packed(dev))
    nb_tx = virtio_dev_rx_packed(dev, vq, pkts, count);
else
    nb_tx = virtio_dev_rx_split(dev, vq, pkts, count);

根據後端設備是否支持VIRTIO_F_RING_PACKED這個feature,分別調用packed和split處理函數。我們先回顧瞭解下我們看下其分別實現的流程。

split方式處理

virtio_dev_rx_split

static __rte_always_inline uint32_t
virtio_dev_rx_split(struct virtio_net *dev, struct vhost_virtqueue *vq,
    struct rte_mbuf **pkts, uint32_t count)
{
    uint32_t pkt_idx = 0;
    uint16_t num_buffers;
    struct buf_vector buf_vec[BUF_VECTOR_MAX];
    uint16_t avail_head;

    rte_prefetch0(&vq->avail->ring[vq->last_avail_idx & (vq->size - 1)]);
    avail_head = *((volatile uint16_t *)&vq->avail->idx);

    for (pkt_idx = 0; pkt_idx < count; pkt_idx++) {
        uint32_t pkt_len = pkts[pkt_idx]->pkt_len + dev->vhost_hlen;
        uint16_t nr_vec = 0;
        /* 爲拷貝當前mbuf後續預留avail desc */
        if (unlikely(reserve_avail_buf_split(dev, vq,
                        pkt_len, buf_vec, &num_buffers,
                        avail_head, &nr_vec) < 0)) {
            vq->shadow_used_idx -= num_buffers;
            break;
        }
        /* 拷貝mbuf到avail desc */
        if (copy_mbuf_to_desc(dev, vq, pkts[pkt_idx],
                        buf_vec, nr_vec,
                        num_buffers) < 0) {
            vq->shadow_used_idx -= num_buffers;
            break;
        }
        /* 更新last_avail_idx */
        vq->last_avail_idx += num_buffers;
    }
    /* 小包的批處理拷貝 */
    do_data_copy_enqueue(dev, vq);

    if (likely(vq->shadow_used_idx)) {
        flush_shadow_used_ring_split(dev, vq); /* 更新used ring */
        vhost_vring_call_split(dev, vq); /* 通知前端 */
    }

    return pkt_idx;
}

其中涉及三處會和packed方式處理不同的地方,從函數名字我們也能看出,就是帶有split的函數,對應一定有packed函數。split收包處理流程這裏不再具體展開,下圖描述了split相關數據結構。下面重點以這個圖爲背景大致描述一下guset接收流程。
在這裏插入圖片描述
圖中黃色部分表示guest內存,綠色部分表示host內存(非共享內存)。可以看到共享內存主要由三部分也就是三個ring構成:desc ring,avail ring,used ring。這三個ring也是split模式的核心構成。

首先是desc ring,有多個desc chain構成,用來指向存放數據的地址。desc中有個flag,主要有以下幾個取值:

/* This marks a buffer as continuing via the next field. */
#define VRING_DESC_F_NEXT 1
/* This marks a buffer as write-only (otherwise read-only). */
#define VRING_DESC_F_WRITE 2
/* This means the buffer contains a list of buffer descriptors. */
#define VRING_DESC_F_INDIRECT 4

其次是avail ring,注意avail->idx 不是desc ring的idx,而是avail->ring的idx,對應的avail->ring[idx]最後一個後端可用的(對前端來說是下一個可用)desc chain的header idx。last_avail_idx也不是desc ring的idx,記錄的也是avail->ring的idx,對應的avail->ring[idx]表示上一輪拷貝用到的最後一個desc chain的header idx(avail->ring[idx+1]爲本輪拷貝可用的第一個desc chain的header idx)。avail ring中也有一個flag,目前只會取值VRING_AVAIL_F_NO_INTERRUPT,其作用是讓guest通過設置這個來告訴後端(如果更新了uesd ring)暫時不用kick前端,作爲前端的一個優化

最後是uesd ring,再講used ring前要提一下shadow_used ring,shadow_used ring是vhost user爲了提高性能分配的一個ring,它和其他三個ring不同,它是host內存,guest是不感知的。仔細觀察上圖就可以看出shadow_used ring和uesd->ring指向的結構是完全一樣的。因爲shadow_used ring正是uesd ring的一個暫存的buff。當後端將數據從對應desc chain記錄的內存拷貝出之後,這些desc chain的idx和len就需要先暫時記錄在shadow_used ring中,等這一批mbuf拷貝完後,再將shadow_used ring一次性拷貝到uesd->ring的對應位置。同樣last_used_idx記錄的也不是desc ring的idx,而是used->ring的idx,對應used->ring[idx]記錄的是上一次後端已經處理好可以給前端釋放(對於guest rx來說)的desc chain的header idx。used ring中也有一個flag,目前只會取值VRING_USED_F_NO_NOTIFY,其作用是讓host使用這個值告訴前端,當用可用的avail ring時不要kick host,dpdk vhost_user默認會設置這個flag,因爲後端採用的是polling模式

另外值得一提的是,整個guest rx涉及到兩次位置的向guest內存拷貝的動作,一個是將mbuf中的數據拷貝到desc中(對應函數copy_mbuf_to_desc),另一處是將shadow_uesd ring拷貝到uesd ring的過程中(對應函數flush_shadow_used_ring_split),所以如果在guest熱遷移過程中這兩處都會涉及到log_page的相關操作

關於split ring的其他一些註釋:

  1. 每個virtqueue由三部分組成:

    1. Descriptor Table
    2. Available Ring
    3. Used Ring
  2. Legacy Interfaces

    1. vq需要嚴格的按照以下順序和pad 佈局

      struct virtq {
      	// The actual descriptors (16 bytes each)
      	struct virtq_desc desc[ Queue Size ];
      	// A ring of available descriptor heads with free-running index.
      	struct virtq_avail avail;
      	// Padding to the next Queue Align boundary.
      	u8 pad[ Padding ];
      	// A ring of used descriptor heads with free-running index.
      	struct virtq_used used;
      };
      
  3. avail desc中的ring存放的是desc chain的header desc id。desc的id表示的是下一個可用(對於前端)的desc id;

packed方式處理

前面回顧分析完split的處理方式後下面重點要分析packed的處理方式,這是virtio1.1改變的重點。packed的關鍵變化是desc ring的變化,爲了更好的利用cache和硬件的親和性(方便硬件實現virtio),將split方式中的三個ring(desc,avail,used)打包成一個packed desc ring。

我們首先看些相對split desc來說packed desc有什麼不同。

/*split desc*/
struct vring_desc {
    uint64_t addr; /* Address (guest-physical). */
    uint32_t len; /* Length. */
    uint16_t flags; /* The flags as indicated above. */
    uint16_t next; /* We chain unused descriptors via this. */
};
/*packed desc*/
struct vring_packed_desc {
    uint64_t addr;
    uint32_t len;
    uint16_t id;
    uint16_t flags;
};

我們看到addr和len名字和含義保持不變,flags看起來也沒有變化,實際上其取值多了幾種。下面我們具體分析其變化的原因,以及每個變化字段的含義。
1、相對split desc去掉了next字段:我們知道在split desc中next字段是記錄一個desc chain中的下一個desc idx使用的,通常配合flags這樣使用:

if ((descs[idx].flags & VRING_DESC_F_NEXT) == 1)
   nextdesc = descs[ descs[idx].next];

但是在packed desc ring中一個desc chain一定是相鄰的(可以理解爲鏈表變爲了數組),所以next字段就用不上了,上面獲取nextdesc的方式可以轉化爲如下方式:

if ((descs[idx].flags & VRING_DESC_F_NEXT) == 1)
    nextdesc = descs[++idx];

2、flags字段的變化:相對split desc,flags字段仍然保留,但是其取值增加了,因爲要把三個ring合一,每個desc就需要更多的信息表明身份(是used還是avail)。在原有flags的基礎上增加了兩個flag:

#define VRING_DESC_F_AVAIL  (1ULL << 7)
#define VRING_DESC_F_USED 	(1ULL << 15)

關於這兩個flag如何使用後面再分析。
3、相對split desc增加了id字段:這個id比較特殊,他是buffer id,注意不是desc的下標idx。那麼這個buffer又是個什麼含義呢?其實可用理解爲前端guest維護的一個mbuf數組,這個buffer就是這個數組的idx,用來發送或接受數據。爲了更準確描述buffer id的來歷,我們看下前端是如果將一個avail buffer關聯到一個desc的:

對每個(foreach)將要發送的buffer, b:

  1. 從desc ring中獲取到下一個可用的desc,d;
  2. 獲取下一個可用的buffer id;
  3. 設置d.addr的值爲b的數據起始物理地址;
  4. 設置d.len的值爲b的數據長度;
  5. 設置d.id爲buffer id;
  6. 採用如下方式生成desc的flag:
    1. 如果b是後端可寫的,則設置VIRTQ_DESC_F_WRITE,否則不設置;
    2. 按照avail ring的Wrap Counter值設置VIRTQ_DESC_F_AVAIL;
    3. 按照avail ring 的Wrap Counter值取反設置VIRTQ_DESC_F_USED;
  7. 調用一下memory barrier確保desc已經被初始化;
  8. 設置d.flags爲剛剛生成的flag;
  9. 如果d是avail ring的最後一個desc,則對Wrap Counter進行翻轉;
  10. 否則增加d指向下一個desc;

附上僞代碼實現:

/* Note: vq->avail_wrap_count is initialized to 1 */
/* Note: vq->sgs is an array same size as the ring */
id = alloc_id(vq);
first = vq->next_avail;
sgs = 0;
for (each buffer element b) {
	sgs++;
	vq->ids[vq->next_avail] = -1;
	vq->desc[vq->next_avail].address = get_addr(b);
	vq->desc[vq->next_avail].len = get_len(b);
	avail = vq->avail_wrap_count ? VIRTQ_DESC_F_AVAIL : 0;
	used = !vq->avail_wrap_count ? VIRTQ_DESC_F_USED : 0;
	f = get_flags(b) | avail | used;
	if (b is not the last buffer element) {
	    f |= VIRTQ_DESC_F_NEXT;
	}
	/* Don't mark the 1st descriptor available until all of them are ready. */
	if (vq->next_avail == first) {
	    flags = f;
	} else {
	    vq->desc[vq->next_avail].flags = f;
	}
	last = vq->next_avail;
	vq->next_avail++;
	if (vq->next_avail >= vq->size) {
		    vq->next_avail = 0;
		    vq->avail_wrap_count \^= 1;
	 }
}
vq->sgs[id] = sgs;
/* ID included in the last descriptor in the list */
vq->desc[last].id = id;
write_memory_barrier();
vq->desc[first].flags = flags;
memory_barrier();
if (vq->device_event.flags != RING_EVENT_FLAGS_DISABLE) {
    notify_device(vq);
}

注意上面實現的一個細節:當需要傳遞多個buffer的時候,第一個desc的flag是延時到最後更新的,這樣可以減少memory_barrier調用次數,一次調用確保之後的desc都已經正常初始化了(爲什麼最後更新flag,以及要調用memory_barrier呢?因爲後端是以flag判斷desc是否可以使用,所以需要確保flag設置時其他字段以及被正確設置寫入內存)。最後再附一張desc ring的圖。
在這裏插入圖片描述
另外注意,由於avail 和uesd都統一到了desc中,但是並不是每個字段都是必須的。avail ring和used ring是如何體現的?

packed把三個ring進行了整合,但virtio的本質思想並沒有變化,整個數據傳輸還是avail和used共同作用完成的,所以三ring合一僅僅是形式的變化,avail ring和used ring並沒有消失。那麼自然就有一個問題:avail ring和used ring是如何體現的?

回答這個問題前,我們先看一下virtio的vq爲了支持packed發生的一些變化。

struct vhost_virtqueue {
…
    bool            used_wrap_counter;
    bool            avail_wrap_counter;}

這兩個wrap_counter分別對應avail ring和used ring,packed方式正式通過這兩個bool型變量以及前面提到的packed desc新增的兩個flag完成avail和uesd的區分的。

首先這兩個wrap_counter在初始化隊列的時候都被初始化爲1;

對於avail ring,當使用了最後一個desc時則將avail_wrap_counter進行翻轉(0變爲1,1變爲0),然後再從第一個開始;對於uesd ring,當使用了最後一個desc時將used_wrap_counter進行翻轉,然後再從第一個開始。

有了上面的前提就可以說明avail desc和used desc是如果表示的了:

avail desc:當desc flags關於VRING_DESC_F_AVAIL的設置和avail_wrap_counter同步,且VRING_DESC_F_USED的設置和avail_wrap_counter相反時,表示desc爲avail desc。 例如avail_wrap_counter爲1時,flags應該設置VRING_DESC_F_AVAIL| ~ VRING_DESC_F_USED,當avail_wrap_counter爲0時,flags應該設置~ VRING_DESC_F_AVAIL|VRING_DESC_F_USED。

used desc:當desc flags關於VRING_DESC_F_USED的設置和used_wrap_counter同步,且VRING_DESC_F_AVAIL的設置也和used_wrap_counter同步時,表示desc爲used desc。 例如used_wrap_counter爲1時,flags應該設置VRING_DESC_F_AVAIL|VRING_DESC_F_USED,當used_wrap_counter爲0時,flags應該設置~ VRING_DESC_F_AVAIL|~ VRING_DESC_F_USED。

綜上可以看出,avail desc的兩個flag總是相反的(只能設置一個),而used desc的兩個flag總是相同的,要麼都設置,要麼都不設置。

看到這裏可能有人會奇怪,爲什麼要搞得這麼麻煩呢?僅僅通過兩個flags應該也可以區分出是uesd還是avail吧。那我們看下面這個圖,以avail desc爲例,假如僅僅靠VRING_DESC_F_AVAIL|~VRING_DESC_F_USED就表示avail desc:
在這裏插入圖片描述
圖中情況表示當前avail ring滿了,沒有uesddesc,這個時候如果後端處理完最後一個avail desc,迴繞到第一個avail desc時,就無法區分這個avail desc是新的avail desc還是已經處理過的desc。而如果結合avail_wrap_counter就很好處理了,假如本輪其值爲1,則遍歷到最後一個avail desc時avail_wrap_counter要被置零了,再繼續遍歷到第一個desc時判斷是avail desc的標準就變爲了~USED|AVAIL,所以第一個desc就不滿足條件了。

所以我們看出引入wrap_counter的作用主要是爲了解決desc ring迴繞問題。在split方式中,由於對於avail ring有avail->idx存放當前最後一個可用avail desc的位置,對於uesd ring有used->idx存放最後一個可用的uesd desc位置,而packed方式中三ring合一,不再有這樣一個變量表示ring的結束位置,所以才引入了這麼個機制。

關於packed ring的其他一些註釋:

  1. Packed virtqueues支持2^15 entries;

  2. 每個packed virtqueue 有三部分構成:

    1. Descriptor Ring
    2. Driver Event Suppression:後端(device)只讀,用來控制後端向前端(driver)的通知(used notifications)
    3. Device Event Suppression:前端(driver)只讀,用來控制前端向後端(device)的通知(avail notifications)
  3. Write Flag,VIRTQ_DESC_F_WRITE

    1. 對於avail desc這個flag用來標記其關聯的buffer是隻讀的還是隻寫的;
    2. 對於used desc這個flag用來表示去關聯的buffer是否有被後端(device)寫入數據;
  4. desc中的len

    1. 對於avail desc,len表示desc關聯的buffer中被寫入的數據長度;
    2. 對於uesd desc,當VIRTQ_DESC_F_WRITE被設置時,len表示後端(device)寫入數據的長度,當VIRTQ_DESC_F_WRITE沒有被設置時,len沒有意義;
  5. Descriptor Chain

    buffer id包含在desc chain的最後一個desc中,另外,VIRTQ_DESC_F_NEXT在used desc中是沒有意義的。

好了,說了這麼多我們大概對packed的實現原理清楚了,那麼接下來就看下具體實現,還是以vm收包方向的後端處理邏輯爲例。

virtio_dev_rx_packed

static __rte_always_inline uint32_t
virtio_dev_rx_packed(struct virtio_net *dev, struct vhost_virtqueue *vq,
    struct rte_mbuf **pkts, uint32_t count)
{
    uint32_t pkt_idx = 0;
    uint16_t num_buffers;
    struct buf_vector buf_vec[BUF_VECTOR_MAX];

    for (pkt_idx = 0; pkt_idx < count; pkt_idx++) {
        uint32_t pkt_len = pkts[pkt_idx]->pkt_len + dev->vhost_hlen;
        uint16_t nr_vec = 0;
        uint16_t nr_descs = 0;
         /* 爲拷貝當前mbuf後續預留avail desc */
        if (unlikely(reserve_avail_buf_packed(dev, vq,
                        pkt_len, buf_vec, &nr_vec,
                        &num_buffers, &nr_descs) < 0)) {
            vq->shadow_used_idx -= num_buffers;
            break;
        }
        rte_prefetch0((void *)(uintptr_t)buf_vec[0].buf_addr);

         /* 拷貝mbuf到avail desc */
        if (copy_mbuf_to_desc(dev, vq, pkts[pkt_idx],
                        buf_vec, nr_vec,
                        num_buffers) < 0) {
            vq->shadow_used_idx -= num_buffers;
            break;
        }

        vq->last_avail_idx += nr_descs;
        if (vq->last_avail_idx >= vq->size) {
            vq->last_avail_idx -= vq->size;
            vq->avail_wrap_counter ^= 1;
        }
    }
    /* 小包的批處理拷貝 */
    do_data_copy_enqueue(dev, vq);

    if (likely(vq->shadow_used_idx)) {
        /* 更新used ring */
        flush_shadow_used_ring_packed(dev, vq);
        /* kick 前端 */
        vhost_vring_call_packed(dev, vq);
    }

    return pkt_idx;
}

函數中帶有packed後綴的都是packed方式的特有處理實現。我們先看reserve_avail_buf_packed,這個函數爲拷貝當前mbuf後續預留avail desc。

reserve_avail_buf_packed

static inline int
reserve_avail_buf_packed(struct virtio_net *dev, struct vhost_virtqueue *vq,
                uint32_t size, struct buf_vector *buf_vec,
                uint16_t *nr_vec, uint16_t *num_buffers,
                uint16_t *nr_descs)
{
    uint16_t avail_idx;
    uint16_t vec_idx = 0;
    uint16_t max_tries, tries = 0;

    uint16_t buf_id = 0;
    uint32_t len = 0;
    uint16_t desc_count;

    *num_buffers = 0;
    avail_idx = vq->last_avail_idx;
    /* 如果支持mergeable特性,則一個mbuf可用使用多個desc chain */
    if (rxvq_is_mergeable(dev))
        max_tries = vq->size - 1;
    else
        max_tries = 1;

    while (size > 0) {
        if (unlikely(++tries > max_tries))
            return -1;
        /* 嘗試填充一個desc chain */
        if (unlikely(fill_vec_buf_packed(dev, vq,
                        avail_idx, &desc_count,
                        buf_vec, &vec_idx,
                        &buf_id, &len,
                        VHOST_ACCESS_RW) < 0))
            return -1;

        len = RTE_MIN(len, size);
        /* 將當前使用的desc chian信息同步到shadow_used_packed ring 中 */
        update_shadow_used_ring_packed(vq, buf_id, len, desc_count);
        size -= len;

        avail_idx += desc_count;
        if (avail_idx >= vq->size)
            avail_idx -= vq->size;

        *nr_descs += desc_count;
        *num_buffers += 1;
    }

    *nr_vec = vec_idx;

    return 0;
}

下面看fill_vec_buf_packed,這個函數是將mbuf填充到當前desc chain中(如果mbuf過大,不保證填完,只負責填充當前desc chian)。

fill_vec_buf_packed

主要倒數第三個參數是返回的buffer id。

static __rte_always_inline int
fill_vec_buf_packed(struct virtio_net *dev, struct vhost_virtqueue *vq,
                uint16_t avail_idx, uint16_t *desc_count,
                struct buf_vector *buf_vec, uint16_t *vec_idx,
                uint16_t *buf_id, uint32_t *len, uint8_t perm)
{
    bool wrap_counter = vq->avail_wrap_counter;
    struct vring_packed_desc *descs = vq->desc_packed;
    uint16_t vec_id = *vec_idx;
    /* 如果avail idx發送了迴繞,則wrap_counter要進行翻轉 */
    if (avail_idx < vq->last_avail_idx)
        wrap_counter ^= 1;
    /* 判斷是否是avail desc */
    if (unlikely(!desc_is_avail(&descs[avail_idx], wrap_counter)))
        return -1;

    *desc_count = 0;
    *len = 0;
    while (1) {
        if (unlikely(vec_id >= BUF_VECTOR_MAX))
            return -1;

        *desc_count += 1; 
        *buf_id = descs[avail_idx].id; /* buf_id記錄的是使用的最後一個avail desc的id */
        if (descs[avail_idx].flags & VRING_DESC_F_INDIRECT) {
            if (unlikely(fill_vec_buf_packed_indirect(dev, vq,
                            &descs[avail_idx],
                            &vec_id, buf_vec,
                            len, perm) < 0))
                return -1;
        } else {
            *len += descs[avail_idx].len;

            if (unlikely(map_one_desc(dev, vq, buf_vec, &vec_id,
                            descs[avail_idx].addr,
                            descs[avail_idx].len,
                            perm)))
                return -1;
        }
        if ((descs[avail_idx].flags & VRING_DESC_F_NEXT) == 0)
            break;
        if (++avail_idx >= vq->size) {
            avail_idx -= vq->size;
            wrap_counter ^= 1;
        }
    }

    *vec_idx = vec_id;
    return 0;
}

其中需要注意的有三點,首先,buf_id記錄的是使用的最後一個avail desc的buffer id,這個id會在shadow_uesd中使用,然後是當avail ring出現翻轉的時候,同步翻轉對應的wrap_counter。再一點就是desc_is_avail函數,用來判斷當前desc是否是avail desc。

desc_is_avail

static inline bool
desc_is_avail(struct vring_packed_desc *desc, bool wrap_counter)
{
    /* VRING_DESC_F_AVAIL的設置和wrap_counter一致,且VRING_DESC_F_USED的設置和wrap_counter相反時表示設avail desc */
    return wrap_counter == !!(desc->flags & VRING_DESC_F_AVAIL) &&
        wrap_counter != !!(desc->flags & VRING_DESC_F_USED);
}

我們看到這個判斷邏輯原理和之前我們講的packed方式中avail和uesd desc是如果區分的相同。即avail desc需要VRING_DESC_F_AVAIL這個flag的設置和avail wrap_counter一致,且VRING_DESC_F_USED的設置和avail wrap_counter相反。

下面回頭看update_shadow_used_ring_packed函數,這個函數將當前使用的desc chian信息同步到shadow_used_packed ring 中。

update_shadow_used_ring_packed

static __rte_always_inline void
update_shadow_used_ring_packed(struct vhost_virtqueue *vq,
             uint16_t desc_idx, uint32_t len, uint16_t count)
{
    uint16_t i = vq->shadow_used_idx++;

    vq->shadow_used_packed[i].id = desc_idx; /*desc chain最後一個avail desc的buffer id*/
    vq->shadow_used_packed[i].len = len;
    vq->shadow_used_packed[i].count = count;
}

注意這裏的count是當前desc chain中使用的desc個數,desc_idx是當前desc chain使用的最後一個desc的buffer idx,而split方式shadow_used的id記錄的是當前desc chain頭部desc的id。

另外一個關鍵的地方,shadow_used_packed相對shadow_used_split 多了一個count字段,用來記錄當前desc chain中使用的desc個數。這個作用我們後面馬上分析。

flush_shadow_used_ring_packed函數用來根據shadow_used ring的信息更新uesd ring。我們看其具體實現。

update_shadow_used_ring_split

static __rte_always_inline void
update_shadow_used_ring_split(struct vhost_virtqueue *vq,
             uint16_t desc_idx, uint32_t len)
{
    uint16_t i = vq->shadow_used_idx++;

    vq->shadow_used_split[i].id = desc_idx;
    vq->shadow_used_split[i].len = len;
}

static __rte_always_inline void
flush_shadow_used_ring_packed(struct virtio_net *dev,
            struct vhost_virtqueue *vq)
{
    int i;
    uint16_t used_idx = vq->last_used_idx;

    /* Split loop in two to save memory barriers */
    for (i = 0; i < vq->shadow_used_idx; i++) {
        vq->desc_packed[used_idx].id = vq->shadow_used_packed[i].id;
        vq->desc_packed[used_idx].len = vq->shadow_used_packed[i].len;
        /* count的作用就一個是用來判斷desc ring發送迴繞 */
        used_idx += vq->shadow_used_packed[i].count;
        if (used_idx >= vq->size)
            used_idx -= vq->size;
    }

    rte_smp_wmb();
    /* 將desc 標記爲uesd desc */
    for (i = 0; i < vq->shadow_used_idx; i++) {
        uint16_t flags;

        if (vq->shadow_used_packed[i].len)
            flags = VRING_DESC_F_WRITE;
        else
            flags = 0;

        if (vq->used_wrap_counter) {
            flags |= VRING_DESC_F_USED;
            flags |= VRING_DESC_F_AVAIL;
        } else {
            flags &= ~VRING_DESC_F_USED;
            flags &= ~VRING_DESC_F_AVAIL;
        }

        vq->desc_packed[vq->last_used_idx].flags = flags;
        /* log page 更新熱遷移bitmap*/
        vhost_log_cache_used_vring(dev, vq,
                    vq->last_used_idx *
                    sizeof(struct vring_packed_desc),
                    sizeof(struct vring_packed_desc));
         /* count的作用另一個是用來更新last_used_idx */
        vq->last_used_idx += vq->shadow_used_packed[i].count;
        if (vq->last_used_idx >= vq->size) {
            vq->used_wrap_counter ^= 1;
            vq->last_used_idx -= vq->size;
        }
    }

    rte_smp_wmb();
    vq->shadow_used_idx = 0;
    vhost_log_cache_sync(dev, vq);
}

注意爲什麼先更新uesd desc的其他字段,最後才一起更新flag,而不是第一次循環就一起吧flag更新了,這個原因其實我們前面講“buffer id”的時候已經說明了。對端(前端)是更加desc 的flag判斷desc是否可用的,所以在更新flag需要有memory barrier,確保其他字段以及正確初始化到內存,爲了減少memory barrier的調用,所以單獨進行flag更新。

這個函數需要關注的地方還是比較多的。首先我們看到shadow_used_packed 中count字段的作用,其一是用來判斷desc ring發送迴繞,可以看到packed方式uesd desc在desc中不是連續的,而是會跳隔:used_idx += vq->shadow_used_packed[i].count,這個在split中是不存在的,因爲split中uesd有單獨的ring,所以uesd是連續的,直接就可以使用起始位置和要拷貝的uesd desc長度就可以判斷used ring迴繞了(注意一個是判斷desc ring迴繞,一個是判斷uesd ring迴繞,packed沒有單獨的uesd ring)。另外一個作用是用來更新last_used_idx,packed中last_used_idx表示的是使用的desc chain的最後的desc idx,而split方式中表示的是使用的desc chain的header idx。

然後就是標記desc爲uesd desc,我們之前已經講過,uesd desc需要VRING_DESC_F_USED和VRING_DESC_F_AVAIL一致且和used_wrap_counter一致。

原文鏈接:http://blog.chinaunix.net/uid-8574039-id-5826432.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章