用於動態內核分析的接口和語言 node
現代的操做系統內核提供自檢 功能,即動態地檢查內核以理解其行爲的能力。這些行爲能夠反映內核問題和性能瓶頸。擁有這些信息時候,您就能夠調優或修改內核以免出現故障。本文探索一個名爲 SystemTap 的開放源碼基礎設施,它爲 Linux® 內核提供這種動態的自檢。 linux
SystemTap 是監控和跟蹤運行中的 Linux 內核的操做的動態方法。這句話的關鍵詞是動態,由於 SystemTap 沒有使用工具構建一個特殊的內核,而是容許您在運行時動態地安裝該工具。它經過一個名爲Kprobes 的應用編程接口(API)來實現該目的,本文將探索這個 API。咱們首先了解之前的一些內核跟蹤方法,而後在深刻探討 SystemTap 的架構及其使用。 shell
SystemTap 與一種名爲 DTrace 的老技術類似,該技術源於 Sun Solaris 操做系統。在 DTrace 中,開發人員能夠用 D 編程語言(C 語言的子集,但修改成支持跟蹤行爲)編寫腳本。DTrace 腳本包含許多探針和相關聯的操做,這些操做在探針 「觸發」 時發生。例如,探針能夠表示簡單的系統調用,也能夠表示更加複雜的交互,好比執行特定的代碼行。清單 1 顯示了 DTrace 腳本的一個簡單例子,它計算每一個進程發出的系統調用的數量(注意,使用字典將計數和進程關聯起來)。該腳本的格式包含探針(在發出系統調用時觸發)和操做(對應的操做腳本)。 編程
1
2
3
4
5
6
|
syscall
::
:
entry
{
@
num
[
pid
,
execname
]
=
count
(
)
;
}
|
DTrace 是 Solaris 最引人注目的部分,因此在其餘操做系統中開發它並不奇怪。DTrace 是在 Common Development and Distribution License (CDDL) 之下發行的,而且被移植到 FreeBSD 操做系統中。 ubuntu
另外一個很是有用的內核跟蹤工具是 ProbeVue,它是 IBM 爲 IBM® AIX® 操做系統 6.1 開發的。您可使用 ProbeVue 探查系統的行爲和性能,以及提供特定進程的詳細信息。這個工具使用一個標準的內核以動態的方式進行跟蹤。清單 2 顯示了 ProbeVue 腳本的一個例子,它指出發出sync 系統調用的特定進程。 api
1
2
3
4
5
|
@
@
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: bash
1
|
$
sudo
apt
-
get
install
systemtap
|
在安裝完成以後,您能夠測試內核看它是否支持 SystemTap。爲此,使用如下簡單的命令行腳本:
1
|
$
sudo
stap
-
ve
'probe begin { log("hello world") exit() }'
|
若是該腳本可以正常運行,您將在標準輸出 [stdout] 中看到 「hello world」。
若是沒有看到這兩個單詞,則還須要其餘工做。對於 Ubuntu 8.10,須要使用一個調試內核映像。
應該使用 apt-get 獲取包 linux-image-debug-generic 就能夠得到它的。但這裏不能直接使用 apt-get,
所以您能夠下載該包並使用 dpkg 安裝它。您能夠下載通用的調用映像包並按照如下的方式安裝它:
1
2
|
$
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 的轉換流程。
在 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 語言背景將很是有幫助。
1
2
3
4
5
6
|
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 進程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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 秒鐘內發出的系統調用的數量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
$
<
strong
>
sudo
stap
profile
.stp
<
/
strong
>
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 守護進程),而後更改關聯數組以將系統調用名映射到計數數據。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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 提供了該腳本的輸出。
1
2
3
4
5
6
|
$
<
strong
>
sudo
stap
syslog_profile
.stp
<
/
strong
>
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 來計算平均值。
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
|
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 時退出腳本,而後發送捕獲的數據。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
$
<
strong
>
sudo
stap
net
.stp
<
/
strong
>
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 提取器以柱狀圖的形式呈現數據。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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 的大小。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
$
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 使用幾個現有的方法並借鑑了之前的內核跟蹤實現。儘管該工具還在緊張開發當中,但它如今已經可使用。請期待將來出現的新特性。
做者: M. Tim Jones, 自由做家