一、KCSAN介紹
KCSAN(Kernel Concurrency Sanitizer)是一種動態競態檢測器,它依賴于編譯時插裝,并使用基于觀察點的采樣方法來檢測競態,其主要目的是檢測數據競爭。
KCSAN是一種檢測LKMM(Linux內核內存一致性模型)定義的數據競爭(data race)的工具,同時它也可以控制報告哪種類型的數據競爭。
KCSAN知道LKMM定義的所有標記原子操作,以及LKMM尚未提到的操作,例如原子位掩碼操作(bit mask)。
KCSAN擴展了LKMM,例如通過提供data_race()標記,來表示存在數據競爭和缺乏原子可能性。
1.1 LKMM(Linux內核內存一致性模型)
Linux內核內存模型目前在源代碼樹中的memory-barrier.txt和atomic_ops.txt文件中有非正式的定義。包含以下組成部分:
變量訪問(Variable Access)
使用READ_ONCE()、WRITE_ONCE()和ACCESS_ONCE()宏來保護從共享(但非原子)變量的加載和存儲;
內存屏障(Memory Barriers)
一類同步屏障指令,是CPU或編譯器在對內存隨機訪問的操作中的一個同步點,使得此點之前的所有讀寫操作都執行后才可以開始執行此點之后的操作。比如barrier、smp_mb/smp_wmb/smp_rmb等;
鎖操作(Locking Operations)
原子操作(Atomic Operations)
控制依賴(Control Dependencies)
Linux內核提供了一個有限的控件依賴的概念,在某些情況下對依賴控件的存儲進行優先加載;
RCU寬限期授權關系(Grace-Period Relationships)
允許更新者等待所有已經存在的讀側臨界區完成,再回收舊的資源;
C11原子原語 (C11 Atomics)
將原子原語的實現委托給編譯器;如果多個體系結構采用這種方法,將減少體系結構特定代碼的數量。
1.2 數據競爭
為什么要關心數據競爭?
C語言的發展獨立于并發性。如果給定的變量或訪問沒有任何特別之處,則變量只會在響應當前線程的存儲時發生變化。
C語言和編譯器的進化對并發性不敏感
優化編譯器正變得越來越豐富
因此,編譯器可以并且使用各種優化,包括負載融合、代碼重新排序和許多其他可能導致并發算法故障的優化。
讀取拆分(單次訪問多次讀取)
存儲拆分(單次訪問多次寫入)讀取融合(編譯器直接使用上一次對這個變量的load結果,而不是真正再去load一次)
存儲融合(編譯器優化寫入變量流程,不再真實寫入)
代碼重排(把一些類似的計算歸在一起,節省占用的寄存器,改善現代超標量微處理器里面各個運算單元的利用效率)
虛擬讀取(編譯器優化會導致多次讀取,導致后續加載異常)
虛擬存儲(編譯器優化會導致多次存儲,導致后續存儲異常)
.....
因此需要告訴編譯器并發代碼,Linux提供內存一致性模型,也提供檢查方法解決此類問題。
1.2.1 訪問方式
普通訪問
標記訪問
1.2.2 同步沖突訪問的檢測條件
在訪問同一個地方并且至少有一個是寫操作
至少有一個是普通訪問(比如x+42)
以下線程打鉤的是標準做法;打叉的是可能存在數據競爭的情況。
1.2.3 哪些不屬于數據競爭
例如:使用不對稱的鎖機制,并且使用READ_ONCE/WRITE_ONCE標記訪問。
二、依賴與配置方案
2.1 版本支持
KCSAN支持GCC/CLANG編譯,需要GCC版本11,CLANG 12以上版本。
x86_64: >=5.8 ARM64: >=5.17
2.2 KCSAN工具鏈支持
cc-option,-fsanitize=thread --param tsan-distinguish-volatile=1
2.3 配置選項支持
三、工作原理與觸發條件
3.1 使用方式
檢查未標記讀取是否寫入競爭,會持續掃描內核的主要分支,在訪問的內存位置上設置觀察點,挑出導致數據爭用的數據,并將其報告給內核日志。
●用“軟觀察點”查找競爭
〇設置觀察點和失速通道;
〇如果監測點已經存在,那么競爭檢查將照常進行;
〇如果值改變了--> 競爭;
〇失速通道隨機延遲,增加觀察競爭狀態的機會;
默認值:任務[1,80]us,中斷[1,20]us。
●為所有檢測內存訪問設置觀察點
〇 注釋標記訪問,僅用于檢查非標記訪問是否存在觀察點;
KCSAN從不在標記的訪問上設置觀察點;
如果對并發訪問的變量的所有訪問都正確地標記了,KCSAN將永遠不會觸發觀察點,因此永遠不會報告訪問。
●采樣: 周期性建立觀察點
〇默認值:平均2000次訪問。
3.2 KCSAN軟觀測點
基于地址頁索引
〇可以溢出到相鄰槽。
〇使用索引確保報告元數據給匹配的生產者/消費者。
具有靈活、可縮放的特點,以數組的形式存放。
代碼片段如下:
入口函數check_access,在check_access數據地址、長度、類型;在check_access函數執行find_watchpoint判斷。需要檢測的ptr已經插樁編譯。
3.3 KCSAN 運行流程
進入check_access函數,格式描述包含數據指針、長度、讀寫類型;
確認是否需要觀測,需要滿足至少一個寫操作且為普通訪問;
如果判定需要觀測,加入觀察列表;
延時一段時長,查看是否有訪問、變更數據等情況;如果有,則生產數據表,并打印數據到控制臺;如果沒有則退出;
在步驟3,如果未發現合適的觀測點,則該數據運行流程退出
3.4 ASSERT檢測機制
KCSAN提供有一種斷言檢測機制,檢查在數據競爭模型以外的情況下提供競爭檢測;
3.4.1 ASSERT集合
3.5 KCSAN特點
四、測試套件
4.1 KUNIT測試模型
KCSAN提供KUNIT的支持
創建多個access_thread線程用于測試用例函數的調用接口;
掛接console跟蹤點,該跟蹤點監控串口輸出數據;如果有數據競爭報錯,可以捕獲并判斷;
啟動測試用例接口函數,實現測試函數的掛接并提供超時判定(缺省執行500毫秒);
在執行超時以后,判斷輸出是否與預想一致;并給出判斷結果。
4.2 測試條件
1. 配置CONFIG_KCSAN_KUNIT_TEST=y使能KUNIT
2. KCSAN功能正常開啟
4.3 測試環境
QEMU Linux 6.11 core 4 GCC11
測試覆蓋:
1. 不同條件下的數據競爭data_race
2.斷言函數數據競爭assert_exclusive_x
3. barrier/lock判定
五、過程與案例分析
5.1 KCSAN啟動過程
1. 在完成KCSAN配置后,系統啟動時有“kcsan:enable early”打印:
2.后臺會實時進行觀測點的監控與比對,如果比中會有”BUG:KCSAN”控制臺打印來描述數據競爭的信息;這些信息包括調用函數、數據競爭地址、CPU號、進程號等;可在不同的測試場景進行壓力測試;
3.在運行過程中,查看“KCSAN kernel debug”節點查看當前的狀態,這些狀態信息包括觀測點、數據競爭、ASSERT報錯等一系列信息;
5.2 案例一
描述:IGMP協議timer超時與事件函數在讀寫mr_ifc_count變量的數據競爭
net: igmp: fix data-race in igmp_ifc_timer_expire()
解決辦法:
1. igmp_ifc_event/ igmp_ifc_timer_expire函數在讀寫mr_ifc_count變量存在數據競爭,需要使用LLKM 訪問保護;
2. 修改調用mr_ifc_count點,使用READ_ONCE/WRITE_ONCE保證編譯器的一致性;
3. mr_ifc_count和in_dev->mr_ifc_count值不等時啟動重傳機制;
5.3 案例二
描述:在taskstats_exit()中分配和測試任務統計時,會有一個競爭在讀寫sig->stats
When assiging and testing taskstats in taskstats_exit() there's a race when writing and reading sig->stats
解決辦法:
1. 結構體成員sig->stats存在數據競爭,需要使用LLKM訪問保護;
2. smp_load_acquire/smp_store_release函數解決CPU數據同步和編譯器同步問題,適用于同一個函數內部的數據競爭;
六、總結
本文從工作原理、運行流程、測試方式等多個方面介紹了KCSAN,旨在讓讀者能夠對KCSAN運行有一個直觀的認識,利用KCSAN在產品中解決一些數據競爭問題;數據競爭是一個復雜問題,用KCSAN能幫助大家快速找到數據競爭問題,進而尋找方法解決或規避,本文更多傳遞是一種發現和解決此類問題的思路。
消殺器技術在不斷地迭代和更新,也讓大家多一份探尋世界、改變世界的機會;借此機會,站在巨人的肩膀上,讓大家看得更遠、走得更遠,愿大家都有一個美好的明天。
審核編輯:劉清
-
C語言
+關注
關注
180文章
7608瀏覽量
137071 -
編譯器
+關注
關注
1文章
1635瀏覽量
49169 -
LINUX內核
+關注
關注
1文章
316瀏覽量
21672 -
rcu
+關注
關注
0文章
21瀏覽量
5455
原文標題:內核并發消殺器(KCSAN)技術分析
文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論