◆ 介紹/proc
在過去那些糟糕的日子里,只能通過直接訪問內核內存(/dev/kmem)獲取進程數據,比如運行ps(1)命令時。為了實現這種訪問,需要超級用戶權限,而且步驟相當復雜。Sun公司從UNIX SVR4開始解決了進程數據訪問問題,現在,可以簡單地通過/proc訪問進程數據。
/proc文件系統不是普通意義上的文件系統,它是一個到運行中進程地址空間的訪問接口。通過/proc,可以用標準Unix系統調用(比如open()、read()、write()、ioctl()等等)訪問進程地址空間。事實上,Solaris ps(1)命令正是利用/proc獲取進程狀態。
S (l) 進程狀態:
O 正在運行 S 休眠: 進程正在等待某個事件發生/完成 R 可運行: 進程位于運行隊列中 Z 僵尸狀態: 進程結束了,但是其父進程未處理SIGCHLD信號 T 進程暫停: 可能是任務控制信號所致,或者正在被 跟蹤調試
/proc下的大文件對應運行中進程的地址空間,不是標準Unix文件。事實上每個文件名對應運行中進程的PID,文件屬主、屬組對應進程擁有者的real-uid和primary-gid。權限控制與普通Unix文件一樣。文件大小是最令人迷惑的地方,事實上相當好理解,對應進程內存映像大小,并不真正占用硬盤空間,所以你不必擔心空間浪費的問題。不要企圖刪除這些文件!觀察圖A中列舉的/proc例子:
--------------------------------------------------------------------------
$ ls -l /proc total 43384 -rw------- 1 root root 0 Apr 2 20:07 00000 -rw------- 1 root root 393216 Apr 2 20:07 00001 -rw------- 1 root root 0 Apr 2 20:07 00002 -rw------- 1 root root 0 Apr 2 20:07 00003 -rw------- 1 root root 1695744 Apr 2 20:07 00081 -rw------- 1 root root 1597440 Apr 2 20:07 00083 -rw------- 1 root root 1777664 Apr 2 20:08 00096 -rw------- 1 root root 1683456 Apr 2 20:08 00099 -rw------- 1 root root 1589248 Apr 2 20:08 00101 -rw------- 1 root root 1445888 Apr 2 20:08 00116 -rw------- 1 root root 1404928 Apr 2 20:08 00126 -rw------- 1 root root 798720 Apr 2 20:08 00135 -rw------- 1 root root 1368064 Apr 2 20:08 00195 -rw------- 1 root root 1585152 Apr 2 20:08 00197 -rw------- 1 root root 1368064 Apr 2 20:08 00200 -rw------- 1 root other 225280 Apr 2 20:08 00201 -rw------- 1 root root 1454080 Apr 2 20:08 00203 -rw------- 1 root root 1519616 Apr 2 20:14 00243 -rw------- 1 rthomas wheel 1499136 Apr 2 20:14 00245 -rw------- 1 rthomas wheel 806912 Apr 2 20:16 00261 $
圖A: /proc例子
--------------------------------------------------------------------------
操作/proc下文件的方式和操作普通Unix文件一樣,可以使用所有你熟悉的系統調用,包括ioctl()。在內核中,針對/proc下文件的vnode操作被轉向procfs。這意味著操作vnode的系統調用(比如lookuppn())實際上最終轉向procfs-savvy系統調用(比如prlookup())。
◆ /proc能告訴我什么
Solaris下使用/proc的工具相當完善,位于/usr/proc/bin目錄中。這些工具提供了一種訪問任意指定進程臨界數據的簡捷辦法。比如,想知道一個進程已經打開了多少文件,你可以使用crash(1M)(見鬼,我不會),但是你是root嗎?不必擔心,可以用/usr/proc/bin/pfiles獲取這種信息,圖B演示了pfiles(1)命令的使用:
--------------------------------------------------------------------------
[scz@ /export/home/scz]> ps PID TTY TIME CMD 637 pts/3 0:00 bash [scz@ /export/home/scz]> pfiles 637 637: -bash Current rlimit: 64 file descriptors 0: S_IFCHR mode:0620 dev:151,0 ino:196787 uid:500 gid:7 rdev:24,3 O_RDWR 1: S_IFCHR mode:0620 dev:151,0 ino:196787 uid:500 gid:7 rdev:24,3 O_RDWR 2: S_IFCHR mode:0620 dev:151,0 ino:196787 uid:500 gid:7 rdev:24,3 O_RDWR 3: S_IFDOOR mode:0444 dev:191,0 ino:1618164880 uid:0 gid:0 size:0 O_RDONLY|O_LARGEFILE FD_CLOEXEC door to nscd[213] 63: S_IFCHR mode:0620 dev:151,0 ino:196787 uid:500 gid:7 rdev:24,3 O_RDWR FD_CLOEXEC [scz@ /export/home/scz]>
圖B: 使用pfiles(1)命令
--------------------------------------------------------------------------
正如上面演示的,/usr/proc/bin下的命令使用很簡單,只需要在命令行上指定PID。然而,留心權限許可設置,與所有普通Unix文件一樣,你無權訪問那些權限設置上禁止訪問的指定PID的進程數據。
花點事件看看proc(1)手冊頁,熟悉其中介紹的命令,你將學會列舉指定進程相關的庫、進程信號設置、進程信任設置,你甚至可以暫停、重啟進程。
◆ 編寫/proc工具
/proc的魅力在于它包含了你可能想知道的關于一個進程的任何信息,你只需要簡單地從中獲取。/usr/include/sys/procfs.h文件中定義了兩個結構,prstatus和prpsinfo,從中可以獲取指定進程的很多信息。下面是個例子,開發者想知道他的應用程序究竟占用了多少內存。簡單!ls /proc就可以知道了。但是,他還想知道更多細節,他需要知道總的映像大小、常駐部分的大小、堆區(heap)大小、棧區(stack)大小。此外,他希望能夠定期跟蹤這些數據信息,類似vmstat(1M)那種方式。如上所述,聽起來象是一個令人生畏的任務。
譯者: Solaris 2.6開始這兩個結構定義在/usr/include/sys/old_procfs.h文件中
然而,通過使用/proc文件系統,我們可以使這項編程挑戰變得容易些。我們寫的這個工具稱做memlook,將顯示指定PID對應的內存統計信息。此外,可以在命令行上指定一個時間間隔,以便定期重新檢測內存利用信息。圖C演示了一次簡單的輸出:
--------------------------------------------------------------------------
$ memlook 245 PID IMAGE RSS HEAP STACK 245 1499136 1044480 24581 8192 $
圖C: memlook的輸出舉例
--------------------------------------------------------------------------
下面是memlook.c的源代碼
-------------------------------------------------------------------------- /* * @(#)memlook.c 1.0 10 Nov 1997 * Robert Owen Thomas robt@cymru.com * memlook.c -- A process memory utilization reporting tool. * * gcc -Wall -O3 -o memlook memlook.c */ #pragma ident "@(#)memlook.c 1.0 10 Nov 1997 Robert Owen Thomas robt@cymru.com"
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/signal.h> #include <sys/syscall.h> #include <sys/procfs.h> #include <sys/param.h> #include <unistd.h> #include <fcntl.h>
int counter = 10;
int showUsage ( const char * ); void getInfo ( int, int );
int main ( int argc, char * argv[] ) { int fd, pid, timeloop = 0; char pidpath[BUFSIZ]; /* /usr/include/stdio.h: #define BUFSIZ 1024 */
switch ( argc ) { case 2: break; case 3: timeloop = atoi( argv[2] ); break; default: showUsage( argv[0] ); break; } /* end of switch */ pid = atoi( argv[1] ); sprintf( pidpath, "/proc/%-d", pid ); /* -表示向左靠 */ if ( ( fd = open( pidpath, O_RDONLY ) ) < 0 ) { perror( pidpath ); exit( 1 ); } if ( 0 < timeloop ) { for ( ; ; ) { getInfo( fd, pid ); sleep( timeloop ); } } getInfo( fd, pid ); close( fd ); exit( 0 ); } /* end of main */
int showUsage ( const char * progname ) { fprintf( stderr, "%s: usage: %s < PID > [time delay]\n", progname, progname ); exit( 3 ); } /* end of showUsage */
void getInfo ( int fd, int pid ) { prpsinfo_t prp; prstatus_t prs;
if ( ioctl( fd, PIOCPSINFO, &prp ) < 0 ) { perror( "ioctl" ); exit( 5 ); } if ( ioctl( fd, PIOCSTATUS, &prs ) < 0 ) { perror( "ioctl" ); exit( 7 ); } if ( counter > 9 ) { fprintf( stdout, "PID\tIMAGE\t\tRSS\t\tHEAP\t\tSTACK\n" ); counter = 0; } fprintf( stdout, "%u\t%-9u\t%-9u\t%-15u\t%-15u\n", pid, ( unsigned int )prp.pr_bysize, ( unsigned int )prp.pr_byrssize, ( unsigned int )prs.pr_brksize, ( unsigned int )prs.pr_stksize ); counter++; } /* end of getInfo */ --------------------------------------------------------------------------
譯者: 作者這里利用了ioctl(),而不是直接讀取/proc下文件,這樣做的好處在于即使系統升級后/proc布局改變,內核中相應ioctl cmd支持也隨之改變,對于應用層的開發者,接口一樣,源代碼可平穩移植。事實上從作者前面舉例來看, memlook.c是在Solaris 2.6以前的版本上開發的,但我并未修改就可以直接用在Solaris 2.6上,雖然此時/proc布局已經發生重大變化。
仔細閱讀prstatus和prpsinfo結構,尋找那些你敢興趣的成員。在未能真正掌握這種技術之前不要針對/proc文件系統使用write()或者ioctl()。針對特定進程胡亂做write()調用,結果未知。
◆ 結論
當痛苦調試程序或者試圖獲取指定進程狀態的時候,/proc文件系統將是你強有力的支持者。通過它可以創建更強大的工具,獲取更多信息。 |