DPDK多線程初步解析

上一篇文章中談到DPDK是一個(gè)高性能的用戶態(tài)驅(qū)動(dòng),改變了網(wǎng)卡驅(qū)動(dòng)原先的中斷為輪詢的模式,那么它的性能到底有多強(qiáng),用數(shù)據(jù)來說明吧。

1. DPDK性能有多強(qiáng)

DPDK的一個(gè)處理器核每秒可以處理約33M個(gè)報(bào)文,大概30納秒處理一個(gè)報(bào)文,在處理器頻率2.7GHz的情況下,處理一個(gè)數(shù)據(jù)報(bào)文需要80個(gè)時(shí)鐘周期。

在傳統(tǒng)的方法上,一個(gè)數(shù)據(jù)報(bào)文到達(dá)網(wǎng)口后,會(huì)經(jīng)歷如下過程:

  1. 寫接受描述符到內(nèi)存,填充數(shù)據(jù)緩沖區(qū)指針,網(wǎng)卡接收到報(bào)文后就根據(jù)該地址把報(bào)文內(nèi)容填進(jìn)去。
  2. 從內(nèi)存中讀取接收描述符(到接收到報(bào)文時(shí),網(wǎng)卡會(huì)更新該結(jié)構(gòu)),從而確認(rèn)是否收到報(bào)文。
  3. 從接收描述符確認(rèn)收到報(bào)文時(shí),從內(nèi)存中讀取控制結(jié)構(gòu)體的指針,再?gòu)膬?nèi)存中讀取控制結(jié)構(gòu)體,把從接收描述符中讀取的信息填充到該控制結(jié)構(gòu)體。
  4. 更新接收隊(duì)列寄存器,表示軟件接收到了新的報(bào)文。
  5. 從內(nèi)存讀取報(bào)文頭部,決定轉(zhuǎn)發(fā)端口。
  6. 從控制結(jié)構(gòu)體把報(bào)文信息填入到發(fā)送隊(duì)列發(fā)送描述符中,更新發(fā)送隊(duì)列寄存器。
  7. 從內(nèi)存中讀取發(fā)送描述符,檢查是否有包被硬件發(fā)送出去。
  8. 如果有的話,則從內(nèi)存中讀取相應(yīng)控制結(jié)構(gòu)體,釋放數(shù)據(jù)緩沖區(qū)。

在這8個(gè)步驟中,有6次內(nèi)存讀,而處理器從一級(jí)cache讀需要3-5時(shí)鐘周期,二級(jí)是十幾個(gè)時(shí)鐘周期,三級(jí)是幾十個(gè)時(shí)鐘周期,而從內(nèi)存讀取數(shù)據(jù),由于收到NUMA架構(gòu)(可以理解為,內(nèi)存也分給了不同的核,每個(gè)核訪問自己的內(nèi)存特別快,訪問別的核的內(nèi)存則需要很長(zhǎng)時(shí)間)的影響,尤其是不在一個(gè)Socket的核之間的內(nèi)存讀取,會(huì)花費(fèi)很長(zhǎng)時(shí)間,所以平均訪問內(nèi)存需要的時(shí)鐘周期大約是幾百個(gè)。處理一個(gè)報(bào)文80個(gè)時(shí)鐘周期,就要求數(shù)據(jù)在cache中,而且一旦不命中,性能會(huì)嚴(yán)重下降。

而在操作系統(tǒng)中,最容易造成性能下降的是線程的調(diào)度,尤其是核間線程的切換,最容易造成cache miss和cache write back。所以在DPDK中利用的是線程的CPU親和綁定的方式,來指定任務(wù)到不同的核上。再進(jìn)一步,可以限制一些核不參與Linux的系統(tǒng)調(diào)度,這樣就可以達(dá)到任務(wù)獨(dú)占的目的,最大限度地避免了cache不命中帶來的性能下降。

查閱DPDK資料,發(fā)現(xiàn)DPDK中的多線程是基于linux系統(tǒng)里的pthread實(shí)現(xiàn)的,lcore指的是EAL線程,并且在命令行參數(shù)中使用“-c”帶十六進(jìn)制參數(shù)作為coremask,該掩碼的意義是為二進(jìn)制數(shù)上為1的一位即表示將要綁定獨(dú)占的線程,例如:掩碼是16進(jìn)制的f,二進(jìn)制對(duì)應(yīng)為1111,即表示cpu0、cpu1、cpu2、cpu3作為邏輯核為程序所用。

2. lcore的初始化如下:
  1. rte_eal_cpu_init()函數(shù)中,通過讀取/sys/devices/system/cpu/cpuX/下的相關(guān)信息,確定當(dāng)前系統(tǒng)有哪些核,以及分別屬于哪些socket(這里的socket是NUMA架構(gòu)中socket,不是網(wǎng)絡(luò)中的套接字)。
  2. eal_parse_args()函數(shù),解析-c參數(shù),確認(rèn)哪些核是可以用的,并且設(shè)置第一個(gè)核為MASTER。
  3. 為每一個(gè)SLAVE核創(chuàng)建線程,并調(diào)用eal_thread_set_affinity()綁定CPU,每個(gè)線程的執(zhí)行的其實(shí)是一個(gè)主體是while死循環(huán)的調(diào)用不同模塊注冊(cè)到lcore_config[lcore_id].f的回調(diào)函數(shù)eal_thread_loop()。

*注:在eal_thread_loop()中,將線程綁定核,然后置于了等待的狀態(tài)。綁定核函數(shù)基于linux原型函數(shù)f_pthread_setaffinity_np,在pthread_shim.c中有對(duì)各種pthread函數(shù)封裝的實(shí)現(xiàn)。

3. lcore的注冊(cè):

不同模塊需要調(diào)用rte_eal_mp_remote_launch(),將自己的回調(diào)函數(shù)注冊(cè)到config[].f中。每個(gè)核上的線程都會(huì)調(diào)用該函數(shù)來實(shí)現(xiàn)自己的處理函數(shù)。lcore啟動(dòng)過程和任務(wù)分發(fā)如下:

多核任務(wù)分發(fā).png

另外,由于現(xiàn)網(wǎng)往往有流量潮汐的影響,所以為了尋求靈活的擴(kuò)展能力,EAL pthread與邏輯核之間允許打破1:1的綁定關(guān)系,允許綁定一個(gè)特定的lcore ID或者lcore ID組。


4. 程序解析

在example文件夾中,我們來看一個(gè)最簡(jiǎn)單的hello world程序。它建立了一個(gè)多核運(yùn)行的環(huán)境,每個(gè)線程都會(huì)打印“hello from core #”,有點(diǎn)類似pthread的入門程序。

注意:在DPDK代碼中,rte(runtime environment)開頭的函數(shù)是作為給開發(fā)者直接調(diào)用的接口,也就是說,只是使用DPDK的話,只要知曉這些函數(shù)的參數(shù)和作用,會(huì)調(diào)用即可,eal(environment abstraction layer)是DPDK核心庫(kù)中提供系統(tǒng)抽象的部分,因?yàn)殡m然現(xiàn)在的源碼是基于linux或者FreeBSD系統(tǒng)運(yùn)行,但它最早期的代碼是不依賴于操作系統(tǒng)的,就像自己本身就是個(gè)mini-os一樣。

helloword的代碼如下:

#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#include <sys/queue.h>

#include <rte_memory.h>
#include <rte_memzone.h>
#include <rte_launch.h>
#include <rte_eal.h>
#include <rte_per_lcore.h>
#include <rte_lcore.h>
#include <rte_debug.h>

static int
lcore_hello(__attribute__((unused)) void *arg)
{
    unsigned lcore_id;
    lcore_id = rte_lcore_id();
    printf("hello from core %u\n", lcore_id);
    return 0;
}

int
main(int argc, char **argv)
{
    int ret;
    unsigned lcore_id;

    ret = rte_eal_init(argc, argv);
    if (ret < 0)
        rte_panic("Cannot init EAL\n");

    /* call lcore_hello() on every slave lcore */
    RTE_LCORE_FOREACH_SLAVE(lcore_id) {
        rte_eal_remote_launch(lcore_hello, NULL, lcore_id);
    }

    /* call it on master lcore too */
    lcore_hello(NULL);

    rte_eal_mp_wait_lcore();
    return 0;
}

rte_eal_init(argc, argv)中兩個(gè)命令行入口參數(shù),可以是一系列很長(zhǎng)很復(fù)雜的設(shè)置,從頂往下追溯:
rte_eal_init→eal_log_level_parse→eal_parse_common_option,發(fā)現(xiàn)在該函數(shù)中,便是對(duì)common opinion進(jìn)行設(shè)置的地方。common opinion如下所示,分別用于命令行設(shè)置不同的值。

const char
eal_short_options[] =
    "b:" /* pci-blacklist */
    "c:" /* coremask */
    "d:" /* driver */
    "h"  /* help */
    "l:" /* corelist */
    "m:" /* memory size */
    "n:" /* memory channels */
    "r:" /* memory ranks */
    "v"  /* version */
    "w:" /* pci-whitelist */
    ;

其中最重要的就是-c,設(shè)置核掩碼,這塊內(nèi)容上面已經(jīng)說過了,運(yùn)行效果如下:

掩碼為1111.png
掩碼為1110.png

整體代碼的結(jié)構(gòu)很像pthread寫的多線程程序,先rte_eal_init()進(jìn)行一系列很復(fù)雜的初始化工作,在官方文檔上寫的這些初始化工作包括:

  • 配置初始化
  • 內(nèi)存初始化
  • 內(nèi)存池初始化
  • 隊(duì)列初始化
  • 告警初始化
  • 中斷初始化
  • PCI初始化
  • 定時(shí)器初始化
  • 檢測(cè)內(nèi)存本地化(NUMA)
  • 插件初始化
  • 主線程初始化
  • 輪詢?cè)O(shè)備初始化
  • 建立主從線程通道
  • 將從線程設(shè)置為等待模式
  • PCI設(shè)備的探測(cè)和初始化

然后RTE_LCORE_FOREACH_SLAVE遍歷所有EAL指定可以使用lcore,通過rte_eal_remote_launch在每個(gè)lcore上,啟動(dòng)指定的線程。

需要注意的是lcore_id是一個(gè)unsigned變量,其實(shí)際作用就相當(dāng)于循環(huán)變量i,因?yàn)楹闞TE_LCORE_FOREACH_SLAVE里會(huì)啟動(dòng)for循環(huán)來遍歷所有可用的核。

#define RTE_LCORE_FOREACH_SLAVE(i)                  \
    for (i = rte_get_next_lcore(-1, 1, 0);              \
         i<RTE_MAX_LCORE;                       \
         i = rte_get_next_lcore(i, 1, 0))

在函數(shù)rte_eal_remote_launch(int (*f)(void *), void *arg, unsigned slave_id))中,第一個(gè)參數(shù)是從線程要調(diào)用的函數(shù),第二個(gè)參數(shù)是調(diào)用的函數(shù)的參數(shù),第三個(gè)參數(shù)是指定的邏輯核。詳細(xì)的函數(shù)執(zhí)行過程如下:

int
rte_eal_remote_launch(int (*f)(void *), void *arg, unsigned slave_id)
{
    int n;
    char c = 0;
    int m2s = lcore_config[slave_id].pipe_master2slave[1]; //主線程對(duì)從線程的管道,管道是一個(gè)大小為2的int數(shù)組
    int s2m = lcore_config[slave_id].pipe_slave2master[0]; //從線程對(duì)主線程的管道

    if (lcore_config[slave_id].state != WAIT)
        return -EBUSY;

    lcore_config[slave_id].f = f;
    lcore_config[slave_id].arg = arg;

    /* send message */
    n = 0;
    while (n == 0 || (n < 0 && errno == EINTR))
        n = write(m2s, &c, 1);     //此處是調(diào)用的linux庫(kù)函數(shù)
    if (n < 0)
        rte_panic("cannot write on configuration pipe\n");

    /* wait ack */
    do {
        n = read(s2m, &c, 1); 
    } while (n < 0 && errno == EINTR);

    if (n <= 0)
        rte_panic("cannot read on configuration pipe\n");

    return 0;
}

lcore_config中的pipe_master2slave[2]和pipe_slave2master[2]分別是主線程到從線程核從線程到主線程的管道,與linux中的管道一樣,是一個(gè)大小為2的數(shù)組,數(shù)組的第一個(gè)元素為讀打開,第二個(gè)元素為寫打開。在這調(diào)用了linux庫(kù)函數(shù)read核write,把c作為消息傳遞。管道的模型如下圖所示:


管道模型

這樣,每個(gè)從線程通過rte_eal_remote_launch函數(shù)運(yùn)行了自定義函數(shù)lcore_hello就打印出了“hello from core #”的輸出。

注:此篇文章部分引用自《深入淺出DPDK》中的觀點(diǎn)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,527評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,687評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,640評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,957評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,682評(píng)論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,011評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,009評(píng)論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,183評(píng)論 0 290
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,714評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,435評(píng)論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,665評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,148評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,838評(píng)論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,251評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,588評(píng)論 1 295
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,379評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,627評(píng)論 2 380

推薦閱讀更多精彩內(nèi)容