8. 公共約定
許多ODP API與參數(shù)和返回值類型共享常見的約定。本節(jié)重點介紹一些比較常見和常用的慣例。
8.1. 句柄和特殊指示器
ODP資源通過具有抽象類型 odp_resource_t 的句柄表示。 所以池有類型為 odp_pool_t 的句柄表示,隊列由類型 odp_queue_t 表示等等。 每個這樣的類型都有一個不同類型的 ODP_RESOURCE_INVALID 用于指示一個句柄沒有合法引用資源。 資源通常通過 odp_resource_create() API來創(chuàng)建,該API返回一個代表創(chuàng)建對象的類型為 odp_resource_t 的句柄。 如果資源耗盡,則無法創(chuàng)建,此時返回的句柄為 ODP_RESOURCE_INVALID 。 無效資源并不一定代表錯誤邏輯。 例如,ODP_EVENT_INVALID響應于從隊列中獲取事件的 odp_queue_deq() 調(diào)用,知識表示隊列為空。
8.2. 涉及范圍
除非在API中特別注明,否則所有ODP資源都是ODP應用程序的全局資源,無論它是作為一個進程還是多個進程運行。 因此,ODP句柄在ODP應用程序中具有通用意義,但在應用程序范圍之外沒有任何意義。
8.3. 資源和名稱
許多ODP資源對象(如池和隊列)在創(chuàng)建時支持與ODP對象關聯(lián)的應用程序指定的字符串名稱。 此名稱有兩個目的:文檔和查找。 查找功能對于允許分為多個進程的ODP應用程序來獲取公共資源的句柄特別有用。
9. 應用程序可移植性注意事項
ODP旨在支持創(chuàng)建可輕松在多個平臺上運行的可移植數(shù)據(jù)面應用程序,同時充分利用運行平臺的硬件加速功能。 本節(jié)討論應用程序開發(fā)人員在使用ODP時應考慮的問題。
首先,應該注意的是,可移植性不是絕對的,也不是一個單值屬性。 雖然任何應用程序可以從一個平臺移植到另一個平臺,但真正的問題是:以什么樣的代價? 這個開銷可以從兩個維度上衡量:移植所需的工作量和移植導致的性能差異。 理想情況下,應用程序應該在平臺之間以最小的工作量完成移植,且具有最小的性能影響。 雖然ODP旨在支持這一理想目標,但每個應用程序都必須評估其移植所要達到的目標以及如何使用ODP來實現(xiàn)這一目標。
9.1. 可移植性和共存性
由于ODP提供的是編程框架而不是編程環(huán)境,因此它可以與其他框架提供的API一起工作,同時具有最小的干擾。 因此,當我們談到ODP環(huán)境中的可移植性時,我們必須說明應用程序中使用ODP API的那些部分的可移植性。 如果應用程序使用非ODP API,則在評估整個應用程序的可移植性時必須考慮這些其他的API。 對于許多應用程序,將某些非可移植的代碼隔離到應用程序的幾個區(qū)域就足夠了,結果是應用程序比沒有使用ODP的應用程序更方便移植。 特別是在處理在生產(chǎn)環(huán)境中運行的現(xiàn)有應用程序時,可能會以增量的方式引入ODP,結果是隨著時間的推移應用程序變得更加具有可移植性。
9.2. 源代碼 vs 二進制文件移植
ODP旨在支持源代碼和二進制文件的可移植性。 源代碼可移植是ODP API規(guī)范本身所固有的。 寫入ODP API規(guī)范的任何應用程序?qū)⒃谌魏畏螼DP規(guī)范的ODP實現(xiàn)之間進行源代碼的移植,最多只需要重新編譯。 這是因為ODP API不會暴露實現(xiàn)細節(jié)或可能因平臺而異的內(nèi)部結構。
對于共享通用指令集架構(ISA)的平臺,ODP還可以通過應用二進制接口(ABI)的規(guī)范提供二進制可移植性。 這在網(wǎng)絡功能虛擬化(NFV)環(huán)境中特別有用。在NFV中,可以在一個平臺上開發(fā)和編譯數(shù)據(jù)平面應用程序以進行分發(fā),然后通過NFV Orchestrator功能部署在許多不同的平臺上。
9.3. ODP應用程序配置文件
為了幫助滿足這些需求,ODP提供了兩個不同的應用程序配置文件,旨在表征不同類型的數(shù)據(jù)平面應用程序的需求:嵌入式配置文件和云端配置文件。
9.3.1. 嵌入式配置文件
當希望程序針對特定平臺,希望在該平臺上實現(xiàn)最佳性能,且源代碼可移植性足夠,那么就可以使用嵌入式配置文件。 如果這樣的應用程序需要支持多個平臺,那么它們只需要針對該平臺的ODP實現(xiàn)進行重新編譯。
嵌入式應用程序通常使用從git存儲庫下載的ODP副本,以便可以根據(jù)應用程序的精確需求進行配置。 要指定應用程序的平臺,使用嵌入的配置文件:
./configure --enable-abi-compat=no ...
應該用作ODP配置選項的一部分。 這允許應用程序使用內(nèi)聯(lián)形式的ODP API來在此平臺上提供最佳性能,并可能包括一些其他的優(yōu)化。 這個結果是得到一個二進制文件,可以在給定的目標平臺上實現(xiàn)最高性能,并可以通過重新編譯移植到其他平臺。
9.3.2. 云端配置文件
相比直線,ODP云端配置文件旨在支持希望與平臺無關的應用程序,并在共享此ABI的所有平臺上進行二進制兼容。 Linux配置中包含的任何ODP實現(xiàn)將被配置為云配置文件,因此在針對分布式ODP副本(通過sudo apt-get install或等效命令安裝的ODP)進行編譯時,不需要額外的操作。
當使用從repo中下載的ODP副本時,配置時將使用云配置文件:
./configure --enable-abi-compat=yes ...
請注意,–enable-abi-compat = yes是默認值,因此不需要指定。除非為此選項指定了no,否則結果將是旨在在云配置文件中運行的應用程序。
9.4. ABI特性
ABI由幾個約定組成,確保根據(jù)一個ODP實現(xiàn)編譯的程序可以在另一個具有可能非常不同的ODP實現(xiàn)但不需要重新編譯的平臺上運行。 這些約定包括:
一組函數(shù)調(diào)用約定,定義函數(shù)如何調(diào)用其他函數(shù),傳遞參數(shù)和接收返回的結果。這些通常由操作系統(tǒng)(如Linux)指定,并獨立于ODP。
避免使用任何ODP API的內(nèi)聯(lián)擴展。這可以確保不同的ODP實現(xiàn)可以維護其不同的內(nèi)部構件,而這些差異對于應用程序是可見的。
共享此ABI定義的所有ODP實現(xiàn)使用的ODP抽象數(shù)據(jù)類型的大小和一致性的協(xié)議。 這意味著,例如,odp_packet_t句柄的大小在ABI的所有成員中是相同的。 由于這些句柄是不透明的,所以ODP實現(xiàn)之間的結構不一樣,因為應用程序從不引用這些可能不同的內(nèi)部結構。
請注意,ABI定義存在于特定指令集架構(ISA)中,如x86-64或AArch64。 二進制文件不能直接在ISA之間進行連接,需要重新編譯。
每個ODP實現(xiàn)將確定其支持的ABI定義(如果有的話)。 當在ABI compabitilty模式下編譯ODP實現(xiàn)時,生成的二進制文件與共享此ABI的所有其他ODP實現(xiàn)自動二進制兼容。 例如,對于x86-64 ISA,odp-linux和odp-dpdk實現(xiàn)都是常見的ABI。
10. 共享內(nèi)存
10.1. 分配共享內(nèi)存
可以通過調(diào)用 odp_shm_reserve() API創(chuàng)建共享內(nèi)存塊。 該API調(diào)用需要傳入共享內(nèi)存塊名稱、塊大小、對齊要求和可選標志參數(shù)。它返回一個 odp_shm_t
結構體。 塊大小和對齊以字節(jié)為單位。
注意,提供的名稱不一定是唯一的,即,在保留不同的塊時,可以使用相同的名稱。
創(chuàng)建一個共享內(nèi)存塊代碼如下:
#define ALIGNMENT 128
#define BLKNAME "shared_items"
odp_shm_t shm;
uint32_t shm_flags = 0;
typedef struct {
...
} shared_data_t;
shm = odp_shm_reserve(BLKNAME, sizeof(shared_data_t), ALIGNMENT, shm_flags);
10.2. 獲取共享內(nèi)存塊地址
上述API調(diào)用返回的 odp_shm_t 句柄檢索指定創(chuàng)建的共享內(nèi)存塊的地址(在調(diào)用者的ODP線程虛擬地址空間中)。
獲取貢獻內(nèi)存塊的代碼如下:
shared_data_t *shared_data;
shared_data = odp_shm_addr(shm);
接口 odp_shm_addr() 返回的地址僅在ODP線程空間中有效。雖然 odp_shm_t 句柄可以在ODP線程之間共享,并且在任何線程中保持有效, 但 odp_shm_addr() 返回的地址可能根據(jù)線程空間而不同(對于同一個shm塊),因此不應該在ODP線程之間共享。 舉個例子,在兩個ODP線程之間通過IPC傳送shm句柄是正確的,并且讓這些線程都執(zhí)行各自的 odp_shm_addr() 來獲取共享內(nèi)存塊的地址。 但是如果直接 odp_shm_addr() 返回的地址從一個ODP線程發(fā)送到另一個ODP線程則可能會失敗(該地址在接收端的地址空間中可能沒意義)。
即使調(diào)用 odp_shm_addr() 的線程與原來調(diào)用 odp_shm_reserve() 的線程不一致, odp_shm_addr() 返回的地址仍然保證根據(jù)在塊創(chuàng)建時提供的對齊要求對齊。
所有共享內(nèi)存塊在任何ODP線程尋址空間中都是連續(xù)的,address~address+size,其中size是共享內(nèi)存塊的大小,這個空間是可讀寫的,映射了整個共享內(nèi)存塊。
10.3. 內(nèi)存行為
默認情況下,ODP線程被假定為緩存一致性系統(tǒng):對共享內(nèi)存塊執(zhí)行的任何更改都將保證最終對共享此內(nèi)存塊的其他ODP線程可見。 然而,ODP中并沒有共享內(nèi)存上任何操作相關聯(lián)的隱式內(nèi)存屏障,也就是說當ODP線程執(zhí)行的改變對另一個ODP線程是不可見的, 故使用共享內(nèi)存塊的程序需要執(zhí)行ODP提供的一些內(nèi)存屏障來保證ODP線程之間共享數(shù)據(jù)的一致性。
如果ODP線程具有單獨的虛擬空間(ODP線程被實現(xiàn)為進程),則給定的共享內(nèi)存塊映射到不同的ODP線程虛擬地址空間上各個ODP線程各不相同。 但是, ODP_SHM_SINGLE_VA 標志可以在 odp_shm_reserve() 調(diào)用時使用,以保證所有ODP線程的地址唯一性,無論其實現(xiàn)或創(chuàng)建的時間如何。
10.4. 根據(jù)名稱查找
上面講過,共享內(nèi)存塊指針可以通過IPC機制在ODP線程之間傳遞,然后執(zhí)行API來獲取當前ODP線的地址。 獲取已創(chuàng)建的共享內(nèi)存塊的指針的更簡單的方法是通過接口 odp_shm_lookup()
調(diào)用來實現(xiàn)。 但是,這需要ODP線程來提高共享內(nèi)存塊的名稱,假如沒有找到對應名稱的內(nèi)存塊,則返回 ODP_SHM_INVALID 。 當使用相同名稱保存多個共享內(nèi)存塊時,查找接口將返回任意這些塊指針中的一個。
根據(jù)名稱查找塊指針和地址:
#define BLKNAME "shared_items"
odp_shm_t shm;
shared_data_t *shared_data;
shm = odp_shm_lookup(BLKNAME);
if (shm != ODP_SHM_INVALID) {
shared_data = odp_shm_addr(shm);
...
}
10.5. 釋放共享內(nèi)存塊
使用 odp_shm_free() API調(diào)用執(zhí)行釋放共享內(nèi)存塊。 該接口需要共享內(nèi)存塊指針作為參數(shù)。 允許任何ODP線程在共享內(nèi)存塊上執(zhí)行 odp_shm_free() ,即申請和釋放共享內(nèi)存塊的線程可能不同。 共享內(nèi)存塊應該只被釋放一次,一旦釋放,共享內(nèi)存塊就不應該被任何ODP線程再次引用。
釋放共享內(nèi)存塊:
if (odp_shm_free(shm) != 0) {
...//handle error
}
10.6. 與外部程序共享內(nèi)存
ODP提供了與ODP實例外部的實體共享內(nèi)存的方法:
與外部非ODP線程共享內(nèi)存塊是通過在調(diào)用 odp_shm_reserve() 時設置 ODP_SHM_PROC
標志來實現(xiàn)的。 這些非ODP線程如何檢索共享內(nèi)存塊依賴于具體的實現(xiàn)和操作系統(tǒng)。
與外部ODP實例(運行于同一個操作系統(tǒng))共享內(nèi)存塊是通過調(diào)用 odp_shm_reserve() 時設置 ODP_SHM_EXPORT
標志來實現(xiàn)的。 在ODP實例A中使用此標志創(chuàng)建的內(nèi)存塊可以通過在ODP實例B上使用 odp_shm_import() 接口映射到遠程ODP實例B上(在相同操作系統(tǒng)中)。
ODP實例間共享內(nèi)存: instance A
odp_shm_t shmA;
shmA = odp_shm_reserve("memoryA", size, 0, ODP_SHM_EXPORT);
ODP實例間共享內(nèi)存: instance B
odp_shm_t shmB;
odp_instance_t odpA;
/* get ODP A instance handle by some OS method */
odpA = ...
/* get the shared memory exported by A:
shmB = odp_shm_import("memoryA", odpA, "memoryB", 0, 0);
每個ODP實例的范圍限制shmA和shmB(您不能在其所屬的ODP實例之外使用它們)。 另請注意,兩個ODP實例必須在完成后調(diào)用odp_shm_free()。
10.7. 共享內(nèi)存創(chuàng)建標志
odp_shm_reserve() API的最后一個參數(shù)是一組ORed標志。當前支持如下幾種標志:
10.7.1. ODP_SHM_PROC
當給出此標志時,分配的共享內(nèi)存將在ODP之外變得可見。 非ODP線程(例如通常的linux進程或linux線程)將能夠使用本機(非ODP)OS調(diào)用(如shm_open()和mmap(對于linux))來訪問內(nèi)存。 每個ODP實施應提供關于在該特定平臺上如何完成此映射的描述。
10.7.2. ODP_SHM_EXPORT
當給出此標志時,分配的共享內(nèi)存將在同一個操作系統(tǒng)上運行的其他ODP實例變得可見。 想要看到此導出內(nèi)存的其他ODP實例應使用 odp_shm_import() ODP函數(shù)。
10.7.3. ODP_SHM_SW_ONLY
該標志指示ODP共享內(nèi)存將僅由ODP應用軟件使用:沒有硬件(如DMA或其他加速器)訪問內(nèi)存。 這個內(nèi)存不會涉及其他的ODP調(diào)用(ODP調(diào)用可能隱含地涉及到HW,這取決于ODP的實現(xiàn)),除了 odp_shm_lookup() 和 odp_shm_free() 。 ODP實現(xiàn)可以使用該標志作為性能優(yōu)化的提示,或者也可以忽略該標志。
10.7.4. ODP_SHM_SINGLE_VA
該標志用于保證共享內(nèi)存被映射的地址的唯一性:沒有該標志,給定的內(nèi)存塊可能會被不同的ODP線程映射到不同的虛擬地址(假設目標具有虛擬地址)。 這意味著 odp_shm_addr() 返回的值在不同的線程中是不同的。 設置此標志保證共享此內(nèi)存塊的所有ODP線程將在在所有ODP線程上調(diào)用 odp_shm_addr() 返回相同的值。 注意,ODP實現(xiàn)可能會限制可以分配這個標志的內(nèi)存數(shù)目。
11. 隊列
隊列是ODP提供的基本事件排序機制,所有的ODP程序都顯式或隱式地使用隊列。 隊列是通過API odp_queue_create() 來創(chuàng)建的,該API返回一個類型為 odp_queue_t 的指針,該指針用于所有使用該隊列的API調(diào)用。 每個Queue具有兩種ODP類型中的一個,POLL和SCHED,用于指示如何使用它們。 POLL隊列由ODP應用程序直接管理,而SCHED隊列使用ODP調(diào)度器提供自動可擴展調(diào)度和同步服務。
POLL queues 操作
odp_queue_t poll_q1 = odp_queue_create("poll queue 1", ODP_QUEUE_TYPE_POLL, NULL);
odp_queue_t poll_q2 = odp_queue_create("poll queue 2", ODP_QUEUE_TYPE_POLL, NULL);
...
odp_event_t ev = odp_queue_deq(poll_q1);
...do something
int rc = odp_queue_enq(poll_q2, ev);
關鍵區(qū)別是,在POLL隊列中,事件出隊操作是應用程序負責的,而SCHED隊列中的事件出隊則是ODP調(diào)度程序完成的。
SCHED queues 操作
odp_queue_param_t qp;
odp_queue_param_init(&qp);
odp_schedule_prio_t prio = ...;
odp_schedule_group_t sched_group = ...;
qp.sched.prio = prio;
qp.sched.sync = ODP_SCHED_SYNC_[NONE|ATOMIC|ORDERED];
qp.sched.group = sched_group;
qp.lock_count = n; /* Only relevant for ordered queues */
odp_queue_t sched_q1 = odp_queue_create("sched queue 1", ODP_QUEUE_TYPE_SCHED, &qp);
...thread init processing
while (1) {
odp_event_t ev;
odp_queue_t which_q;
ev = odp_schedule(&which_q, <wait option>);
...process the event
}
當使用sched queue時,發(fā)送者選擇一個目標隊列,將事件發(fā)送到隊列中。 發(fā)送者不知道哪個ODP線程(哪個core)或硬件加速器將處理這個事件,但是隊列中的所有事件最終將被調(diào)度和處理。
可以看出,可以在SCHED隊列創(chuàng)建時指定隊列的其他屬性,它們控制調(diào)度程序如何處理其中包含的事件,這些屬性包括組,優(yōu)先級和同步類。
11.1. 組
調(diào)度器的工作是從最高優(yōu)先級的SCHED queue中返回下一個事件給調(diào)用者。SCHED queue必須是調(diào)用者有資格接收的隊列。 這個是由隊列創(chuàng)建時設置的隊列調(diào)度器組及調(diào)用者的調(diào)度器組掩碼來指定的。 調(diào)度器組由 odp_scheduler_group_t 類型的指針表示,并由 odp_scheduler_group_create() 接口創(chuàng)建。 ODP預定義了多個調(diào)度器組,包括 ODP_SCHED_GROUP_ALL ODP_SCHED_GROUP_WORKER 及 ODP_SCHED_GROUP_CONTROL 。 應用程序可以自由創(chuàng)建其他調(diào)度器組,線程可以使用 odp_scheduler_group_join() 和 odp_scheduler_group_leave() 加入或離開調(diào)度器組。
11.2. 優(yōu)先級
數(shù)據(jù)結構 odp_queue_param_t 的prio字段指定隊列調(diào)度的優(yōu)先級,即如何選擇符合條件的調(diào)度器組中的隊列進行調(diào)度。 隊列的默認調(diào)度優(yōu)先級是 NORMAL 但可以根據(jù)需求設置成 HIGHEST 或 LOWEST 。
11.3. 同步
除了在多核心環(huán)境中為ODP應用提供自動可擴展性的調(diào)度功能之外,調(diào)度程序的另一個主要功能是提供事件同步服務,大大簡化并行處理環(huán)境中的應用程序編程。 隊列的SYNC模式?jīng)Q定調(diào)度程序如何處理來自同一隊列的多個事件的同步處理。 ODP支持三種類型的隊列調(diào)度器同步區(qū):并行,原子和有序。
11.4. 并行隊列
指定 ODP_SCHED_SYNC_NONE 模式的SCHED隊列在處理事件的方式上是不受限制的。
在并行隊列中保存的所有事件都有資格同時進行調(diào)度,并且它們之間的所有必需的同步都是應用程序負責的。 源自并行隊列的事件也因此具有最高的吞吐率,但是它們也可能涉及大量的應用程序工作。 在上圖中,四個線程正在調(diào)用 odp_schedule() 來獲取要處理的事件。 調(diào)度程序已經(jīng)將來自第一個隊列的三個事件并行分配給三個線程。 第四個線程正在處理來自第三個隊列的單個事件。 第二個隊列可能是空的,優(yōu)先級較低,或者不是在與調(diào)度程序服務的任何線程匹配的調(diào)度程序組中。
11.5. 原子隊列
原子隊列簡化事件同步,因為每一次只有一個線程可以處理給定原子隊列中的事件。 由于鎖定是由調(diào)度程序隱式完成的,因此原子隊列調(diào)度的事件可以被鎖定。 請注意,如果使用 odp_schedule_multi() ,調(diào)用者可能會從同一個原子隊列接收一個或多個事件。 在這種情況下,這些多個事件都共享相同的原子調(diào)度上下文。
在這個例子中,無論在一個原子隊列中可能有多少個事件,每次只有一個調(diào)用線程可以接收它的調(diào)度事件。 這里兩個線程處理來自兩個不同原子隊列的事件。 請注意,不同的原子隊列之間不存在來自同一原子隊列的事件之間的同步。 與原子隊列相關聯(lián)的隊列上下文將持續(xù)到下次調(diào)用調(diào)度程序或直到應用程序通過調(diào)用 odp_schedule_release_atomic() 顯式釋放它。 請注意,雖然原子隊列簡化了編程,但原子隊列的串行性質(zhì)可能會削弱擴展性。
11.6. 有序隊列
有序隊列同時提供了并行隊列的可擴展性和原子隊列的易同步性。
當從有序隊列調(diào)度事件時,調(diào)度程序從隊列中并行分配多個事件到不同的線程, 并且調(diào)度程序還可以確保輸出隊列上的這些事件的相對順序與其起始有序隊列的序列相同。
與原子隊列一樣,與有序隊列相關聯(lián)的排序保證是指源自同一隊列的事件,而不是源自不同隊列的事件。 因此,在該圖中,三個線程分別來自第一有序隊列的處理事件5,3和4。 不管這些線程如何完成處理,這些事件將以它們的輸出隊列的原始相對順序顯示。
11.6.1. 有序保證
無論事件是否被發(fā)送到不同的輸出隊列,相對順序都將被保留。 例如,如果某些事件被發(fā)送到輸出隊列A,而其他事件被發(fā)送到輸出隊列B,則這些輸出隊列上的事件將仍然與它們的發(fā)起隊列具有相同的相對順序。 類似地,如果處理消耗事件,使得對于它們中的一些(例如,作為IP片段重新組合處理的一部分)不發(fā)出輸出,其他事件仍然將相對于這些序列間隙被正確地排序。 最后,如果針對給定的順序排入多個事件(例如,作為MTU注意事項的分組分割處理的一部分),則這些事件中的每一個將占據(jù)目標輸出隊列中的發(fā)起者的序列。 在這種情況下,這些事件的相對順序?qū)凑站€程 odp_queue_enq() 調(diào)用它們的順序。
與有序隊列調(diào)度事件相關聯(lián)的有序上下文將持續(xù)到下一個調(diào)度程序調(diào)用,或者直到調(diào)用 odp_schedule_release_ordered() 的顯式釋放。 該調(diào)用可以用作性能咨詢,線程不再需要為當前上下文訂購保證。 因此,當前調(diào)度程序上下文中的任何后續(xù)排隊將被視為線程在并行隊列上下文中運行。
11.6.2. 順序鎖定
調(diào)度器處理有序隊列的另一個強大功能是順序鎖。 與每個有序隊列相關聯(lián)的多個順序鎖是在隊列創(chuàng)建時由lock_count參數(shù)指定的。 順序鎖提供了有效的方式來在順序上下文中執(zhí)行順序處理。 例如,相關順序5,6和7的假定事件由三個不同的線程并行執(zhí)行。 順序鎖將使這些線程能夠同步,以便它們可以在其起始隊列順序中執(zhí)行一些關鍵部分。 每個有序隊列支持的順序鎖的數(shù)量取決于具體實現(xiàn)(可通過 odp_config_max_ordered_locks_per_queue() API查詢)。 如果實現(xiàn)支持多個有序鎖,則這些鎖可用于保護給定有序上下文中的不同有序臨界區(qū)。
11.6.3. 小結:有序隊列
要了解這些注意事項如何組合在一起,請考慮以下代碼: Processing with Ordered Queues
void worker_thread()
odp_init_local();
...other initialization processing
while (1) {
ev = odp_schedule(&which_q, ODP_SCHED_WAIT);
...process events in parallel
odp_schedule_order_lock(0);
...critical section processed in order
odp_schedule_order_unlock(0);
...continue processing in parallel
odp_queue_enq(dest_q, ev);
}
}
這段代碼表示了在有序隊列上運行的典型工作線程的簡化結構。 并行處理多個事件,并使用有序隊列確保它們以與發(fā)起的順序相同的順序放置在dest_q上。 在并行處理的同時,使用有序鎖可以使關鍵部分在整個并行流程中按順序進行處理。 當一個線程到達 odp_schedule_order_lock() 調(diào)用時,它等待直到所有先前事件的鎖定的鎖定順序已經(jīng)解決,然后進入臨界區(qū)。 odp_schedule_order_unlock() 調(diào)用釋放關鍵部分,并允許下一個訂單輸入。
11.7. 隊列調(diào)度小結
有序和并行隊列由于并行事件處理而提高了原子隊列的吞吐量,但要求應用程序采取措施確保上下文數(shù)據(jù)同步(如果需要)。
12. 報文處理
ODP應用程序旨在處理數(shù)據(jù)包。 為了幫助處理數(shù)據(jù)包,使得應用程序能夠操作數(shù)據(jù)包數(shù)據(jù)及其元數(shù)據(jù)。 數(shù)據(jù)包通過各自實現(xiàn)的抽象數(shù)據(jù)類型 odp_packet_t 來引用。
當報文到達源 odp_pktio_t 并且被應用程序直接或間接(通過調(diào)度隊列)接收時,報文對象被創(chuàng)建。 當他們通過相關聯(lián)的的傳輸隊列傳輸?shù)?odp_pktio_t 時,他們可能被隱式釋放,或者通過調(diào)用 odp_packet_free() 來釋放。
有時,應用程序可能直接發(fā)起一個數(shù)據(jù)包,或者通過從現(xiàn)有數(shù)據(jù)包導出新的數(shù)據(jù)包,ODP提供了對應的API以實現(xiàn)這些處理。 應用程序創(chuàng)建的數(shù)據(jù)包可以通過回環(huán)口重新送回并進行分類,或者應用程序可以根據(jù)需要進行自己的解析。
與數(shù)據(jù)包相關聯(lián)的各種屬性(如解析結果)存儲在元數(shù)據(jù)中,ODP提供了對應的API以允許應用程序操作并修改這些信息。
12.1. 數(shù)據(jù)包結構和概念
報文是由符合諸如以太網(wǎng)架構格式的八位字節(jié)序組成,可以通過 ODP PKTIO抽象接口進行接收和發(fā)送。 數(shù)據(jù)包長度是指數(shù)據(jù)包的字節(jié)數(shù)。 ODP中數(shù)據(jù)包的數(shù)據(jù)是通過偏移來引用的,因為他們反映了數(shù)據(jù)包的邏輯內(nèi)容和結構,而與特定的ODP實現(xiàn)和如何存儲數(shù)據(jù)無關。
這些概念如下圖所示:
報文數(shù)據(jù)包括0個或多個報頭,0個或多個字節(jié)的payload,再接0個或多個報尾。 這里顯示的是允許應用程序檢查和檢索數(shù)據(jù)包的各個部分并操作其結構的各種API。
為了支持數(shù)據(jù)包操作,預定義了headroom和tailroom與數(shù)據(jù)包相關聯(lián)。 可以通過操作這些區(qū)域來調(diào)整數(shù)據(jù)包。 典型的數(shù)據(jù)包處理包括通過數(shù)據(jù)包接收時調(diào)用 odp_pull_head() 從數(shù)據(jù)包中剝離報頭,數(shù)據(jù)包發(fā)送時調(diào)用 odp_push_head() 插入新的報頭。 注意,因為headroom和tailroom表示保留的區(qū)域,因此這些區(qū)域在通過相關的push操作成為報文的一部分之前,不能被ODP應用程序?qū)ぶ坊蛑苯邮褂谩?類似的,通過pull操作刪除的字節(jié)就不能被訪問了。
12.2. 報文段和尋址
ODP平臺使用一系列方法和技術來有效存儲和處理數(shù)據(jù)包。 這些技術各個平臺各不相同,因此為了保證可移植性,ODP提供一些數(shù)據(jù)包引用的約定。
ODP API通過抽象數(shù)據(jù)結構 odp_packet_t 來引用數(shù)據(jù)包對象。 描述數(shù)據(jù)包的系統(tǒng)元數(shù)據(jù)的各種位與數(shù)據(jù)包相關聯(lián)。 通過參考元數(shù)據(jù),ODP應用程序通過最小化檢查報文數(shù)據(jù)的需求來加速報文處理。 這是因為,通過解析和分類功能來填充元數(shù)據(jù),該功能與通過ODP調(diào)度程序呈現(xiàn)給應用程序之前發(fā)生的入口處理相耦合。
當ODP應用程序需要檢查數(shù)據(jù)報內(nèi)容時,它通過API調(diào)用來獲取數(shù)據(jù)包的訪問地址,該API調(diào)用可用于應用程序的一致性訪問。 為了確保可移植性,ODP應用程序假定底層實現(xiàn)將數(shù)據(jù)包存儲在預定于的實現(xiàn)及大小可管理的段中。 這表示應用程序可以通過正常的存儲器訪問來引用數(shù)據(jù)包的連續(xù)可尋址部分。 ODP提供API,允許應用程序按照需要以有效和便攜的方式對數(shù)據(jù)包進行操作。 通過將這些與數(shù)據(jù)包提供的元數(shù)據(jù)結合,ODP應用程序可以完全獨立于平臺的方式運行,同時在支持ODP的各種平臺上實現(xiàn)最佳性能。
報文段尋址及元數(shù)據(jù)的關系如下圖所示:
報文元數(shù)據(jù)在解析階段設置,標識報文中各種報頭的起始偏移量。 報文本身被存儲為由ODP實現(xiàn)管理的區(qū)域的一系列段。 段0是數(shù)據(jù)包的第一個段,并且通常是數(shù)據(jù)包的headroom和報頭所在的位置。 根據(jù)報文長度的不同,附加段可以是報文的一部分,并且包含報文剩余的有效載荷和尾部。 應用程序不需要關注段,除非當前應用程序需要對報文進行尋址。 因此,例如,如果應用程序進行類似 odp_packet_l4_ptr() 的調(diào)用來獲取數(shù)據(jù)包4層頭的地址時,從該調(diào)用返回的長度就是從4層頭部開始的可連續(xù)尋址的長度。 這是因為以下字節(jié)占用了不同的段,并且可能存儲在不同地方。 為了獲得對這些字節(jié)的訪問,應用程序只需要尋址到該偏移量,并且能夠?qū)ぶ氛加孟乱粋€段的數(shù)據(jù)包字節(jié)等。 請注意,任何數(shù)據(jù)包可尋址性調(diào)用的返回長度始終是剩余數(shù)據(jù)包長度或其包含段的大小中較小的。 因此,上圖中的段2的映射將返回僅擴展到分組結尾的長度,因為剩余的字節(jié)是為數(shù)據(jù)包保留的尾部的一部分,并且應用程序不可用,直到可用通過適當?shù)腁PI調(diào)用。
不僅PUSH/PULL API允許應用程序?qū)Ξ斍岸谓Y構中的數(shù)據(jù)包執(zhí)行高效操作,ODP還提供了允許段添加/刪除的API。 odp_packet_extend_head() 及 odp_packet_trunc_head() API允許在數(shù)據(jù)包開始處插入或刪除段,而 odp_packet_extend_tail() 及 odp_packet_trunc_tail() 允許在數(shù)據(jù)包尾部添加刪除段。 通過向數(shù)據(jù)包添加一個或多個段來擴展數(shù)據(jù)包,可以允許實現(xiàn)自定義長度的數(shù)據(jù)包。 截斷數(shù)據(jù)包會刪除一個或多個數(shù)據(jù)段,以縮小數(shù)據(jù)包的大小。
12.3. 元數(shù)據(jù)處理
如上所述,作為報文接收階段分類處理的一部分,報文元數(shù)據(jù)通常是由解析器設置。 需要注意的是,作為處理數(shù)據(jù)包的一部分,應用程序可能會更改此元數(shù)據(jù),以反映報文內(nèi)容和結構的變化。 雖然更改元數(shù)據(jù)可能會影響一些ODP API,更改元數(shù)據(jù)旨在記錄應用程序?qū)?shù)據(jù)包的更改,但本身不會導致進行這些更改。 例如,如果應用程序通過使用 odp_packet_l3_offset_set() API來更改L3偏移量,那么后續(xù)對 odp_packet_l3_ptr() 的調(diào)用將返回一個從該更改的偏移開始的地址。 更改屬性,如 odp_packet_has_udp_set() 這種操作本身不會將非UDP數(shù)據(jù)包變成有效的UDP數(shù)據(jù)包。 預估應用程序更改數(shù)據(jù)包時應該謹慎,以確保生成的元數(shù)據(jù)更改正確反映應用程序?qū)?shù)據(jù)包的修訂。
12.4. 報文操作
ODP報文操作API主要分成兩類:不改變數(shù)據(jù)包段結構的以及可能會改變數(shù)據(jù)包段結構的。 上面已經(jīng)舉過這樣的例子。 PUSH/PULL API允許對數(shù)據(jù)包headroom/tailroom進行處理,這不會導致數(shù)據(jù)包原有分段的改變。 而EXTEND/TRUNC API提供相同的功能,但是潛在的,可能會將添加段到數(shù)據(jù)包,或者從數(shù)據(jù)包刪除段。
具有執(zhí)行功能類似的兩類API的原因是在大多數(shù)操作實現(xiàn)中,不改變報文段結構的操作將比那些改變段結構的操作更加高效。 為了解決這個問題,可能涉及數(shù)據(jù)包段更改的API總是將 odp_packet_t 作為返回值。 應用程序應該使用這個新返回的指針。
為了使以這種方式操作數(shù)據(jù)包的應用程序能夠最有效地運行,這些API的返回值遵循標準約定。 通常,小于零的返回值表示錯誤,輸入數(shù)據(jù)包將不會有變化。 返回值為零表示成功,但也表示任何緩存的數(shù)據(jù)包的可尋址性仍然有效。 大于零的返回值也表示成功,但潛在地改變了數(shù)據(jù)包可尋址性。 例如,如果應用程序先前通過 odp_packet_l3_ptr() API獲得了數(shù)據(jù)包的第3層頭的可尋址性,則返回值為0將意味著應用程序可能會繼續(xù)使用該指針來訪問L3頭, 而返回值大于零意味著應用程序應該重新發(fā)出該調(diào)用以重新獲得可尋址性,因為報文段可能已經(jīng)改變,因此舊指針可能不再有效。
12.4.1. 報文拷貝
數(shù)據(jù)包最簡單的操作是制作數(shù)據(jù)包的全部或部分副本。 odp_packet_copy() 和 odp_packet_copy_part() API用于返回包含現(xiàn)有數(shù)據(jù)包的整體或選定部分的新數(shù)據(jù)包。 請注意,這些操作還指定新數(shù)據(jù)包申請的報文池。
12.4.2. 報文數(shù)據(jù)拷貝及移動
ODP提供了幾個API,使得數(shù)據(jù)包的部分可以復制到存儲區(qū)域、另一個數(shù)據(jù)包或單個數(shù)據(jù)包中,如下所示:
當源或目的地址是ODP數(shù)據(jù)包時,這些API提供邊界檢查。 這意味著,數(shù)據(jù)必須在 0..odp_packet_len()-1 的偏移范圍內(nèi)。 對于涉及內(nèi)存區(qū)域的操作,調(diào)用方負責確保 odp_packet_copy_to/from_mem() 引用的內(nèi)存區(qū)域有效。
在單個數(shù)據(jù)包中處理數(shù)據(jù)時,提供了兩個類似的API:odp_packet_copy_data() 和 odp_packet_move_data() 。 其中,移動操作更為通用,并且即使在源和目的數(shù)據(jù)區(qū)域重疊時也可以使用移動操作。 僅當調(diào)用者知道這兩個區(qū)域不重疊時,才能使用復制操作,且此時復制更高效。 當處理重疊的存儲區(qū)域時,odp_packet_move_data() 操作就好像是源區(qū)域首先被復制到不重疊的單獨存儲區(qū)域,然后再從該區(qū)域復制到目標區(qū)域。