輸出Linux內核信息工具:SystemTap及其應用

原文地址:http://linux.chinaunix.net/docs/2006-12-15/3479.shtml

應用地址:http://blog.yufeng.info/archives/688

SystemTap 是一種新穎的 Linux 內核診斷工具,提供了一種從運行中的 Linux 內核快速和安全地獲取信息的能力。SystemTap 是內核開發人員和系統管理員的福音,因爲這使得他們可以通過編寫或者重用簡單的腳本來收集內核的實時數據,而不需要再忍受修改源碼、編譯內核、重啓系統的漫長煎熬。本文介紹了 SystemTap 的安裝、使用和基本原理,並用一些有趣的例子揭示了 SystemTap 提供的強大能力。

在 SystemTap 出現之前,對於 Linux 程序員或者系統管理員而言,調試內核往往是一場噩夢。例如,你懷疑傳遞給系統調用 read 的參數 fd 出了問題,想把它打印出來,你需要做的是:首先得到一份內核源碼,找到 sys_read() 的函數體中插入 printk() 語句,接下來重新編譯內核,然後用新的內核重新啓動系統。謝天謝地,你總算看到了你想要看到的東西,不過你馬上會發現遇到了一個新的麻煩:除非重新啓動系統到原來的內核,printk() 會無休止地打印下去。

SystemTap 的目的就是要把人們從這種泥潭中解救出來。SystemTap 提供了一個簡單的命令行接口和強大的腳本語言,同時預定義了豐富的腳本庫。基於內核中的 kprobe,SystemTap允許你自由地從運行中的內核無害地收集調試信息和性能數據,來用於之後的分析和處理。你可以隨時開始或者停止這種收集過程,而無需漫長的修改代碼、編譯內核和重啓系統的悲慘循環。SystemTap 使得上面的問題變得簡單了,簡單得只需要一條命令就可以做到:

stap -e 'probe syscall.read { printf("fd = %d\n",fd) }'


SystemTap的功能和Sun的DTrace和IBM的dprobe工具相似。但是和它們不同的是, SystemTap是遵循GPL的開源軟件項目。它的出現使得Linux社區也擁有了功能強大而且易於使用的動態內核調試工具。目前,SystemTap 的主要開發成員來自於RedHat、IBM、Intel和Hitachi,其中還包括來自IBM中國開發中心的工程師。

安裝SystemTap

在安裝SystemTap之前,需要確保系統中已經安裝了其它兩個軟件包:

  • kernel-debuginfo RPM:SystemTap需要通過內核調試信息來定位內核函數和變量的位置。對於通常的發行版,並沒有安裝kernel-debuginfo RPM,我們可以到發行版的下載站點下載。對於我的ThinkPad上的Fedora Core 6,這個地址是:http://download.fedora.redhat.com/pub/fedora/linux/core/6/i386/debug/
  • elfutils RPM:SystemTap需要elfutils軟件包提供的庫函數來分析調試信息。目前的SystemTap要求安裝elfutils-0.123以上版本。目前最新的版本是0.124-0.1。如果需要,我們可以從SystemTap的站點下載RPM或者源碼來升級。下載地址是:ftp://sources.redhat.com/pub/SystemTap/elfutils/i386/

接下來就可以安裝SystemTap了,這有通過RPM或者源碼安裝兩種方式:

1. 通過RPM安裝 Fedora Core 6缺省情況下已經安裝了systemtap。如果沒有,也可以從如下的地址下載: http://download.fedora.redhat.com/pub/fedora/linux/
core/updates/testing/6/i386/SystemTap-0.5.10-1.fc6.i386.rpm

2.通過源碼安裝:

從SystemTap的FTP站點下載最新的源碼

ftp://sources.redhat.com/pub/SystemTap/snapshots/SystemTap-20061104.tar.bz2

然後安裝如下:

/root > tar -jxf SystemTap-20061104.tar.bz2
/root > cd src
/root/src> ./configure
/root/src> make
/root/src> make install

運行SystemTap

運行SystemTap首先需要root權限。

運行SystemTap有三種形式:

1. 從文件(通常以.stp作爲文件名後綴)中讀入並運行腳本:stap [選項] 文件名

2. 從標準輸入中讀入並運行腳本: stap [選項] -

3. 運行命令行中的腳本:stap [選項] -e 腳本

4. 直接運行腳本文件(需要可執行屬性並且第一行加上#!/usr/bin/stap):./腳本文件名使用"Ctrl+C"中止SystemTap的運行。

systemtap的選項還在不斷的擴展和更新中,其中最常用的選項包括:

-v -- 打印中間信息

-p NUM -- 運行完Pass Num後停止(缺省是運行到Pass 5)

-k -- 運行結束後保留臨時文件不刪除

-b -- 使用RelayFS文件系統來將數據從內核空間傳輸到用戶空間

-M -- 僅當使用-b選項時有效,運行結束時不合並每個CPU的單獨數據文件

-o FILE -- 輸出到文件,而不是輸出到標準輸出

-c CMD -- 啓動探測後,運行CMD命令,直到命令結束後退出

-g -- 採用guru模式,允許腳本中嵌入C語句

其它更多選項請參看stap的手冊。

SystemTap的語法

我們利用一個簡單的systemtap腳本來介紹一下SystemTap的語法:


#!/usr/local/bin/stap
global count
function report(stat) {
        printf("stat=%d\n", stat)
}
probe kernel.function("sys_read") {
        ++count
}
probe end {
        report()
}



  • 探測點(probe):每個systemtap腳本中至少需要定義一個探測點,也就是指定了在內核的什麼位置進行探測。探測點名稱後面緊跟的一組大括號內定義了每次內核運行到該探測點時需要運行的操作,這些操作完成後再返回探測點,繼續下面的指令。這裏給出了systemtap目前支持的所有探測點類型。
  • 全局變量(global):用來定義全局變量。單個探測點函數體中使用的局部變量不需要預先定義,但是如果一個變量需要在多個探測點函數體中使用,則需要定義爲全局變量。
  • 函數(function):用來定義探測點函數體中需要用到的函數。除了可以用腳本語言定義函數以外,還可以用C語言來定義函數,只是這時函數名後面的大括號對需要換成%{ %}。例如,前面的report()函數可以寫成:
    	function report(stat) %{
    		_stp_printf("stat=%d\n", THIS->stat);
    	%}

SystemTap的例子

瞭解了SystemTap的基本用法,下面讓我們來看幾個有趣的例子。

統計當前系統中調用最多的前10個系統調用

在進行性能分析的時候,我們常常需要知道那些函數調用次數最多,纔能有的放矢地展開分析。下面這個簡單的例子可以打印出在過去的5秒鐘裏調用次數最多的那些系統調用。

#!/usr/bin/env stap
#
# display the top 10 syscalls called in last 5 seconds
#
global syscalls
function print_top () {
        cnt=0
        log ("SYSCALL\t\t\t\tCOUNT")
        foreach ([name] in syscalls-) {
                printf("%-20s\t\t%5d\n",name, syscalls[name])
                if (cnt++ == 10)
                        break
        }
        printf("--------------------------------------\n")
        delete syscalls
}
probe syscall.* {
        syscalls[probefunc()]++
}
probe timer.ms(5000) {
        print_top ()
}


它的輸出結果一目瞭然:


 

看看是誰在偷偷動我的文件

有時候,我們如果中了惡意的病毒軟件,會發現某些文件莫名其妙的被修改,下面這個例子可以幫你監視誰在修改你的文件。

#!/usr/bin/env stap
#
# monitor who is messing my file of secrets
#
probe generic.fop.open {
        if(filename == "secrets")
                printf("%s is opening my file: %s\n", execname(), filename)
}


我們運行這個腳本,在另外一個窗口做一些操作,來看看它的輸出結果:



 

打印ANSI字符串

SystemTap不僅僅是一個簡單的調試工具,強大的腳本語言能力讓它同樣能做一些有趣的事情,下面這個例子就可以對輸出的字符進行美化:

#!/usr/bin/env stap
#
# print colorful ANSI strings
#
probe begin {
        printf("a \\ b |");
        for (c = 40; c < 48; c++)
                printf("   %d   ", c);
        printf("\12");
        for (l = 0; l < 71; l++)
                printf("-");
        printf("\12");

        for (r = 30; r < 38; r++)
                for (t = 0; t < 2; t++) {
                        printf("%d    |", r);
                        for (c = 40; c < 48; c++)
                                printf("\033[%d;%d%s %s \033[0;0m",
                                        r, c, !t ? "m" : ";1m", !t ? "Normal" : "Bold  ");
                        printf("\12");
                }
        exit();
}


來看看它的輸出:


 

SystemTap的基本原理


 

現在,大家已經熟悉了SystemTap的基本用法。在結束之前,讓我們再來了解一下SystemTap的基本原理和工作流程以加深理解。

可以看出,SystemTap運行的過程依次分爲五個階段,通常稱爲Pass 1 - Pass 5。就像前面介紹用法的時候提到的,在命令行中加上-p NUM選項可以使得SystemTap在運行完Pass NUM之後停止,而不是運行到Pass 5。這允許你分析SystemTap在每一個階段的輸出,對於調試腳本尤其有用。

下面來介紹每一個階段的主要功能:

  • Pass 1 - parse:這個階段主要是檢查輸入腳本是否存在語法錯誤,例如大括號是否匹配,變量定義是否規範等
  • Pass 2 - elaborate:這個階段主要是對輸入腳本中定義的探測點或者用到的函數展開,不但需要綜合SystemTap的預定義腳本庫,還需要分析內核或者內核模塊的調試信息
  • Pass 3 - translate: 在這個階段,將展開後的腳本轉換成C文件。前三個階段的功能類似於編譯器,將.stp文件編譯成爲完整的.c文件,因此又被合起來稱爲轉換器(translator)
  • Pass 4 - build:在這個階段,將C源文件編譯成內核模塊,在這過程中還會用到SystemTap的運行時庫函數。
  • Pass 5 - run:這個階段,將編譯好的內核模塊插入內核,開始進行數據收集和傳輸。

小結

SystemTap是一個全新的工具,但已經表現出了強大的功能和廣泛的適用性。SystemTap使得動態收集Linux內核信息和性能數據變得輕而易舉,這就使人可以從繁瑣的數據採集中解放出來,而專注於數據的處理和分析,這無疑是內核開發人員和系統管理人員的福音。隨着越來越多用戶的體驗,越來越多的bug會被報告和修正,越來越多的新功能會被添加,SystemTap也會變得越來越穩定和完善。

原文鏈接:http://www-128.ibm.com/developerworks/cn/linux/l-systemtap/index.html




有了偉大的systemtap, 我們可以用stap腳本來了解誰在消耗我們的cache了:

#這個命令行用來調查誰在加數據入page_cache
[root@my031045 ~]# stap -e 'probe vfs.add_to_page_cache {printf("dev=%d, devname=%s, ino=%d, index=%d, nrpages=%d\n", dev, devname, ino, index, nrpages )}'
...
dev=2, devname=N/A, ino=0, index=2975, nrpages=1777
dev=2, devname=N/A, ino=0, index=3399, nrpages=2594
dev=2, devname=N/A, ino=0, index=3034, nrpages=1778
dev=2, devname=N/A, ino=0, index=3618, nrpages=2595
dev=2, devname=N/A, ino=0, index=1694, nrpages=106
dev=2, devname=N/A, ino=0, index=1703, nrpages=107
dev=2, devname=N/A, ino=0, index=1810, nrpages=210
dev=2, devname=N/A, ino=0, index=1812, nrpages=211
...

這時候我們拷貝個大文件:

[chuba@my031045 ~]$ cp huge_foo.file  bar
 
#這時候我們可以看到文件的內容被猛的添加到cache去:
...
dev=8388614, devname=sda6, ino=2399271, index=39393, nrpages=39393
dev=8388614, devname=sda6, ino=2399271, index=39394, nrpages=39394
dev=8388614, devname=sda6, ino=2399271, index=39395, nrpages=39395
dev=8388614, devname=sda6, ino=2399271, index=39396, nrpages=39396
dev=8388614, devname=sda6, ino=2399271, index=39397, nrpages=39397
dev=8388614, devname=sda6, ino=2399271, index=39398, nrpages=39398
dev=8388614, devname=sda6, ino=2399271, index=39399, nrpages=39399
dev=8388614, devname=sda6, ino=2399271, index=39400, nrpages=39400
dev=8388614, devname=sda6, ino=2399271, index=39401, nrpages=39401
dev=8388614, devname=sda6, ino=2399271, index=39402, nrpages=39402
dev=8388614, devname=sda6, ino=2399271, index=39403, nrpages=39403
dev=8388614, devname=sda6, ino=2399271, index=39404, nrpages=39404
dev=8388614, devname=sda6, ino=2399271, index=39405, nrpages=39405
dev=8388614, devname=sda6, ino=2399271, index=39406, nrpages=39406
dev=8388614, devname=sda6, ino=2399271, index=39407, nrpages=39407
dev=8388614, devname=sda6, ino=2399271, index=39408, nrpages=39408
dev=8388614, devname=sda6, ino=2399271, index=39409, nrpages=39409
dev=8388614, devname=sda6, ino=2399271, index=39410, nrpages=39410
dev=8388614, devname=sda6, ino=2399271, index=39411, nrpages=39411
...

此外加入我們想了解下系統的cache都誰在用呢, 那個文件用到多少頁了呢?
我們有個腳本可以做到,這裏非常謝謝 子團 讓我使用他的代碼。

[chuba@my031045 ~]# stap -g viewcache.stp
 
在另外的shell裏面
[chuba@my031045 ~]# dmesg
...
inode: 116397109, num: 5
inode: 116397111, num: 2
inode: 116397112, num: 1
inode: 116397149, num: 2
inode: 116397152, num: 1
inode: 116397336, num: 2
inode: 116397343, num: 1
inode: 116397371, num: 4
inode: 116397372, num: 2
...

非常清楚的看出來每個inode佔用了多少頁,用工具轉換下就知道哪個文件耗費了多少內存。

點擊下載viewcache.stp

另外小TIPS:

從inode到文件名的轉換
find / -inum your_inode

從文件名到inode的轉換
stat -c “%i” your_filename
或者 ls -i your_filename

我們套用了下就馬上知道那個文件佔用的cache很多。

[chuba@my031045 ~]$ sudo find / -inum 2399248
/home/chuba/kernel-debuginfo-2.6.18-164.el5.x86_64.rpm

玩的開心。

參考資料:
page cache和buffer cache的區別:
這篇文章總結的最靠譜: http://blog.chinaunix.net/u/1595/showart.php?id=2209511

後記:
linux下有個這樣的系統調用可以知道頁面的狀態:mincore – determine whether pages are resident in memory
同時有人作個腳本fincore更方便大家的使用, 點擊下載fincore

後來子團告訴我還有這個工具: https://code.google.com/p/linux-ftools/



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章