linux tracepoint例子,tracepoint介绍
內核中的每個tracepoint提供一個鉤子來調用probe函數。一個tracepoint可以打開或關閉。打開時,probe函數關聯到tracepoint;關閉時,probe函數不關聯到tracepoint。tracepoint關閉時對kernel產生的影響很小,只是增加了極少的時間開銷(一個分支條件判斷),極小的空間開銷(一條函數調用語句和幾個數據結構)。當一個tracepoint打開時,用戶提供的probe函數在每次這個tracepoint執行是都會被調用。
如果用戶準備為kernel加入新的tracepoint,每個tracepoint必須以下列格式聲明:
#include DECLARE_TRACE(tracepoint_name,
TPPROTO(trace_function_prototype),
TPARGS(trace_function_args));
上面的宏定義了一個新的tracepoint叫tracepoint_name。與這個tracepoint關聯的probe函數必須與TPPROTO宏定義的函數prototype一致,probe函數的參數列表必須與TPARGS宏定義的一致。
或許用一個例子來解釋會比較容易理解。Kernel里面已經包含了一些tracepoints,其中一個叫做sched_wakeup,這個tracepoint在每次scheduler喚醒一個進程時都會被調用。它是這樣定義的:
DECLARE_TRACE(sched_wakeup,
TPPROTO(struct rq *rq, struct task_struct *p),
TPARGS(rq, p))
實際在kernel中插入這個tracepoint點的是一行如下代碼:
trace_sched_wakeup(rq, p);
注意,插入tracepoint的函數名就是將trace_前綴添加到tracepoint_name的前面。除非有一個實際的probe函數關聯到這個tracepoint,trace_sched_wakeup()這個只是一個空函數。下面的操作就是將一個probe函數關聯到一個tracepoint:
void my_sched_wakeup_tracer(struct rq *rq, struct task_struct *p);
register_trace_sched_wakeup(my_sched_wakeup_tracer);
register_trace_sched_wakeup()函數實際上是DEFINE_TRACE()定義的,它把probe函數my_sched_wakeup_tracer()和tracepoint sched_wakeup關聯起來。
當需要獲取內核的debug信息時,通常你會通過以下printk的方式打印信息:
void trace_func() { //…… printk("輸出信息"); //…… }1
2
3
4
5
6
缺點:
內核中printk是統一控制的,各個模塊的printk都會被打印,無法只打印需要關注的模塊。
如果需要修改/新增打印信息,需要修改所有受影響的printk語句。這些printk分散在代碼多處,每個地方都需要修改。
嵌入式系統中,如果printk信息量大,console(如果有)有大量的打印輸出,用戶無法在console輸入命令,影響人機交互。
二、內核解決方案
內核采用“插樁”的方法抓取log,“插樁”也稱為trace point。每種trace point有一個name、一個enable開關、一系列樁函數、注冊樁函數的函數、卸載樁函數的函數。“樁函數”功能類似于printk,不過“樁函數”并不會把信息打印到console,而是輸出到內核的ring buffer(環形緩沖區),緩沖區中的信息通過debugfs對用戶呈現。
邏輯架構如下:
接下來說明涉及到一些內核數據結構,代碼參考:
數據結構
代碼路徑
DEFINE_TRACE(name)
DECLARE_TRACE(name, proto, args)
include/linux/tracepoint.h
struct tracepoint
include/linux/tracepoint-defs.h
trace point依次執行樁函數,每個樁函數實現不同的debug功能。內核通過register_trace_##name將樁函數添加到trace point中,通過unregister_trace_##name從trace point中移除。(注:##表示字符串連接)。
內核通過DEFINE_TRACE(name)定義struct tracepoint變量來描述trace point。
struct tracepoint { const char *name; /* Tracepoint name */ struct static_key key; int (*regfunc)(void); void (*unregfunc)(void); struct tracepoint_func __rcu *funcs; };1
2
3
4
5
6
7
@name* trace point的名字,內核中通過hash表管理所有的trace point,找到對應的hash slot后,需要通過name來識別具體的trace point。
@keytrace point狀態,1表示disable,0表示enable。
@regfunc添加樁函數的函數
@unregfunc卸載樁函數的函數
@funcstrace point中所有的樁函數鏈表
內核通過#define DECLARE_TRACE(name, proto, args)定義trace point用到的函數,定義的函數原型如下(從代碼中摘取了幾個,不止以下3個):
static inline void trace_##name(proto) register_trace_##name(void (*probe)(data_proto), void *data) unregister_trace_##name(void (*probe)(data_proto), void *data)1
2
3
4
5
#define __DECLARE_TRACE(name, proto, args, cond, data_proto, data_args)//\ extern struct tracepoint __tracepoint_##name; \ static inline void trace_##name(proto) \ { \ if (static_key_false(&__tracepoint_##name.key)) \ __DO_TRACE(&__tracepoint_##name, \ TP_PROTO(data_proto), \ TP_ARGS(data_args), \ TP_CONDITION(cond), 0); \ if (IS_ENABLED(CONFIG_LOCKDEP) && (cond)) { \ rcu_read_lock_sched_notrace(); \ rcu_dereference_sched(__tracepoint_##name.funcs);\ rcu_read_unlock_sched_notrace(); \ } \ } \ __DECLARE_TRACE_RCU(name, PARAMS(proto), PARAMS(args), \ PARAMS(cond), PARAMS(data_proto), PARAMS(data_args)) \ static inline int \
register_trace_##name(void (*probe)(data_proto), void *data) \ { \ return tracepoint_probe_register(&__tracepoint_##name, \ (void *)probe, data); \ } \ static inline int \
register_trace_prio_##name(void (*probe)(data_proto), void *data,\ int prio) \ { \ return tracepoint_probe_register_prio(&__tracepoint_##name, \ (void *)probe, data, prio); \ } \ static inline int \
unregister_trace_##name(void (*probe)(data_proto), void *data) \ { \ return tracepoint_probe_unregister(&__tracepoint_##name,\ (void *)probe, data); \ } \ static inline void \
check_trace_callback_type_##name(void (*cb)(data_proto)) \ { \ } \ static inline bool\
trace_##name##_enabled(void) \ { \ return static_key_false(&__tracepoint_##name.key); \ }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
???第2行聲明一個外部trace point變量。"static inline"部分定義了一些trace point用到的公共函數。
???第5行判斷trace point是否disable,如果沒有disable,那么調用__DO_TRACE遍歷執行trace point中的樁函數(通過“函數指針”來實現執行樁函數)。
???trace point提供了統一的框架,用void *指向任何函數,所以各個trace point取出樁函數指針后,需要轉換成自己的函數指針類型, TP_PROTO(data_proto)傳遞函數指針類型用于轉換,具體的轉換在:(–> 這一行)
#define __DO_TRACE(tp, proto, args, cond, rcuidle)//\ do { \ struct tracepoint_func *it_func_ptr; \ void *it_func; \ void *__data; \ //......................... it_func_ptr = rcu_dereference_raw((tp)->funcs); \
\ if (it_func_ptr) { \ do { \
it_func = (it_func_ptr)->func; \
__data = (it_func_ptr)->data; \ --> ((void(*)(proto))(it_func))(args); \ } while ((++it_func_ptr)->func); \ } \ //......................... } while (0)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
樁函數的proto的傳遞的例
DEFINE_EVENT_CONDITION(f2fs__submit_page_bio, f2fs_submit_page_write, --> TP_PROTO(struct page *page, struct f2fs_io_info *fio), TP_ARGS(page, fio), TP_CONDITION(page->mapping) );1
2
3
4
5
第2行(–>)聲明了樁函數原型。
#define DEFINE_EVENT_CONDITION(template, name, proto, args, cond) DEFINE_EVENT(template, name, PARAMS(proto), PARAMS(args))1
2
#define DEFINE_EVENT(template, name, proto, args) DECLARE_TRACE(name, PARAMS(proto), PARAMS(args))1
2
#define DECLARE_TRACE(name, proto, args) __DECLARE_TRACE(name, PARAMS(proto), PARAMS(args), cpu_online(raw_smp_processor_id()), PARAMS(void *__data, proto), PARAMS(__data, args))1
2
3
4
5
???至此執行到__DECLARE_TRACE宏,參考前面說明,提到了何時轉換成樁函數指針類型。
???從上面可以看出trace point的機制很簡單,就是把用于debug的函數指針組織在一個struct trace point變量中,然后依次執行各個函數指針。不過為了避免各個模塊重復寫代碼,內核用了比較復雜的宏而已。
???另外我們也可以發現,使用trace point必須要通過register_trace_##name將樁函數(也就是我們需要的debug函數)添加到trace point中,這個工作只能通過moudule或者修改內核代碼實現,對于開發者來說,操作比較麻煩。ftrace開發者們意識到了這點,所以提供了trace event功能,開發者不需要自己去注冊樁函數了,易用性較好,后面文章會談到trace event是如何實現的以及如何使用。
總結
以上是生活随笔為你收集整理的linux tracepoint例子,tracepoint介绍的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c语言用正数的形式求最大值最小值,C语言
- 下一篇: c语言程序存为bin程序,对文件进行加密