2009 年 12 月 03 日
現代的操作系統內核提供 自檢 功能,即動態地檢查內核以理解其行為的能力。這些行為可以反映內核問題和性能瓶頸。擁有這些信息時候,您就可以調優或修改內核以避免出現故障。本文探索一 個名為 SystemTap 的開放源碼基礎設施,它為 Linux? 內核提供這種動態的自檢。
?
SystemTap 是監控和跟蹤運行中的 Linux 內核的操作的動態方法。這句話的關鍵詞是 動態 ,因為 SystemTap 沒有使用工具構建一個特殊的內核,而是允許您在運行時動態地安裝該工具。它通過一個名為 Kprobes 的應用編程接口(API)來實現該目的,本文將探索這個 API。我們首先了解以前的一些內核跟蹤方法,然后在深入探討 SystemTap 的架構及其使用。
SystemTap 與一種名為
DTrace
的老技術相似,該技術源于 Sun Solaris 操作系統。在 DTrace 中,開發人員可以用 D 編程語言(
C
語言的子集,但修改為支持跟蹤行為)編寫腳本。DTrace 腳本包含許多探針和相關聯的操作,這些操作在探針 “觸發” 時發生。例如,探針可以表示簡單的系統調用,也可以表示更加復雜的交互,比如執行特定的代碼行。清單 1 顯示了 DTrace 腳本的一個簡單例子,它計算每個進程發出的系統調用的數量(注意,使用字典將計數和進程關聯起來)。該腳本的格式包含探針(在發出系統調用時觸發)和操作 (對應的操作腳本)。
清單 1. 計算每個進程的系統調用的簡單 DTrace 腳本
syscall:::entry { @num[pid,execname] = count(); } |
?
DTrace 是 Solaris 最引人注目的部分,所以在其他操作系統中開發它并不奇怪。DTrace 是在 Common Development and Distribution License (CDDL) 之下發行的,并且被移植到 FreeBSD 操作系統中。
另一個非常有用的內核跟蹤工具是
ProbeVue
,它是 IBM 為 IBM? AIX? 操作系統 6.1 開發的。您可以使用 ProbeVue 探查系統的行為和性能,以及提供特定進程的詳細信息。這個工具使用一個標準的內核以動態的方式進行跟蹤。清單 2 顯示了 ProbeVue 腳本的一個例子,它指出發出
sync
系統調用的特定進程。
清單 2. 指出哪個進程調用 sync 的簡單 ProbeVue 腳本
@@syscall:*:sync:entry { printf( "sync() syscall invoked by process ID %d\n", __pid ); exit(); } |
?
考慮到 DTrace 和 ProbeVue 在各自的操作系統中的巨大作用,為 Linux 操作系統策劃一個實現該功能的開源項目是勢不可擋的。SystemTap 從 2005 年開始開發,它提供與 DTrace 和 ProbeVue 類似的功能。許多社區還進一步完善了它,包括 Red Hat、Intel、Hitachi 和 IBM 等。
這些解決方案在功能上都是類似的,在觸發探針時使用探針和相關聯的操作腳本。現在,我們看一下 SystemTap 的安裝,然后探索它的架構和使用。
?
![]() ![]() |
![]()
|
?
您可能僅需一個 SystemTap 安裝就可以支持 SystemTap,具體情況取決于您的分發版和內核。對于其他情況,需要使用一個調試內核映像。這個小節介紹在 Ubuntu version 8.10 (Intrepid Ibex) 上安裝 SystemTap 的步驟,但這并不是一個具有代表性的 SystemTap 安裝。在 參 考資料 部分中,您可以找到在其他分發版和版本上安裝 SystemTap 的更多信息。
對大部分用戶而言,安裝 SystemTap 都非常簡單。對于 Ubuntu,使用
apt-get
:
$
sudo apt-get install systemtap
|
?
在安裝完成之后,您可以測試內核看它是否支持 SystemTap。為此,使用以下簡單的命令行腳本:
$
sudo stap -ve 'probe begin { log("hello world") exit() }'
|
?
如果該腳本能夠正常運行,您將在標準輸出 [stdout] 中看到 “hello world”。如果沒有看到這兩個單詞,則還需要其他工作。對于 Ubuntu 8.10,需要使用一個調試內核映像。應該使用
apt-get
獲取包
linux-image-debug-generic
就可以獲得它的。但這里不能直接使用
apt-get
, 因此您可以下載該包并使用
dpkg
安裝它。您可以下載通用的調用映像包并按照以下的方式安裝它:
$ wget http://ddebs.ubuntu.com/pool/main/l/linux/ linux-image-debug-2.6.27-14-generic_2.6.27-14.39_i386.ddeb $ sudo dpkg -i linux-image-debug-2.6.27-14-generic_2.6.27-14.39_i386.ddeb |
?
現在,已經安裝了通用的調試映像。對于 Ubuntu 8.10,還需要一個步驟:SystemTap 分發版有一個問題,但可以通過修改 SystemTap 源代碼輕松解決。查看 參 考資料 獲得如何更新運行時 time.c 文件的信息。
如果您使用定制的內核,則需要確保啟用內核選項
CONFIG_RELAY
、
CONFIG_DEBUG_FS
、
CONFIG_DEBUG_INFO
和
CONFIG_KPROBES
。
?
![]() ![]() |
![]()
|
?
讓我們深入探索 SystemTap 的某些細節,理解它如何在運行的內核中提供動態探針。您還將看到 SystemTap 是如何工作的,從構建進程腳本到在運行的內核中激活腳本。
SystemTap 用于檢查運行的內核的兩種方法是 Kprobes 和 返回探針 。但是理解任何內核的最關 鍵要素是內核的映射,它提供符號信息(比如函數、變量以及它們的地址)。有了內核映射之后,就可以解決任何符號的地址,以及更改探針的行為。
Kprobes 從 2.6.9 版本開始就添加到主流的 Linux 內核中,并且為探測內核提供一般性服務。它提供一些不同的服務,但最重要的兩種服務是 Kprobe 和 Kretprobe。Kprobe 特定于架構,它在需要檢查的指令的第一個字節中插入一個斷點指令。當調用該指令時,將執行針對探針的特定處理函數。執行完成之后,接著執行原始的指令(從 斷點開始)。
Kretprobes 有所不同,它操作調用函數的返回結果。注意,因為一個函數可能有多個返回點,所以聽起來事情有些復雜。不過,它實際使用一種稱為 trampoline 的簡單技術。您將向函數條目添加一小段代碼,而不是檢查函數中的每個返回點。這段代碼使用 trampoline 地址替換堆棧上的返回地址 —— Kretprobe 地址。當該函數存在時,它沒有返回到調用方,而是調用 Kretprobe(執行它的功能),然后從 Kretprobe 返回到實際的調用方。
圖 1 展示了 SystemTap 的基本流程,涉及到 3 個交互實用程序和 5 個階段。該流程首先從 SystemTap 腳本開始。您使用
stap
實用程序將 stap 腳本轉換成提供探針行為的內核模塊。stap 流程從將腳本轉換成解析樹開始 (pass 1)。然后使用細化(elaboration)步驟 (pass 2) 中關于當前運行的內核的符號信息解析符號。接下來,轉換流程將解析樹轉換成
C
源代碼 (pass 3) 并使用解析后的信息和
tapset 腳本
(SystemTap 定義的庫,包含有用的功能)。stap 的最后步驟是構造使用本地內核模塊構建進程的內核模塊 (pass 4)。
有了可用的內核模塊之后,
stap
完成了自己的任務,并將控制權交給其他兩個實用程序 SystemTap:
staprun
和
stapio
。這兩個實用程序協調工作,負責將模塊安裝到內核中并將輸出發送到 stdout (pass 5)。如果在 shell 中按組合鍵 Ctrl-C 或腳本退出,將執行清除進程,這將導致卸載模塊并退出所有相關的實用程序。
SystemTap 的一個有趣特性是緩存腳本轉換的能力。如果安裝后的腳本沒有更改,您可以使用現有的模塊,而不是重新構建模塊。圖 2 顯示了 user-space 和 kernel-space 元素以及基于 stap 的轉換流程。
圖 2. 從 kernel/user-space 角度了解 SystemTap 流程
![]() ![]() |
![]()
|
?
在 SystemTap 中編寫腳本非常簡單,但也很靈活,有許多您需要使用的選項。 參 考資料 提供一個詳述語言和可行性的手冊的鏈接,但這個小節僅討論一些例子,讓您初步了解 SystemTap 腳本。
SystemTap 腳本由探針和在觸發探針時需要執行的代碼塊組成。探針有許多預定義模式,表 1 列出了其中的一部分。這個表列舉了幾種探針類型,包括調用內核函數和從內核函數返回。
探針類型 說明
begin
|
在腳本開始時觸發 |
end
|
在腳本結束時觸發 |
kernel.function("sys_sync")
|
調用
sys_sync
時觸發
|
kernel.function("sys_sync").call
|
同上 |
kernel.function("sys_sync").return
|
返回
sys_sync
時觸發
|
kernel.syscall.*
|
進行任何系統調用時觸發 |
kernel.function("*@kernel/fork.c:934")
|
到達 fork.c 的第 934 行時觸發 |
module("ext3").function("ext3_file_write")
|
調用 ext3
write
函數時觸發
|
timer.jiffies(1000)
|
每隔 1000 個內核 jiffy 觸發一次 |
timer.ms(200).randomize(50)
|
每隔 200 毫秒觸發一次,帶有線性分布的隨機附加時間(-50 到 +50) |
我們通過一個簡單的例子來理解如何構造探針,并將代碼與該探針相關聯。清單 3 顯示了一個樣例探針,它在調用內核系統調用
sys_sync
時觸發。當該探針觸發時,您希望計算調用的次數,并發送這個計數以及表示調用進程 ID(PID)的信息。首先,聲明一個任何探針都可以使用的全局值(全局名稱空間對所有探針都是通用的),然后將它初始化為 0。其次,定義您的探針,它是一個探測內核函數
sys_sync
的條目。與探針相關聯的腳本將遞增
count
變量,然后發出一條消息,該消息定義調用的次數和當前調用的 PID。注意,這個例子與
C
語言中的探針非常相似(探針定義語法除外),如果具有
C
語言背景將非常有幫助。
global count=0 probe kernel.function("sys_sync") { count++ printf( "sys_sync called %d times, currently by pid %d\n", count, pid ); } |
?
您還可以聲明探針可以調用的函數,尤其是希望供多個探針調用的通用函數。這個工具還支持遞歸到給定深度。
SystemTap 允許定義多種類型的變量,但類型是從上下文推斷得出的,因此不需要使用類型聲明。在 SystemTap 中,您可以找到數字(64 位簽名的整數)、整數(64 位)、字符串和字面量(字符串或整數)。您還可以使用關聯數組和統計數據(我們稍后討論)。
SystemTap 提供
C
語言中常用的所有必要操作符,并且用法也是一樣的。您還可以找到算術操作符、二進制操作符、賦值操作符和指針廢棄。您還看到從
C
語言帶來的簡化,其中包括字符串連接、關聯數組元素和合并操作符。
在探針內部,SystemTap 提供一組類似于
C
一樣易于使用的語句。注意,盡管該語言允許您開發復雜的腳本,但每個探針只能執行 1000 條語句(這個數量是可配置的)。表 2 列出了一小部分語句作為例子。注意,在這里的許多元素和
C
中的一樣,盡管有一些附加的東西是特定于 SystemTap 的。
if (exp) {} else {}
|
標準的
if-then-else
語句
|
for (exp1 ; exp2 ; exp3 ) {}
|
一個
for
循環
|
while (exp) {}
|
標準的
while
循環
|
do {} while (exp)
|
一個
do-while
循環
|
break
|
退出迭代 |
continue
|
繼續迭代 |
next
|
從探針返回 |
return
|
從函數返回一個表達式 |
foreach (VAR in ARRAY) {}
|
迭代一個數組,將當前的鍵分配給
VAR
|
本文在樣例腳本中探索了統計數據和聚合功能,因為這是
C
語言中不存在的。
最后,SystemTap 提供許多內部函數,這些函數提供關于當前上下文的額外信息。例如,您可以使用
caller()
識別當前的調用函數,使用
cpu()
識別當前的處理器號碼,以及使用
pid()
返回 PID。SystemTap 還提供許多其他函數,提供對調用堆棧和當前注冊表的訪問。
?
![]() ![]() |
![]()
|
?
在簡單介紹了 SystemTap 的要點之后,我們接下來通過一些簡單的例子來了解 SystemTap 的工作原理。本文還展示了該腳本語言的一些有趣方面,比如聚合。
前一個小節探索了一個監控
sync
系統調用的簡單腳本。現在,我們查看一個更加具有代表性的腳本,它可以監控所有系統調用并收集與它們相關的額外信息。
清單 4 顯示的簡單腳本包含一個全局變量定義和 3 個獨立的探針。在首次加載腳本時調用第一個探針(
begin
探針)。在這個探針中,您可以發出一條表示腳本在內核中運行的文本消息。接下來是一個
syscall
探針。注意這里使用的通配符 (
*
),它告訴 SystemTap 監控所有匹配的系統調用。當該探針觸發時,將為特定的 PID 和進程名增加一個關聯數組元素。最后一個探針是 timer 探針。這個探針在 10,000 毫秒(10 秒)之后觸發。與這個探針相關聯的腳本將發送收集到的數據(遍歷每個關聯數組成員)。當遍歷了所有成員之后,將調用
exit
調用,這導致卸載模塊和退出所有相關的 SystemTap 進程。
global syscalllist probe begin { printf("System Call Monitoring Started (10 seconds)...\n") } probe syscall.* { syscalllist[pid(), execname()]++ } probe timer.ms(10000) { foreach ( [pid, procname] in syscalllist ) { printf("%s[%d] = %d\n", procname, pid, syscalllist[pid, procname] ) } exit() } |
?
清單 4 中的腳本的輸出如清單 5 所示。從這個腳本中您可以看到運行在用戶空間中的每個進程,以及在 10 秒鐘內發出的系統調用的數量。
$
sudo stap profile.stp
System Call Monitoring Started (10 seconds)...
stapio[16208] = 104
gnome-terminal[6416] = 196
Xorg[5525] = 90
vmware-guestd[5307] = 764
hald-addon-stor[4969] = 30
hald-addon-stor[4988] = 15
update-notifier[6204] = 10
munin-node[5925] = 5
gnome-panel[6190] = 33
ntpd[5830] = 20
pulseaudio[6152] = 25
miniserv.pl[5859] = 10
syslogd[4513] = 5
gnome-power-man[6215] = 4
gconfd-2[6157] = 5
hald[4877] = 3
$
|
?
在這個例子中,您稍微修改了上一個腳本,讓它收集一個進程的系統調用數據。此外,除了僅捕捉計數之外,還捕捉針對目標進程的特定系統調用。清 單 6 顯示了該腳本。
這個例子根據特定的進程進行了測試(在本例中為
syslog
守護進程),然后更改關聯數組以將系統調用名映射到計數數據。
清單 6. 新系統調用監控腳本 (syslog_profile.stp)
global syscalllist probe begin { printf("Syslog Monitoring Started (10 seconds)...\n") } probe syscall.* { if (execname() == "syslogd") { syscalllist[name]++ } } probe timer.ms(10000) { foreach ( name in syscalllist ) { printf("%s = %d\n", name, syscalllist[name] ) } exit() } |
?
清單 7 提供了該腳本的輸出。
清單 7. 新腳本的 SystemTap 輸出 (syslog_profile.stp)
$
sudo stap syslog_profile.stp
Syslog Monitoring Started (10 seconds)...
writev = 3
rt_sigprocmask = 1
select = 1
$
|
?
聚合實例時捕捉數字值的統計數據的出色方法。當您捕捉大量數據時,這個方法非常高效有用。在這個例子中,您收集關于網絡包接收和發送的數據。 清單 8 定義兩個新的探針來捕捉網絡 I/O。每個探針捕捉特定網絡設備名、PID 和進程名的包長度。在用戶按 Ctrl-C 調用的 end 探針提供發送捕獲的數據的方式。在本例中,您將遍歷
recv
聚合的內容、為每個元組(設備名、PID 和進程名)相加包的長度,然后發出該數據。注意,這里使用提取器來相加元組:
@count
提取器獲取捕獲到的長度(包計數)。您還可以使用
@sum
提取器來執行相加操作,分別使用
@min
或
@max
來收集最短或最長的程度,以及使用
@avg
來計算平均值。
global recv, xmit probe begin { printf("Starting network capture (Ctl-C to end)\n") } probe netdev.receive { recv[dev_name, pid(), execname()] <<< length } probe netdev.transmit { xmit[dev_name, pid(), execname()] <<< length } probe end { printf("\nEnd Capture\n\n") printf("Iface Process........ PID.. RcvPktCnt XmtPktCnt\n") foreach ([dev, pid, name] in recv) { recvcount = @count(recv[dev, pid, name]) xmitcount = @count(xmit[dev, pid, name]) printf( "%5s %-15s %-5d %9d %9d\n", dev, name, pid, recvcount, xmitcount ) } delete recv delete xmit } |
?
清單 9 提供了清單 8 中的腳本的輸出。注意,當用戶按 Ctrl-C 時退出腳本,然后發送捕獲的數據。
$
sudo stap net.stp
Starting network capture (Ctl-C to end)
^C
End Capture
Iface Process........ PID.. RcvPktCnt XmtPktCnt
eth0 swapper 0 122 85
eth0 metacity 6171 4 2
eth0 gconfd-2 6157 5 1
eth0 firefox 21424 48 98
eth0 Xorg 5525 36 21
eth0 bash 22860 1 0
eth0 vmware-guestd 5307 1 1
eth0 gnome-screensav 6244 6 3
Pass 5: run completed in 0usr/50sys/37694real ms.
$
|
?
最后一個例子展示 SystemTap 用其他形式呈現數據有多么簡單 —— 在本例中以柱狀圖的形式顯示數據。返回到是一個例子中,將數據捕獲到一個名為
histogram
的聚合中(見清單 10)。然后,使用
netdev
接收和發送探針以捕捉包長度數據。當探針結束時,您將使用
@hist_log
提取器以柱狀圖的形式呈現數據。
清單 10. 步驟和呈現柱狀圖數據 (nethist.stp)
global histogram probe begin { printf("Capturing...\n") } probe netdev.receive { histogram <<< length } probe netdev.transmit { histogram <<< length } probe end { printf( "\n" ) print( @hist_log(histogram) ) } |
?
清單 11 顯示了清單 10 的腳本的輸出。在這個例子中,使用了一個瀏覽器會話、一個 FTP 會話和
ping
來生成網絡流量。
@hist_log
提取器是一個以 2 為底數的對數柱狀圖(如下所示)。還可以步驟其他柱狀圖,從而使您能夠定義 bucket 的大小。
$ sudo stap nethist.stp Capturing... ^C value |-------------------------------------------------- count 8 | 0 16 | 0 32 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 1601 64 |@ 52 128 |@ 46 256 |@@@@ 164 512 |@@@ 140 1024 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 2033 2048 | 0 4096 | 0 $ |
?
![]() ![]() |
![]()
|
?
本文僅探索了 SystemTap 的最簡單的功能。在 參 考資料 部分中,您可以找到許多教程、例子和語言參考的鏈接,這些資源提供了解 SystemTap 所需的所有詳細信息。SystemTap 使用幾個現有的方法并借鑒了以前的內核跟蹤實現。盡管該工具還在緊張開發當中,但它現在已經可以使用。請期待未來出現的新特性。
?
學 習
-
查看
SystemTap 項目的 Web 站點
了解最新的信息,包括當前的版本、文檔和鏈接,以及如何參與 SystemTap 項目。SystemTap 使用 Kprobes 作為將探針安裝到運行的內核中的底層方法。在 Sourceware Web 站點上更多地了解
Kprobes
。
-
IBM Redpaper “
SystemTap: Instrumenting the Linux Kernel for Analyzing Performance and Functional Problems
” 提供關于如何使用 SystemTap 的更多信息。
-
針對 IBM 系統上的 Linux 的 IBM Blueprint 顯示了如何在 Red Hat Enterprise Linux and SUSE Linux Enterprise Server 上
安 裝和使用 SystemTap
。另一個 Blueprint 討論如何使用
SystemTap GUI
,這是一個基于 Eclipse 的工具,它簡化了 SystemTap 腳本的編寫和內核事件的可視化。
-
了 解如何
為 Ubuntu 8.10 修改 SystemTap
,以改正運行時的 time.c 文件中的 bug。
-
論 文 “
Dynamic Instrumentation of Production Systems
” 來自 2004 USENIX,它介紹了 DTrace 工具,由來自 Sun Microsystems 的作者發表。
-
architecture paper from 2005
介紹了 SystemTap 架構和設計格式。從本文中可以了解 SystemTap 背后的動機和需求。除了提供大量關于 SystemTap 的技術細節之外,本文還是設計文檔的出色模型。
-
這份在 2006 Ottawa Linux 研討會總結得出的
Kprobes 教程
簡明扼要地介紹了使用 Kprobes 探測內核。您還可以閱讀有趣的文章 “
使用 Kprobes 調試內核
”(developerWorks,2004 年 8 月)。
-
在這個題為 “
Dynamic Tracing and Performance Analysis Using SystemTap
” 的演示中,來自 Intel 的 Josh Stone 提供一份關于 SystemTap 的出色教程。這個演示相當全面地介紹了 SystemTap 及其語言和使用。
-
SystemTap Language Reference
是了解 SystemTap 及其所有功能的優秀資源。
-
Wikipedia 提供大量關于
SystemTap
、
DTrace
和
ProbeVue
的有用資源。您還可以找到關于這些技術的演示和教程的外部鏈接。
-
在
developerWorks Linux 專區
尋找為 Linux 開發人員(包括
Linux 新手入門
) 準備的更多參考資料,查閱我們
最受歡迎的 文章和教程
。
-
在 developerWorks 上查閱所有
Linux 技巧
和
Linux 教程
。
-
隨時關注 developerWorks
技術活動
和
網絡廣播
。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
