LWN:代碼tag功能的新框架!
關(guān)注了就能看到更多這么棒的文章哦~
A framework for code tagging
By Jonathan Corbet
September 1, 2022
DeepL assisted translation
https://lwn.net/Articles/906660/
內(nèi)核代碼有時(shí)很需要進(jìn)行內(nèi)省,因?yàn)樗?jīng)常需要對(duì)自己進(jìn)行引用。為了實(shí)現(xiàn)這種內(nèi)?。╥ntrospection),內(nèi)核已經(jīng)開發(fā)了幾種機(jī)制來識(shí)別代碼中的特定位置,并執(zhí)行與這些位置相關(guān)的操作。由 Suren Baghdasaryan 和 Kent Overstreet 發(fā)布的代碼標(biāo)記框架(code-tagging framework)這組 patch,試圖用一個(gè)框架來取代之前各種臨時(shí)(ad hot)實(shí)現(xiàn),并增加一些新的應(yīng)用。
內(nèi)核有很多場(chǎng)合都需要確認(rèn)出代碼中的特定位置。例如,內(nèi)核代碼通常不允許產(chǎn)生 page fault,但是訪問用戶空間內(nèi)存的函數(shù)往往會(huì)出現(xiàn)這種情況。為了在這種情況下能采取正確行為,內(nèi)核 build 的過程會(huì)記下每個(gè)用戶空間的訪問操作的位置;當(dāng)發(fā)生 page fault 時(shí),就會(huì)檢查這個(gè)列表,如果發(fā)現(xiàn) page fault 發(fā)生在預(yù)期的位置,就會(huì)正常處理。內(nèi)核的動(dòng)態(tài)調(diào)試機(jī)制(dynamic debugging mechanism)則是另一個(gè)例子;每個(gè)用來調(diào)試的 print 語句都被 track 起來,并且可以獨(dú)立啟用。
實(shí)現(xiàn)這種機(jī)制的通常方法是在內(nèi)核二進(jìn)制文件中創(chuàng)建一個(gè)特殊的 ELF section;然后在這個(gè) section 里面填寫那些記錄了內(nèi)核中感興趣的位置信息的 struct 結(jié)構(gòu)。在運(yùn)行時(shí),內(nèi)核可以找到這個(gè) section,從中查找到包含了它所需信息的 struct 的數(shù)組。tagging framework 的核心是一組用來使創(chuàng)建和訪問這個(gè)特殊 section 更加容易的函數(shù)和宏。
一個(gè) code tag 表示代碼本身的一個(gè)位置;該位置用一個(gè)新增的 struct 來表示:
struct codetag {
unsigned int flags;
unsigned int lineno;
const char *modname;
const char *function;
const char *filename;
};
這個(gè)結(jié)構(gòu)中記錄了一個(gè)位置,但沒有其他信息;它是為了嵌入到另一個(gè)跟特定的 tagging application 相關(guān)的結(jié)構(gòu)中的。例如,patch set 中的很大一部分代碼是專門用來創(chuàng)建一個(gè)追蹤內(nèi)存分配的機(jī)制的;它可以記錄每個(gè)調(diào)用位置分配和釋放了多少內(nèi)存,從而用來追蹤內(nèi)存泄漏。為了做到這一點(diǎn),它將在每個(gè)內(nèi)存分配的位置創(chuàng)建一個(gè) tag,這個(gè) structure 如下:
struct alloc_tag {
struct codetag ct;
unsigned long last_wrap;
struct raw_lazy_percpu_counter call_count;
struct raw_lazy_percpu_counter bytes_allocated;
};
raw_lazy_percpu_counter 是一個(gè)此 patch set 中新增的 counter 類型。這樣我們就有了一個(gè) structure,可以將這些 counter 與存儲(chǔ)在 codetag struct 中的位置聯(lián)系起來。
這些結(jié)構(gòu)中的一個(gè)會(huì)被放置在特殊的 alloc_tags ELF section,并使用了宏定義的技巧:
#define DEFINE_ALLOC_TAG(_alloc_tag) \
static struct alloc_tag _alloc_tag __used __aligned(8) \
__section("alloc_tags") = { .ct = CODE_TAG_INIT }
然后再繼續(xù)使用一些宏定義的技巧,替換掉當(dāng)前的 alloc_pages() 函數(shù),來增加放置 tag 和記住這個(gè) allocation 調(diào)用的功能:
#define alloc_tag_add(_ref, _bytes) \
do { \
DEFINE_ALLOC_TAG(_alloc_tag); \
if (_ref && !WARN_ONCE(_ref->ct, "alloc_tag was not cleared")) \
__alloc_tag_add(&_alloc_tag, _ref, _bytes); \
} while (0)
#define pgtag_alloc_pages(gfp, order) \
({ \
struct page *_page = _alloc_pages((gfp), (order)); \
\
if (_page) \
alloc_tag_add(get_page_tag_ref(_page), PAGE_SIZE << (order)); \
_page; \
})
#define alloc_pages(gfp, order) pgtag_alloc_pages(gfp, order)
最終的結(jié)果是,對(duì) alloc_pages()的每次調(diào)用都會(huì)改為同時(shí)也創(chuàng)建了一個(gè)靜態(tài)的 alloc_tag 結(jié)構(gòu),記錄了所有調(diào)用的位置;這個(gè)結(jié)構(gòu)被放在 alloc_tags 部分中。當(dāng)進(jìn)行內(nèi)存分配的調(diào)用時(shí),該結(jié)構(gòu)中的兩個(gè) counter 會(huì)相應(yīng)地增加(相關(guān)實(shí)現(xiàn)在__alloc_tag_add()函數(shù)中,本文未展示)。此外,代碼中還在所分配 page 的 page_ext 結(jié)構(gòu)中記錄了內(nèi)存分配調(diào)用位置的 tag 的位置;這讓內(nèi)核得以跟蹤哪個(gè)調(diào)用位置分配了每個(gè) page。當(dāng)分配的 page 今后被釋放時(shí),這些信息就可以用來減少該調(diào)用位置的 counter 計(jì)數(shù)。
所有這些工作的最終成果,就是一個(gè)數(shù)組,其中放置了所有 alloc_pages()調(diào)用位置,每個(gè)調(diào)用位置都記錄了這里所分配的、尚未被釋放的內(nèi)存數(shù)量。該 framework 還包括用于遍歷這個(gè)數(shù)組以及在 debugfs 文件系統(tǒng)中顯示其內(nèi)容的相關(guān)功能的支持。不難看出,這些信息對(duì)于試圖追蹤內(nèi)存泄漏的開發(fā)者來說是非常有用的。這組 patch 的其他一些改動(dòng)包括為 slab 分配器增加了類似的跟蹤功能,并且能夠存儲(chǔ)每次分配的調(diào)用棧,從而提供更多關(guān)于內(nèi)存泄漏的真正來源的信息。
這個(gè)框架可以用在另一個(gè)完全不同的場(chǎng)景,那就是動(dòng)態(tài)故障注入(dynamic fault injection)。例如,驅(qū)動(dòng)程序代碼可以包括這樣的代碼:
if (dynamic_fault("foo-driver-init"))
return -EIO; /* Simulate a failure */
這個(gè) dynamic_fault()函數(shù),也同樣會(huì)在調(diào)用的位置放置一個(gè) code tag。它通常會(huì)返回 false,所以下面一行模擬出現(xiàn)失敗的代碼就不會(huì)被運(yùn)行。不過在 /sys/kernel/debug/dynamic_faults 下會(huì)出現(xiàn)一個(gè)新的開關(guān),可以用來啟用這個(gè) fault 點(diǎn),從而測(cè)試驅(qū)動(dòng)程序的錯(cuò)誤處理是否可以正常工作。
這組 patch 中還有更多內(nèi)容,包括 latency 跟蹤機(jī)制,以及重新實(shí)現(xiàn)的 dynamic debug 的功能。目前看來,code-tagging framework 使得開發(fā)者可以更容易地采用對(duì)性能影響最小的方式來給內(nèi)核添加這類功能。
圍繞這個(gè) patch set 的早期討論大部分是由 Peter Zijlstra 的提問所引發(fā)的,他詢問這個(gè)新的機(jī)制比起內(nèi)核的 tracepoint 機(jī)制提供了什么新的功能。Overstreet 回答說,代碼標(biāo)記機(jī)制有很多的優(yōu)點(diǎn)。其中包括從 boot 一開始就捕捉所有的活動(dòng),而不僅僅是在 tracing 功能啟動(dòng)之后才能開始追蹤,并且性能更好,更容易使用,而且不會(huì)出現(xiàn) event dropped 的問題。他說,這個(gè)問題應(yīng)該反過來問:tracing 功能的支持者應(yīng)該說明如何使用該子系統(tǒng)來提供類似的能力,并具有相當(dāng)水平的性能和使用方便性。
作為回應(yīng),Zijlstra 指出,使用 ftrace 就沒有必要 attach 到 tracepoint 上;將自定義處理程序(custom handler)attach 到 tracepoint 上,就可以解決對(duì)性能和 dropped event 的擔(dān)憂。Mel Gorman 補(bǔ)充說,tracepoint 的方法更靈活,適用于舊內(nèi)核,而且更廣泛。他還指出了 Oscar Salvador 的 patch set,其中實(shí)現(xiàn)了另一種內(nèi)存泄漏檢測(cè)方法。Michal Hocko 擔(dān)心 review 和維護(hù)如此規(guī)模的 patch set 比較有困難。
這是一個(gè)新的、大型的 patch set;它可能會(huì)被討論一段時(shí)間。code-tagging 部分本身看起來應(yīng)該是一個(gè)相對(duì)沒有爭(zhēng)議的代碼清理工作;理論上,它可以用一個(gè)框架取代內(nèi)核中的多種獨(dú)立實(shí)現(xiàn)。不過,每一個(gè)額外的改動(dòng)都可能需要額外的討論;我們不能簡(jiǎn)單地沖到內(nèi)存管理子系統(tǒng)里面修改掉核心分配器的代碼而不需要回答人們的質(zhì)疑。這組 patch 有可能最終被分割成多個(gè)部分,從而讓每個(gè)部分都能獨(dú)立地根據(jù)自己特有的優(yōu)勢(shì)來考慮是否合入。
全文完
LWN 文章遵循 CC BY-SA 4.0 許可協(xié)議。
長(zhǎng)按下面二維碼關(guān)注,關(guān)注 LWN 深度文章以及開源社區(qū)的各種新近言論~
