編者按
軟件和硬件,既相互依存又需要某種程度上的相互獨立。通過軟件和硬件之間的接口把兩者連接在一起。軟硬件接口,有很多含義:比如指令集是CPU軟件和硬件之間的接口;比如一些硬件模塊(包括IO接口模塊、GPU、各種加速引擎等)暴露出來的可讀寫寄存器,則為控制接口;再比如,CPU和GPU或其他硬件模塊之間通過DMA進行數(shù)據(jù)交互的(軟硬件間的)數(shù)據(jù)傳輸接口。 軟硬件接口,是個非常龐大的命題。本文是《軟硬件融合》圖書內容的節(jié)選,聚焦在軟件和硬件之間的數(shù)據(jù)交互接口。
軟件和硬件之間數(shù)據(jù)交互接口
我們在計算機的基礎課程里一般都學過IO交互的四種模式:寄存器模式、中斷模式、DMA模式和通道模式。隨著計算機技術的發(fā)展,除了IO設備,還有很多獨立的硬件組件通過各種類型總線跟CPU連接在一起。接口已經(jīng)不僅僅是用于IO數(shù)據(jù)傳輸場景,也用于軟件(運行于CPU的程序)和其他硬件之間的數(shù)據(jù)交互場景。 注:本文用“軟硬件接口”特指軟件和硬件之間數(shù)據(jù)交互的接口。
1 軟硬件接口定義
傳統(tǒng)的非硬件緩存一致性總線,是需要軟件驅動顯式的控制設備來進行數(shù)據(jù)交互的。通過梳理軟硬件接口的演進,逐步給出軟硬件接口的定義。 a. 軟硬件接口演進
(a) CPU輪詢? ? ? ? ? ? ? ? ? ?(b) CPU中斷???? ?? ? ? ???? ? (c) DMA方式
(d) 共享隊列??? ? ? ???? ???
(e) 用戶態(tài)輪詢???? ?? ? ? ??? ?
(f) 多隊列 圖1 軟硬件接口的演進 軟硬件接口是在IO接口基礎上的擴展,如圖1,我們結合IO交互的四種模式,重新梳理一下軟硬件接口的演進:
第一階段,使用軟件輪詢硬件狀態(tài)。如圖1(a),最開始是通過軟件輪詢,這時候軟件和硬件的交互非常簡單。發(fā)送的時候,軟件會定期的去查詢硬件的狀態(tài),當發(fā)送緩沖為空的時候,就把數(shù)據(jù)寫入到硬件的緩存寄存器;接收的時候,軟件會定期的查詢硬件的狀態(tài),當接收緩沖區(qū)有數(shù)據(jù)的時候,就把數(shù)據(jù)讀取到軟件。
第二階段,使用中斷模式。如圖1(b),隨著CPU的性能快速提升,統(tǒng)計發(fā)現(xiàn),輪詢的失敗次數(shù)很高,大量的CPU時間被浪費在硬件狀態(tài)查詢而不是數(shù)據(jù)傳輸,因此引入中斷模式。只有當發(fā)送緩沖存在空閑區(qū)域可以讓軟件存放一定量待發(fā)送數(shù)據(jù)的時候,或者接收緩沖已經(jīng)有一定量數(shù)據(jù)待軟件接收的時候,硬件會發(fā)起中斷,CPU收到中斷后進入中斷服務程序,在中斷服務程序里處理數(shù)據(jù)的發(fā)送和接收。
第三階段,引入DMA。如圖1(c),前面的兩種情況下,都需要CPU來完成數(shù)據(jù)的傳輸,依然會有大量的CPU消耗。因此引入了專用的數(shù)據(jù)搬運模塊DMA來完成CPU和硬件之間的數(shù)據(jù)傳輸,某種程度上,DMA可以看做是用于代替CPU進行數(shù)據(jù)搬運的加速器。發(fā)送的時候,當數(shù)據(jù)在CPU內存準備好,CPU告訴DMA源地址和數(shù)據(jù)的大小,DMA收到這些信息后主動把數(shù)據(jù)從CPU內存搬到硬件內部。同樣的,接收的時候,CPU開辟好一片內存空間并告知DMA目標地址和空間的長度,DMA負責把硬件內部的數(shù)據(jù)搬運到CPU內存。
第四階段,專門的共享隊列。如圖1(d),引入了DMA之后,如果只有一片空間用于軟件和硬件之間的數(shù)據(jù)交換,則軟件和硬件之間的數(shù)據(jù)交換則是同步的。例如在接收的時候,當DMA把數(shù)據(jù)搬運到CPU內存之后,CPU需要馬上進行處理并釋放內存,CPU處理的時候DMA則只能停止工作。后來引入了乒乓緩沖的機制,當一個內存緩沖區(qū)用于DMA傳輸數(shù)據(jù)的時候,另一個緩沖區(qū)的數(shù)據(jù)由CPU進行處理,實現(xiàn)DMA傳輸和CPU處理的并行。更進一步的,演變成更多緩沖區(qū)組成的循環(huán)緩沖隊列。這樣,CPU的數(shù)據(jù)處理和DMA的數(shù)據(jù)傳輸則完全異步的完成,并且CPU對數(shù)據(jù)的處理以及DMA對數(shù)據(jù)的搬運都可以批量操作完成后,再同步狀態(tài)信息給對方。
第五階段,用戶態(tài)的軟件輪詢共享隊列驅動。如圖1(e),進一步的,隨著帶寬和內存的增加,導致數(shù)據(jù)頻繁的在用戶態(tài)應用程序、內核的堆棧、驅動以及硬件之間交互,并且緩沖區(qū)也越來越大,這些都不可避免的增加系統(tǒng)消耗,并且?guī)砀嗟难舆t;而且,數(shù)據(jù)交互頻繁,導致的中斷的開銷也是非常龐大的。因此,通過用戶態(tài)的PMD(Polling Mode Driver,輪詢模式驅動)可以高效的在硬件和用戶態(tài)的應用程序直接傳遞數(shù)據(jù),不需要中斷,完全繞開內核,以此來提升性能和降低延遲。
第六階段,支持多隊列。如圖1(f),隨著硬件設計規(guī)模擴大,硬件資源越來越多,在單個設備里,可以通過多隊列的支持,來提高并行性。驅動也需要加入對多隊列的支持,這樣我們甚至可以為每個應用程序配置專用的隊列或隊列組,通過多隊列的并行性來提升性能和應用數(shù)據(jù)的安全性。
說明:本小節(jié)所講的內容,主要是基于傳統(tǒng)的非緩存一致性總線的數(shù)據(jù)交互演進。隨著跨芯片的緩存數(shù)據(jù)一致性總線開始流行,通過硬件完成數(shù)據(jù)交互,在提升性能的同時,也簡化了軟件設計。 b. 軟硬件接口的組成部分 粗略的說,軟硬件接口是由驅動(Driver)和設備(Device)組成,驅動和設備的交互也即軟件和硬件的交互。更確切一些的說,軟硬件接口包括交互的驅動軟件、硬件設備的接口部分邏輯,也包括內存中的共享隊列,還包括傳輸控制和數(shù)據(jù)信息的總線。
圖2軟硬件接口硬件架構示意模型 如圖2,是軟硬件接口硬件架構的示意模型。軟硬件接口的組件詳細介紹如下:
驅動軟件。驅動是提供一定的接口API,讓上層的軟件能夠更加方便的與硬件交互。驅動負責硬件設備控制面的配置、運行控制以及設備數(shù)據(jù)面的數(shù)據(jù)傳輸控制等。驅動屏蔽硬件的接口細節(jié),對上層軟件提供標準的API函數(shù)接口。驅動屏蔽硬件細節(jié),提供標準API給上層軟件,在單機系統(tǒng)是非常有價值的。通過不同版本的驅動,既可以屏蔽硬件細節(jié),又可以跟不同的操作系統(tǒng)平臺兼容。在云計算場景,要求要更嚴格一些,云場景期望是完全標準的硬件接口。驅動是代表軟件與硬件交互的接口,但依然是軟件的一部分,在云計算虛擬機驅動也會遷移到新的環(huán)境,這就要求新的運行環(huán)境和原始環(huán)境一致。也就是說,在IO直通模式下,需要雙方的硬件接口本身就是一致的。
設備硬件接口子模塊,包括DMA和內部緩沖。高速的設備一般都有專用的DMA,專門負責數(shù)據(jù)搬運。驅動會通知DMA共享隊列狀態(tài)信息,然后DMA會讀取內存中的共享隊列描述符,并根據(jù)描述符信息負責在CPU內存和內部緩沖之間搬運數(shù)據(jù)。
共享隊列。特定的跟硬件DMA格式兼容的共享隊列數(shù)據(jù)結構,軟件和硬件通過共享隊列交互數(shù)據(jù)。每個共享隊列包括隊列的頭和尾指針、組成隊列的各個描述符項以及每個描述符項所指向的實際的數(shù)據(jù)塊。共享隊列位于軟件側的CPU內存里,由軟件驅動負責管理。
傳輸?shù)目偩€:軟硬件交互可以說是上層功能,需要底層接口總線的承載。例如,在片內通常是通過AXI-Lite總線來實現(xiàn)軟件對硬件的寄存器讀寫控制,而數(shù)據(jù)總線則是通過AXI實現(xiàn)硬件DMA對軟件的CPU內存的讀寫訪問。芯片間的總線常見的主要是PCIe,通過PCIe的TLP包來承載上層的各種類型的讀寫訪問。
2 生產(chǎn)者消費者模型
生產(chǎn)者消費者問題(Producer-Consumer Problem)是多進程同步問題的經(jīng)典案例之一,描述了共享固定大小緩沖區(qū)的兩個進程,即所謂的“生產(chǎn)者”和“消費者”,在實際運行時如何處理交互的問題。 如圖3所示,生產(chǎn)者的主要作用是生成一定量的數(shù)據(jù)放到緩沖區(qū)中,然后重復此過程。與此同時,消費者也在緩沖區(qū)消耗這些數(shù)據(jù)。問題的關鍵就是要保證生產(chǎn)者不會在緩沖區(qū)滿時加入數(shù)據(jù),消費者不會在緩沖區(qū)中空時消耗數(shù)據(jù)。
圖3 經(jīng)典生產(chǎn)者消費者模型 解決問題的基本辦法是:讓生產(chǎn)者在緩沖區(qū)滿時休眠,等到消費者消耗緩沖區(qū)中的數(shù)據(jù),從而緩沖區(qū)有了空閑區(qū)域的時候,生產(chǎn)者才能被喚醒,開始繼續(xù)往緩沖區(qū)添加數(shù)據(jù);同樣,也需要讓消費者在緩沖區(qū)空時進入休眠,等到生產(chǎn)者往緩沖區(qū)添加數(shù)據(jù)之后,再喚醒消費者繼續(xù)消耗數(shù)據(jù)。 a. 進程間通信 一個生產(chǎn)者進程,一個消費者進程,生產(chǎn)者進程通過共享緩沖傳遞數(shù)據(jù)給消費者進程。如果程序員不夠小心,沒有考慮多進程間相互影響的話,很可能寫出下面這段會導致“死鎖”的代碼。
// 該算法使用了兩個系統(tǒng)庫函數(shù):sleep 和 wakeup。 // 調用 sleep的進程會被阻斷,直到有另一個進程用wakeup喚醒之。 // 代碼中的itemCount用于記錄緩沖區(qū)中的數(shù)據(jù)項數(shù)。 int?itemCount?=?0; procedure producer() { while (true) { item = produceItem(); if (itemCount == BUFFER_SIZE) { sleep(); } putItemIntoBuffer(item); itemCount = itemCount + 1; if (itemCount == 1) { wakeup(consumer); } } } procedure consumer() { while (true) { if (itemCount == 0) { sleep(); } item = removeItemFromBuffer(); itemCount = itemCount - 1; if (itemCount == BUFFER_SIZE - 1) { wakeup(producer); } consumeItem(item); } }上面代碼中的問題在于它可能導致競爭條件,進而引發(fā)死鎖??紤]下面的情形:
消費者進程把最后一個itemCount的內容讀出來(注意它現(xiàn)在是零),消費者進程返回到while的起始處,現(xiàn)在進入if塊。
就在調用sleep之前,OS調度,決定將CPU時間片讓給生產(chǎn)者進程,于是消費者進程在執(zhí)行sleep之前就被中斷了,生產(chǎn)者進程開始執(zhí)行。
生產(chǎn)者進程生產(chǎn)出一項數(shù)據(jù)后將其放入緩沖區(qū),然后在itemCount上加 1;由于緩沖區(qū)在上一步加1之前為空,生產(chǎn)者嘗試喚醒消費者。
遺憾的是,消費者并沒有在休眠,喚醒指令不起作用。當消費者恢復執(zhí)行的時候,執(zhí)行 sleep,一覺不醒(出現(xiàn)這種情況的原因在于,消費者只能被生產(chǎn)者在itemCount為1的情況下喚醒)。
生產(chǎn)者不停地循環(huán)執(zhí)行,直到緩沖區(qū)滿,隨后進入休眠。
由于兩個進程都進入了永遠的休眠,死鎖情況出現(xiàn)了。因此,該算法是不完善的。我們可以通過引入信號量(Semaphore)的方式來完善這個算法。信號量能夠實現(xiàn)對某個特定資源的互斥訪問。
// 該方法使用了兩個信號燈,fillCount和emptyCount; // fillCount用于記錄緩沖區(qū)中存在的數(shù)據(jù)項數(shù)量; // emptyCount用于記錄緩沖區(qū)中空閑空間數(shù)量; // 當有新數(shù)據(jù)項被放入緩沖區(qū)時,fillCount增加,emptyCount減少; // 當有新數(shù)據(jù)項被取出緩沖區(qū)時,fillCount減少,emptyCount增加; // 如果在生產(chǎn)者嘗試減少emptyCount的時候發(fā)現(xiàn)其值為零,那么生產(chǎn)者就進入休眠。 // 等到有數(shù)據(jù)項被消耗,emptyCount增加的時候,生產(chǎn)者才被喚醒。 //?消費者的行為類似。 semaphore fillCount = 0; // 生產(chǎn)的項目 semaphore emptyCount = BUFFER_SIZE; // 剩余空間 procedure producer() { while (true) { item = produceItem(); down(emptyCount); putItemIntoBuffer(item); up(fillCount); } } procedure consumer() { while (true) { down(fillCount); item = removeItemFromBuffer(); up(emptyCount); consumeItem(item); } }b.分布式消息隊列服務 消息隊列中間件是分布式系統(tǒng)中重要的組件,主要解決應用耦合、異步消息、流量削峰等問題。消息(Message)是指在應用之間傳送的數(shù)據(jù),消息可以非常簡單,比如只包含文本字符串,也可以更復雜,可能包含嵌入對象。消息隊列(Message Queue)是一種應用間的通信方式,消息發(fā)送后可以立即返回,有消息系統(tǒng)來確保信息的可靠傳遞,消息發(fā)布者只管把消息發(fā)布到MQ中而不管誰來取,消息使用者只管從MQ中取消息而不管誰發(fā)布的,這樣發(fā)布者和使用者都不用知道對方的存在。 如圖4,消息隊列一般由三部分組成:
Producer:消息生產(chǎn)者,負責產(chǎn)生和發(fā)送消息到 Broker。
Broker:消息處理中心。負責消息存儲、確認、重試等,一般其中會包含多個Queue。
Consumer:消息消費者,負責從 Broker 中獲取消息,并進行相應處理。
圖4 消息隊列模型 消息隊列具有如下特性:
異步性。將耗時的同步操作,通過發(fā)送消息的方式,進行了異步化處理。減少了同步等待的時間。
松耦合。消息隊列減少了服務之間的耦合性,不同的服務可以通過消息隊列進行通信,而不用關心彼此的實現(xiàn)細節(jié),只要定義好消息的格式就行。
分布式。通過對消費者的橫向擴展,降低了消息隊列阻塞的風險,以及單個消費者產(chǎn)生單點故障的可能性(當然消息隊列本身也可以做成分布式集群)。
可靠性。消息隊列一般會把接收到的消息存儲到本地硬盤上(當消息被處理完之后,存儲信息根據(jù)不同的消息隊列實現(xiàn),有可能將其刪除),這樣即使應用掛掉或者消息隊列本身掛掉,消息也能夠重新加載。
互聯(lián)網(wǎng)場景使用較多的消息隊列有ActiveMQ、RabbitMQ、ZeroMQ、Kafka、MetaMQ、RocketMQ等。 c. 驅動和設備通信 NIC(Network Interface Adapter,網(wǎng)絡接口卡)是典型的IO設備,網(wǎng)絡數(shù)據(jù)包的傳輸有發(fā)送Tx和接收Rx兩個方向。通過貢獻的Tx Queue和Rx Queue來交互數(shù)據(jù)傳輸。我們以網(wǎng)絡Tx的傳輸為例,介紹基于生產(chǎn)者消費者模型的驅動和設備數(shù)據(jù)交互。 如圖5,給出了網(wǎng)絡包處理Tx發(fā)送的基本原理示意圖(Rx接收跟Tx發(fā)送類似,控制流程一致,數(shù)據(jù)方向相反)??梢钥吹?,在Tx的時候,驅動是生產(chǎn)者,設備是消費者,他們通過內存中共享的環(huán)形隊列傳輸數(shù)據(jù)。一般在環(huán)形隊列中的是用于描述數(shù)據(jù)的描述符,通過指針指向實際的數(shù)據(jù)塊。當上層應用通過驅動把數(shù)據(jù)寫到環(huán)形隊列以后,驅動會把環(huán)形隊列相關的狀態(tài)信息告知設備端。設備端接收到信息后DMA開始工作,首先讀取環(huán)形隊列中的相應描述符,然后通過描述符信息搬運實際的數(shù)據(jù)塊到硬件內部。搬運完成后硬件通過中斷告知驅動,然后驅動會釋放此塊緩沖。
圖5 網(wǎng)絡驅動和設備通信示意圖
3 用戶態(tài)輪詢驅動:DPDK和SPDK
DPDK和SPDK是當前主流的開源高速接口框架,核心的技術是用戶態(tài)的輪詢驅動。DPDK/SPDK支持兩個核心的設備類型:DPDK聚焦高性能網(wǎng)絡處理,SPDK聚焦高性能存儲處理。 a. DPDK介紹
DPDK(Data Plane Development Kit,數(shù)據(jù)平面開發(fā)套件)是在用戶態(tài)中運行的一組軟件庫和驅動程序,可加速在CPU架構上運行的數(shù)據(jù)包處理工作負載。DPDK由英特爾大約在2010年創(chuàng)建,現(xiàn)在作為Linux基金會下的一個開源項目提供,在拓展通用CPU的應用方面發(fā)揮了重要作用。
?
? |
? | ? |
? | |
(a) 基于Linux內核的包處理 | (b) 基于DPDK的包處理 |
圖6 基于DPDK的包處理 如圖6(a),傳統(tǒng)Linux網(wǎng)絡驅動存在如下一些問題:
中斷開銷大,大量數(shù)據(jù)傳輸會頻繁觸發(fā)中斷,中斷開銷系統(tǒng)無法承受;
數(shù)據(jù)包從內核緩沖區(qū)拷貝到用戶緩沖區(qū),帶來系統(tǒng)調用和數(shù)據(jù)包復制的開銷;
對于很多網(wǎng)絡功能來說,TCP/IP協(xié)議并非數(shù)據(jù)轉發(fā)必需;
操作系統(tǒng)調度帶來的緩存替換也會對性能產(chǎn)生負面影響。
如圖6(b),DPDK最核心的功能是提供了用戶態(tài)的輪詢模式驅動,為了加速網(wǎng)絡IO,DPDK允許傳入的網(wǎng)絡數(shù)據(jù)包直通到用戶空間而沒有內存復制的開銷,不需要用戶空間和內核空間切換時的上下文處理。DPDK可在高吞吐量和低延遲敏感的場景加速特定的網(wǎng)絡功能,如無線核心、無線訪問、有線基礎設施、路由器、負載均衡器、防火墻、視頻流、VoIP等。DPDK所使用的優(yōu)化技術主要有:
用戶態(tài)驅動,減少內核態(tài)用戶態(tài)切換開銷,減少緩沖區(qū)拷貝;
輪詢模式驅動(PMD, Polling Mode Driver),不需要中斷,沒有中斷開銷,并且對隊列及數(shù)據(jù)及時處理,降低延遲;
固定處理器核,減少線程切換的開銷,減少緩存失效,同時要考慮NUMA特性,確保內存和處理器核在同一個NUMA域中;
大頁機制,減少TLB未命中幾率;
非鎖定的同步,避免等待;
內存對齊和緩存對齊,有利于內存到緩存的加載效率;
DDIO機制,從IO設備把數(shù)據(jù)直接送到L3緩存,而不是送到內存。
b. SPDK介紹 在數(shù)據(jù)中心中,固態(tài)存儲介質正在逐漸替換機械HDD,NVMe在性能、功耗和機架密度方面具有明顯的優(yōu)勢。因為固態(tài)存儲吞吐量提升,存儲軟件需要花費更多的CPU資源;因為固態(tài)存儲延遲性能的大幅提升,存儲軟件的處理延遲則開始凸顯??偨Y來說,隨著存儲介質性能的進一步提升,存儲軟件棧的性能和效率越來越成為存儲系統(tǒng)的瓶頸。 如圖7,SPDK(Storage Performance Development Kit,存儲性能開發(fā)套件)利用了很多DPDK的組件,在DPDK的基礎上,加入了存儲的相關組件。SPDK的核心技術依然是用戶態(tài)的PMD。SPDK已經(jīng)證明,使用一些處理器內核和一些NVMe驅動器進行存儲,而無需額外的卸載硬件,可以輕松實現(xiàn)每秒數(shù)百萬個IO。
圖7 SPDK基于DPDK和一些新的組件 SPDK由許多組件組成,這些組件相互連接并共享用戶態(tài)和輪詢模式操作的通用功能。每個組件都是為了克服特定場景的性能瓶頸而開發(fā)的,并且,每個組件也可以集成到非SPDK架構中,從而使客戶可以利用SPDK的技術來加速自己的軟件應用。從底層到上層,SPDK的組件包括:
用戶態(tài)PMD驅動:基于PCIe的NVMe驅動、NVMeoF驅動,以及英特爾QuickData驅動(QuickData為Intel志強處理器平臺的IO加速引擎)。
后端塊設備:Ceph RADOS塊設備(Ceph為開源的分布式存儲系統(tǒng),RADOS為Ceph的分布式集群封裝),Blobstore塊設備(VM或數(shù)據(jù)庫交互的虛擬設備),Linux AIO(異步IO)。
存儲服務:塊設備抽象層(bdev),Blobstore。
存儲協(xié)議:iSCSI Target端,NVMeoF Target端,vhost-scsi Target端,vhost-blk Target端。
編輯:黃飛
評論