spdk_nvme_helloworld分析

概述

本文基于SPDK v23.1版本的hello_world示例來說明SPDK的nvme命令處理流程,代碼架構(gòu)如下:

example\nvme\hello_world.c

int main(int argc, char **argv)
{
    spdk_env_opts_init(&opts);
    rc = parse_args(argc, argv, &opts);  // 參數(shù)解析

    opts.name = "hello_world";
    if (spdk_env_init(&opts) < 0) {  // spdk環(huán)境初始化,最終調(diào)用的是dpdk的環(huán)境初始化
    }

    // 掃描設(shè)備,并將驅(qū)動和設(shè)備綁定,調(diào)用用戶提供的回調(diào)`probe_cb`和`attach_cb`
    rc = spdk_nvme_probe(&g_trid, NULL, probe_cb, attach_cb, NULL); 

    hello_world(); // IO qpair創(chuàng)建循集、nvme的讀寫
    cleanup();     // 資源釋放

    return rc;
}

標(biāo)準(zhǔn)的NVMe處理涉及到NVMe子系統(tǒng)、HOST CPU蔗草、HOST 內(nèi)存三方面暇榴,下圖展示這三者之間的關(guān)系:

  • NVMe子系統(tǒng)作為PCIe總線的Endpoint存在,可以直接與RC連接蕉世,也可以通過一個Switch連接到PCIe總線蔼紧;
  • NVMe命令存放在HOST內(nèi)存的SQ中,命令處理完NVMe子系統(tǒng)會生成一個完成命令并放到HOST內(nèi)存的CQ中狠轻;
  • NVMe協(xié)議對于SQ/CQ的個數(shù)并沒有要求一一對應(yīng)奸例,但是SPDK中是一一對應(yīng)的都放在一個qpair中


    nvme子系統(tǒng).png

初始化SPDK環(huán)境

接口:int spdk_env_init(const struct spdk_env_opts *opts)

這里需要的參數(shù)opts可以通過接口spdk_env_opts_init(&opts)來設(shè)置,以及通過當(dāng)前程序提供的參數(shù)來修改parse_args(argc, argv, &opts)

默認(rèn)的opts參數(shù)配置如下:

[ DPDK EAL parameters: hello_world --no-shconf -c 0x1 --huge-unlink --log-level=lib.eal:6 --log-level=lib.cryptodev:5 --log-level=user1:6 --iova-mode=pa --base-virtaddr=0x200000000000 --match-allocations --file-prefix=spdk_pid76450 ]

最終調(diào)用DPDK的接口rte_eal_init來完成SPDK環(huán)境的初始化向楼,為后面的操作做好準(zhǔn)備工作

設(shè)備查找

設(shè)備的注冊

SPDK對設(shè)備的管理類似Linux的設(shè)備驅(qū)動模型:包含bus查吊、devicedriver三個部分湖蜕。如下:

/**
 * Structure describing the PCI bus
 */
struct rte_pci_bus {
    struct rte_bus bus;               /**< Inherit the generic class */
    RTE_TAILQ_HEAD(, rte_pci_device) device_list; /**< List of PCI devices */
    RTE_TAILQ_HEAD(, rte_pci_driver) driver_list; /**< List of PCI drivers */
};

另外逻卖,SPDK對于傳輸使用的協(xié)議或者總線虛擬化成一個transport,主要包含PCIE昭抒、TCP评也、FabricRDMA等類型灭返。本文是基于example\nvme\hello_world來說明盗迟,此示例使用的是PCIE類型的transport

main函數(shù)執(zhí)行之前會進(jìn)行設(shè)備的注冊,SPDK中使用的是gnuattribute特性來實(shí)現(xiàn)

  1. bus注冊

       RTE_REGISTER_BUS(pci, rte_pci_bus.bus);
    
       #define RTE_REGISTER_BUS(nm, bus) \
       RTE_INIT_PRIO(businitfn_ ##nm, BUS) \
       {\
           (bus).name = RTE_STR(nm);\
           rte_bus_register(&bus); \         // TAILQ_INSERT_TAIL(&rte_bus_list, bus, next);熙含,將bus放到rte_bus_list鏈表中罚缕,pci對應(yīng)的bus為rte_pci_bus.bus
       }
    
       #define RTE_INIT_PRIO(func, prio) \
       static void __attribute__((constructor(RTE_PRIO(prio)), used)) func(void)
    
  2. driver注冊前半部分

       SPDK_PCI_DRIVER_REGISTER(nvme, nvme_pci_driver_id,
            SPDK_PCI_DRIVER_NEED_MAPPING | SPDK_PCI_DRIVER_WC_ACTIVATE);
    
       #define SPDK_PCI_DRIVER_REGISTER(name, id_table, flags) \
       __attribute__((constructor)) static void _spdk_pci_driver_register_##name(void) \
       { \
           spdk_pci_driver_register(#name, id_table, flags); \   // 會將driver加到鏈表g_pci_drivers中
       }      
    
  3. transport注冊

       SPDK_NVME_TRANSPORT_REGISTER(pcie, &pcie_ops);
         .name = "PCIE",
         .type = SPDK_NVME_TRANSPORT_PCIE,
    

    注冊使用的宏如下(黃色部分保證宏在main之前執(zhí)行,這種用法在spdk中很多怎静,用到時(shí)再記錄):

       #define SPDK_NVME_TRANSPORT_REGISTER(name, transport_ops) \
       static void __attribute__((constructor)) _spdk_nvme_transport_register_##name(void) \
       { \
           spdk_nvme_transport_register(transport_ops); \
       }
       spdk_nvme_transport_register(transport_ops); //在g_spdk_transports中申請一個空間存放這種transport對應(yīng)的ops邮弹,并且把申請的空間放到鏈表g_spdk_nvme_transports中
    ```c
    
    
  4. main函數(shù)執(zhí)行過程中的spdk初始化中會做設(shè)備的注冊以及driver的后半部注冊黔衡,將設(shè)備和驅(qū)動都注冊到bus中

spdk_env_init.png

兩個回調(diào)接口

probe_cb: 找到NVMe controller之后進(jìn)行回調(diào),hello_world示例中只做了一條日志打印腌乡。
attach_cb: NVMe controller連接到用戶空間驅(qū)動程序后調(diào)用盟劫,hello_world示例中做了兩件事,一是將初始化好的controller連接到g_controllers中导饲;二是將NS注冊到controller中捞高。

設(shè)備查找流程

總體入口為SPDK接口

int
spdk_nvme_probe(const struct spdk_nvme_transport_id *trid, void *cb_ctx,
        spdk_nvme_probe_cb probe_cb, spdk_nvme_attach_cb attach_cb,
        spdk_nvme_remove_cb remove_cb)

其中三個callback參數(shù)都可以用戶自定義氯材,在hello_world示例中使用了probe_cbattach_cb

整體調(diào)用關(guān)系如下圖所示

image-20230314135128751.png
  • 調(diào)用DPDK的接口dpdk_bus_probe將device和對應(yīng)的driver做綁定操作
  • 調(diào)用用戶自定義probe_cb
  • 創(chuàng)建controller渣锦、admin qpaircontroller bar空間等
  • 初始化controller
  • 調(diào)用用戶自定義的attach_cb

其中QPair是SPDK的一種結(jié)構(gòu)如下圖所示(Admin和IO是一樣的結(jié)構(gòu)):

qpair結(jié)構(gòu).png
  • SQ和CQ內(nèi)存區(qū)域既可基于DPDK所管理的大頁內(nèi)存來構(gòu)建氢哮,也可基于CMB進(jìn)行構(gòu)建袋毙。
  • 為了便于請求對象的復(fù)用管理,每個QP會引入一個free_req對象池來緩存nvme_request對象實(shí)例冗尤,同時(shí)還會引入一個free_tr對象池(緩存nvme_tracker對象听盖,索引為cmdId),來跟蹤每個nvme_request的執(zhí)行情況(在其執(zhí)行結(jié)束時(shí)觸發(fā)相應(yīng)的回調(diào))裂七,在nvme_request的內(nèi)部主要維護(hù)了spdk_nvme_cmd數(shù)據(jù)結(jié)構(gòu)皆看,由于其和SQ采用不同的物理空間,因此在提交命令的時(shí)候需要做一次數(shù)據(jù)拷貝背零。
  • 針對執(zhí)行失敗的請求腰吟,QP并不會將其丟棄,而是先加入queued_req隊(duì)列徙瓶,以便后續(xù)做retry處理毛雇,當(dāng)queued_req不為空的時(shí)候,后續(xù)新的請求都會提交到該隊(duì)列侦镇,以保證之前失敗的請求先做執(zhí)行灵疮。
  • 最后,每個QP還會綁定兩個doorbell寄存器(每個doorbell占用4字節(jié)空間)壳繁,以便向設(shè)備控制通知cmd的就緒情況震捣,以及cpl的完成情況(基于MMIO方式更新)

IO處理

創(chuàng)建IO qpair

根據(jù)NVMe協(xié)議要求:先創(chuàng)建IO CQ再創(chuàng)建IO SQ,如下圖所示

image-20230314141340176.png
  1. SPDK使用polling機(jī)制來檢查CQ闹炉,而不是使用MSI/MSI-X等中斷形式伍派,在創(chuàng)建CQ時(shí)對DW11的IV/IEN字段都設(shè)置為0,只設(shè)置了PC字段為1(即使用連續(xù)地址)

    代碼如下:

    int
    nvme_pcie_ctrlr_cmd_create_io_cq(struct spdk_nvme_ctrlr *ctrlr,
                  struct spdk_nvme_qpair *io_que, spdk_nvme_cmd_cb cb_fn,
                  void *cb_arg)
    {
     ......
     req = nvme_allocate_request_null(ctrlr->adminq, cb_fn, cb_arg);
     ......
     cmd->cdw11_bits.create_io_cq.pc = 1;
     cmd->dptr.prp.prp1 = pqpair->cpl_bus_addr;
    
     return nvme_ctrlr_submit_admin_request(ctrlr, req);
    }
    
    

    NVMe協(xié)議描述如下:

    image-20230315155753473.png
  1. 通過循環(huán)檢查CQE中的Phase Tag(P)字段來確定哪些是新來的CQE

    代碼如下:

    int32_t
    nvme_pcie_qpair_process_completions(struct spdk_nvme_qpair *qpair, uint32_t max_completions)
    {
     ......
     pqpair->stat->polls++;
    
     while (1) {
         cpl = &pqpair->cpl[pqpair->cq_head];
    
         ......
         next_cpl = &pqpair->cpl[next_cq_head];
         next_is_valid = (next_cpl->status.p == next_phase);
         if (next_is_valid) {
             __builtin_prefetch(&pqpair->tr[next_cpl->cid]);
         }
         ......
    
         tr = &pqpair->tr[cpl->cid];
         /* Prefetch the req's STAILQ_ENTRY since we'll need to access it
          * as part of putting the req back on the qpair's free list.
          */
         __builtin_prefetch(&tr->req->stailq);
         pqpair->sq_head = cpl->sqhd;
    
         if (tr->req) {
             nvme_pcie_qpair_complete_tracker(qpair, tr, cpl, true);
         } else {
             SPDK_ERRLOG("cpl does not map to outstanding cmd\n");
             spdk_nvme_qpair_print_completion(qpair, cpl);
             assert(0);
         }
    
         if (++num_completions == max_completions) {
             break;
         }
     }
    
     ......
    
     return num_completions;
    }
    

    NVMe協(xié)議對CQE中Phase Tag的描述如下

image-20230315161226333.png
  1. Create IO CQ命令提交時(shí)SPDK設(shè)置了一個回調(diào)接口剩胁,在收到此命令的CQE之后調(diào)用此回調(diào)函數(shù)诉植,而在此回調(diào)函數(shù)中做了Create IO SQ的命令處理,命令處理過程與Create IO CQ類似

構(gòu)造IO讀寫

SPDK中的命令處理流程為:一個命令執(zhí)行之前會添加此命令執(zhí)行完成之后的回調(diào)接口昵观,也即當(dāng)此命令執(zhí)行完并且收到對應(yīng)CQE時(shí)會調(diào)用回調(diào)接口晾腔。

所以Hello_world示例通過此特性來實(shí)現(xiàn)了先寫再讀的操作舌稀,在寫命令時(shí)設(shè)置回調(diào)write_complete,而在write_complete里面執(zhí)行nvme的讀操作灼擂,操作流程與前面的create_io_cqcreate_io_sq類似

image-20230316093212680.png

參考

《NVMe用戶態(tài)驅(qū)動》

《SPDK源碼》

《剖析SPDK讀寫NVMe盤過程--從hello_world開始》

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末壁查,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子剔应,更是在濱河造成了極大的恐慌睡腿,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件峻贮,死亡現(xiàn)場離奇詭異席怪,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)纤控,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進(jìn)店門挂捻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人船万,你說我怎么就攤上這事刻撒。” “怎么了耿导?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵声怔,是天一觀的道長。 經(jīng)常有香客問我舱呻,道長醋火,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任狮荔,我火速辦了婚禮胎撇,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘殖氏。我一直安慰自己晚树,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布雅采。 她就那樣靜靜地躺著爵憎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪婚瓜。 梳的紋絲不亂的頭發(fā)上宝鼓,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天,我揣著相機(jī)與錄音巴刻,去河邊找鬼愚铡。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的沥寥。 我是一名探鬼主播碍舍,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼邑雅!你這毒婦竟也來了片橡?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤淮野,失蹤者是張志新(化名)和其女友劉穎捧书,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體骤星,經(jīng)...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡经瓷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了妈踊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片了嚎。...
    茶點(diǎn)故事閱讀 38,626評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡泪漂,死狀恐怖廊营,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情萝勤,我是刑警寧澤露筒,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站敌卓,受9級特大地震影響慎式,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜趟径,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一瘪吏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蜗巧,春花似錦掌眠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至望拖,卻和暖如春渺尘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背说敏。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工鸥跟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人盔沫。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓医咨,卻偏偏與公主長得像蚂夕,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子腋逆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評論 2 348

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