自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Linux 內(nèi)核靜態(tài)追蹤技術(shù)的實(shí)現(xiàn)

系統(tǒng) Linux
本文簡單分享一下內(nèi)核的靜態(tài)追蹤技術(shù)的實(shí)現(xiàn)。追蹤,其實(shí)就是收集代碼在執(zhí)行時的一些信息,以便協(xié)助排查問題。

[[434855]]

前言:最近在探索 Node.js 調(diào)試和診斷方向的內(nèi)容,因?yàn)?Node.js 提供的能力有時候可能無法解決問題,比如堆內(nèi)存沒有變化,但是 rss 一直上漲。所以需要深入一點(diǎn)去了解更多的排查問題方式。而這些方向往往都涉及到底層的東西,所以就自然需要去了解內(nèi)核提供的一些技術(shù),內(nèi)核提供的能力,經(jīng)過多年的發(fā)展,可謂是百花齊放,而且非常復(fù)雜。本文簡單分享一下內(nèi)核的靜態(tài)追蹤技術(shù)的實(shí)現(xiàn)。追蹤,其實(shí)就是收集代碼在執(zhí)行時的一些信息,以便協(xié)助排查問題。

1 Tracepoint

Tracepoints 是一種靜態(tài)插樁的技術(shù),實(shí)現(xiàn)雖然復(fù)雜,但是概念上比較簡單。比如我們打日志的時候,就類似這種情況,我們在業(yè)務(wù)代碼里,寫了很多 log 用來記錄進(jìn)程在運(yùn)行時的信息。Tracepoints 則是內(nèi)核提供的一種基于鉤子的插樁技術(shù)。不過和打日志不一樣的是,我們想在哪里打就在哪里加對應(yīng)的代碼,而 Tracepoints 則幾乎是依賴于內(nèi)核決定哪里可以插樁,說幾乎是因?yàn)槲覀円部梢詫憙?nèi)核模塊注冊到內(nèi)核來通知插樁點(diǎn)。下面來通過一個例子看一下 Tracepoint 的使用和實(shí)現(xiàn)(例子來自內(nèi)核文檔 tracepoints.rst)。分析之前先看一下兩個非常重要的宏。第一個是 DECLARE_TRACE。

  1. #define DECLARE_TRACE(name, proto, args)                \ 
  2.     __DECLARE_TRACE(name, PARAMS(proto), PARAMS(args),      \ 
  3.             cpu_online(raw_smp_processor_id()),     \ 
  4.             PARAMS(void *__data, proto),            \ 
  5.             PARAMS(__data, args)) 

我們只需要關(guān)注主體的實(shí)現(xiàn),而不需要關(guān)注參數(shù),繼續(xù)展開。

  1. #define __DECLARE_TRACE(name, proto, args, cond, data_proto, data_args) \ 
  2.     extern struct tracepoint __tracepoint_##name;           \ 
  3.     // 執(zhí)行鉤子函數(shù) 
  4.     static inline void trace_##name(proto)              \ 
  5.     {                               \ 
  6.         if (static_key_false(&__tracepoint_##name.key))     \ 
  7.             __DO_TRACE(&__tracepoint_##name,        \ 
  8.                 TP_PROTO(data_proto),           \ 
  9.                 TP_ARGS(data_args),         \ 
  10.                 TP_CONDITION(cond), 0);         \ 
  11.     }                               \ 
  12.     // 注冊鉤子函數(shù) 
  13.     static inline int                       \ 
  14.     register_trace_##name(void (*probe)(data_proto), void *data)    \ 
  15.     {                               \ 
  16.         return tracepoint_probe_register(&__tracepoint_##name,  \ 
  17.                         (void *)probe, data);   \ 
  18.     }                               \    
  19.     // 注銷鉤子函數(shù)                    
  20.     static inline int                       \ 
  21.     unregister_trace_##name(void (*probe)(data_proto), void *data)  \ 
  22.     {                               \ 
  23.         return tracepoint_probe_unregister(&__tracepoint_##name,\ 
  24.                         (void *)probe, data);   \ 
  25.     }                               \ 
  26.     static inline bool                      \ 
  27.     trace_##name##_enabled(void)                    \ 
  28.     {                               \ 
  29.         return static_key_false(&__tracepoint_##name.key);  \ 
  30.     } 

__DECLARE_TRACE 主要是實(shí)現(xiàn)了幾個函數(shù),我們只需要關(guān)注注冊鉤子和執(zhí)行鉤子函數(shù)(格式是 register_trace_${yourname} 和 trace_${yourame})。接下來看第二個宏 DEFINE_TRACE。

  1. #define DEFINE_TRACE_FN(name, reg, unreg)                \ 
  2.     struct tracepoint __tracepoint_##name#define DEFINE_TRACE(name)                      \ 
  3.     DEFINE_TRACE_FN(nameNULLNULL); 

我省略了一些代碼,DEFINE_TRACE 主要是定義了一個 tracepoint 結(jié)構(gòu)體。了解了兩個宏之后,來看一下如何使用 Tracepoint。

1.1 使用

include/trace/events/subsys.h

  1. #include <linux/tracepoint.h>DECLARE_TRACE(subsys_eventname, 
  2.     TP_PROTO(int firstarg, struct task_struct *p), 
  3.     TP_ARGS(firstarg, p)); 

首先在頭文件里通過 DECLARE_TRACE 宏定義了一系列函數(shù)。subsys/file.c

  1. #include <trace/events/subsys.h> 
  2.  
  3.  
  4.  
  5. DEFINE_TRACE(subsys_eventname);void somefct(void){ 
  6.  
  7.     ... 
  8.     trace_subsys_eventname(arg, task); 
  9.     ... 
  10.  
  11.  
  12.  
  13.  
  14. // 實(shí)現(xiàn)自己的鉤子函數(shù)并注冊到內(nèi)核 
  15.  
  16. void callback(...) {} 
  17.  
  18. register_trace_subsys_eventname(callback); 

然后在實(shí)現(xiàn)文件里通過 DEFINE_TRACE 定義一個 tracepoint 結(jié)構(gòu)體。接著調(diào)用 register_trace_subsys_eventname 函數(shù)把自定義的鉤子函數(shù)注冊到內(nèi)核,然后在需要收集信息的地方調(diào)用處理鉤子的函數(shù) trace_subsys_eventname。

1.2 實(shí)現(xiàn)

了解了使用之后,接下來看看實(shí)現(xiàn)。首先看一下注冊鉤子函數(shù)。

  1. int tracepoint_probe_register(struct tracepoint *tp, void *probe, void *data){ 
  2.     return tracepoint_probe_register_prio(tp, probe, data, TRACEPOINT_DEFAULT_PRIO); 
  3.  
  4.  
  5.  
  6.  
  7. int tracepoint_probe_register_prio(struct tracepoint *tp, void *probe, 
  8.  
  9.                    void *data, int prio){ 
  10.     struct tracepoint_func tp_func; 
  11.     int ret; 
  12.  
  13.     mutex_lock(&tracepoints_mutex); 
  14.     tp_func.func = probe; 
  15.     tp_func.data = data; 
  16.     tp_func.prio = prio; 
  17.     ret = tracepoint_add_func(tp, &tp_func, prio); 
  18.     mutex_unlock(&tracepoints_mutex); 
  19.     return ret; 
  20.  

tracepoint_probe_register_prio 中定義了一個 tracepoint_func 結(jié)構(gòu)體用于表示鉤子信息,然后調(diào)用 tracepoint_add_func,其中 tp 就剛才自定義的 tracepoint 結(jié)構(gòu)體。

  1. static int tracepoint_add_func(struct tracepoint *tp, struct tracepoint_func *func, int prio){ 
  2.     struct tracepoint_func *old, *tp_funcs; 
  3.     int ret; 
  4.     // 拿到鉤子列表 
  5.     tp_funcs = rcu_dereference_protected(tp->funcs, lockdep_is_held(&tracepoints_mutex)); 
  6.     // 插入新的鉤子到列表 
  7.     old = func_add(&tp_funcs, func, prio); 
  8.     rcu_assign_pointer(tp->funcs, tp_funcs); 
  9.     return 0;}static struct tracepoint_func * func_add(struct tracepoint_func **funcs, struct tracepoint_func *tp_func, 
  10.      int prio){ 
  11.     struct tracepoint_func *new; 
  12.     int nr_probes = 0; 
  13.     int pos = -1; 
  14.     /* + 2 : one for new probe, one for NULL func */ 
  15.     new = allocate_probes(nr_probes + 2); 
  16.     pos = 0; 
  17.     new[pos] = *tp_func; 
  18.     new[nr_probes + 1].func = NULL
  19.     *funcs = new; 
  20.  

注冊函數(shù)的邏輯其實(shí)就是往自定義的結(jié)構(gòu)體的隊(duì)列里插入一個新的節(jié)點(diǎn)。接下來再看一下處理鉤子的邏輯。

  1. #define __DO_TRACE(tp, proto, args, cond, rcuidle)          \ 
  2.     do {                                \ 
  3.         struct tracepoint_func *it_func_ptr;            \ 
  4.         void *it_func;                      \ 
  5.         void *__data;                       \ 
  6.         int __maybe_unused __idx = 0;               \ 
  7.         // 拿到隊(duì)列 
  8.         it_func_ptr = rcu_dereference_raw((tp)->funcs);     \ 
  9.         // 非空則執(zhí)行里面的節(jié)點(diǎn)的回調(diào) 
  10.         if (it_func_ptr) {                  \ 
  11.             do {                        \ 
  12.                 it_func = (it_func_ptr)->func;      \ 
  13.                 __data = (it_func_ptr)->data;       \ 
  14.                 ((void(*)(proto))(it_func))(args);  \ 
  15.             } while ((++it_func_ptr)->func);        \ 
  16.         }                           \ 
  17.     } while (0) 

邏輯上和我們在應(yīng)用層的類似。在執(zhí)行鉤子,也就是我們的回調(diào)時,我們可以通過內(nèi)核接口把信息寫到 ring buffer,然后應(yīng)用層可以通過 debugfs 獲取到這個信息。

2 trace event

有了 Tracepoint 機(jī)制后,我們就可以寫模塊加載到內(nèi)核中實(shí)現(xiàn)自己的插樁點(diǎn)。但是內(nèi)核也為我們內(nèi)置提供了非常多的插樁點(diǎn)。具體是通過 trace event 來實(shí)現(xiàn)的。下面看一個例子。

  1. #define TRACE_EVENT(name, proto, args, struct, assign, print)   \ 
  2.     DECLARE_TRACE(name, PARAMS(proto), PARAMS(args))TRACE_EVENT(consume_skb, 
  3.  
  4.     TP_PROTO(struct sk_buff *skb), 
  5.  
  6.     TP_ARGS(skb), 
  7.  
  8.     TP_STRUCT__entry( 
  9.         __field(    void *, skbaddr ) 
  10.     ), 
  11.  
  12.     TP_fast_assign( 
  13.         __entry->skbaddr = skb; 
  14.     ), 
  15.  
  16.     TP_printk("skbaddr=%p", __entry->skbaddr)); 

上面定義了一個宏 TRACE_EVENT,它本質(zhì)上是對 DECLARE_TRACE 的封裝,所以這里是定義了一系列的函數(shù)(注冊鉤子、處理鉤子)。然后在 consume_skb 函數(shù)中處理了注冊的鉤子。

  1. void consume_skb(struct sk_buff *skb){ 
  2.     trace_consume_skb(skb); 
  3.     __kfree_skb(skb); 
  4.  

3. 總結(jié)

內(nèi)核提供了非常豐富但是也非常復(fù)雜的機(jī)制,從而用戶可以通過內(nèi)核的能力獲取到更底層的數(shù)據(jù),用以排查問題和做性能優(yōu)化。我們可以看到插樁的這種機(jī)制是一種靜態(tài)的機(jī)制,我們通常需要依賴當(dāng)前版本的內(nèi)核所支持的樁,從而獲得對應(yīng)的信息,但其實(shí)內(nèi)核也提供了動態(tài)追蹤的能力,可以實(shí)現(xiàn)熱插拔獲取信息的能力。總的來說,Linux 下的追蹤技術(shù)多種多樣,雖然非常復(fù)雜,但是上層也提供了各種更方便的工具,這些能力是我們深入排查問題的利器。

 

責(zé)任編輯:姜華 來源: 編程雜技
相關(guān)推薦

2021-11-15 04:00:07

Linux 內(nèi)核動態(tài)

2025-04-01 02:00:22

2025-04-02 00:33:00

2016-12-08 09:57:09

LinuxDTrace技術(shù)

2022-03-03 18:18:53

BPF解釋器系統(tǒng)

2021-07-11 06:45:18

Linux內(nèi)核靜態(tài)

2021-10-06 09:46:17

trace-cmd追蹤內(nèi)核Linux

2025-03-07 08:30:00

pwruLinux網(wǎng)絡(luò)包追蹤

2023-03-01 23:56:11

2023-03-01 23:53:30

Linuxshutdown進(jìn)程

2023-03-10 14:56:37

Linuxconnect系統(tǒng)

2025-01-02 11:06:22

2023-02-28 09:47:42

2021-09-30 09:43:11

Linux內(nèi)核Zstd補(bǔ)丁

2023-11-24 11:24:16

Linux系統(tǒng)

2024-04-15 11:24:32

庫存跟蹤技術(shù)NFC藍(lán)牙

2017-01-12 19:15:03

Linux內(nèi)核調(diào)試自構(gòu)proc

2022-05-24 12:34:32

Docker容器Linux容器進(jìn)程

2020-11-13 18:33:38

跨鏡追蹤識別技術(shù)人臉識別

2013-11-07 13:59:56

Linux內(nèi)核
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號