:question: 提問清單
Linux 核心提供一套 Resizable, Scalable, Concurrent Hash Table,名為 rhashtable
(見 include/linux/rhashtable.h 及 lib/rhashtable.c),描述於論文〈Resizable, Scalable, Concurrent Hash Tables via Relativistic Programming〉。摘譯 LWN 文章,並佐以原論文,描述 rhashtable 何以落實高效的並行雜湊表,搭配撰寫 Linux 核心模組作為試驗。
本任務應整理「2022 年開發紀錄」素材,並於 Linux v5.15+ 重現實驗,著重於 Linux 核心原始程式碼的應用案例 (如 mac80211_hwsim
)。
關於核心模組掛載詳情請見Linux 核心模組運作原理
如果無法直接禁用 Secure Boot,並且在 BIOS 或 UEFI 設定界面中只看到 "Secure Boot Control" 選項,那麼禁用 "Secure Boot Control" 選項
linux-headers
套件linux-headers
套件已正確安裝於開發環境預期得到以下輸出:
test_rhashtable.c
Makefile
編譯核心模組的命令:
test_rhashtable.ko
之後,可將其掛載:直接使用 dmesg 會顯示以下權限不足的警告
"dmesg: read kernel buffer failed: Operation not permitted"
$ sudo dmesg -c
用法,清除核心緩衝區中的訊息並將其輸出到終端機上(可以清掉之前的資訊,只看新增的資訊,很方便)。
顯示核心資訊
- v5.13 rcuhashbash
- 更早的來源版本: rcuhashbash-resize (v3.17)
2022 開發紀錄 中提到為了簡單起見 relativistic hash tables 透過整數因子 (integral factors) 來改變 buckets 的數量,也就是說 hashtable 中 buckets 的數量是加倍或減半的,對應到論文〈Resizable, Scalable, Concurrent Hash Tables via Relativistic Programming〉描述
For simplicity, relativistic hash tables constrain resizing to change the number of buckets by integral factors—for instance, doubling or halving the number of buckets. This guarantees two constraints: First, when shrinking the table, each bucket of the new table will contain all entries from multiple buckets of the old table; and second, when growing the table, each bucket of the new table will contain entries from at most one bucket of the old table.
在 LWN 文章中也有相對應的描述
In practice, it means that the size of a table can only change by an integer factor; normally that means that the size of a table can only be doubled or halved.
commit ae2212a7216b674633bdc3bd2e24947a0665efb8
Author: Madhuparna Bhowmik madhuparnabhowmik10@gmail.com
Date: Sun Jul 12 18:40:02 2020 +0530
rculist: Introduce list/hlist_for_each_entry_srcu() macros
list/hlist_for_each_entry_rcu() provides an optional cond argument
to specify the lock held in the updater side.
However for SRCU read side, not providing the cond argument results
into false positive as whether srcu_read_lock is held or not is not
checked implicitly. Therefore, on read side the lockdep expression
srcu_read_lock_held(srcu struct) can solve this issue.
However, the function still fails to check the cases where srcu
protected list is traversed with rcu_read_lock() instead of
srcu_read_lock(). Therefore, to remove the false negative,
this patch introduces two new list traversal primitives :
list_for_each_entry_srcu() and hlist_for_each_entry_srcu().
Both of the functions have non-optional cond argument
as it is required for both read and update side, and simply checks
if the cond is true. For regular read side the lockdep expression
srcu_read_lock_head() can be passed as the cond argument to
list/hlist_for_each_entry_srcu().
Suggested-by: Paolo Bonzini <pbonzini@redhat.com>
Tested-by: Suraj Upadhyay <usuraj35@gmail.com>
Tested-by: Naresh Kamboju <naresh.kamboju@linaro.org>
[ paulmck: Add "true" per kbuild test robot feedback. ]
Signed-off-by: Madhuparna Bhowmik <madhuparnabhowmik10@gmail.com>
Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
在 linuux/rculist.h 中對於 hlist_for_each_entry_rcu
的描述
cond
,參考 LWN-Lockdep-RCU、RCU and lockdep checking,RCU 透過允許 readers 和 writers 同時進入 critical section 來改善可擴展能力 (scalability) 問題,同時因為 rcu_dereference()
在 2.6.32 版本後出現次數大幅增加,故引入 lockdep 方式來檢查 rcu_dereference()
rcu_dereference_raw()
提取 RCU-protected value(pointer of index) 且 without lockdep-RCU checks,由 __list_check_rcu
展開 RCU_LOCKDEP_WARN()
來檢查 lockdep下載程式碼
編譯程式碼
在 Makefile 內寫好編譯、清除、掛載、卸載可直接使用。
執行編譯後,將會出現以下警告資訊,這是由於實作中以 rcuhashbash_try_lookup()
負責查找 hashtable 中的值,如果成功找到,則回傳 true
,並且 rcuhashbash_try_lookup()
會被不同的演算法實作 (RP,DDDS,rwlock) 中被呼叫。
__attribute__((unused))
消除。產生 rcuhashbash-resize.ko
之後,可將其掛載:
將其卸載:
dmesg 顯示資訊的命令。
出現以下訊息
由於印出狀態的函式
rcuhashbash_print_stats()
存在於rcuhashbash_exit()
中,所以要卸載過rcuhashbash-resize.ko
才會印出完整的狀態資訊。
在 rcuhashbash-resize.c
中包含 3 個演算法 (RP, DDDS, rwlock) 的比較,從 \(2^{13}\) 擴張 (expansion) 至 \(2^{14}\), 再從 \(2^{14}\) 縮小 (shrink) 回 \(2^{13}\),此處可對應論文提及之 integral factors (hashtable buckets 的數量是加倍或減半)
編譯完成後使用 modinfo
查看 module_param
以及 MODULE_PARM_DESC
對核心模組的相關設定
對於每組測試參數,執行 10 組 benchmark 測試,每次 10 秒,然後將結果取平均值。
測試案例 (Expect lookups to take less time in a table with more buckets)
測試腳本
dash
bash
(selected "NO")do_measurement.sh
執行 do_measurement.sh
畫實驗產生的數據圖 script.gp
實驗環境
原始數據
Readers | RP | DDDS | rwlock |
---|---|---|---|
1 | 307665798 | 307405065 | 278619902 |
2 | 609246377 | 607262585 | 341572765 |
4 | 1162583704 | 1156299133 | 315475526 |
8 | 1942473238 | 1924575980 | 287988264 |
16 | 2479381351 | 2442075521 | 322175441 |
Readers | RP | DDDS | rwlock |
---|---|---|---|
1 | 450585149 | 449389794 | 398910323 |
2 | 889254383 | 890039215 | 382165321 |
4 | 1700583442 | 1693820498 | 340461121 |
8 | 2835046421 | 2828206776 | 318158465 |
16 | 3593027567 | 3565010193 | 321311198 |
Readers | RP | DDDS | rwlock |
---|---|---|---|
1 | 415006421 | 349717949 | 1734373 |
2 | 818310957 | 692473055 | 2686819 |
4 | 1572226023 | 1322716490 | 2754268 |
8 | 2620503729 | 2211407562 | 2923274 |
16 | 3324667883 | 2758353157 | 65682783 |
圖示及結果討論
固定 8k buckets without resizing, 3 種演算法比較
3 種演算法 (RP, DDDS, rwlock) resizing 比較
RP - 固定 size (8k, 16k) 以及 resize 比較
DDDS - 固定 size (8k, 16k) 以及 resize 比較
rwlock - 固定 size (8k, 16k) 以及 resize 比較
結果上來說如同論文所述,RP 以及 DDDS 對於 resize 並沒有喪失太多效能,且隨著 concurrent readers 增加,RP 表現略優於 DDDS,而 rwlock 的表現在 resize hashtable 就差強人意了。
〈Resizable, Scalable, Concurrent Hash Tables via Relativistic Programming〉在 2011 年發表,RCU 在 Linux 核心中廣泛被使用,以 lock-free synchronization 的機制,透過區分 readers 和 writers 以及延遲回收 (deferred destruction) 來減少 lock 的使用,其優缺點包含 (節錄自 SOSP'2015):
最早的 commit 在 2014 年被納入 kernel 中,而 Read-Log-Update (RLU) 作為 RCU 的擴充在 2015 年 SOSP 研討會上被提出,RLU 保有了 RCU 非同步走訪 (unsynchronized traversals) 的特性,同時能夠支援 multi-location atomic updates,論文中也同樣在 kernel space 對於 rhashtable 進行測試
Thus, to compare the performance of the kernel implementation of RCU with RLU, we create a Linux kernel module along the same line as Triplett et al. with the RCU hash table
具體的實作方法是透過紀錄所有的更動在 per-thread log 中,如下圖 (節錄自 SOSP'2015)
修正圖片存取權限
:notes: jserv
Fixed, thanks
Example
g-clock
) 是 22
,\(T_2\) 一開始有空的 write-log (w-log
) 以及 \(\infty\) 的區域寫時鐘 (local write clock, w-clock
)
g-clock
並儲存到自己的 l-clock
中,接著讀取 object (\(O_1\), \(O_2\)),此時還沒有任何 object 被 lock 住,所以就直接讀取w-log
中。同時,\(T_1\) 讀取 \(O_2\) 且偵測到它被 \(T_2\) 給 locked 了,故 \(T_1\) 因此決定必須要 steal 新的 copied object,故 \(T_1\) 會去比較他的 l-clock
和 \(T_2\) 的 w-clock
l-clock
大於等於 \(T_2\) 的 w-clock
才會去 steal 新的 copy23
),將其寫入到自己的 w-clock
以及全域的 g-clock
中 (這裡的順序很重要,因為這個 g-clock
就會決定新舊資料的差別)。此時操作就開始分為 "old" 和 "new",也就是在 clock increment 之前和之後,此時 \(T_2\) 就必須要等待舊的操作結束,也就是 \(T_1\)。
l-clock
和 \(T_2\) 的 w-clock
認知到這個操作是 "new" 的,所以就去 steal \(T_2\) 中的 write-log 中的新 copy object \(O_2\),如此一來新的操作都只會去讀取新的 object copies。雖然 RLU 保有了 RCU 緣有的優點且額外實現了 multi-location atomic updates,但尚無法稱其為一種通用的 concurrency control,How about RLU? Is RLU a generic concurrency control? 中指出 RLU 相較於 RCU 已經更為通用且高效,但尚未能夠稱為是通用的方法,因為 RLU 僅實踐 write-read synchronization,並不包含 write-write synchronization
RLU is certainly more generic and efficient than RCU but it does not allow for generic code to be used.
而這點在論文中也有提及
The basic idea of RLU described above provides read-write synchronization for object accesses. It does not however ensure write-write synchronization, which must be managedby the programmer if needed (as with RCU)
為了避免 write-write conflicts, RLU 會在需要更動 object 時進行上鎖,參照論文 pseudo code line 36
〈Read-Log-Update〉中也進行了和〈Resizable, Scalable, Concurrent Hash Tables via Relativistic Programming〉相同的實驗以此來比較吞吐量 (在 SOSP 中有提及 RLU 的挑戰之一是在保有 RCU 的效能),論文實測出來的結果吞吐量是差不多的 (實驗待補),但 resize 卻慢 RCU 兩倍左右,這是因為 RLU 在 duplicating nodes 的 overhead,但 resize 這件事情本身發生的並不頻繁,所以可以推敲出 resize 的 latency 相較於 concurrent lookups 更顯得沒那麼重要。
在 selinux-kernel/net/bridge/br_vlan_tunnel.c 中,運用了 rhashtable
來實現網路橋接 (bridge) 的功能。
TODO: 提供 br_vlan_tunnel.c
對應的測試方式,避免「舉燭」,要能實際操作。「舉燭」,要能夠實際操作。
:notes: jserv
具體來說, br_vlan_tunnel.c
用於管理和處理 VLAN (VLAN tunnel port) 通道連接埠的資料,包括增加、刪除、查詢 VLAN 相關資料。這些函式與連接埠設備的VLAN連接埠相關聯,透過程式管理與處理目的元資料 (dst_metadata) ,確保資料封包按照預期的方式在連接埠設備上傳遞和處理。
br_vlan_tunid_cmp
tunnel_id
來比較是否與給定的資料一致。ptr
指標指向 struct net_bridge_vlan
結構體的 vle
。
struct net_bridge_vlan
是一個表示網路橋接中VLAN配置的資料結構。
透過將ptr
指向struct net_bridge_vlan
可以在雜湊表中儲存和查詢與特定通道 ID 相關的 VLAN 配置。
arg->key
是一個指向 void
的指標,而 vle->tinfo.tunnel_id
是 __be64
類型的指標,故須要轉型。arg->key
與 vle->tinfo.tunnel_id
是否相同。br_vlan_tunnel_rht_params
br_vlan_tunnel_rht_params
的 struct rhashtable_params
結構體,設定配置 rhashtable
的參數。.head_offset
用於確認雜湊表的節點結構中,指向雜湊表頭部的指標位置。在這裡為 tnode
的偏移量。offsetof 概念.key_offset
用於確認雜湊表的節點結構中,指向關鍵字的指標位置。.key_len
為關鍵字的長度。.nelem_hint
為雜湊表內的預計儲存的元素數量。.obj_cmpfn
指向比較函式的指標,用於比較關鍵字。.automatic_shrinking
是否允許雜湊表 resize。br_vlan_tunnel_lookup
rhashtable
雜湊表中查詢指定的通道 ID (tunnel_id
)。tb1
為一個指向 rhashtable
的指標,在此 rhashtable
查詢目標。tunnel_id
為一個 __be64
型別的通道 ID ,表示為要查詢的目標。vlan_tunnel_info_release
struct net_bridge_vlan
結構體內部與 VLAN 相關的通道資料。rtnl_dereference
巨集來取得指向 tunnel_dst
成員的指標 tdst
。
rtnl_dereference
用於對指標解指標,並進行 RCU(Read Copy Update)處理。
WRITE_ONCE
將 tunnel_id
成員的值設定為 0 。RCU_INIT_POINTER
將 tunnel_dst
成員的值設定為 NULL
。tdst->dst
指向目標的資源。vlan_tunnel_info_del
rcu_access_pointer
檢查 tunnel_dst
是否為 null
。rhashtable_remove_fast
從 &vg->tunnel_hash
雜湊表中快速移除 VLAN
通道節點資訊。
&vlan->tnode
要移除的節點。br_vlan_tunnel_rht_params
雜湊表內的參數。vlan_tunnel_info_release
釋放資源。__vlan_tunnel_info_add
vg
: VLAN 群組。vlan
: 要添加資料的 VLAN。tun_id
: 通道 ID 。rtnl_dereference
檢查 vlan->tinfo.tunnel_dst
是否為 null
,如果不是,表示通道內已有資料,回傳錯誤代碼 -EEXIST
。__ip_tun_set_dst
函式建立一個 metadata
,用來表示通道的目的地。__ip_tun_set_dst
傳入參數為下列:TUNNEL_KEY
表示通道類型, TUNNEL_KEY
表示使用 Key 通道KEY
表示通道 IDmetadata
是否為 NULL
,是的話,回傳錯誤代碼 -EEXIST
。IP_TUNNEL_INFO_TX
和 IP_TUNNEL_INFO_BRIDGE
的值設定成 mode
內的對應值。表示該通道是用於傳輸與橋接。rcu_assign_pointer
將 metadata
賦值給 vlan->tinfo.tunnel_dst
將 VLAN 通道資料與通道目的地連結起來。WRITE_ONCE
將通道 ID (key
) 寫入 vlan->tinfo.tunnel_id
。表示該 VLAN 通道資料與通道 ID 的對應。rhashtable_lookup_insert_fast
函式將 VLAN 通道資料添加到 tunnel_hash
雜湊表中。vlan_tunnel_init
vg
指向 net_bridge_vlan_group
作為輸入參數。vlan_tunnel_deinit
vg
指向 net_bridge_vlan_group
作為輸入參數。br_vlan_tunnel.c
下載程式碼
進入 Repository
編譯程式碼
$ sudo make -C /lib/modules/$(shell uname -r)/build M=$(PWD)
會出現Command 'shell' not found
,手動輸入版本資訊可以解決此問題。
版本資訊$ uname -r
。
將其掛載:
想想要用什麼方法測試??
將其卸載:
kfifo 為 lock-free synchronization 的實作之一,支援 single reader single writer