TraceEvent是內(nèi)核中一種探測的機(jī)制,據(jù)說在不使能的時候是沒有損耗的。據(jù)說使用起來挺簡單,但是要看懂著實(shí)需要花些力氣。
例子
從例子中學(xué)習(xí),一般都是比較好的方法。內(nèi)核開發(fā)者也比較nice,在內(nèi)核源碼samples/trace_events目錄下就有這么一個例子。
其中文件一共有三個:
這個例子以內(nèi)核模塊的形式存在,所以只要執(zhí)行make就可以編譯完成。
總的來說,要定義和使用tracepoint,只要做兩點(diǎn)。
用TRACE_EVENT來定義一個新的tracepoint
在需要的地方,使用函數(shù)trace_XXX打印輸出
有了例子我們就要跑一跑,來看看如何使用的。
首先我們要編譯出我們的例子,這時候需要加上打開兩個編譯配置
CONFIG_SAMPLES
CONFIGSAMPLETRACE_EVENTS
編譯
make M=samples/trace_events
然后加載這個例子模塊
modprobe trace-events-sample
因?yàn)橛脩?a target="_blank">接口在debugfs上,所以還要確保debugfs掛載了。
mount -t debugfs none /sys/kernel/debug/
此時我們就能在 /sys/kernel/debug/tracing/events/sample-trace/ 目錄下看到該模塊創(chuàng)建好的trace event了。
接下來,我們就可以打開這個探測時間,并且查看探測的輸出了。
cd /sys/kernel/debug/tracing
echo 1 > events/sample-trace/enable
cat trace
echo 0 > events/sample-trace/enable
通過cat trace觀察,可以看出系統(tǒng)運(yùn)行時的一些狀態(tài)。
讓我們進(jìn)一步再來看看events/sample-trace這個目錄:
可以看到
目錄名稱sample-trace由TRACE_SYSTEM這個宏定義,所以通過查找這個宏,就能知道有多少events的大類
每一個TRACE_EVENT都有一個自己的目錄
源文件中trace_XXX的函數(shù)就是執(zhí)行探測記錄的地方了。那么這些函數(shù)是怎么定義的呢?
TRACE_EVENT定義
看完了例子,我們就該看代碼實(shí)現(xiàn)了。講真,這是我見過的最長的宏展開了。之前在qemu上看到的那個hmp-command和這個比起來簡直就是個小屁孩。
先來看一下例子中是如何定義一個trace event的。和其他定義不同,定義trace event的定義在頭文件,而非源文件。我把trace-events-sample.h文件做一個簡要的打開。
中間我省略了很多TRACEEVENT及其變體,每一個TRACEEVENT對應(yīng)了一個trace point。
可以看到,一個trace event的定義需要涉及到起碼兩個頭文件。
史上最長宏定義
你以為就這么簡單嗎?當(dāng)然不是,作為有多年閱讀c語言代碼的老司機(jī),看到真正的定義,我都差點(diǎn)沒有吐出來。。。
好了,不扯淡了。怎么能很好的解釋這個宏展開的過程呢?還是用一張圖吧。倒吸一口氣,準(zhǔn)備一次無盡的代碼閱讀。
終于完了,也不知道有沒有漏掉什么。。。大家如果真的想要看實(shí)際代碼中展開后的代碼,可以運(yùn)行
make samples/trace_events/trace-events-sample.i
生成的文件是經(jīng)過預(yù)處理后得到的源代碼。不過相信我,你可能不太會愿意去看這個(捂臉)
回過頭來再看這展開,讓我們來總結(jié)一下這個過程:
一共包含了兩個頭文件:linux/tracepoint.h 和 trace/define_trace.h
在trace/definetrace.h中,反復(fù)定義了TRACEEVENT且再次包含samples/trace_events/trace-events-sample.h,實(shí)現(xiàn)了一個宏定義多次展開的效果
究竟定義了什么?
哪怕有了上面這個圖,我想大部分人也是不會去看的?;蛘哒f,看了可能也不知道這些宏展開究竟定義了些什么?
幫人幫到底,送佛送到西
既然都幫大家做了宏展開,那我就干脆再用一張圖展示一下這么多宏定義究竟定義了些什么。
經(jīng)過了一番云里霧里的宏展開,實(shí)際上就是(主要)定義出了這么一個數(shù)據(jù)結(jié)構(gòu) --traceeventcall。而且這個數(shù)據(jù)結(jié)構(gòu)關(guān)聯(lián)了幾個重要的小伙伴
tracepoint
trace_event_class
trace_event
后續(xù),我們將逐漸看到在初始化和使能的過程中,這些數(shù)據(jù)結(jié)構(gòu)之間的愛恨情仇。
注冊trace_event
有了數(shù)據(jù)結(jié)構(gòu),想要使用這個功能,我們能想到的第一步就是要把相關(guān)的數(shù)據(jù)結(jié)構(gòu)注冊到某個地方,這樣下次才能夠被使用到是不是?
這個秘密隱藏在了剛才宏展開的最后一次展開中,大家可以回過去搜“section("ftraceevents") &event##name;”。有內(nèi)核代碼經(jīng)驗(yàn)的朋友可能已經(jīng)猜到了,這個意思是我們把一部分的內(nèi)容強(qiáng)制保存在了一個名為ftraceevents的section中。這些是什么呢?對了就是traceevent_call結(jié)構(gòu)體的指針們。
有了這個信息,我們再來看鏈接文件的定義:
我們看到ftraceevents這個section被包含在_startftrace_events之間。那就沿著這條線繼續(xù)。
看到了么?我們依次從_start|stopftraceevents之間拿出每一個內(nèi)容,再執(zhí)行eventinit()。而這個類型正好是traceeventcall,和剛才的定義吻合上。
但是eventinit()里面又調(diào)用了什么call->class->rawinit(call),這是什么個鬼?別急,這個我已經(jīng)給你寫好了。請?zhí)氐絼偛沤忉宼raceeventcall的圖上找找,這個rawinit函數(shù)就是traceeventrawinit。最后這個通過registertraceevent將traceeventcall.event注冊到系統(tǒng)中,而這個event的類型是trace_event。
怎么樣,是不是夠刺激的?
最后我們再來展示一下trace_event注冊到系統(tǒng)中后的樣子吧。
trace_event結(jié)構(gòu)會在兩個地方注冊:
ftraceeventlist:這個鏈表用來遍歷事件的號碼
event_hash[128]: 這個哈希表用來查找
有沒有看到其中funcs的成員第一個是之前定義的 tracerawoutput_##name?我猜這個就是最后輸出到trace文件的代碼,你覺得呢?
好了,數(shù)據(jù)結(jié)構(gòu)注冊完了,接下來是什么呢?
注冊traceeventcall
在上一節(jié)中,我們看到了內(nèi)核通過編譯鏈接的方法找到了traceeventcall,并且將其中的traceevent注冊到了系統(tǒng)中,現(xiàn)在我們來看看traceevent_call是如何注冊到系統(tǒng)中的。
這個過程就在event_init()函數(shù)下面一點(diǎn)。一共有兩個步驟:
添加到ftrace_events鏈表
添加到trace_array的events
第一步就在剛才的代碼片段中l(wèi)istadd(&call->list, &ftraceevents),而第二步則是通過函數(shù)_traceearlyaddevents()。
__trace_early_add_events() list_for_each(call, &ftrace_events, list) __trace_early_add_new_event(call, tr)
經(jīng)過這次注冊,將traceeventcall和trace_array連接了起來:
創(chuàng)建tracefs
在使用trace工具的時候,會通過tracefs往某些文件里讀寫來控制ftrace。trace_event也不例外,所以我們要先來看一下tracefs的構(gòu)建,為后續(xù)的代碼閱讀做好準(zhǔn)備。
說起來這個過程有點(diǎn)繞,因?yàn)閯?chuàng)建tracefs的地方和剛才那些注冊函數(shù)不在一個地方(系統(tǒng)啟動時)。
具體細(xì)節(jié)可以看源代碼,這里解釋兩點(diǎn):
createeventtoplevel_files 創(chuàng)建了和trace event相關(guān)的根目錄的一些文件
eventcreatedir則是會對每一個tracearray->events上的traceevent_file調(diào)用,創(chuàng)建每個event的目錄
而這個tracearray->events則是由, 剛才看到的函數(shù)traceearlyaddnew_event()添加的。
初始化過程的梳理
到這里估計你已經(jīng)暈了,沒事我自己寫得也暈了。讓我們來梳理一下整個初始化過程,明確一下這個注冊和tracefs的創(chuàng)建順序。
(1) 從特定的section中拿到traceeventcall數(shù)據(jù)結(jié)構(gòu),并注冊了trace_event
(2) 將traceeventcall添加到了ftrace_events鏈表
(3) 將每一個traceeventcall以traceeventfile的形式添加到trace_array.events
(4) 為每一個trace_array.events創(chuàng)建自己的tracefs
廢了這么大力氣我們都做了什么呢?
關(guān)聯(lián)了tracefs和traceeventfile,也就是我嗯定義的traceeventcall。
所以,每當(dāng)我們操作一個tracefs文件的時候,后面就對應(yīng)這相應(yīng)的traceeventfile和traceeventcall了。
OK, 我們已經(jīng)為tracefs的操作做好了準(zhǔn)備,讓我們來看看打開trace event選項(xiàng)時的動作吧。
打開事件
在查看trace文件中的事件記錄前,我們需要使能這個事件。
echo 1 > events/sample-trace/enable
所以有個開關(guān)來控制事件。而當(dāng)我們寫這個文件的時候,觸發(fā)到的內(nèi)核函數(shù)就是剛才我們注冊tracefs對應(yīng)的ops中的eventenablewrite。
繞暈了,其實(shí)呢就是通過某種方式設(shè)置了tracepoint結(jié)構(gòu)體中的funcs成員。剛才我們在traceeventcall結(jié)構(gòu)體中已經(jīng)看到了tracepoint結(jié)構(gòu),這次該好好看一眼了。
主角終于登場了,經(jīng)過這么一頓騷操作后,我們將之前定義好的 traceeventrawevent##name掛到了tracepoint的funcs列表中。當(dāng)然我還省去了重要的一步--設(shè)置key。
輸出事件
終于到了最后了。之前說的都是定義和初始化,終于要看到調(diào)用的情況了。在例子中我們看到,當(dāng)我們需要輸出一個事件時,就會調(diào)用trace_XXX()。這次該輪到它出場了。
先來看看trace_XXX這個函數(shù)的定義,它也藏在了我們剛才宏定義的展開中,這次我們仔細(xì)看一眼
每次我們調(diào)用traceXXX()函數(shù)的時候,先檢查key是否使能了,如果使能了才繼續(xù)往下走。接著我們再打開DOTRACE來看看。
聯(lián)系上上一小節(jié)的tracepoint結(jié)構(gòu)體是不是能想到啥?對了,就是遍歷tracepoint->funcs數(shù)組,然后調(diào)用它們。
好了,終于完整的看完了TRACE_EVENT的定義和使用流程。小編累了,大家也累了,今天就到這里吧。
-
Linux
+關(guān)注
關(guān)注
87文章
11326瀏覽量
209964 -
C語言
+關(guān)注
關(guān)注
180文章
7613瀏覽量
137247 -
源碼
+關(guān)注
關(guān)注
8文章
649瀏覽量
29311
原文標(biāo)題:Linux TraceEvent - 我見過的史上最長宏定義
文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論