DPDK QOS 5 -- Enqueue && Dequeue詳細流程分析

入隊流程

我們先看看,官網上是怎麼說的:

Enqueue Pipeline

The sequence of steps per packet:

Access the mbuf to read the data fields required to identify the destination queue for the packet. These fields are: port, subport, traffic class and queue within traffic class, and are typically set by the classification stage.

Access the queue structure to identify the write location in the queue array. If the queue is full, then the packet is discarded.

Access the queue array location to store the packet (i.e. write the mbuf pointer).

It should be noted the strong data dependency between these steps, as steps 2 and 3 cannot start before the result from steps 1 and 2 becomes available, which prevents the processor out of order execution engine to provide any significant performance optimizations.

Given the high rate of input packets and the large amount of queues, it is expected that the data structures accessed to enqueue the current packet are not present in the L1 or L2 data cache of the current core, thus the above 3 memory accesses would result (on average) in L1 and L2 data cache misses. A number of 3 L1/L2 cache misses per packet is not acceptable for performance reasons.

The workaround is to prefetch the required data structures in advance. The prefetch operation has an execution latency during which the processor should not attempt to access the data structure currently under prefetch, so the processor should execute other work. The only other work available is to execute different stages of the enqueue sequence of operations on other input packets, thus resulting in a pipelined implementation for the enqueue operation.

Fig. 41.6 illustrates a pipelined implementation for the enqueue operation with 4 pipeline stages and each stage executing 2 different input packets. No input packet can be part of more than one pipeline stage at a given time.

 

Fig. 41.6 Prefetch Pipeline for the Hierarchical Scheduler Enqueue Operation

The congestion management scheme implemented by the enqueue pipeline described above is very basic: packets are enqueued until a specific queue becomes full, then all the packets destined to the same queue are dropped until packets are consumed (by the dequeue operation). This can be improved by enabling RED/WRED as part of the enqueue pipeline which looks at the queue occupancy and packet priority in order to yield the enqueue/drop decision for a specific packet (as opposed to enqueuing all packets / dropping all packets indiscriminately).

根據上面瞭解大概的流程,我們來梳理整個入隊的過程:

VPP 的work線程把報文入對應接口的Ring隊列的時候,需要根據一定的規則分類數據包(FILTER、FLOW、規則),並設置報文需要入隊的subport\pipe\tc\queue參數(如何根據分類確定這4個參數,也就是不同的CLASS),並存放在報文描述符的sched字段裏面。

然後HQOS線程從Ring隊列出隊報文,開始真正的QOS入隊流程,函數是rte_sched_port_enqueue

DPDK的QOS入隊流程實現了4級流水線,每次流水線同時處理2個報文,除去stage 0獲取報文,每次流水線可以處理6個報文,如果入隊的報文數不夠6個,會走到小於6個報文的處理流程,我們先梳理清楚這個邏輯,在來看後面的流水線實現。

1、預取所有報文的rte_mbuf到L1 Cache。

2、然後每個報文調用rte_sched_port_enqueue_qptrs_prefetch0,完成根據報文攜帶的sched字段完成subport、pipe、tc、queue值的解析,然後根據這四個值,根據前面的數據結構的關係計算出具體struct rte_sched_queue *queue的qindex,就是前面圖裏面的q0對應的下標,然後得到具體要入隊的queue,同時預取queue。

3、然後每個報文調用rte_sched_port_qbase,根據隊列的qindx定位真正入隊的基地址

port->queue_array + (qindex >> 4)* port->qsize_sum + port->qsize_add[qindex & 0xF]) 獲取存放rte_mbuf指針的指定隊列基地址qbase。

獲取了qbase後調用rte_sched_port_enqueue_qwa_prefetch0 獲取隊列大小以及指定的隊列寫入的位置,然後預取要寫入的位置以及qindx對應的位圖的位置。

    qsize = rte_sched_port_qsize(port, qindex);

    q_qw = qbase + (q->qw & (qsize - 1));

4、走完前面的3步終於報文要入真正的隊列了,調用函數rte_sched_port_enqueue_qwa完成真正的入隊,期間可能被Tail Drop或者被WRED丟棄,如果沒有被丟棄,就會真正的入隊,並且置位qindex對應的位圖,好讓dequeue出隊報文。

            /* Enqueue packet */

        qbase[q->qw & (qsize - 1)] = pkt;

        q->qw++;

        /* Activate queue in the port bitmap */

        rte_bitmap_set(port->bmp, qindex);

 

流水線

如果報文>=6個,就會走到我們4級流水線的流程裏面,讓我們繼續來看

準備工作

爲了讓4級流水線跑起來,我們需要做準備工作如:

運行流水線

當準備工作完成後,我們開始了真正的流水線處理,整體流程如下,需要和前面的準備工作結合一起看:

收尾工作

如果報文剛好6個或者不是2的倍數,我們需要做收尾的工作如下:

 

 

出隊流程

我們先看看,官網上是怎麼說的出隊流程:

Dequeue State Machine

The sequence of steps to schedule the next packet from the current pipe is:

Identify the next active pipe using the bitmap scan operation, prefetch pipe.

Read pipe data structure. Update the credits for the current pipe and its subport. Identify the first active traffic class within the current pipe, select the next queue using WRR, prefetch queue pointers for all the 16 queues of the current pipe.

Read next element from the current WRR queue and prefetch its packet descriptor.

Read the packet length from the packet descriptor (mbuf structure). Based on the packet length and the available credits (of current pipe, pipe traffic class, subport and subport traffic class), take the go/no go scheduling decision for the current packet.

To avoid the cache misses, the above data structures (pipe, queue, queue array, mbufs) are prefetched in advance of being accessed. The strategy of hiding the latency of the prefetch operations is to switch from the current pipe (in grinder A) to another pipe (in grinder B) immediately after a prefetch is issued for the current pipe. This gives enough time to the prefetch operation to complete before the execution switches back to this pipe (in grinder A).

The dequeue pipe state machine exploits the data presence into the processor cache, therefore it tries to send as many packets from the same pipe TC and pipe as possible (up to the available packets and credits) before moving to the next active TC from the same pipe (if any) or to another active pipe.

Fig. 41.7 Pipe Prefetch State Machine for the Hierarchical Scheduler Dequeue Operation

QOS調度就是根據HQOS每個層的調度算法選擇一個此時優先級最高的報文來發送。

DPDK在PIPE層是採用輪詢的方法調度,也就是WRR 1:1的來調度,也就是說,每個PIPE之間是平等的,沒有先後之分,誰準備好了數據誰就發送;

對於同一PIPE中的4個TC,採用SP的調度,對於同一TC下的4個隊列,採用的是WRR(比例可配置)的方法。
爲了加速內存數據的讀取,充分利用cache,DPDK分4個階段來讀取 QOS 隊列中的報文,也就是上面說的狀態機,流水線,從隊列中調度出一個報文用於發送。

出隊函數

grinder

爲了瞭解狀態機的運行,我們先來認識下grinder。

數據結構如下:

出隊狀態機的4個階段中

e_GRINDER_PREFETCH_PIPE,

e_GRINDER_PREFETCH_TC_QUEUE_ARRAYS,

e_GRINDER_PREFETCH_MBUF,

e_GRINDER_READ_MBUF,

前面的一個階段是爲了後面的一個階段讀取數據做內存預取準備工作, 每個階段都需要等待前一階段內存準備好後才能執行,由於latency of the prefetch,每個階段之間就有一個時間空閒,如果CPU連續地執行上面 4 個階段,則預取內存的效果沒有得到體現。爲了有效利用CPU,充分利用cache,在操作的時候內存已經在cache裏面,dequeue設計了8個grinder,每一個 grinder執行4個階段的步驟,輪留執行8個grinder,每個grinder只執行一個階段,這樣,可以保證每個grinder階段執行時,本階段所要訪問的數據已經在緩存中準備好了,相當於實現了入隊差不多的流水線。

還有8個grinder分階段執行的好處還在於實現了各 PIPE之間的隨機輪詢,誰先準備好數據誰先發送。

圖如下:

 

狀態機

 PREFETCH_PIPE

這個狀態是初始化的狀態,主要是函數grinder_next_pipe獲取下一個pipe,如果緩存的有的話直接從緩存獲取,如果沒有的話,scan 隊列的qindex位圖,一次獲取64 bit,從位圖中選擇下一個active的PIPE(active PIPE指至少有一個隊列有報文),並填充grinder裏面的pipe緩存,整體流程如下:

PREFETCH_TC_QUEUE_ARRAYS

這個狀態的處理比較簡單,整體流程如下:

PREFETCH_MBUF

這個流程更簡單了,根據前面獲取的qbase和隊列對應的qr獲取報文,此時並同時預取MBUF到cache line,爲下一個階段做好準備。

READ_MBUF

經過前面3個狀態的處理,完事具備了,就差最後出隊報文,我們來看下這個狀態的處理。

從報文描述符中獲取報文長度,然後去過令牌桶,如果有足夠的令牌,本TC可以發送報文。

如果不能發送,或者TC隊列爲空,那麼需要重新找下一個TC來發送。

如果當前PIPE的所有TC都沒有可以發送的報文,則需要重新找一個緩存的PIPE來發送。

如果當前grinder緩存的PIPE也沒有可以發送的報文,重新從位圖選取一個組新的PIPE cache,然後重新來一輪循環。

整體流程圖如下:

 

queue的WRR實現

在QOS裏面FQ或者WFQ隊列的WRR算法注重於在時間上調度上的公平性,即完全按照其權值來進行調度,誰的權值大,誰優先。

而本次DPDK的WRR更多的是在空間上考慮公平性,打散整個結果,而考慮其處於的位置也許是最好的方案,讓我們一起看下整個處理的過程:

初始化的時候根據傳入的隊列的weight[0 .. 3]的比例,根據最小公倍數計算每個隊列的cost,然後保存在wrr_cost[0..3]裏面,也就是權重越大的cost越小,如weight[0..3]={2:3:4:5},那麼wrr_cost[0..3]={30:20:15:12}

在grinder選中新的TC調度的時候,會調用grinder_wrr_load初始化grinder的WRR參數,wrr_tokens初始化爲0,wrr_mask如果有隊列初始化爲0xFFFF,否則初始化爲0, wrr_cost初始化爲前面計算的cost值

調度的時候調用grinder_wrr每次選中需要調度出隊的隊列,代碼如下:

經過第一步操作後,如果有激活的隊列wrr_tokens變成0,否則值變成0xFFFF。

第二步,選擇4個隊列中,wrr_tokens最小的,也就是本次需要出隊的隊列

第三步,所有隊列的wrr_tockens減去最小的wrr_tockes_min

那麼如何證明算法的正確性,我們先假設4個隊列的報文長度相等

假設有3個具有不同weight的{A:a,B:b,C:c}元素供選擇,cost值分別爲{A:b*c,B:a*c,C:a*b}在一輪循環a+b+c中,3個元素被選中的次數爲x\y\z,那麼

x + y + z = a + b +c

A的tokens新增 x*b*c

B的tokens新增 y*a*c

C的tokens新增 z*a*b

然後每次循環選擇tokens最小的隊列,所有的隊列都要減去這個值,那麼問題轉化爲,每個元素一輪遞增的tokens量中有多少個其它元素的個數。

x = x*b*c / (y*a*c + z*a*b)

y = y*a*c / (x*b*c + z*a*b)

z = z*a*b / (y*a*c + z*b*c)

解方程後可知,x = a, y = b, z = c

個人覺得有點繞,何不直接使用weight,不選最小的,選擇最大的即可。

目前的這個實現有下面的幾個問題:

  • 如果報文長度不相等,也就實現不了按比例調度了
  • 如果報文長度相等才實現按比例調度,也就沒有實現按照字節的比例調度,這個可以作爲後期的一個改進,但目前在葉子節點的問題不大

PIPE的WRR調度

PIPE 本次是放在8個grinder裏面調度的,誰有報文誰先發送,也可以簡單理解爲WRR爲1:1的調度,暫不考慮PIPE之間的如WRR[1:2:3………]的調度。

基於類的保證帶寬(限制帶寬)實現

經過上面的分析我們知道當前DPDK的QOS所有的PIPE是按照WRR調度,誰準備好誰就發送,PIPE下面的TC是SP調度,滿足不了CBWFQ裏面保證帶寬的需求。

要實現CBWFQ的保證帶寬和限制帶寬,需要修改DPDK QOS的代碼,後續實現後,在分析

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