使用 Linux 核心跟蹤點

作者:

Mathieu Desnoyers

本文件介紹 Linux 核心跟蹤點及其用法。 它提供瞭如何在核心中插入跟蹤點並將探測函式連線到跟蹤點的示例,並提供了一些探測函式的示例。

跟蹤點的目的

程式碼中放置的跟蹤點提供了一個鉤子來呼叫你可以在執行時提供的函式(探測)。 跟蹤點可以是“開啟”(探測已連線到它)或“關閉”(未附加探測)。 當跟蹤點“關閉”時,它沒有任何影響,除了增加少量時間開銷(檢查分支的條件)和空間開銷(在檢測函式的末尾新增幾個位元組用於函式呼叫,並在單獨的部分中新增資料結構)。 當跟蹤點“開啟”時,每次執行跟蹤點時,都會在呼叫者的執行上下文中呼叫你提供的函式。 當提供的函式結束執行時,它會返回到呼叫者(從跟蹤點位置繼續)。

你可以將跟蹤點放在程式碼中的重要位置。 它們是輕量級鉤子,可以傳遞任意數量的引數,這些引數的原型在標頭檔案中放置的跟蹤點宣告中描述。

它們可以用於跟蹤和效能統計。

用法

跟蹤點需要兩個要素

  • 跟蹤點定義,放在標頭檔案中。

  • 跟蹤點語句,在 C 程式碼中。

要使用跟蹤點,應包含 linux/tracepoint.h。

在 include/trace/events/subsys.h 中

#undef TRACE_SYSTEM
#define TRACE_SYSTEM subsys

#if !defined(_TRACE_SUBSYS_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_SUBSYS_H

#include <linux/tracepoint.h>

DECLARE_TRACE(subsys_eventname,
        TP_PROTO(int firstarg, struct task_struct *p),
        TP_ARGS(firstarg, p));

#endif /* _TRACE_SUBSYS_H */

/* This part must be outside protection */
#include <trace/define_trace.h>

在 subsys/file.c 中(必須新增跟蹤語句的位置)

#include <trace/events/subsys.h>

#define CREATE_TRACE_POINTS
DEFINE_TRACE(subsys_eventname);

void somefct(void)
{
        ...
        trace_subsys_eventname_tp(arg, task);
        ...
}
其中
  • subsys_eventname 是事件的唯一識別符號

    • subsys 是子系統的名稱。

    • eventname 是要跟蹤的事件的名稱。

  • TP_PROTO(int firstarg, struct task_struct *p) 是此跟蹤點呼叫的函式的原型。

  • TP_ARGS(firstarg, p) 是引數名稱,與原型中找到的相同。

  • 如果在多個原始檔中使用標頭檔案,則 #define CREATE_TRACE_POINTS 只能出現在一個原始檔中。

透過 register_trace_subsys_eventname() 為特定跟蹤點提供探測(要呼叫的函式)來連線函式(探測)到跟蹤點。 透過 unregister_trace_subsys_eventname() 刪除探測; 它將刪除探測。

必須在模組退出函式的末尾之前呼叫 tracepoint_synchronize_unregister(),以確保沒有呼叫者留下使用探測。 這一點,以及在探測呼叫周圍停用搶佔的事實,確保了探測刪除和模組解除安裝是安全的。

跟蹤點機制支援插入同一跟蹤點的多個例項,但必須對核心中的給定跟蹤點名稱進行單一定義,以確保不會發生型別衝突。 跟蹤點的名稱修飾使用原型來確保型別正確。 編譯器在註冊站點進行探測型別正確性的驗證。 跟蹤點可以放置在行內函數、內聯靜態函式、展開迴圈以及常規函式中。

這裡建議使用命名方案“subsys_event”作為一種旨在限制衝突的約定。 跟蹤點名稱對核心是全域性的:無論它們是在核心核心映象中還是在模組中,它們都被認為是相同的。

如果跟蹤點必須在核心模組中使用,可以使用 EXPORT_TRACEPOINT_SYMBOL_GPL() 或 EXPORT_TRACEPOINT_SYMBOL() 匯出定義的跟蹤點。

如果需要為跟蹤點引數做一些工作,並且該工作僅用於跟蹤點,則可以使用以下 if 語句封裝該工作

if (trace_foo_bar_enabled()) {
        int i;
        int tot = 0;

        for (i = 0; i < count; i++)
                tot += calculate_nuggets();

        trace_foo_bar_tp(tot);
}

所有 trace_<tracepoint>_tp() 呼叫都有一個匹配的 trace_<tracepoint>_enabled() 函式定義,如果跟蹤點已啟用,則返回 true,否則返回 false。 trace_<tracepoint>_tp() 應該始終位於 if (trace_<tracepoint>_enabled()) 的塊中,以防止跟蹤點啟用和檢查之間出現競爭。

使用 trace_<tracepoint>_enabled() 的優點是它使用跟蹤點的 static_key 來允許使用跳轉標籤實現 if 語句,並避免條件分支。

注意

便捷宏 TRACE_EVENT 提供了一種定義跟蹤點的替代方法。 請注意,DECLARE_TRACE(foo) 建立一個函式“trace_foo_tp()”,而 TRACE_EVENT(foo) 建立一個函式“trace_foo()”,並且還將跟蹤點作為跟蹤事件公開在 /sys/kernel/tracing/events 目錄中。 請檢視 http://lwn.net/Articles/379903, http://lwn.net/Articles/381064http://lwn.net/Articles/383362 獲取一系列包含更多詳細資訊的文章。

如果需要從標頭檔案呼叫跟蹤點,則不建議直接呼叫或使用 trace_<tracepoint>_enabled() 函式呼叫,因為如果從設定了 CREATE_TRACE_POINTS 的檔案中包含標頭檔案,則標頭檔案中的跟蹤點可能會產生副作用,並且 trace_<tracepoint>() 的內聯也並不小,如果被其他行內函數使用,可能會使核心膨脹。 相反,請包含 tracepoint-defs.h 並使用 tracepoint_enabled()。

在一個 C 檔案中

void do_trace_foo_bar_wrapper(args)
{
        trace_foo_bar_tp(args); // for tracepoints created via DECLARE_TRACE
                                //   or
        trace_foo_bar(args);    // for tracepoints created via TRACE_EVENT
}

在標頭檔案中

DECLARE_TRACEPOINT(foo_bar);

static inline void some_inline_function()
{
        [..]
        if (tracepoint_enabled(foo_bar))
                do_trace_foo_bar_wrapper(args);
        [..]
}