linux高级工具命令(一)strace
strace命令用法
ldd命令行用法如下:
ldd [--version] [-v|--verbose] [-d|--data-relocs] [-r|--function-relocs] [--help] FILE...
各选项说明如下:
(1) --version : 此选项用于打印出ldd的版本号.
(2) -v 或 --verbose : 此选项指示ldd输出关于所依赖的动态链接库的尽可能详细的信息.
(3) -d 或 --data-relocs : 此选项执行重定位,并且显示不存在的函数.
(4) -r 或 --function-relocs : 此选项执行数据对象与函数的重定位,同时报告不存在的对象.
(5) --help : 此选项用于打印出ldd的帮助信息.
注: 上述选项中,常用-v(或--verbose)选项.
例2:
$ ldd -v ady
libmy.so => ./libmy.so (0x40026000)
libc.so.6 => /lib/libc.so.6 (0x40028000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
Version information:
./ady:
libc.so.6 (GLIBC_2.1.3) => /lib/libc.so.6
libc.so.6 (GLIBC_2.0) => /lib/libc.so.6
./libmy.so:
libc.so.6 (GLIBC_2.1.3) => /lib/libc.so.6
libc.so.6 (GLIBC_2.0) => /lib/libc.so.6
/lib/libc.so.6:
ld-linux.so.2 (GLIBC_2.1.1) => /lib/ld-linux.so.2
ld-linux.so.2 (GLIBC_2.2.3) => /lib/ld-linux.so.2
ld-linux.so.2 (GLIBC_2.1) => /lib/ld-linux.so.2
ld-linux.so.2 (GLIBC_2.2) => /lib/ld-linux.so.2
ld-linux.so.2 (GLIBC_2.0) => /lib/ld-linux.so.2
$
注:本例用-v选项以显示尽可能多的信息,所以例中除列出ady所需要的动态链接库外,还列出了程序所需动态链接库版本方面的信息.
strace命令用法
调用:
strace [ -dffhiqrtttTvxx ] [ -acolumn ] [ -eexpr ] ...
[ -ofile ] [ -ppid ] ... [ -sstrsize ] [ -uusername ] [ command [ arg ... ] ]
strace -c [ -eexpr ] ... [ -Ooverhead ] [ -Ssortby ] [ command [ arg ... ] ]
功能:
跟踪程序执行时的系统调用和所接收的信号.通常的用法是strace执行一直到command结束.
并且将所调用的系统调用的名称、参数和返回值输出到标准输出或者输出到-o指定的文件.
strace是一个功能强大的调试,分析诊断工具.你将发现他是一个极好的帮手在你要调试一个无法看到源码或者源码无法在编译的程序.
你将轻松的学习到一个软件是如何通过系统调用来实现他的功能的.而且作为一个程序设计师,你可以了解到在用户态和内核态是如何通过系统调用和信号来实现程序的功能的.
strace的每一行输出包括系统调用名称,然后是参数和返回值.这个例子:
strace cat /dev/null
他的输出会有:
open(/"/dev/null/",O_RDONLY) = 3
有错误产生时,一般会返回-1.所以会有错误标志和描述:
open(/"/foor/bar/",)_RDONLY) = -1 ENOENT (no such file or directory)
信号将输出喂信号标志和信号的描述.跟踪并中断这个命令/"sleep 600/":
sigsuspend({}
--- SIGINT (Interrupt) ---
+++ killed by SIGINT +++
参数的输出有些不一致.如shell命令中的 /">>tmp/",将输出:
open(/"tmp/",O_WRONLY|O_APPEND|A_CREAT,0666) = 3
对于结构指针,将进行适当的显示.如:/"ls -l /dev/null/":
lstat(/"/dev/null/",{st_mode=S_IFCHR|0666},st_rdev=makdev[1,3],...}) = 0
请注意/"struct stat/" 的声明和这里的输出.lstat的第一个参数是输入参数,而第二个参数是向外传值.
当你尝试/"ls -l/" 一个不存在的文件时,会有:
lstat(/foot/ball/",0xb004) = -1 ENOENT (no such file or directory)
char*将作为C的字符串类型输出.没有字符串输出时一般是char* 是一个转义字符,只输出字符串的长度.
当字符串过长是会使用/".../"省略.如在/"ls -l/"会有一个gepwuid调用读取password文件:
read(3,/"root::0:0:System Administrator://"...,1024) = 422
当参数是结构数组时,将按照简单的指针和数组输出如:
getgroups(4,[0,2,4,5]) = 4
关于bit作为参数的情形,也是使用方括号,并且用空格将每一项参数隔开.如:
sigprocmask(SIG_BLOCK,[CHLD TTOU],[]) = 0
这里第二个参数代表两个信号SIGCHLD 和 SIGTTOU.如果bit型参数全部置位,则有如下的输出:
sigprocmask(SIG_UNBLOCK,~[],NULL) = 0
这里第二个参数全部置位.
参数说明:
-c 统计每一系统调用的所执行的时间,次数和出错的次数等.
-d 输出strace关于标准错误的调试信息.
-f 跟踪由fork调用所产生的子进程.
-ff 如果提供-o filename,则所有进程的跟踪结果输出到相应的filename.pid中,pid是各进程的进程号.
-F 尝试跟踪vfork调用.在-f时,vfork不被跟踪.
-h 输出简要的帮助信息.
-i 输出系统调用的入口指针.
-q 禁止输出关于脱离的消息.
-r 打印出相对时间关于,,每一个系统调用.
-t 在输出中的每一行前加上时间信息.
-tt 在输出中的每一行前加上时间信息,微秒级.
-ttt 微秒级输出,以秒了表示时间.
-T 显示每一调用所耗的时间.
-v 输出所有的系统调用.一些调用关于环境变量,状态,输入输出等调用由于使用频繁,默认不输出.
-V 输出strace的版本信息.
-x 以十六进制形式输出非标准字符串
-xx 所有字符串以十六进制形式输出.
-a column
设置返回值的输出位置.默认为40.
-e expr
指定一个表达式,用来控制如何跟踪.格式如下:
[qualifier=][!]value1[,value2]...
qualifier只能是 trace,abbrev,verbose,raw,signal,read,write其中之一.value是用来限定的符号或数字.默认的qualifier是 trace.感叹号是否定符号.例如:
-eopen等价于 -e trace=open,表示只跟踪open调用.而-etrace!=open表示跟踪除了open以外的其他调用.有两个特殊的符号 all 和 none.
注意有些shell使用!来执行历史记录里的命令,所以要使用//.
-e trace=set
只跟踪指定的系统调用.例如:-e trace=open,close,rean,write表示只跟踪这四个系统调用.默认的为set=all.
-e trace=file
只跟踪有关文件操作的系统调用.
-e trace=process
只跟踪有关进程控制的系统调用.
-e trace=network
跟踪与网络有关的所有系统调用.
-e strace=signal
跟踪所有与系统信号有关的系统调用
-e trace=ipc
跟踪所有与进程通讯有关的系统调用
-e abbrev=set
设定strace输出的系统调用的结果集.-v 等与 abbrev=none.默认为abbrev=all.
-e raw=set
将指定的系统调用的参数以十六进制显示.
-e signal=set
指定跟踪的系统信号.默认为all.如signal=!SIGIO(或者signal=!io),表示不跟踪SIGIO信号.
-e read=set
输出从指定文件中读出的数据.例如:
-e read=3,5
-e write=set
输出写入到指定文件中的数据.
-o filename
将strace的输出写入文件filename
-p pid
跟踪指定的进程pid.
-s strsize
指定输出的字符串的最大长度.默认为32.文件名一直全部输出.
-u username
以username的UID和GID执行被跟踪的命令.
用strace调试程序
在理想世界里,每当一个程序不能正常执行一个功能时,它就会给出一个有用的错误提示,告诉你在足够的改正错误的线索。但遗憾的是,我们不是生活在理想世界里,起码不总是生活在理想世界里。有时候一个程序出现了问题,你无法找到原因。
这就是调试程序出现的原因。strace是一个必不可少的调试工具,strace用来监视系统调用。你不仅可以调试一个新开始的程序,也可以调试一个已经在运行的程序(把strace绑定到一个已有的PID上面)。
首先让我们看一个真实的例子:
启动KDE时出现问题
前一段时间,我在启动KDE的时候出了问题,KDE的错误信息无法给我任何有帮助的线索。
代码:
_KDE_IceTransSocketCreateListener: failed to bind listener
_KDE_IceTransSocketUNIXCreateListener: ...SocketCreateListener() failed
_KDE_IceTransMakeAllCOTSServerListeners: failed to create listener for local
Cannot establish any listening sockets DCOPServer self-test failed.
对我来说这个错误信息没有太多意义,只是一个对KDE来说至关重要的负责进程间通信的程序无法启动。我还可以知道这个错误和ICE协议(Inter Client Exchange)有关,除此之外,我不知道什么是KDE启动出错的原因。
我决定采用strace看一下在启动dcopserver时到底程序做了什么:
代码:
strace -f -F -o ~/dcop-strace.txt dcopserver
这里 -f -F选项告诉strace同时跟踪fork和vfork出来的进程,-o选项把所有strace输出写到~/dcop-strace.txt里面,dcopserver是要启动和调试的程序。
再次出现错误之后,我检查了错误输出文件dcop-strace.txt,文件里有很多系统调用的记录。在程序运行出错前的有关记录如下:
代码:
27207 mkdir("/tmp/.ICE-unix", 0777) = -1 EEXIST (File exists)
27207 lstat64("/tmp/.ICE-unix", {st_mode=S_IFDIR|S_ISVTX|0755, st_size=4096, ...}) = 0
27207 unlink("/tmp/.ICE-unix/dcop27207-1066844596") = -1 ENOENT (No such file or directory)
27207 bind(3, {sin_family=AF_UNIX, path="/tmp/.ICE-unix/dcop27207-1066844596"}, 38) = -1 EACCES (Permission denied)
27207 write(2, "_KDE_IceTrans", 13) = 13
27207 write(2, "SocketCreateListener: failed to "..., 46) = 46
27207 close(3) = 0 27207 write(2, "_KDE_IceTrans", 13) = 13
27207 write(2, "SocketUNIXCreateListener: ...Soc"..., 59) = 59
27207 umask(0) = 0 27207 write(2, "_KDE_IceTrans", 13) = 13
27207 write(2, "MakeAllCOTSServerListeners: fail"..., 64) = 64
27207 write(2, "Cannot establish any listening s"..., 39) = 39
其中第一行显示程序试图创建/tmp/.ICE-unix目录,权限为0777,这个操作因为目录已经存在而失败了。第二个系统调用(lstat64)检查了目录状态,并显示这个目录的权限是0755,这里出现了第一个程序运行错误的线索:程序试图创建属性为0777的目录,但是已经存在了一个属性为 0755的目录。第三个系统调用(unlink)试图删除一个文件,但是这个文件并不存在。这并不奇怪,因为这个操作只是试图删掉可能存在的老文件。
但是,第四行确认了错误所在。他试图绑定到/tmp/.ICE-unix/dcop27207-1066844596,但是出现了拒绝访问错误。. ICE_unix目录的用户和组都是root,并且只有所有者具有写权限。一个非root用户无法在这个目录下面建立文件,如果把目录属性改成0777,则前面的操作有可能可以执行,而这正是第一步错误出现时进行过的操作。
所以我运行了chmod 0777 /tmp/.ICE-unix之后KDE就可以正常启动了,问题解决了,用strace进行跟踪调试只需要花很短的几分钟时间跟踪程序运行,然后检查并分析输出文件。
说明:运行chmod 0777只是一个测试,一般不要把一个目录设置成所有用户可读写,同时不设置粘滞位(sticky bit)。给目录设置粘滞位可以阻止一个用户随意删除可写目录下面其他人的文件。一般你会发现/tmp目录因为这个原因设置了粘滞位。KDE可以正常启动之后,运行chmod +t /tmp/.ICE-unix给.ICE_unix设置粘滞位。
解决库依赖问题
starce 的另一个用处是解决和动态库相关的问题。当对一个可执行文件运行ldd时,它会告诉你程序使用的动态库和找到动态库的位置。但是如果你正在使用一个比较老的glibc版本(2.2或更早),你可能会有一个有bug的ldd程序,它可能会报告在一个目录下发现一个动态库,但是真正运行程序时动态连接程序(/lib/ld-linux.so.2)却可能到另外一个目录去找动态连接库。这通常因为/etc/ld.so.conf和 /etc/ld.so.cache文件不一致,或者/etc/ld.so.cache被破坏。在glibc 2.3.2版本上这个错误不会出现,可能ld-linux的这个bug已经被解决了。
尽管这样,ldd并不能把所有程序依赖的动态库列出来,系统调用dlopen可以在需要的时候自动调入需要的动态库,而这些库可能不会被ldd列出来。作为glibc的一部分的NSS(Name Server Switch)库就是一个典型的例子,NSS的一个作用就是告诉应用程序到哪里去寻找系统帐号数据库。应用程序不会直接连接到NSS库,glibc则会通过dlopen自动调入NSS库。如果这样的库偶然丢失,你不会被告知存在库依赖问题,但这样的程序就无法通过用户名解析得到用户ID了。让我们看一个例子:
whoami程序会给出你自己的用户名,这个程序在一些需要知道运行程序的真正用户的脚本程序里面非常有用,whoami的一个示例输出如下:
代码:
# whoami
root
假设因为某种原因在升级glibc的过程中负责用户名和用户ID转换的库NSS丢失,我们可以通过把nss库改名来模拟这个环境:
代码:
# mv /lib/libnss_files.so.2 /lib/libnss_files.so.2.backup
# whoami
whoami: cannot find username for UID 0
这里你可以看到,运行whoami时出现了错误,ldd程序的输出不会提供有用的帮助:
代码:
# ldd /usr/bin/whoami
libc.so.6 => /lib/libc.so.6 (0x4001f000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
你只会看到whoami依赖Libc.so.6和ld-linux.so.2,它没有给出运行whoami所必须的其他库。这里时用strace跟踪whoami时的输出:
代码:
strace -o whoami-strace.txt whoami
open("/lib/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/lib/i686/mmx/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/i686/mmx", 0xbffff190) = -1 ENOENT (No such file or directory)
open("/lib/i686/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/i686", 0xbffff190) = -1 ENOENT (No such file or directory)
open("/lib/mmx/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/mmx", 0xbffff190) = -1 ENOENT (No such file or directory)
open("/lib/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib", {st_mode=S_IFDIR|0755, st_size=2352, ...}) = 0
open("/usr/lib/i686/mmx/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/i686/mmx", 0xbffff190) = -1 ENOENT (No such file or directory)
open("/usr/lib/i686/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
你可以发现在不同目录下面查找libnss.so.2的尝试,但是都失败了。如果没有strace这样的工具,很难发现这个错误是由于缺少动态库造成的。现在只需要找到libnss.so.2并把它放回到正确的位置就可以了。
[BOLD]限制strace只跟踪特定的系统调用[/BOLD]
如果你已经知道你要找什么,你可以让strace只跟踪一些类型的系统调用。例如,你需要看看在configure脚本里面执行的程序,你需要监视的系统调用就是execve。让strace只记录execve的调用用这个命令:
代码:
strace -f -o configure-strace.txt -e execve ./configure
部分输出结果为:
代码:
2720 execve("/usr/bin/expr", ["expr", "a", ":", "(a)"], [/* 31 vars */]) = 0
2725 execve("/bin/basename", ["basename", "./configure"], [/* 31 vars */]) = 0
2726 execve("/bin/chmod", ["chmod", "+x", "conftest.sh"], [/* 31 vars */]) = 0
2729 execve("/bin/rm", ["rm", "-f", "conftest.sh"], [/* 31 vars */]) = 0
2731 execve("/usr/bin/expr", ["expr", "99", "+", "1"], [/* 31 vars */]) = 0
2736 execve("/bin/ln", ["ln", "-s", "conf2693.file", "conf2693"], [/* 31 vars */]) = 0
你已经看到了,strace不仅可以被程序员使用,普通系统管理员和用户也可以使用strace来调试系统错误。必须承认,strace的输出不总是容易理解,但是很多输出对大多数人来说是不重要的。你会慢慢学会从大量输出中找到你可能需要的信息,像权限错误,文件未找到之类的,那时strace就会成为一个有力的工具了。
linux高级工具命令(四)valgrind做内存检查
C/C++开发其中最令人头疼的一个问题就是内存管理,有时候为了查找一个内存泄漏或者一个内存访问越界,需要要花上好几天时间,如果有一款工具能够帮助我们做这件事情就好了,valgrind正好就是这样的一款工具。
Valgrind是一款基于模拟linux下的程序调试器和剖析器的软件套件,可以运行于x86, amd64和ppc32架构上。valgrind包含一个核心,它提供一个虚拟的CPU运行程序,还有一系列的工具,它们完成调试,剖析和一些类似的任务。valgrind是高度模块化的,所以开发人员或者用户可以给它添加新的工具而不会损坏己有的结构。
valgrind的官方网址是:http://valgrind.org
你可以在它的网站上下载到最新的valgrind,它是开放源码和免费的。
一、介绍
valgrind包含几个标准的工具,它们是:
1、memcheck
memcheck探测程序中内存管理存在的问题。它检查所有对内存的读/写操作,并截取所有的malloc/new/free/delete调用。因此memcheck工具能够探测到以下问题:
1)使用未初始化的内存
2)读/写已经被释放的内存
3)读/写内存越界
4)读/写不恰当的内存栈空间
5)内存泄漏
6)使用malloc/new/new[]和free/delete/delete[]不匹配。
2、cachegrind
cachegrind是一个cache剖析器。它模拟执行CPU中的L1, D1和L2 cache,因此它能很精确的指出代码中的cache未命中。如果你需要,它可以打印出cache未命中的次数,内存引用和发生cache未命中的每一行代码,每一个函数,每一个模块和整个程序的摘要。如果你要求更细致的信息,它可以打印出每一行机器码的未命中次数。在x86和amd64上,cachegrind通过CPUID自动探测机器的cache配置,所以在多数情况下它不再需要更多的配置信息了。
3、helgrind
helgrind查找多线程程序中的竞争数据。helgrind查找内存地址,那些被多于一条线程访问的内存地址,但是没有使用一致的锁就会被查出。这表示这些地址在多线程间访问的时候没有进行同步,很可能会引起很难查找的时序问题。
二、valgrind对你的程序都做了些什么
valgrind被设计成非侵入式的,它直接工作于可执行文件上,因此在检查前不需要重新编译、连接和修改你的程序。要检查一个程序很简单,只需要执行下面的命令就可以了
valgrind --tool=tool_name program_name
比如我们要对ls -l命令做内存检查,只需要执行下面的命令就可以了
valgrind --tool=memcheck ls -l
不管是使用哪个工具,valgrind在开始之前总会先取得对你的程序的控制权,从可执行关联库里读取调试信息。然后在valgrind核心提供的虚拟CPU上运行程序,valgrind会根据选择的工具来处理代码,该工具会向代码中加入检测代码,并把这些代码作为最终代码返回给valgrind核心,最后valgrind核心运行这些代码。
如果要检查内存泄漏,只需要增加--leak-check=yes就可以了,命令如下
valgrind --tool=memcheck --leak-check=yes ls -l
不同工具间加入的代码变化非常的大。在每个作用域的末尾,memcheck加入代码检查每一片内存的访问和进行值计算,代码大小至少增加12倍,运行速度要比平时慢25到50倍。
valgrind模拟程序中的每一条指令执行,因此,检查工具和剖析工具不仅仅是对你的应用程序,还有对共享库,GNU C库,X的客户端库都起作用。
三、现在开始
首先,在编译程序的时候打开调试模式(gcc编译器的-g选项)。如果没有调试信息,即使最好的valgrind工具也将中能够猜测特定的代码是属于哪一个函数。打开调试选项进行编译后再用valgrind检查,valgrind将会给你的个详细的报告,比如哪一行代码出现了内存泄漏。
当检查的是C++程序的时候,还应该考虑另一个选项 -fno-inline。它使得函数调用链很清晰,这样可以减少你在浏览大型C++程序时的混乱。比如在使用这个选项的时候,用memcheck检查openoffice就很容易。当然,你可能不会做这项工作,但是使用这一选项使得valgrind生成更精确的错误报告和减少混乱。
一些编译优化选项(比如-O2或者更高的优化选项),可能会使得memcheck提交错误的未初始化报告,因此,为了使得valgrind的报告更精确,在编译的时候最好不要使用优化选项。
如果程序是通过脚本启动的,可以修改脚本里启动程序的代码,或者使用--trace-children=yes选项来运行脚本。
下面是用memcheck检查ls -l命令的输出报告,在终端下执行下面的命令
valgrind --tool=memcheck ls -l
程序会打印出ls -l命令的结果,最后是valgrind的检查报告如下:
==4187==
==4187== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 19 from 2)
==4187== malloc/free: in use at exit: 15,154 bytes in 105 blocks.
==4187== malloc/free: 310 allocs, 205 frees, 60,093 bytes allocated.
==4187== For counts of detected errors, rerun with: -v
==4187== searching for pointers to 105 not-freed blocks.
==4187== checked 145,292 bytes.
==4187==
==4187== LEAK SUMMARY:
==4187== definitely lost: 0 bytes in 0 blocks.
==4187== possibly lost: 0 bytes in 0 blocks.
==4187== still reachable: 15,154 bytes in 105 blocks.
==4187== suppressed: 0 bytes in 0 blocks.
==4187== Reachable blocks (those to which a pointer was found) are not shown.
==4187== To see them, rerun with: --show-reachable=yes
这里的“4187”指的是执行ls -l的进程ID,这有利于区别不同进程的报告。memcheck会给出报告,分配置和释放了多少内存,有多少内存泄漏了,还有多少内存的访问是可达的,检查了多少字节的内存。
下面举两个用valgrind做内存检查的例子
例子一 (test.c):
#include <string.h>
int main(int argc, char *argv[])
{
char *ptr;
ptr = (char*) malloc(10);
strcpy(ptr, "01234567890");
return 0;
}
编译程序
gcc -g -o test test.c
用valgrind执行命令
valgrind --tool=memcheck --leak-check=yes ./test
报告如下
==4270== Memcheck, a memory error detector.
==4270== Copyright (C) 2002-2006, and GNU GPL'd, by Julian Seward et al.
==4270== Using LibVEX rev 1606, a library for dynamic binary translation.
==4270== Copyright (C) 2004-2006, and GNU GPL'd, by OpenWorks LLP.
==4270== Using valgrind-3.2.0, a dynamic binary instrumentation framework.
==4270== Copyright (C) 2000-2006, and GNU GPL'd, by Julian Seward et al.
==4270== For more details, rerun with: -v
==4270==
==4270== Invalid write of size 1
==4270== at 0x4006190: strcpy (mc_replace_strmem.c:271)
==4270== by 0x80483DB: main (test.c:8)
==4270== Address 0x4023032 is 0 bytes after a block of size 10 alloc'd
==4270== at 0x40044F6: malloc (vg_replace_malloc.c:149)
==4270== by 0x80483C5: main (test.c:7)
==4270==
==4270== Invalid write of size 1
==4270== at 0x400619C: strcpy (mc_replace_strmem.c:271)
==4270== by 0x80483DB: main (test.c:8)
==4270== Address 0x4023033 is 1 bytes after a block of size 10 alloc'd
==4270== at 0x40044F6: malloc (vg_replace_malloc.c:149)
==4270== by 0x80483C5: main (test.c:7)
==4270==
==4270== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 12 from 1)
==4270== malloc/free: in use at exit: 10 bytes in 1 blocks.
==4270== malloc/free: 1 allocs, 0 frees, 10 bytes allocated.
==4270== For counts of detected errors, rerun with: -v
==4270== searching for pointers to 1 not-freed blocks.
==4270== checked 51,496 bytes.
==4270==
==4270==
==4270== 10 bytes in 1 blocks are definitely lost in loss record 1 of 1
==4270== at 0x40044F6: malloc (vg_replace_malloc.c:149)
==4270== by 0x80483C5: main (test.c:7)
==4270==
==4270== LEAK SUMMARY:
==4270== definitely lost: 10 bytes in 1 blocks.
==4270== possibly lost: 0 bytes in 0 blocks.
==4270== still reachable: 0 bytes in 0 blocks.
==4270== suppressed: 0 bytes in 0 blocks.
==4270== Reachable blocks (those to which a pointer was found) are not shown.
==4270== To see them, rerun with: --show-reachable=yes
从这份报告可以看出,进程号是4270,test.c的第8行写内存越界了,引起写内存越界的是strcpy函数,
第7行泄漏了10个字节的内存,引起内存泄漏的是malloc函数。
例子二(test2.c)
#include <stdio.h>
int foo(int x)
{
if (x < 0) {
printf("%d ", x);
}
return 0;
}
int main(int argc, char *argv[])
{
int x;
foo(x);
return 0;
}
编译程序
gcc -g -o test2 test2.c
用valgrind做内存检查
valgrind --tool=memcheck ./test2
输出报告如下
==4285== Memcheck, a memory error detector.
==4285== Copyright (C) 2002-2006, and GNU GPL'd, by Julian Seward et al.
==4285== Using LibVEX rev 1606, a library for dynamic binary translation.
==4285== Copyright (C) 2004-2006, and GNU GPL'd, by OpenWorks LLP.
==4285== Using valgrind-3.2.0, a dynamic binary instrumentation framework.
==4285== Copyright (C) 2000-2006, and GNU GPL'd, by Julian Seward et al.
==4285== For more details, rerun with: -v
==4285==
==4285== Conditional jump or move depends on uninitialised value(s)
==4285== at 0x8048372: foo (test2.c:5)
==4285== by 0x80483B4: main (test2.c:16)
==4285==p p
==4285== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 12 from 1)
==4285== malloc/free: in use at exit: 0 bytes in 0 blocks.
==4285== malloc/free: 0 allocs, 0 frees, 0 bytes allocated.
==4285== For counts of detected errors, rerun with: -v
==4285== All heap blocks were freed -- no leaks are possible.
从这份报告可以看出进程PID是4285,test2.c文件的第16行调用了foo函数,在test2.c文件的第5行foo函数使用了一个未初始化的变量。
valgrind还有很多使用选项,具体可以查看valgrind的man手册页和valgrind官方网站的在线文档。
linux高级工具命令(五)vmstat介绍
通过STATSPACK收集服务器信息,主要通过收集VMSTAT的信息来展现服务器状况。VMSTAT工具是最常见的UNIX监控工具,可以展现给定时间间隔的服务器的状态值。
一般VMSTAT工具的使用是通过两个数字参数来完成的,第一个参数是采样的时间间隔数,单位是秒,第二个参数是采样的次数。如:
[oracle@localhost oracle]$vmstat 2
procs -----------memory---------- ---swap-- -----io---- --system-- ----cpu----
r b swpd free buff cache si so bi bo in cs us sy id wa
0 0 195804 3812 40616 1598656 0 0 0 0 1024 66976 0 0 100 0
0 0 195804 3812 40616 1598656 0 0 0 12 1048 66961 0 0 100 0
0 0 195804 3812 40616 1598656 0 0 0 28 1054 67067 0 0 100 0
0 0 195804 3812 40616 1598656 0 0 0 0 1020 66988 0 0 100 0
(注:目前系统几乎空闲,并且不同操作系统VMSTAT输出内容有所不同)
目前说来,对于服务器监控有用处的度量主要有:
r(运行队列)
pi(页导入)
us(用户CPU)
sy(系统CPU)
id(空闲)
(如果r经常大于4 ,且id经常少于40,表示cpu的负荷很重。)
(如果bi,bo 长期不等于0,表示内存不足)
通过VMSTAT识别CPU瓶颈
r(运行队列)展示了正在执行和等待CPU资源的任务个数。当这个值超过了CPU数目,就会出现CPU瓶颈了
获得CPU个数的命令(LINUX环境):
cat /proc/cpuinfo|grep processor|wc -l
当r值超过了CPU个数,就会出现CPU瓶颈,解决办法大体几种:
1. 最简单的就是增加CPU个数
2. 通过调整任务执行时间,如大任务放到系统不繁忙的情况下进行执行,进而平衡系统任务
3. 调整已有任务的优先级
通过VMSTAT识别CPU满负荷
首先需要声明一点的是,vmstat中CPU的度量是百分比的。当us+sy的值接近100的时候,表示CPU正在接近满负荷工作。但要注意的是,CPU 满负荷工作并不能说明什么,UNIX总是试图要CPU尽可能的繁忙,使得任务的吞吐量最大化。唯一能够确定CPU瓶颈的还是r(运行队列)的值。
通过VMSTAT识别RAM瓶颈
数据库服务器都只有有限的RAM,出现内存争用现象是Oracle的常见问题。
首先察看RAM的数量,命令如下(LINUX环境):
[oracle@oracle-db02 ~]$ free
total used free shared buffers cached
Mem: 2074924 2071112 3812 0 40616 1598656
-/+ buffers/cache: 431840 1643084
Swap: 3068404 195804 2872600
当然可以使用top等其他命令来显示RAM。
当内存的需求大于RAM的数量,服务器启动了虚拟内存机制,通过虚拟内存,可以将RAM段移到SWAP DISK的特殊磁盘段上,这样会 出现虚拟内存的页导出和页导入现象,页导出并不能说明RAM瓶颈,虚拟内存系统经常会对内存段进行页导出,但页导入操作就表明了服务器需要更多的内存了, 页导入需要从SWAP DISK上将内存段复制回RAM,导致服务器速度变慢。
解决的办法有几种:
1. 最简单的,加大RAM
2. 改小SGA,使得对RAM需求减少
3. 减少RAM的需求(如:减少PGA)
linux高级工具命令(六)lsof查看文件
通过查看打开的文件,了解更多关于系统的信息。了解应用程序打开了哪些文件或者哪个应用程序打开了特定的文件,作为系统管理员,这将使得您能够作出更好的决策。例如,您不应该卸载具有打开文件的文件系统。使用 lsof,您可以检查打开的文件,并根据需要在卸载之前中止相应的进程。同样地,如果您发现了一个未知的文件,那么可以找出到底是哪个应用程序打开了这个文件。
在 UNIX? 环境中,文件无处不在,这便产生了一句格言:“任何事物都是文件”。通过文件不仅仅可以访问常规数据,通常还可以访问网络连接和硬件。在有些情况下,当您使用 ls 请求目录清单时,将出现相应的条目。在其他情况下,如传输控制协议 (TCP) 和用户数据报协议 (UDP) 套接字,不存在相应的目录清单。但是在后台为该应用程序分配了一个文件描述符,无论这个文件的本质如何,该文件描述符为应用程序与基础操作系统之间的交互提供了通用接口。
因为应用程序打开文件的描述符列表提供了大量关于这个应用程序本身的信息,所以能够查看这个列表将是很有帮助的。完成这项任务的实用程序称为 lsof,它对应于“list open files”(列出打开的文件)。几乎在每个 UNIX 版本中都有这个实用程序,但奇怪的是,大多数供应商并没有将其包含在操作系统的初始安装中。要获取更多关于 lsof 的信息,请参见参考资料部分。
只需输入 lsof 就可以生成大量的信息,如清单 1 所示。因为 lsof 需要访问核心内存和各种文件,所以必须以 root 用户的身份运行它才能够充分地发挥其功能。
bash-3.00# lsof COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME sched 0 root cwd VDIR 136,8 1024 2 / init 1 root cwd VDIR 136,8 1024 2 / init 1 root txt VREG 136,8 49016 1655 /sbin/init init 1 root txt VREG 136,8 51084 3185 /lib/libuutil.so.1 vi 2013 root 3u VREG 136,8 0 8501 /var/tmp/ExXDaO7d ...
每行显示一个打开的文件,除非另外指定,否则将显示所有进程打开的所有文件。Command、PID 和 User 列分别表示进程的名称、进程标识符 (PID) 和所有者名称。Device、SIZE/OFF、Node 和 Name 列涉及到文件本身的信息,分别表示指定磁盘的名称、文件的大小、索引节点(文件在磁盘上的标识)和该文件的确切名称。根据 UNIX 版本的不同,可能将文件的大小报告为应用程序在文件中进行读取的当前位置(偏移量)。清单 1 来自一台可以报告该信息的 Sun Solaris 10 计算机,而 Linux? 没有这个功能。
FD 和 Type 列的含义最为模糊,它们提供了关于文件如何使用的更多信息。FD 列表示文件描述符,应用程序通过文件描述符识别该文件。Type 列提供了关于文件格式的更多描述。我们来具体研究一下文件描述符列,清单 1 中出现了三种不同的值。cwd 值表示应用程序的当前工作目录,这是该应用程序启动的目录,除非它本身对这个目录进行更改。txt 类型的文件是程序代码,如应用程序二进制文件本身或共享库,再比如本示例的列表中显示的 init 程序。最后,数值表示应用程序的文件描述符,这是打开该文件时返回的一个整数。在清单 1 输出的最后一行中,您可以看到用户正在使用 vi 编辑 /var/tmp/ExXDaO7d,其文件描述符为 3。u 表示该文件被打开并处于读取/写入模式,而不是只读 (r) 或只写 (w) 模式。有一点不是很重要但却很有帮助,初始打开每个应用程序时,都具有三个文件描述符,从 0 到 2,分别表示标准输入、输出和错误流。正因为如此,大多数应用程序所打开的文件的 FD 都是从 3 开始。
与 FD 列相比,Type 列则比较直观。根据具体操作系统的不同,您会发现将文件和目录称为 REG 和 DIR(在 Solaris 中,称为 VREG 和 VDIR)。其他可能的取值为 CHR 和 BLK,分别表示字符和块设备;或者 UNIX、FIFO 和 IPv4,分别表示 UNIX 域套接字、先进先出 (FIFO) 队列和网际协议 (IP) 套接字。
尽管与使用 lsof 没有什么直接的关系,但对 /proc 目录进行简要的介绍是有必要的。/proc 是一个目录,其中包含了反映内核和进程树的各种文件。这些文件和目录并不存在于磁盘中,因此当您对这些文件进行读取和写入时,实际上是在从操作系统本身获取相关信息。大多数与 lsof 相关的信息都存储于以进程的 PID 命名的目录中,所以 /proc/1234 中包含的是 PID 为 1234 的进程的信息。
在 /proc 目录的每个进程目录中存在着各种文件,它们可以使得应用程序简单地了解进程的内存空间、文件描述符列表、指向磁盘上的文件的符号链接和其他系统信息。lsof 实用程序使用该信息和其他关于内核内部状态的信息来产生其输出。稍后我将把 lsof 的输出与 /proc 目录中的信息联系起来。
前面,我向您介绍了如何简单地运行不带任何参数的 lsof,以便显示关于每个进程所打开的文件的信息。本文余下的部分将重点关注如何使用 lsof 来显示所需的信息以及如何正确地对其进行解释。
lsof 常见的用法是查找应用程序打开的文件的名称和数目。您可能想尝试找出某个特定应用程序将日志数据记录到何处,或者正在跟踪某个问题。例如,UNIX 限制了进程能够打开文件的数目。通常这个数值很大,所以不会产生问题,并且在需要时,应用程序可以请求更大的值(直到某个上限)。如果您怀疑应用程序耗尽了文件描述符,那么可以使用 lsof 统计打开的文件数目,以进行验证。
要指定单个进程,可以使用 -p 参数,后面加上该进程的 PID。因为这样做不仅会返回该应用程序所打开的文件,还会返回共享库和代码,所以通常需要对输出进行筛选。要完成此任务,可以使用 -d 标志根据 FD 列进行筛选,使用 -a 标志表示两个参数都必须满足 (AND)。如果没有 -a 标志,缺省的情况是显示匹配任何一个参数 (OR) 的文件。清单 2 显示了 sendmail 进程打开的文件,并使用 txt 对这些文件进行筛选。
sh-3.00# lsof -a -p 605 -d ^txt COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME sendmail 605 root cwd VDIR 136,8 1024 23554 /var/spool/mqueue sendmail 605 root 0r VCHR 13,2 6815752 /devices/pseudo/mm@0:null sendmail 605 root 1w VCHR 13,2 6815752 /devices/pseudo/mm@0:null sendmail 605 root 2w VCHR 13,2 6815752 /devices/pseudo/mm@0:null sendmail 605 root 3r DOOR 0t0 58 /var/run/name_service_door(door to nscd[81]) (FA:->0x30002b156c0)
se dmail 60 root 4 VCHR 21,0
清单 2 为 lsof 指定了三个参数。第一个是 -a,它表示当所有的参数都为真时,才显示这个文件。第二个参数是 -p 605,它限制仅输出 PID 为 605 的进程,可以通过 ps 命令获取这个信息。最后一个参数 -d ^txt,它表示筛选出其中 txt 类型的记录(脱字符号 [^] 表示排除)。
清单 2 的输出提供了关于进程行为的信息。如 cwd 行所示,该应用程序的工作目录为 /var/spool/mqueue。文件描述符 0、1 和 2 分配给了 /dev/null(Solaris 大量使用符号链接,所以这里显示了相应的伪设备)。FD 3 是一个 Solaris 门(高速远程过程调用 (RPC) 接口),以只读模式打开。FD 4 中的内容比较有趣,因为它是一个字符设备的只读句柄,实质上是 /dev/log。从这个文件中,您可以收集该应用程序向 UNIX syslog 守护进程进行的记录,所以 /etc/syslog.conf 规定了日志文件的位置。
作为一个网络应用程序,sendmail 对网络端口进行监听。文件描述符 5、6 和 7 可以告诉您,该应用程序正以 IPv4 和 IPv6 模式监听简单邮件传输协议 (SMTP) 端口,并以 IPv4 模式监听提交端口。最后一个文件描述符是只写的,并且指向 /var/run/sendmail.pid。FD 列中的大写 W 表示该应用程序具有对整个文件的写锁。该文件用于确保每次只能打开一个应用程序实例。
在其他情况下,您有一个文件或目录,并且需要知道哪个应用程序控制了该文件(打开了该文件)。清单 2 显示了由 sendmail 进程打开了 /var/run/sendmail.pid。如果您不知道这个信息,那么在给定文件名的情况下,lsof 可以提供该信息。清单 3 显示了相应的输出。
bash-3.00# lsof /var/run/sendmail.pid
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
sendmail 605 root 8wW VREG 281,3 32 8778600 /var/run/sendmail.pid
正如输出所示,进程 sendmail(PID 为 605)控制了文件 /var/run/sendmail.pid,并且通过排它锁打开该文件以便进行写入。如果出于某种原因,您需要删除这个文件,那么正确的做法是中止该进程,而不是直接删除这个文件。否则,这个守护进程下次可能无法正常启动,或者可能稍后会启动另一个实例,从而导致争用。
有时您只知道在文件系统的某处打开了文件。在卸载文件系统时,如果该文件系统中有任何打开的文件,那么操作将会失败。通过指定装入点的名称,您可以使用 lsof 显示一个文件系统中所有打开的文件。清单 4 显示了如何尝试卸载 /export/home,然后使用 lsof 找出谁在使用该文件系统。
bash-3.00# umount /export/home umount: /export/home busy bash-3.00# lsof /export/home COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME bash 1943 root cwd VDIR 136,7 1024 4 /export/home/sean bash 2970 sean cwd VDIR 136,7 1024 4 /export/home/sean ct 3030 sean cwd VDIR 136,7 1024 4 /export/home/sean ct 3030 sean 1w VREG 136,7 0 25 /export/home/sean/output
在这个示例中,用户 sean 正在其 home 目录中进行一些操作。有两个 bash(一种 Shell)实例正在运行,并且当前目录设置为 sean 的 home 目录。还有一个名为 ct 的应用程序正运行于相同的目录,并且其标准输出(文件描述符 1)重定向到一个名为 output 的文件。要成功地卸载 /export/home,应该在通知用户以确保情况正常之后,中止这些进程。
这个示例说明了应用程序的当前工作目录非常重要,因为它仍保持着文件资源,并且可以防止文件系统被卸载。这就是为什么大部分守护进程(后台进程)将它们的目录更改为根目录、或服务特定的目录(如 sendmail 示例中的 /var/spool/mqueue)的原因,以避免该守护进程阻止卸载不相关的文件系统。如果 sendmail 从 /export/home/sean 目录启动,并且没有将其目录更改为 /var/spool/mqueue,那么在卸载 /export/home 前必须中止它。
如果您对非装入点目录中打开的文件感兴趣,那么必须通过 +d 或 +D 指定该目录的名称,具体使用其中的哪一个标志取决于您需要递归到子目录(+D)或者不需要递归到子目录(+d)。例如,要查看 /export/home/sean 中所有打开的文件,可以使用 lsof +D /export/home/sean。在前面的示例中,相关的目录是一个装入点,而这里与前面的示例存在细微的差别,并且限制了 lsof 和内核之间的交互。这还会引起潜在的问题,即 lsof /export/home 与 lsof /export/home/(请注意尾部的斜杠)有所区别。第一种方式可以正常工作,因为它指向了装入点。第二种方式不会生成任何输出,因为它指向了目录。如果您在 Shell 中使用 Tab 键自动完成命令,那么可能碰到这个问题,其中会帮助您添加结尾的斜杠。在这种情况下,您可以删除这个斜杠或者使用 +D 指定目录。前者是首选的方法,因为与指定任意的目录相比,其执行速度更快。
在前面的部分中,我们研究了 lsof 的基本用法,即显示打开的文件和控制它们的进程之间的关系。当您想对系统进行一些烦琐的操作,而又不希望破坏别人重要的文档时,这种方法很有帮助。您还可以使用相同的方法执行一些高难度的 UNIX 操作。
当 UNIX 计算机受到入侵时,常见的情况是日志文件被删除,以掩盖攻击者的踪迹。管理错误也可能导致意外删除重要的文件,比如在清理旧日志时,意外地删除了数据库的活动事务日志。有时可以恢复这些文件,并且 lsof 可以为您提供帮助。
当进程打开了某个文件时,只要该进程保持打开该文件,即使将其删除,它依然存在于磁盘中。这意味着,进程并不知道文件已经被删除,它仍然可以向打开该文件时提供给它的文件描述符进行读取和写入。除了该进程之外,这个文件是不可见的,因为已经删除了其相应的目录条目。
前面曾在转到 /proc 目录部分中说过,通过在适当的目录中进行查找,您可以访问进程的文件描述符。在随后的内容中,您看到了 lsof 可以显示进程的文件描述符和相关的文件名。您能明白我的意思吗?
但愿它真的这么简单!当您向 lsof 传递文件名时,比如在 lsof /file/I/deleted 中,它首先使用 stat() 系统调用获得有关该文件的信息,不幸的是,这个文件已经被删除。在不同的操作系统中,lsof 可能可以从核心内存中捕获该文件的名称。清单 5 显示了一个 Linux 系统,其中意外地删除了 Apache 日志,我正使用 grep 工具查找是否有人打开了该文件。
# lsof | grep error_log httpd 2452 root 2w REG 33,2 499 3090660 /var/log/httpd/error_log (deleted) httpd 2452 root 7w REG 33,2 499 3090660 /var/log/httpd/error_log (deleted) ... more httpd processes ...
在这个示例中,您可以看到 PID 2452 打开文件的文件描述符为 2(标准错误)和 7。因此,可以在 /proc/2452/fd/7 中查看相应的信息,如清单 6 所示。
# cat /proc/2452/fd/7 [Sun Apr 30 04:02:48 2006] [notice] Digest: generating secret for digest authentication [Sun Apr 30 04:02:48 2006] [notice] Digest: done [Sun Apr 30 04:02:48 2006] [notice] LDAP: Built with OpenLDAP LDAP SDK
Linux 的优点在于,它保存了文件的名称,甚至可以告诉我们它已经被删除。在遭到破坏的系统中查找相关内容时,这是非常有用的内容,因为攻击者通常会删除日志以隐藏他们的踪迹。Solaris 并不提供这些信息。然而,我们知道 httpd 守护进程使用了 error_log 文件,所以可以使用 ps 命令找到这个 PID,然后可以查看这个守护进程打开的所有文件。
# lsof -a -p 8663 -d ^txt COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME httpd 8663 nobody cwd VDIR 136,8 1024 2 / httpd 8663 nobody 0r VCHR 13,2 6815752 /devices/pseudo/mm@0:null httpd 8663 nobody 1w VCHR 13,2 6815752 /devices/pseudo/mm@0:null httpd 8663 nobody 2w VREG 136,8 185 145465 / (/dev/dsk/c0t0d0s0) httpd 8663 nobody 4r DOOR 0t0 58 /var/run/name_service_door (door to nscd[81]) (FA:->0x30002b156c0) httpd 8663 nobody 15w VREG 136,8 185 145465 / (/dev/dsk/c0t0d0s0) httpd 8663 nobody 16u IPv4 0x300046d27c0 0t0 TCP *:80 (LISTEN) httpd 8663 nobody 17w VREG 136,8 0 145466 /var/apache/logs/access_log httpd 8663 nobody 18w VREG 281,3 0 9518013 /var/run (swap)
我使用 -a 和 -d 参数对输出进行筛选,以排除代码程序段,因为我知道需要查找的是哪些文件。Name 列显示出,其中的两个文件(FD 2 和 15)使用磁盘名代替了文件名,并且它们的类型为 VREG(常规文件)。在 Solaris 中,删除的文件将显示文件所在的磁盘的名称。通过这个线索,就可以知道该 FD 指向一个删除的文件。实际上,查看 /proc/8663/fd/15 就可以得到所要查找的数据。
如果可以通过文件描述符查看相应的数据,那么您就可以使用 I/O 重定向将其复制到文件中,如 cat /proc/8663/fd/15 > /tmp/error_log 。此时,您可以中止该守护进程(这将删除 FD,从而删除相应的文件),将这个临时文件复制到所需的位置,然后重新启动该守护进程。
对于许多应用程序,尤其是日志文件和数据库,这种恢复删除文件的方法非常有用。正如您所看到的,有些操作系统(以及不同版本的 lsof)比其他的系统更容易查找相应的数据。
网络连接也是文件,这意味着可以使用 lsof 获得关于它们的信息。您曾在清单 2 中看到过这样的示例。该示例假设您已经知道 PID,但是有时候并非如此。如果您只知道相应的端口,那么可以使用 -i 参数利用套接字信息进行搜索。清单 8 显示了对 TCP 端口 25 的搜索
# lsof -i :25 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME sendmail 605 root 5u IPv4 0x300010ea640 0t0 TCP *:smtp (LISTEN) sendmail 605 root 6u IPv6 0x3000431c180 0t0 TCP *:smtp (LISTEN)
需要以 protocol:@ip:port 的形式向 lsof 实用程序传递相关信息,其中的 protocol 为 TCP 或 UDP(可以使用 4 或 6 作为前缀,表示 IP 的版本),IP 为可解析的名称或 IP 地址,而 port 为数字或表示该服务的名称(来自 /etc/services)。需要一个或多个元素(端口、IP、协议)。在清单 8 中,:25 表示端口 25。输出显示,进程 605 正在使用 IPv6 和 IPv4 监听端口 25。如果您对 IPv4 不感兴趣,那么可以将筛选器改为 6:25,以表示监听端口 25 的 IPv6 套接字,或者直接使用 6 表示所有的 IPv6 连接。
除了显示出这些守护进程正在监听的对象,lsof 还可以发现发生的连接,同样是使用 -i 参数。清单 9 显示了搜索与 192.168.1.10 之间的所有连接。
# lsof -i @192.168.1.10 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME sshd 1934 root 6u IPv6 0x300046d21c0 0t1303608 TCP sun:ssh->linux:40379 (ESTABLISHED) sshd 1937 root 4u IPv6 0x300046d21c0 0t1303608 TCP sun:ssh->linux:40379 (ESTABLISHED)
在这个示例中,sun 和 linux 之间有两个 IPv6 连接。对其进行更仔细的研究可以看出,这些连接来自于两个不同的进程,但它们却是相同的,这是因为两台主机是相同的,并且端口也是相同的(ssh 和 40379)。这是由于进入主进程的连接分叉出一个处理程序,并将该套接字传递给它。您还可以看到,名为 sun 的计算机正在使用端口 22 (ssh),而 linux 具有端口 40379。这表示,sun 是该连接的接收者,因为它关联于该服务的已知端口。40379 是源或临时端口,并且仅对这个连接有意义。
因为,至少在 UNIX 中,套接字是另一类文件,所以 lsof 可以获得关于这些连接的详细信息,并找出谁对它们负责。
结束语
UNIX 大量使用了文件。作为系统管理员,lsof 允许您对核心内存进行查看,以找出系统当前如何使用这些文件。lsof 最简单的用法可以告诉您哪些进程打开了哪些文件,以及哪些文件由哪些进程打开。在收集关于应用程序工作情况的信息时,或在进行某些可能损坏数据的操作前确保文件未被使用时,这一点特别重要lsof 更高级的用法可以帮助您查找删除的文件,并获得关于网络连接的信息。这是一个功能强大的工具,它几乎可以用于任何地方。
///
lsof的功能很多,特別提醒大家, -c,-g,-p,-u,这四个参数最有用。更详细的资料请参看:man lsof。
1) lsof abc.txt 显示开启文件abc.txt的进程
2) lsof -i :22 知道22端口现在运行什么程序
3) lsof -c nsd 显示nsd进程现在打开的文件
4) lsof -g gid 显示归属gid的进程情况
5) lsof +d /usr/local/ 显示/usr/local目录下被进程开启的文件
6) lsof +D /usr/local/ 同上,但是会搜索目录下的目录,时间较长
7) lsof -d 4 显示使用fd为4的进程
8) lsof -i [i] 用以显示符合条件的进程情况
语法: lsof -i[46] [protocol][@hostname|hostaddr][:service|port]
46 --> IPv4 or IPv6
protocol --> TCP or UDP
hostname --> Internet host name
hostaddr --> IPv4位置
service --> /etc/service中的 service name (可以不只一个)
port --> 端口号 (可以不只一个)
例子: TCP:25 - TCP and port 25
@1.2.3.4 - Internet IPv4 host address 1.2.3.4
tcp@ohaha.ks.edu.tw:ftp - TCP protocol host:ohaha.ks.edu.tw service name:ftp
9) lsof -n 不将IP转换为hostname,缺省是不加上-n参数
例子: lsof -i tcp@ohaha.ks.edu.tw:ftp -n
10) lsof -p 12 看进程号为12的进程打开了哪些文件
11) lsof +|-r [t] 控制lsof不断重复执行,缺省是15s刷新
-r,lsof会永远不断的执行,直到收到中断信号
+r,lsof会一直执行,直到没有档案被显示
例子:不断查看目前ftp连接的情况:lsof -i tcp@ohaha.ks.edu.tw:ftp -r
12) lsof -s 列出打开文件的大小,如果没有大小,则留下空白
13) lsof -u username 以UID,列出打开的文件
以下给大家介绍一下我在工作中,经常使用的技巧:
1、查看文件系统阻塞
根据工作需要,系统管理员想卸载一个文件系统并执行umount /mountpoint,但程序报告常常显示:umount: /mountpoint: device is busy;这是因为该文件系统上有正在打开的文件而不允许你这么做。这时,我们需要知道哪些文件、程序及用户仍在使用该系统,以便通知用户退出该系统,可 以使用lsof识别正在打开一个特定文件系统的进程,执行如下命令:
/usr/sbin/lsof /mountpoint
在这里,mountpoint就是安装位置。例如:
# /usr/sbin /lsof /home
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
bash12134 meng cwdDIR8,5 4096 32705 /home/meng
telnet 12176 meng cwdDIR8,5 4096 32705 /home/meng
bash19809 meng cwdDIR8,5 4096 32705 /home/meng
bash20276 meng cwdDIR8,5 4096 32705 /home/meng
su 20315 root cwdDIR8,5 4096 32705 /home/meng
bash20316 root cwdDIR8,5 4096 32705 /home/meng
csh 20374 root cwdDIR8,5 4096 32705 /home/meng
lsof 20396 root cwdDIR8,5 4096 32705 /home/meng
lsof 20397 root cwdDIR8,5 4096 32705 /home/meng
显然,所有使用这些被打开的文件的进程都需要在文件系统能够被卸载前被终止。管理员以root身份,kill掉占用这个文件系统的进程,解除文件系统阻塞。
2、搜索打开的网络连接
如果想搜索IP地址为10.645.64.23的远程连接主机的所有网络连接,可以执行如下命令:
/usr/sbin/lsof –i@10.65.64.23可以打开系统中该远程知己所有打开的套接字。
# lsof -i@10.65.64.23
COMMAND PID USER FD TYPE DEVICE SIZE/OFF INODE NAME
telnetd 6605 root0u inet 0x14813f00 0t0 TCP xpp3:telnet->linuxone:33143 (ESTABLISHED)
telnetd 6605 root1u inet 0x14813f00 0t0 TCP xpp3:telnet->linuxone:33143 (ESTABLISHED)
telnetd 6605 root2u inet 0x14813f00 0t0 TCP xpp3:telnet->linuxone:33143 (ESTABLISHED)
3、寻找本地断开的打开文件
用户经常遇到这种情况,当一个进程正在向一个文件写数据时,该文件的目录可能被移动。这就产生了一个非常大的问题。例如,用户可能发现正在向/data写数据,但是却看不到文件增大,LSOF这个工具可以找到这样的错误,例如:
/usr/sbin/lsof +L1,通常可以看到下面的信息:
# lsof +L1
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINKNODE NAME
svrMgt_mi458 root 4r VREG 8,00 0 3418 / (/dev/rz0a)
yes 677 root 1w VREG 8,0 186523648 0 92888 / (/de v/rz0a)
# lsof +L1
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
svrMgt_mi458 root 4r VREG 8,0 0 0 3418 / (/dev/rz0a)
yes 677 root1w VREG 8,0 2735882240 92888 / (/dev/rz0a)
我们可以用kill -9 PID命令来结束PID显示的命令排除错误,释放空间。
我们还可以用-a选项来限制lsof报告单文件系统中的链接数量。例如,为了限制到/data部分的输出,可以输入:/usr/sbin/lsof –a +L1 /data
4、搜索被程序打开的所有文件及打开的文件相关联进程
如果想知道执行PID号为637的sendmail命令打开了哪些文件的话,可以执行lsof -p 637命令。输出的结果如下:
# lsof -p 637
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
sendmail 637 root cwd VDIR8,6 512 470400 /usr/var/spool/mqueue
sendmail 637 root txt VREG8,6 466944 9650 /usr (/dev/rz0g)
sendmail 637 root txt VREG8,0 139264 16016 /sbin/loader
sendmail 637 root txt VREG8,0 1663104 38402 /shlib/libc.so
sendmail 637 root0r VCHR2,2 0t0 9607 /dev/null
sendmail 637 root1w VCHR2,2 0t0 9607 /dev/null
sendmail 637 root2w VCHR2,2 0t0 9607 /dev/null
sendmail 637 root3u unix 0x0c2fc280 0t0->0x1ead2b40
sendmail 637 root4u inet 0x0c34c200 0t0TCP *:smtp (LISTEN)
上述输出信息显示了该程序当前打开的所有文件、设备、库及套接字等。
执行下面的命令可以发现哪些进程正在使用某个特定的文件,如下所示,可以看出,只有系统记录后台进程syslogd打开messages这个文件。
# lsof /var/adm/messages
COMMAND PID USER FD TYPE DEVICE SIZE/OFF INODE NAME
syslogd 147 root 16w VREG8,6 2653365 22501 /usr/var/adm/messages
5、其它使用命令(更详细的资料请man lsof,这部分参看了一些资料给大家总结一下)
若沒有加上任何的参数,lsof 会列出所有被程序打开的文件。
参数可以相互结合,ex: -a -b -c 等同于 -abc
-? -h 这两个参数意思相同,显示出 lsof 的使用说明。
-a 参数被视为 AND (注意:-a参数一但加上 ,会影响全部的参数。)
-c c 显示出以字母 c开头进程现在打开的文件
例:显示以init进程现在打开的文件
# lsof -c init
COMMAND PID USER FD TYPE DEVICE SIZE/OFF INODE NAME
init 1 root cwd VDIR 4095,365376 8192 2 /
init 1 root txt VREG 4095,365376 286720 463 /sbin/init
+d s 依照文件夹s来搜寻,此参数将不会继续深入搜寻此文件夹
例:显示在/usr/users/tongxl目录下被程序正在打开的文件(如下所示)
# lsof +d /usr/users/tongxl
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
ksh 26946 root cwd VDIR8,6 512 51281 /usr/users/tongxl/c
a.out 26953 root cwd VDIR8,6 512 51281 /usr/users/tongxl/c
+D D 同上,但是会搜索目录下的目录,时间较长。(注意︰lsof以此参数进行时,须花费较多的动态记忆体。尤其在处理较大的文件夹时,请务必审慎使用之。)
例:显示在/usr/local/文件夹下被程序正在打开的文件(如下)很明显可以看出二者的差别
# lsof +D /usr/users/tongxl
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
ksh 26946 root cwd VDIR8,6 512 51281 /usr/users/tongxl/c
a.out 26953 root cwd VDIR8,6 512 51281 /usr/users/tongxl/c
a.out 26953 root txt VREG8,624576 51311 /usr/users/tongxl/c/a.out
-d s 此参数以file descriptor (FD)值显示结果,可以采用范围表示,如 1-3 或 3-10 但 最前面的数一定要比最后面的数小。
举例:以FD为4显示
# lsof -d 4
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
syslogd 147 root4u inet 0x1fe0b980 0t0UDP *:syslog
binlogd 151 root4u inet 0x1fe0bd40 0t0UDP *:*
portmap 319 root4u inet 0x1fe0b740 0t0UDP *:111
mountd321 root4u VREG8,6 253 22516 /usr (/dev/rz0g)
nfsd 323 root4u inet 0x0c349e00 0t0TCP *:2049 (LISTEN)
rpc.statd 330 root4u inet 0x1ab42000 0t0TCP xpp3:1024 (LISTEN)
rpc.lockd 332 root4u inet 0x1fe0bbc0 0t0UDP xpp3:1028
snmpd 449 root4u unix 0x1aaf6500 0t0/var/esnmp/esnmpd
svrMgt_mi 457 root4r VREG8,00 3424 / (/dev/rz0a)
os_mibs 458 root4u inet 0x1ab475c0 0t0UDP *:*
cpq_mibs 460 root4u unix 0x1aaf77c0 0t0/var/esnmp/esnmp_sub460
advfsd472 root4u inet 0x0c320000 0t0TCP *:AdvFS (LISTEN)
insightd 475 root4r VDIR8,6 512 25610 /usr (/dev/rz0g)
inetd 506 root4u inet 0x1ab26700 0t0TCP *:ftp (LISTEN)
lpd 567 root4wW VREG8,64 451219 /usr (/dev/rz0g)
dtlogin 605 root4w VREG8,64 344028 /usr (/dev/rz0g)
Xdec 616 root4w VREG8,64 344028 /usr (/dev/rz0g)
sendmail 702 root4u inet 0x0c321900 0t0TCP *:smtp (LISTEN)
dtlogin 891 root4w VREG8,64 344028 /usr (/dev/rz0g)
dxconsole 907 root4w VREG8,64 344028 /usr (/dev/rz0g)
dtgreet 908 root4w VREG8,64 344028 /usr (/dev/rz0g)
-g [s] 以程序的PGID (process group IDentification)显示,也可以采用范围(1-3)或个别(3,5)表示,若没有特别指定,则显示全部。
举例:以PGID为3显示
# lsof -g 3
COMMAND PID PGID USER FD TYPE DEVICE SIZE/OFF NODE NAME
kloadsrv 33 root cwd VDIR8,0 2560 2 /
kloadsrv 33 root txt VREG8,0 221184 16041 /sbin/kloadsrv
kloadsrv 33 root0r VCHR0,0 0t0 9608 /dev/console
kloadsrv 33 root1w VCHR0,0 0t0 9608 /dev/console
kloadsrv 33 root2w VCHR0,0 0t0 9608 /dev/console
-i [i] 用以监听有关的任何符合的位址。若没有相关位置被指定,则监听全部。
语法: lsof -i[46] [protocol][@hostname|hostaddr][:service|port]
46 --> IPv4 or IPv6
protocol --> TCP or UDP
hostname --> Internet host name
hostaddr --> IPv4位置
service --> /etc/service中的 service name (可以不只一个)
port --> 埠号 (可以不只一个)
# lsof -i tcp@xp001
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
telnetd 26862 root0u inet 0x0c349000 0t0 TCP xpp3:telnet->xp001:3807 (ESTABLISHED)
telnetd 26862 root1u inet 0x0c349000 0t0 TCP xpp3:telnet->xp001:3807 (ESTABLISHED)
telnetd 26862 root2u inet 0x0c349000 0t0 TCP xpp3:telnet->xp001:3807 (ESTABLISHED)
telnetd 26986 root0u inet 0x1ab27100 0t0 TCP xpp3:telnet->xp001:3988 (ESTABLISHED)
telnetd 26986 root1u inet 0x1ab27100 0t0 TCP xpp3:telnet->xp001:3988 (ESTABLISHED)
telnetd 26986 root2u inet 0x1ab27100 0t0 TCP xpp3:telnet->xp001:3988 (ESTABLISHED)
-l此参数禁止将user ID转换为登入名称。(预设显示登入名称)
# lsof -l|more
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
kernel0 0 cwd VDIR8,02560 2 /
init 1 0 cwd VDIR8,02560 2 /
init 1 0 txt VREG8,0 286720 16015 / (/dev/rz0a)
kloadsrv 3 0 cwd VDIR8,02560 2 /
kloadsrv 3 0 txt VREG8,0 221184 16041 /sbin/kloadsrv
kloadsrv 3 0 0r VCHR0,0 0t0 9608 /dev/console
kloadsrv 3 0 1w VCHR0,0 0t0 9608 /dev/console
kloadsrv 3 0 2w VCHR0,0 0t0 9608 /dev/console
+|-L [l] +或-表示正在打开或取消显示文件连结数. 若只有单纯的+L,后面没有任何数字,则表示显示全部。若其后有加上数字,只有文件连结数少于该数字的会被列出。
-n不将IP位址转换成hostname,预设是不加上-n参数。
举例: lsof -i tcp@xp001 -n
(您可以和上两张图比较一下,原先的hostname便回ip位置了)
# lsof -i tcp@xp001 -n
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
telnetd 26862 root0u inet 0x0c349000 0t0 TCP 10.65.69.147:telnet->10.65.69.131:3807 (ESTABLISHED)
telnetd 26862 root1u inet 0x0c349000 0t0 TCP 10.65.69.147:telnet->10.65.69.131:3807 (ESTABLISHED)
telnetd 26862 root2u inet 0x0c349000 0t0 TCP 10.65.69.147:telnet->10.65.69.131:3807 (ESTABLISHED)
telnetd 26986 root0u inet 0x1ab27100 0t0 TCP 10.65.69.147:telnet->10.65.69.131:3988 (ESTABLISHED)
telnetd 26986 root1u inet 0x1ab27100 0t0 TCP 10.65.69.147:telnet->10.65.69.131:3988 (ESTABLISHED)
telnetd 26986 root2u inet 0x1ab27100 0t0 TCP 10.65.69.147:telnet->10.65.69.131:3988 (ESTABLISHED)
# lsof -i tcp@xp001
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
telnetd 26862 root0u inet 0x0c349000 0t0 TCP xpp3:telnet->xp001:3807 (ESTABLISHED)
telnetd 26862 root1u inet 0x0c349000 0t0 TCP xpp3:telnet->xp001:3807 (ESTABLISHED)
telnetd 26862 root2u inet 0x0c349000 0t0 TCP xpp3:telnet->xp001:3807 (ESTABLISHED)
telnetd 26986 root0u inet 0x1ab27100 0t0 TCP xpp3:telnet->xp001:3988 (ESTABLISHED)
telnetd 26986 root1u inet 0x1ab27100 0t0 TCP xpp3:telnet->xp001:3988 (ESTABLISHED)
telnetd 26986 root2u inet 0x1ab27100 0t0 TCP xpp3:telnet->xp001:3988 (ESTABLISHED)
-s列出文件的大小,若该文件没有大小,则留下空白。
# lsof -s
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
kernel0 root cwd VDIR8,0 2560 2 /
init 1 root cwd VDIR8,0 2560 2 /
init 1 root txt VREG8,0 286720 16015 / (/dev/rz0a)
kloadsrv 3 root cwd VDIR8,0 2560 2 /
kloadsrv 3 root txt VREG8,0 221184 16041 /sbin/kloadsrv
kloadsrv 3 root0r VCHR0,09608 /dev/console
kloadsrv 3 root1w VCHR0,09608 /dev/console
kloadsrv 3 root2w VCHR0,09608 /dev/console
-u s 以login name(登入名称)或UID,列出所正在打开文件。
# lsof -u tongxl
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
csh 26939 tongxl cwd VDIR8,6 1024 243236 /usr -- tongxl
csh 26939 tongxl txt VREG8,6 253952 12856 /usr (/dev/rz0g)
csh 26939 tongxl txt VREG8,0 139264 16016 /sbin/loader
csh 26939 tongxl txt VREG8,0 1663104 38402 /shlib/libc.so
csh 26939 tongxl0r VCHR1,0 0t0 9612 /dev/tty
csh 26939 tongxl 15u VCHR6,20t328 9618 /dev/pts/2
csh 26939 tongxl 16u VCHR6,20t328 9618 /dev/pts/2
csh 26939 tongxl 17u VCHR6,20t328 9618 /dev/pts/2
csh 26939 tongxl 18u VCHR6,20t328 9618 /dev/pts/2
csh 26939 tongxl 19u VCHR6,20t328 9618 /dev/pts/2
csh 26990 tongxl cwd VDIR8,6 1024 243236 /usr -- tongxl
csh 26990 tongxl txt VREG8,6 253952 12856 /usr (/dev/rz0g)
csh 26990 tongxl txt VREG8,0 139264 16016 /sbin/loader
csh 26990 tongxl txt VREG8,0 1663104 38402 /shlib/libc.so
csh 26990 tongxl0r VCHR1,0 0t0 9612 /dev/tty
csh 26990 tongxl 15u VCHR6,1 0t147797 9616 /dev/pts/1
csh 26990 tongxl 16u VCHR6,1 0t147797 9616 /dev/pts/1
csh 26990 tongxl 17u VCHR6,1 0t147797 9616 /dev/pts/1
csh 26990 tongxl 18u VCHR6,1 0t147797 9616 /dev/pts/1
csh 26990 tongxl 19u VCHR6,1 0t147797 9616 /dev/pts/
strace命令用法
ldd命令行用法如下:
ldd [--version] [-v|--verbose] [-d|--data-relocs] [-r|--function-relocs] [--help] FILE...
各选项说明如下:
(1) --version : 此选项用于打印出ldd的版本号.
(2) -v 或 --verbose : 此选项指示ldd输出关于所依赖的动态链接库的尽可能详细的信息.
(3) -d 或 --data-relocs : 此选项执行重定位,并且显示不存在的函数.
(4) -r 或 --function-relocs : 此选项执行数据对象与函数的重定位,同时报告不存在的对象.
(5) --help : 此选项用于打印出ldd的帮助信息.
注: 上述选项中,常用-v(或--verbose)选项.
例2:
$ ldd -v ady
libmy.so => ./libmy.so (0x40026000)
libc.so.6 => /lib/libc.so.6 (0x40028000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
Version information:
./ady:
libc.so.6 (GLIBC_2.1.3) => /lib/libc.so.6
libc.so.6 (GLIBC_2.0) => /lib/libc.so.6
./libmy.so:
libc.so.6 (GLIBC_2.1.3) => /lib/libc.so.6
libc.so.6 (GLIBC_2.0) => /lib/libc.so.6
/lib/libc.so.6:
ld-linux.so.2 (GLIBC_2.1.1) => /lib/ld-linux.so.2
ld-linux.so.2 (GLIBC_2.2.3) => /lib/ld-linux.so.2
ld-linux.so.2 (GLIBC_2.1) => /lib/ld-linux.so.2
ld-linux.so.2 (GLIBC_2.2) => /lib/ld-linux.so.2
ld-linux.so.2 (GLIBC_2.0) => /lib/ld-linux.so.2
$
注:本例用-v选项以显示尽可能多的信息,所以例中除列出ady所需要的动态链接库外,还列出了程序所需动态链接库版本方面的信息.
strace命令用法
调用:
strace [ -dffhiqrtttTvxx ] [ -acolumn ] [ -eexpr ] ...
[ -ofile ] [ -ppid ] ... [ -sstrsize ] [ -uusername ] [ command [ arg ... ] ]
strace -c [ -eexpr ] ... [ -Ooverhead ] [ -Ssortby ] [ command [ arg ... ] ]
功能:
跟踪程序执行时的系统调用和所接收的信号.通常的用法是strace执行一直到command结束.
并且将所调用的系统调用的名称、参数和返回值输出到标准输出或者输出到-o指定的文件.
strace是一个功能强大的调试,分析诊断工具.你将发现他是一个极好的帮手在你要调试一个无法看到源码或者源码无法在编译的程序.
你将轻松的学习到一个软件是如何通过系统调用来实现他的功能的.而且作为一个程序设计师,你可以了解到在用户态和内核态是如何通过系统调用和信号来实现程序的功能的.
strace的每一行输出包括系统调用名称,然后是参数和返回值.这个例子:
strace cat /dev/null
他的输出会有:
open(/"/dev/null/",O_RDONLY) = 3
有错误产生时,一般会返回-1.所以会有错误标志和描述:
open(/"/foor/bar/",)_RDONLY) = -1 ENOENT (no such file or directory)
信号将输出喂信号标志和信号的描述.跟踪并中断这个命令/"sleep 600/":
sigsuspend({}
--- SIGINT (Interrupt) ---
+++ killed by SIGINT +++
参数的输出有些不一致.如shell命令中的 /">>tmp/",将输出:
open(/"tmp/",O_WRONLY|O_APPEND|A_CREAT,0666) = 3
对于结构指针,将进行适当的显示.如:/"ls -l /dev/null/":
lstat(/"/dev/null/",{st_mode=S_IFCHR|0666},st_rdev=makdev[1,3],...}) = 0
请注意/"struct stat/" 的声明和这里的输出.lstat的第一个参数是输入参数,而第二个参数是向外传值.
当你尝试/"ls -l/" 一个不存在的文件时,会有:
lstat(/foot/ball/",0xb004) = -1 ENOENT (no such file or directory)
char*将作为C的字符串类型输出.没有字符串输出时一般是char* 是一个转义字符,只输出字符串的长度.
当字符串过长是会使用/".../"省略.如在/"ls -l/"会有一个gepwuid调用读取password文件:
read(3,/"root::0:0:System Administrator://"...,1024) = 422
当参数是结构数组时,将按照简单的指针和数组输出如:
getgroups(4,[0,2,4,5]) = 4
关于bit作为参数的情形,也是使用方括号,并且用空格将每一项参数隔开.如:
sigprocmask(SIG_BLOCK,[CHLD TTOU],[]) = 0
这里第二个参数代表两个信号SIGCHLD 和 SIGTTOU.如果bit型参数全部置位,则有如下的输出:
sigprocmask(SIG_UNBLOCK,~[],NULL) = 0
这里第二个参数全部置位.
参数说明:
-c 统计每一系统调用的所执行的时间,次数和出错的次数等.
-d 输出strace关于标准错误的调试信息.
-f 跟踪由fork调用所产生的子进程.
-ff 如果提供-o filename,则所有进程的跟踪结果输出到相应的filename.pid中,pid是各进程的进程号.
-F 尝试跟踪vfork调用.在-f时,vfork不被跟踪.
-h 输出简要的帮助信息.
-i 输出系统调用的入口指针.
-q 禁止输出关于脱离的消息.
-r 打印出相对时间关于,,每一个系统调用.
-t 在输出中的每一行前加上时间信息.
-tt 在输出中的每一行前加上时间信息,微秒级.
-ttt 微秒级输出,以秒了表示时间.
-T 显示每一调用所耗的时间.
-v 输出所有的系统调用.一些调用关于环境变量,状态,输入输出等调用由于使用频繁,默认不输出.
-V 输出strace的版本信息.
-x 以十六进制形式输出非标准字符串
-xx 所有字符串以十六进制形式输出.
-a column
设置返回值的输出位置.默认为40.
-e expr
指定一个表达式,用来控制如何跟踪.格式如下:
[qualifier=][!]value1[,value2]...
qualifier只能是 trace,abbrev,verbose,raw,signal,read,write其中之一.value是用来限定的符号或数字.默认的qualifier是 trace.感叹号是否定符号.例如:
-eopen等价于 -e trace=open,表示只跟踪open调用.而-etrace!=open表示跟踪除了open以外的其他调用.有两个特殊的符号 all 和 none.
注意有些shell使用!来执行历史记录里的命令,所以要使用//.
-e trace=set
只跟踪指定的系统调用.例如:-e trace=open,close,rean,write表示只跟踪这四个系统调用.默认的为set=all.
-e trace=file
只跟踪有关文件操作的系统调用.
-e trace=process
只跟踪有关进程控制的系统调用.
-e trace=network
跟踪与网络有关的所有系统调用.
-e strace=signal
跟踪所有与系统信号有关的系统调用
-e trace=ipc
跟踪所有与进程通讯有关的系统调用
-e abbrev=set
设定strace输出的系统调用的结果集.-v 等与 abbrev=none.默认为abbrev=all.
-e raw=set
将指定的系统调用的参数以十六进制显示.
-e signal=set
指定跟踪的系统信号.默认为all.如signal=!SIGIO(或者signal=!io),表示不跟踪SIGIO信号.
-e read=set
输出从指定文件中读出的数据.例如:
-e read=3,5
-e write=set
输出写入到指定文件中的数据.
-o filename
将strace的输出写入文件filename
-p pid
跟踪指定的进程pid.
-s strsize
指定输出的字符串的最大长度.默认为32.文件名一直全部输出.
-u username
以username的UID和GID执行被跟踪的命令.
用strace调试程序
在理想世界里,每当一个程序不能正常执行一个功能时,它就会给出一个有用的错误提示,告诉你在足够的改正错误的线索。但遗憾的是,我们不是生活在理想世界里,起码不总是生活在理想世界里。有时候一个程序出现了问题,你无法找到原因。
这就是调试程序出现的原因。strace是一个必不可少的调试工具,strace用来监视系统调用。你不仅可以调试一个新开始的程序,也可以调试一个已经在运行的程序(把strace绑定到一个已有的PID上面)。
首先让我们看一个真实的例子:
启动KDE时出现问题
前一段时间,我在启动KDE的时候出了问题,KDE的错误信息无法给我任何有帮助的线索。
代码:
_KDE_IceTransSocketCreateListener: failed to bind listener
_KDE_IceTransSocketUNIXCreateListener: ...SocketCreateListener() failed
_KDE_IceTransMakeAllCOTSServerListeners: failed to create listener for local
Cannot establish any listening sockets DCOPServer self-test failed.
对我来说这个错误信息没有太多意义,只是一个对KDE来说至关重要的负责进程间通信的程序无法启动。我还可以知道这个错误和ICE协议(Inter Client Exchange)有关,除此之外,我不知道什么是KDE启动出错的原因。
我决定采用strace看一下在启动dcopserver时到底程序做了什么:
代码:
strace -f -F -o ~/dcop-strace.txt dcopserver
这里 -f -F选项告诉strace同时跟踪fork和vfork出来的进程,-o选项把所有strace输出写到~/dcop-strace.txt里面,dcopserver是要启动和调试的程序。
再次出现错误之后,我检查了错误输出文件dcop-strace.txt,文件里有很多系统调用的记录。在程序运行出错前的有关记录如下:
代码:
27207 mkdir("/tmp/.ICE-unix", 0777) = -1 EEXIST (File exists)
27207 lstat64("/tmp/.ICE-unix", {st_mode=S_IFDIR|S_ISVTX|0755, st_size=4096, ...}) = 0
27207 unlink("/tmp/.ICE-unix/dcop27207-1066844596") = -1 ENOENT (No such file or directory)
27207 bind(3, {sin_family=AF_UNIX, path="/tmp/.ICE-unix/dcop27207-1066844596"}, 38) = -1 EACCES (Permission denied)
27207 write(2, "_KDE_IceTrans", 13) = 13
27207 write(2, "SocketCreateListener: failed to "..., 46) = 46
27207 close(3) = 0 27207 write(2, "_KDE_IceTrans", 13) = 13
27207 write(2, "SocketUNIXCreateListener: ...Soc"..., 59) = 59
27207 umask(0) = 0 27207 write(2, "_KDE_IceTrans", 13) = 13
27207 write(2, "MakeAllCOTSServerListeners: fail"..., 64) = 64
27207 write(2, "Cannot establish any listening s"..., 39) = 39
其中第一行显示程序试图创建/tmp/.ICE-unix目录,权限为0777,这个操作因为目录已经存在而失败了。第二个系统调用(lstat64)检查了目录状态,并显示这个目录的权限是0755,这里出现了第一个程序运行错误的线索:程序试图创建属性为0777的目录,但是已经存在了一个属性为 0755的目录。第三个系统调用(unlink)试图删除一个文件,但是这个文件并不存在。这并不奇怪,因为这个操作只是试图删掉可能存在的老文件。
但是,第四行确认了错误所在。他试图绑定到/tmp/.ICE-unix/dcop27207-1066844596,但是出现了拒绝访问错误。. ICE_unix目录的用户和组都是root,并且只有所有者具有写权限。一个非root用户无法在这个目录下面建立文件,如果把目录属性改成0777,则前面的操作有可能可以执行,而这正是第一步错误出现时进行过的操作。
所以我运行了chmod 0777 /tmp/.ICE-unix之后KDE就可以正常启动了,问题解决了,用strace进行跟踪调试只需要花很短的几分钟时间跟踪程序运行,然后检查并分析输出文件。
说明:运行chmod 0777只是一个测试,一般不要把一个目录设置成所有用户可读写,同时不设置粘滞位(sticky bit)。给目录设置粘滞位可以阻止一个用户随意删除可写目录下面其他人的文件。一般你会发现/tmp目录因为这个原因设置了粘滞位。KDE可以正常启动之后,运行chmod +t /tmp/.ICE-unix给.ICE_unix设置粘滞位。
解决库依赖问题
starce 的另一个用处是解决和动态库相关的问题。当对一个可执行文件运行ldd时,它会告诉你程序使用的动态库和找到动态库的位置。但是如果你正在使用一个比较老的glibc版本(2.2或更早),你可能会有一个有bug的ldd程序,它可能会报告在一个目录下发现一个动态库,但是真正运行程序时动态连接程序(/lib/ld-linux.so.2)却可能到另外一个目录去找动态连接库。这通常因为/etc/ld.so.conf和 /etc/ld.so.cache文件不一致,或者/etc/ld.so.cache被破坏。在glibc 2.3.2版本上这个错误不会出现,可能ld-linux的这个bug已经被解决了。
尽管这样,ldd并不能把所有程序依赖的动态库列出来,系统调用dlopen可以在需要的时候自动调入需要的动态库,而这些库可能不会被ldd列出来。作为glibc的一部分的NSS(Name Server Switch)库就是一个典型的例子,NSS的一个作用就是告诉应用程序到哪里去寻找系统帐号数据库。应用程序不会直接连接到NSS库,glibc则会通过dlopen自动调入NSS库。如果这样的库偶然丢失,你不会被告知存在库依赖问题,但这样的程序就无法通过用户名解析得到用户ID了。让我们看一个例子:
whoami程序会给出你自己的用户名,这个程序在一些需要知道运行程序的真正用户的脚本程序里面非常有用,whoami的一个示例输出如下:
代码:
# whoami
root
假设因为某种原因在升级glibc的过程中负责用户名和用户ID转换的库NSS丢失,我们可以通过把nss库改名来模拟这个环境:
代码:
# mv /lib/libnss_files.so.2 /lib/libnss_files.so.2.backup
# whoami
whoami: cannot find username for UID 0
这里你可以看到,运行whoami时出现了错误,ldd程序的输出不会提供有用的帮助:
代码:
# ldd /usr/bin/whoami
libc.so.6 => /lib/libc.so.6 (0x4001f000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
你只会看到whoami依赖Libc.so.6和ld-linux.so.2,它没有给出运行whoami所必须的其他库。这里时用strace跟踪whoami时的输出:
代码:
strace -o whoami-strace.txt whoami
open("/lib/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/lib/i686/mmx/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/i686/mmx", 0xbffff190) = -1 ENOENT (No such file or directory)
open("/lib/i686/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/i686", 0xbffff190) = -1 ENOENT (No such file or directory)
open("/lib/mmx/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/mmx", 0xbffff190) = -1 ENOENT (No such file or directory)
open("/lib/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib", {st_mode=S_IFDIR|0755, st_size=2352, ...}) = 0
open("/usr/lib/i686/mmx/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/i686/mmx", 0xbffff190) = -1 ENOENT (No such file or directory)
open("/usr/lib/i686/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
你可以发现在不同目录下面查找libnss.so.2的尝试,但是都失败了。如果没有strace这样的工具,很难发现这个错误是由于缺少动态库造成的。现在只需要找到libnss.so.2并把它放回到正确的位置就可以了。
[BOLD]限制strace只跟踪特定的系统调用[/BOLD]
如果你已经知道你要找什么,你可以让strace只跟踪一些类型的系统调用。例如,你需要看看在configure脚本里面执行的程序,你需要监视的系统调用就是execve。让strace只记录execve的调用用这个命令:
代码:
strace -f -o configure-strace.txt -e execve ./configure
部分输出结果为:
代码:
2720 execve("/usr/bin/expr", ["expr", "a", ":", "(a)"], [/* 31 vars */]) = 0
2725 execve("/bin/basename", ["basename", "./configure"], [/* 31 vars */]) = 0
2726 execve("/bin/chmod", ["chmod", "+x", "conftest.sh"], [/* 31 vars */]) = 0
2729 execve("/bin/rm", ["rm", "-f", "conftest.sh"], [/* 31 vars */]) = 0
2731 execve("/usr/bin/expr", ["expr", "99", "+", "1"], [/* 31 vars */]) = 0
2736 execve("/bin/ln", ["ln", "-s", "conf2693.file", "conf2693"], [/* 31 vars */]) = 0
你已经看到了,strace不仅可以被程序员使用,普通系统管理员和用户也可以使用strace来调试系统错误。必须承认,strace的输出不总是容易理解,但是很多输出对大多数人来说是不重要的。你会慢慢学会从大量输出中找到你可能需要的信息,像权限错误,文件未找到之类的,那时strace就会成为一个有力的工具了。
linux高级工具命令(四)valgrind做内存检查
C/C++开发其中最令人头疼的一个问题就是内存管理,有时候为了查找一个内存泄漏或者一个内存访问越界,需要要花上好几天时间,如果有一款工具能够帮助我们做这件事情就好了,valgrind正好就是这样的一款工具。
Valgrind是一款基于模拟linux下的程序调试器和剖析器的软件套件,可以运行于x86, amd64和ppc32架构上。valgrind包含一个核心,它提供一个虚拟的CPU运行程序,还有一系列的工具,它们完成调试,剖析和一些类似的任务。valgrind是高度模块化的,所以开发人员或者用户可以给它添加新的工具而不会损坏己有的结构。
valgrind的官方网址是:http://valgrind.org
你可以在它的网站上下载到最新的valgrind,它是开放源码和免费的。
一、介绍
valgrind包含几个标准的工具,它们是:
1、memcheck
memcheck探测程序中内存管理存在的问题。它检查所有对内存的读/写操作,并截取所有的malloc/new/free/delete调用。因此memcheck工具能够探测到以下问题:
1)使用未初始化的内存
2)读/写已经被释放的内存
3)读/写内存越界
4)读/写不恰当的内存栈空间
5)内存泄漏
6)使用malloc/new/new[]和free/delete/delete[]不匹配。
2、cachegrind
cachegrind是一个cache剖析器。它模拟执行CPU中的L1, D1和L2 cache,因此它能很精确的指出代码中的cache未命中。如果你需要,它可以打印出cache未命中的次数,内存引用和发生cache未命中的每一行代码,每一个函数,每一个模块和整个程序的摘要。如果你要求更细致的信息,它可以打印出每一行机器码的未命中次数。在x86和amd64上,cachegrind通过CPUID自动探测机器的cache配置,所以在多数情况下它不再需要更多的配置信息了。
3、helgrind
helgrind查找多线程程序中的竞争数据。helgrind查找内存地址,那些被多于一条线程访问的内存地址,但是没有使用一致的锁就会被查出。这表示这些地址在多线程间访问的时候没有进行同步,很可能会引起很难查找的时序问题。
二、valgrind对你的程序都做了些什么
valgrind被设计成非侵入式的,它直接工作于可执行文件上,因此在检查前不需要重新编译、连接和修改你的程序。要检查一个程序很简单,只需要执行下面的命令就可以了
valgrind --tool=tool_name program_name
比如我们要对ls -l命令做内存检查,只需要执行下面的命令就可以了
valgrind --tool=memcheck ls -l
不管是使用哪个工具,valgrind在开始之前总会先取得对你的程序的控制权,从可执行关联库里读取调试信息。然后在valgrind核心提供的虚拟CPU上运行程序,valgrind会根据选择的工具来处理代码,该工具会向代码中加入检测代码,并把这些代码作为最终代码返回给valgrind核心,最后valgrind核心运行这些代码。
如果要检查内存泄漏,只需要增加--leak-check=yes就可以了,命令如下
valgrind --tool=memcheck --leak-check=yes ls -l
不同工具间加入的代码变化非常的大。在每个作用域的末尾,memcheck加入代码检查每一片内存的访问和进行值计算,代码大小至少增加12倍,运行速度要比平时慢25到50倍。
valgrind模拟程序中的每一条指令执行,因此,检查工具和剖析工具不仅仅是对你的应用程序,还有对共享库,GNU C库,X的客户端库都起作用。
三、现在开始
首先,在编译程序的时候打开调试模式(gcc编译器的-g选项)。如果没有调试信息,即使最好的valgrind工具也将中能够猜测特定的代码是属于哪一个函数。打开调试选项进行编译后再用valgrind检查,valgrind将会给你的个详细的报告,比如哪一行代码出现了内存泄漏。
当检查的是C++程序的时候,还应该考虑另一个选项 -fno-inline。它使得函数调用链很清晰,这样可以减少你在浏览大型C++程序时的混乱。比如在使用这个选项的时候,用memcheck检查openoffice就很容易。当然,你可能不会做这项工作,但是使用这一选项使得valgrind生成更精确的错误报告和减少混乱。
一些编译优化选项(比如-O2或者更高的优化选项),可能会使得memcheck提交错误的未初始化报告,因此,为了使得valgrind的报告更精确,在编译的时候最好不要使用优化选项。
如果程序是通过脚本启动的,可以修改脚本里启动程序的代码,或者使用--trace-children=yes选项来运行脚本。
下面是用memcheck检查ls -l命令的输出报告,在终端下执行下面的命令
valgrind --tool=memcheck ls -l
程序会打印出ls -l命令的结果,最后是valgrind的检查报告如下:
==4187==
==4187== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 19 from 2)
==4187== malloc/free: in use at exit: 15,154 bytes in 105 blocks.
==4187== malloc/free: 310 allocs, 205 frees, 60,093 bytes allocated.
==4187== For counts of detected errors, rerun with: -v
==4187== searching for pointers to 105 not-freed blocks.
==4187== checked 145,292 bytes.
==4187==
==4187== LEAK SUMMARY:
==4187== definitely lost: 0 bytes in 0 blocks.
==4187== possibly lost: 0 bytes in 0 blocks.
==4187== still reachable: 15,154 bytes in 105 blocks.
==4187== suppressed: 0 bytes in 0 blocks.
==4187== Reachable blocks (those to which a pointer was found) are not shown.
==4187== To see them, rerun with: --show-reachable=yes
这里的“4187”指的是执行ls -l的进程ID,这有利于区别不同进程的报告。memcheck会给出报告,分配置和释放了多少内存,有多少内存泄漏了,还有多少内存的访问是可达的,检查了多少字节的内存。
下面举两个用valgrind做内存检查的例子
例子一 (test.c):
#include <string.h>
int main(int argc, char *argv[])
{
char *ptr;
ptr = (char*) malloc(10);
strcpy(ptr, "01234567890");
return 0;
}
编译程序
gcc -g -o test test.c
用valgrind执行命令
valgrind --tool=memcheck --leak-check=yes ./test
报告如下
==4270== Memcheck, a memory error detector.
==4270== Copyright (C) 2002-2006, and GNU GPL'd, by Julian Seward et al.
==4270== Using LibVEX rev 1606, a library for dynamic binary translation.
==4270== Copyright (C) 2004-2006, and GNU GPL'd, by OpenWorks LLP.
==4270== Using valgrind-3.2.0, a dynamic binary instrumentation framework.
==4270== Copyright (C) 2000-2006, and GNU GPL'd, by Julian Seward et al.
==4270== For more details, rerun with: -v
==4270==
==4270== Invalid write of size 1
==4270== at 0x4006190: strcpy (mc_replace_strmem.c:271)
==4270== by 0x80483DB: main (test.c:8)
==4270== Address 0x4023032 is 0 bytes after a block of size 10 alloc'd
==4270== at 0x40044F6: malloc (vg_replace_malloc.c:149)
==4270== by 0x80483C5: main (test.c:7)
==4270==
==4270== Invalid write of size 1
==4270== at 0x400619C: strcpy (mc_replace_strmem.c:271)
==4270== by 0x80483DB: main (test.c:8)
==4270== Address 0x4023033 is 1 bytes after a block of size 10 alloc'd
==4270== at 0x40044F6: malloc (vg_replace_malloc.c:149)
==4270== by 0x80483C5: main (test.c:7)
==4270==
==4270== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 12 from 1)
==4270== malloc/free: in use at exit: 10 bytes in 1 blocks.
==4270== malloc/free: 1 allocs, 0 frees, 10 bytes allocated.
==4270== For counts of detected errors, rerun with: -v
==4270== searching for pointers to 1 not-freed blocks.
==4270== checked 51,496 bytes.
==4270==
==4270==
==4270== 10 bytes in 1 blocks are definitely lost in loss record 1 of 1
==4270== at 0x40044F6: malloc (vg_replace_malloc.c:149)
==4270== by 0x80483C5: main (test.c:7)
==4270==
==4270== LEAK SUMMARY:
==4270== definitely lost: 10 bytes in 1 blocks.
==4270== possibly lost: 0 bytes in 0 blocks.
==4270== still reachable: 0 bytes in 0 blocks.
==4270== suppressed: 0 bytes in 0 blocks.
==4270== Reachable blocks (those to which a pointer was found) are not shown.
==4270== To see them, rerun with: --show-reachable=yes
从这份报告可以看出,进程号是4270,test.c的第8行写内存越界了,引起写内存越界的是strcpy函数,
第7行泄漏了10个字节的内存,引起内存泄漏的是malloc函数。
例子二(test2.c)
#include <stdio.h>
int foo(int x)
{
if (x < 0) {
printf("%d ", x);
}
return 0;
}
int main(int argc, char *argv[])
{
int x;
foo(x);
return 0;
}
编译程序
gcc -g -o test2 test2.c
用valgrind做内存检查
valgrind --tool=memcheck ./test2
输出报告如下
==4285== Memcheck, a memory error detector.
==4285== Copyright (C) 2002-2006, and GNU GPL'd, by Julian Seward et al.
==4285== Using LibVEX rev 1606, a library for dynamic binary translation.
==4285== Copyright (C) 2004-2006, and GNU GPL'd, by OpenWorks LLP.
==4285== Using valgrind-3.2.0, a dynamic binary instrumentation framework.
==4285== Copyright (C) 2000-2006, and GNU GPL'd, by Julian Seward et al.
==4285== For more details, rerun with: -v
==4285==
==4285== Conditional jump or move depends on uninitialised value(s)
==4285== at 0x8048372: foo (test2.c:5)
==4285== by 0x80483B4: main (test2.c:16)
==4285==p p
==4285== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 12 from 1)
==4285== malloc/free: in use at exit: 0 bytes in 0 blocks.
==4285== malloc/free: 0 allocs, 0 frees, 0 bytes allocated.
==4285== For counts of detected errors, rerun with: -v
==4285== All heap blocks were freed -- no leaks are possible.
从这份报告可以看出进程PID是4285,test2.c文件的第16行调用了foo函数,在test2.c文件的第5行foo函数使用了一个未初始化的变量。
valgrind还有很多使用选项,具体可以查看valgrind的man手册页和valgrind官方网站的在线文档。
linux高级工具命令(五)vmstat介绍
通过STATSPACK收集服务器信息,主要通过收集VMSTAT的信息来展现服务器状况。VMSTAT工具是最常见的UNIX监控工具,可以展现给定时间间隔的服务器的状态值。
一般VMSTAT工具的使用是通过两个数字参数来完成的,第一个参数是采样的时间间隔数,单位是秒,第二个参数是采样的次数。如:
[oracle@localhost oracle]$vmstat 2
procs -----------memory---------- ---swap-- -----io---- --system-- ----cpu----
r b swpd free buff cache si so bi bo in cs us sy id wa
0 0 195804 3812 40616 1598656 0 0 0 0 1024 66976 0 0 100 0
0 0 195804 3812 40616 1598656 0 0 0 12 1048 66961 0 0 100 0
0 0 195804 3812 40616 1598656 0 0 0 28 1054 67067 0 0 100 0
0 0 195804 3812 40616 1598656 0 0 0 0 1020 66988 0 0 100 0
(注:目前系统几乎空闲,并且不同操作系统VMSTAT输出内容有所不同)
目前说来,对于服务器监控有用处的度量主要有:
r(运行队列)
pi(页导入)
us(用户CPU)
sy(系统CPU)
id(空闲)
(如果r经常大于4 ,且id经常少于40,表示cpu的负荷很重。)
(如果bi,bo 长期不等于0,表示内存不足)
通过VMSTAT识别CPU瓶颈
r(运行队列)展示了正在执行和等待CPU资源的任务个数。当这个值超过了CPU数目,就会出现CPU瓶颈了
获得CPU个数的命令(LINUX环境):
cat /proc/cpuinfo|grep processor|wc -l
当r值超过了CPU个数,就会出现CPU瓶颈,解决办法大体几种:
1. 最简单的就是增加CPU个数
2. 通过调整任务执行时间,如大任务放到系统不繁忙的情况下进行执行,进而平衡系统任务
3. 调整已有任务的优先级
通过VMSTAT识别CPU满负荷
首先需要声明一点的是,vmstat中CPU的度量是百分比的。当us+sy的值接近100的时候,表示CPU正在接近满负荷工作。但要注意的是,CPU 满负荷工作并不能说明什么,UNIX总是试图要CPU尽可能的繁忙,使得任务的吞吐量最大化。唯一能够确定CPU瓶颈的还是r(运行队列)的值。
通过VMSTAT识别RAM瓶颈
数据库服务器都只有有限的RAM,出现内存争用现象是Oracle的常见问题。
首先察看RAM的数量,命令如下(LINUX环境):
[oracle@oracle-db02 ~]$ free
total used free shared buffers cached
Mem: 2074924 2071112 3812 0 40616 1598656
-/+ buffers/cache: 431840 1643084
Swap: 3068404 195804 2872600
当然可以使用top等其他命令来显示RAM。
当内存的需求大于RAM的数量,服务器启动了虚拟内存机制,通过虚拟内存,可以将RAM段移到SWAP DISK的特殊磁盘段上,这样会 出现虚拟内存的页导出和页导入现象,页导出并不能说明RAM瓶颈,虚拟内存系统经常会对内存段进行页导出,但页导入操作就表明了服务器需要更多的内存了, 页导入需要从SWAP DISK上将内存段复制回RAM,导致服务器速度变慢。
解决的办法有几种:
1. 最简单的,加大RAM
2. 改小SGA,使得对RAM需求减少
3. 减少RAM的需求(如:减少PGA)
linux高级工具命令(六)lsof查看文件
通过查看打开的文件,了解更多关于系统的信息。了解应用程序打开了哪些文件或者哪个应用程序打开了特定的文件,作为系统管理员,这将使得您能够作出更好的决策。例如,您不应该卸载具有打开文件的文件系统。使用 lsof,您可以检查打开的文件,并根据需要在卸载之前中止相应的进程。同样地,如果您发现了一个未知的文件,那么可以找出到底是哪个应用程序打开了这个文件。
在 UNIX? 环境中,文件无处不在,这便产生了一句格言:“任何事物都是文件”。通过文件不仅仅可以访问常规数据,通常还可以访问网络连接和硬件。在有些情况下,当您使用 ls 请求目录清单时,将出现相应的条目。在其他情况下,如传输控制协议 (TCP) 和用户数据报协议 (UDP) 套接字,不存在相应的目录清单。但是在后台为该应用程序分配了一个文件描述符,无论这个文件的本质如何,该文件描述符为应用程序与基础操作系统之间的交互提供了通用接口。
因为应用程序打开文件的描述符列表提供了大量关于这个应用程序本身的信息,所以能够查看这个列表将是很有帮助的。完成这项任务的实用程序称为 lsof,它对应于“list open files”(列出打开的文件)。几乎在每个 UNIX 版本中都有这个实用程序,但奇怪的是,大多数供应商并没有将其包含在操作系统的初始安装中。要获取更多关于 lsof 的信息,请参见参考资料部分。
只需输入 lsof 就可以生成大量的信息,如清单 1 所示。因为 lsof 需要访问核心内存和各种文件,所以必须以 root 用户的身份运行它才能够充分地发挥其功能。
bash-3.00# lsof COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME sched 0 root cwd VDIR 136,8 1024 2 / init 1 root cwd VDIR 136,8 1024 2 / init 1 root txt VREG 136,8 49016 1655 /sbin/init init 1 root txt VREG 136,8 51084 3185 /lib/libuutil.so.1 vi 2013 root 3u VREG 136,8 0 8501 /var/tmp/ExXDaO7d ...
每行显示一个打开的文件,除非另外指定,否则将显示所有进程打开的所有文件。Command、PID 和 User 列分别表示进程的名称、进程标识符 (PID) 和所有者名称。Device、SIZE/OFF、Node 和 Name 列涉及到文件本身的信息,分别表示指定磁盘的名称、文件的大小、索引节点(文件在磁盘上的标识)和该文件的确切名称。根据 UNIX 版本的不同,可能将文件的大小报告为应用程序在文件中进行读取的当前位置(偏移量)。清单 1 来自一台可以报告该信息的 Sun Solaris 10 计算机,而 Linux? 没有这个功能。
FD 和 Type 列的含义最为模糊,它们提供了关于文件如何使用的更多信息。FD 列表示文件描述符,应用程序通过文件描述符识别该文件。Type 列提供了关于文件格式的更多描述。我们来具体研究一下文件描述符列,清单 1 中出现了三种不同的值。cwd 值表示应用程序的当前工作目录,这是该应用程序启动的目录,除非它本身对这个目录进行更改。txt 类型的文件是程序代码,如应用程序二进制文件本身或共享库,再比如本示例的列表中显示的 init 程序。最后,数值表示应用程序的文件描述符,这是打开该文件时返回的一个整数。在清单 1 输出的最后一行中,您可以看到用户正在使用 vi 编辑 /var/tmp/ExXDaO7d,其文件描述符为 3。u 表示该文件被打开并处于读取/写入模式,而不是只读 (r) 或只写 (w) 模式。有一点不是很重要但却很有帮助,初始打开每个应用程序时,都具有三个文件描述符,从 0 到 2,分别表示标准输入、输出和错误流。正因为如此,大多数应用程序所打开的文件的 FD 都是从 3 开始。
与 FD 列相比,Type 列则比较直观。根据具体操作系统的不同,您会发现将文件和目录称为 REG 和 DIR(在 Solaris 中,称为 VREG 和 VDIR)。其他可能的取值为 CHR 和 BLK,分别表示字符和块设备;或者 UNIX、FIFO 和 IPv4,分别表示 UNIX 域套接字、先进先出 (FIFO) 队列和网际协议 (IP) 套接字。
尽管与使用 lsof 没有什么直接的关系,但对 /proc 目录进行简要的介绍是有必要的。/proc 是一个目录,其中包含了反映内核和进程树的各种文件。这些文件和目录并不存在于磁盘中,因此当您对这些文件进行读取和写入时,实际上是在从操作系统本身获取相关信息。大多数与 lsof 相关的信息都存储于以进程的 PID 命名的目录中,所以 /proc/1234 中包含的是 PID 为 1234 的进程的信息。
在 /proc 目录的每个进程目录中存在着各种文件,它们可以使得应用程序简单地了解进程的内存空间、文件描述符列表、指向磁盘上的文件的符号链接和其他系统信息。lsof 实用程序使用该信息和其他关于内核内部状态的信息来产生其输出。稍后我将把 lsof 的输出与 /proc 目录中的信息联系起来。
前面,我向您介绍了如何简单地运行不带任何参数的 lsof,以便显示关于每个进程所打开的文件的信息。本文余下的部分将重点关注如何使用 lsof 来显示所需的信息以及如何正确地对其进行解释。
lsof 常见的用法是查找应用程序打开的文件的名称和数目。您可能想尝试找出某个特定应用程序将日志数据记录到何处,或者正在跟踪某个问题。例如,UNIX 限制了进程能够打开文件的数目。通常这个数值很大,所以不会产生问题,并且在需要时,应用程序可以请求更大的值(直到某个上限)。如果您怀疑应用程序耗尽了文件描述符,那么可以使用 lsof 统计打开的文件数目,以进行验证。
要指定单个进程,可以使用 -p 参数,后面加上该进程的 PID。因为这样做不仅会返回该应用程序所打开的文件,还会返回共享库和代码,所以通常需要对输出进行筛选。要完成此任务,可以使用 -d 标志根据 FD 列进行筛选,使用 -a 标志表示两个参数都必须满足 (AND)。如果没有 -a 标志,缺省的情况是显示匹配任何一个参数 (OR) 的文件。清单 2 显示了 sendmail 进程打开的文件,并使用 txt 对这些文件进行筛选。
sh-3.00# lsof -a -p 605 -d ^txt COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME sendmail 605 root cwd VDIR 136,8 1024 23554 /var/spool/mqueue sendmail 605 root 0r VCHR 13,2 6815752 /devices/pseudo/mm@0:null sendmail 605 root 1w VCHR 13,2 6815752 /devices/pseudo/mm@0:null sendmail 605 root 2w VCHR 13,2 6815752 /devices/pseudo/mm@0:null sendmail 605 root 3r DOOR 0t0 58 /var/run/name_service_door(door to nscd[81]) (FA:->0x30002b156c0)
se dmail 60 root 4 VCHR 21,0
清单 2 为 lsof 指定了三个参数。第一个是 -a,它表示当所有的参数都为真时,才显示这个文件。第二个参数是 -p 605,它限制仅输出 PID 为 605 的进程,可以通过 ps 命令获取这个信息。最后一个参数 -d ^txt,它表示筛选出其中 txt 类型的记录(脱字符号 [^] 表示排除)。
清单 2 的输出提供了关于进程行为的信息。如 cwd 行所示,该应用程序的工作目录为 /var/spool/mqueue。文件描述符 0、1 和 2 分配给了 /dev/null(Solaris 大量使用符号链接,所以这里显示了相应的伪设备)。FD 3 是一个 Solaris 门(高速远程过程调用 (RPC) 接口),以只读模式打开。FD 4 中的内容比较有趣,因为它是一个字符设备的只读句柄,实质上是 /dev/log。从这个文件中,您可以收集该应用程序向 UNIX syslog 守护进程进行的记录,所以 /etc/syslog.conf 规定了日志文件的位置。
作为一个网络应用程序,sendmail 对网络端口进行监听。文件描述符 5、6 和 7 可以告诉您,该应用程序正以 IPv4 和 IPv6 模式监听简单邮件传输协议 (SMTP) 端口,并以 IPv4 模式监听提交端口。最后一个文件描述符是只写的,并且指向 /var/run/sendmail.pid。FD 列中的大写 W 表示该应用程序具有对整个文件的写锁。该文件用于确保每次只能打开一个应用程序实例。
在其他情况下,您有一个文件或目录,并且需要知道哪个应用程序控制了该文件(打开了该文件)。清单 2 显示了由 sendmail 进程打开了 /var/run/sendmail.pid。如果您不知道这个信息,那么在给定文件名的情况下,lsof 可以提供该信息。清单 3 显示了相应的输出。
bash-3.00# lsof /var/run/sendmail.pid
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
sendmail 605 root 8wW VREG 281,3 32 8778600 /var/run/sendmail.pid
正如输出所示,进程 sendmail(PID 为 605)控制了文件 /var/run/sendmail.pid,并且通过排它锁打开该文件以便进行写入。如果出于某种原因,您需要删除这个文件,那么正确的做法是中止该进程,而不是直接删除这个文件。否则,这个守护进程下次可能无法正常启动,或者可能稍后会启动另一个实例,从而导致争用。
有时您只知道在文件系统的某处打开了文件。在卸载文件系统时,如果该文件系统中有任何打开的文件,那么操作将会失败。通过指定装入点的名称,您可以使用 lsof 显示一个文件系统中所有打开的文件。清单 4 显示了如何尝试卸载 /export/home,然后使用 lsof 找出谁在使用该文件系统。
bash-3.00# umount /export/home umount: /export/home busy bash-3.00# lsof /export/home COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME bash 1943 root cwd VDIR 136,7 1024 4 /export/home/sean bash 2970 sean cwd VDIR 136,7 1024 4 /export/home/sean ct 3030 sean cwd VDIR 136,7 1024 4 /export/home/sean ct 3030 sean 1w VREG 136,7 0 25 /export/home/sean/output
在这个示例中,用户 sean 正在其 home 目录中进行一些操作。有两个 bash(一种 Shell)实例正在运行,并且当前目录设置为 sean 的 home 目录。还有一个名为 ct 的应用程序正运行于相同的目录,并且其标准输出(文件描述符 1)重定向到一个名为 output 的文件。要成功地卸载 /export/home,应该在通知用户以确保情况正常之后,中止这些进程。
这个示例说明了应用程序的当前工作目录非常重要,因为它仍保持着文件资源,并且可以防止文件系统被卸载。这就是为什么大部分守护进程(后台进程)将它们的目录更改为根目录、或服务特定的目录(如 sendmail 示例中的 /var/spool/mqueue)的原因,以避免该守护进程阻止卸载不相关的文件系统。如果 sendmail 从 /export/home/sean 目录启动,并且没有将其目录更改为 /var/spool/mqueue,那么在卸载 /export/home 前必须中止它。
如果您对非装入点目录中打开的文件感兴趣,那么必须通过 +d 或 +D 指定该目录的名称,具体使用其中的哪一个标志取决于您需要递归到子目录(+D)或者不需要递归到子目录(+d)。例如,要查看 /export/home/sean 中所有打开的文件,可以使用 lsof +D /export/home/sean。在前面的示例中,相关的目录是一个装入点,而这里与前面的示例存在细微的差别,并且限制了 lsof 和内核之间的交互。这还会引起潜在的问题,即 lsof /export/home 与 lsof /export/home/(请注意尾部的斜杠)有所区别。第一种方式可以正常工作,因为它指向了装入点。第二种方式不会生成任何输出,因为它指向了目录。如果您在 Shell 中使用 Tab 键自动完成命令,那么可能碰到这个问题,其中会帮助您添加结尾的斜杠。在这种情况下,您可以删除这个斜杠或者使用 +D 指定目录。前者是首选的方法,因为与指定任意的目录相比,其执行速度更快。
在前面的部分中,我们研究了 lsof 的基本用法,即显示打开的文件和控制它们的进程之间的关系。当您想对系统进行一些烦琐的操作,而又不希望破坏别人重要的文档时,这种方法很有帮助。您还可以使用相同的方法执行一些高难度的 UNIX 操作。
当 UNIX 计算机受到入侵时,常见的情况是日志文件被删除,以掩盖攻击者的踪迹。管理错误也可能导致意外删除重要的文件,比如在清理旧日志时,意外地删除了数据库的活动事务日志。有时可以恢复这些文件,并且 lsof 可以为您提供帮助。
当进程打开了某个文件时,只要该进程保持打开该文件,即使将其删除,它依然存在于磁盘中。这意味着,进程并不知道文件已经被删除,它仍然可以向打开该文件时提供给它的文件描述符进行读取和写入。除了该进程之外,这个文件是不可见的,因为已经删除了其相应的目录条目。
前面曾在转到 /proc 目录部分中说过,通过在适当的目录中进行查找,您可以访问进程的文件描述符。在随后的内容中,您看到了 lsof 可以显示进程的文件描述符和相关的文件名。您能明白我的意思吗?
但愿它真的这么简单!当您向 lsof 传递文件名时,比如在 lsof /file/I/deleted 中,它首先使用 stat() 系统调用获得有关该文件的信息,不幸的是,这个文件已经被删除。在不同的操作系统中,lsof 可能可以从核心内存中捕获该文件的名称。清单 5 显示了一个 Linux 系统,其中意外地删除了 Apache 日志,我正使用 grep 工具查找是否有人打开了该文件。
# lsof | grep error_log httpd 2452 root 2w REG 33,2 499 3090660 /var/log/httpd/error_log (deleted) httpd 2452 root 7w REG 33,2 499 3090660 /var/log/httpd/error_log (deleted) ... more httpd processes ...
在这个示例中,您可以看到 PID 2452 打开文件的文件描述符为 2(标准错误)和 7。因此,可以在 /proc/2452/fd/7 中查看相应的信息,如清单 6 所示。
# cat /proc/2452/fd/7 [Sun Apr 30 04:02:48 2006] [notice] Digest: generating secret for digest authentication [Sun Apr 30 04:02:48 2006] [notice] Digest: done [Sun Apr 30 04:02:48 2006] [notice] LDAP: Built with OpenLDAP LDAP SDK
Linux 的优点在于,它保存了文件的名称,甚至可以告诉我们它已经被删除。在遭到破坏的系统中查找相关内容时,这是非常有用的内容,因为攻击者通常会删除日志以隐藏他们的踪迹。Solaris 并不提供这些信息。然而,我们知道 httpd 守护进程使用了 error_log 文件,所以可以使用 ps 命令找到这个 PID,然后可以查看这个守护进程打开的所有文件。
# lsof -a -p 8663 -d ^txt COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME httpd 8663 nobody cwd VDIR 136,8 1024 2 / httpd 8663 nobody 0r VCHR 13,2 6815752 /devices/pseudo/mm@0:null httpd 8663 nobody 1w VCHR 13,2 6815752 /devices/pseudo/mm@0:null httpd 8663 nobody 2w VREG 136,8 185 145465 / (/dev/dsk/c0t0d0s0) httpd 8663 nobody 4r DOOR 0t0 58 /var/run/name_service_door (door to nscd[81]) (FA:->0x30002b156c0) httpd 8663 nobody 15w VREG 136,8 185 145465 / (/dev/dsk/c0t0d0s0) httpd 8663 nobody 16u IPv4 0x300046d27c0 0t0 TCP *:80 (LISTEN) httpd 8663 nobody 17w VREG 136,8 0 145466 /var/apache/logs/access_log httpd 8663 nobody 18w VREG 281,3 0 9518013 /var/run (swap)
我使用 -a 和 -d 参数对输出进行筛选,以排除代码程序段,因为我知道需要查找的是哪些文件。Name 列显示出,其中的两个文件(FD 2 和 15)使用磁盘名代替了文件名,并且它们的类型为 VREG(常规文件)。在 Solaris 中,删除的文件将显示文件所在的磁盘的名称。通过这个线索,就可以知道该 FD 指向一个删除的文件。实际上,查看 /proc/8663/fd/15 就可以得到所要查找的数据。
如果可以通过文件描述符查看相应的数据,那么您就可以使用 I/O 重定向将其复制到文件中,如 cat /proc/8663/fd/15 > /tmp/error_log 。此时,您可以中止该守护进程(这将删除 FD,从而删除相应的文件),将这个临时文件复制到所需的位置,然后重新启动该守护进程。
对于许多应用程序,尤其是日志文件和数据库,这种恢复删除文件的方法非常有用。正如您所看到的,有些操作系统(以及不同版本的 lsof)比其他的系统更容易查找相应的数据。
网络连接也是文件,这意味着可以使用 lsof 获得关于它们的信息。您曾在清单 2 中看到过这样的示例。该示例假设您已经知道 PID,但是有时候并非如此。如果您只知道相应的端口,那么可以使用 -i 参数利用套接字信息进行搜索。清单 8 显示了对 TCP 端口 25 的搜索
# lsof -i :25 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME sendmail 605 root 5u IPv4 0x300010ea640 0t0 TCP *:smtp (LISTEN) sendmail 605 root 6u IPv6 0x3000431c180 0t0 TCP *:smtp (LISTEN)
需要以 protocol:@ip:port 的形式向 lsof 实用程序传递相关信息,其中的 protocol 为 TCP 或 UDP(可以使用 4 或 6 作为前缀,表示 IP 的版本),IP 为可解析的名称或 IP 地址,而 port 为数字或表示该服务的名称(来自 /etc/services)。需要一个或多个元素(端口、IP、协议)。在清单 8 中,:25 表示端口 25。输出显示,进程 605 正在使用 IPv6 和 IPv4 监听端口 25。如果您对 IPv4 不感兴趣,那么可以将筛选器改为 6:25,以表示监听端口 25 的 IPv6 套接字,或者直接使用 6 表示所有的 IPv6 连接。
除了显示出这些守护进程正在监听的对象,lsof 还可以发现发生的连接,同样是使用 -i 参数。清单 9 显示了搜索与 192.168.1.10 之间的所有连接。
# lsof -i @192.168.1.10 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME sshd 1934 root 6u IPv6 0x300046d21c0 0t1303608 TCP sun:ssh->linux:40379 (ESTABLISHED) sshd 1937 root 4u IPv6 0x300046d21c0 0t1303608 TCP sun:ssh->linux:40379 (ESTABLISHED)
在这个示例中,sun 和 linux 之间有两个 IPv6 连接。对其进行更仔细的研究可以看出,这些连接来自于两个不同的进程,但它们却是相同的,这是因为两台主机是相同的,并且端口也是相同的(ssh 和 40379)。这是由于进入主进程的连接分叉出一个处理程序,并将该套接字传递给它。您还可以看到,名为 sun 的计算机正在使用端口 22 (ssh),而 linux 具有端口 40379。这表示,sun 是该连接的接收者,因为它关联于该服务的已知端口。40379 是源或临时端口,并且仅对这个连接有意义。
因为,至少在 UNIX 中,套接字是另一类文件,所以 lsof 可以获得关于这些连接的详细信息,并找出谁对它们负责。
结束语
UNIX 大量使用了文件。作为系统管理员,lsof 允许您对核心内存进行查看,以找出系统当前如何使用这些文件。lsof 最简单的用法可以告诉您哪些进程打开了哪些文件,以及哪些文件由哪些进程打开。在收集关于应用程序工作情况的信息时,或在进行某些可能损坏数据的操作前确保文件未被使用时,这一点特别重要lsof 更高级的用法可以帮助您查找删除的文件,并获得关于网络连接的信息。这是一个功能强大的工具,它几乎可以用于任何地方。
///
lsof的功能很多,特別提醒大家, -c,-g,-p,-u,这四个参数最有用。更详细的资料请参看:man lsof。
1) lsof abc.txt 显示开启文件abc.txt的进程
2) lsof -i :22 知道22端口现在运行什么程序
3) lsof -c nsd 显示nsd进程现在打开的文件
4) lsof -g gid 显示归属gid的进程情况
5) lsof +d /usr/local/ 显示/usr/local目录下被进程开启的文件
6) lsof +D /usr/local/ 同上,但是会搜索目录下的目录,时间较长
7) lsof -d 4 显示使用fd为4的进程
8) lsof -i [i] 用以显示符合条件的进程情况
语法: lsof -i[46] [protocol][@hostname|hostaddr][:service|port]
46 --> IPv4 or IPv6
protocol --> TCP or UDP
hostname --> Internet host name
hostaddr --> IPv4位置
service --> /etc/service中的 service name (可以不只一个)
port --> 端口号 (可以不只一个)
例子: TCP:25 - TCP and port 25
@1.2.3.4 - Internet IPv4 host address 1.2.3.4
tcp@ohaha.ks.edu.tw:ftp - TCP protocol host:ohaha.ks.edu.tw service name:ftp
9) lsof -n 不将IP转换为hostname,缺省是不加上-n参数
例子: lsof -i tcp@ohaha.ks.edu.tw:ftp -n
10) lsof -p 12 看进程号为12的进程打开了哪些文件
11) lsof +|-r [t] 控制lsof不断重复执行,缺省是15s刷新
-r,lsof会永远不断的执行,直到收到中断信号
+r,lsof会一直执行,直到没有档案被显示
例子:不断查看目前ftp连接的情况:lsof -i tcp@ohaha.ks.edu.tw:ftp -r
12) lsof -s 列出打开文件的大小,如果没有大小,则留下空白
13) lsof -u username 以UID,列出打开的文件
以下给大家介绍一下我在工作中,经常使用的技巧:
1、查看文件系统阻塞
根据工作需要,系统管理员想卸载一个文件系统并执行umount /mountpoint,但程序报告常常显示:umount: /mountpoint: device is busy;这是因为该文件系统上有正在打开的文件而不允许你这么做。这时,我们需要知道哪些文件、程序及用户仍在使用该系统,以便通知用户退出该系统,可 以使用lsof识别正在打开一个特定文件系统的进程,执行如下命令:
/usr/sbin/lsof /mountpoint
在这里,mountpoint就是安装位置。例如:
# /usr/sbin /lsof /home
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
bash12134 meng cwdDIR8,5 4096 32705 /home/meng
telnet 12176 meng cwdDIR8,5 4096 32705 /home/meng
bash19809 meng cwdDIR8,5 4096 32705 /home/meng
bash20276 meng cwdDIR8,5 4096 32705 /home/meng
su 20315 root cwdDIR8,5 4096 32705 /home/meng
bash20316 root cwdDIR8,5 4096 32705 /home/meng
csh 20374 root cwdDIR8,5 4096 32705 /home/meng
lsof 20396 root cwdDIR8,5 4096 32705 /home/meng
lsof 20397 root cwdDIR8,5 4096 32705 /home/meng
显然,所有使用这些被打开的文件的进程都需要在文件系统能够被卸载前被终止。管理员以root身份,kill掉占用这个文件系统的进程,解除文件系统阻塞。
2、搜索打开的网络连接
如果想搜索IP地址为10.645.64.23的远程连接主机的所有网络连接,可以执行如下命令:
/usr/sbin/lsof –i@10.65.64.23可以打开系统中该远程知己所有打开的套接字。
# lsof -i@10.65.64.23
COMMAND PID USER FD TYPE DEVICE SIZE/OFF INODE NAME
telnetd 6605 root0u inet 0x14813f00 0t0 TCP xpp3:telnet->linuxone:33143 (ESTABLISHED)
telnetd 6605 root1u inet 0x14813f00 0t0 TCP xpp3:telnet->linuxone:33143 (ESTABLISHED)
telnetd 6605 root2u inet 0x14813f00 0t0 TCP xpp3:telnet->linuxone:33143 (ESTABLISHED)
3、寻找本地断开的打开文件
用户经常遇到这种情况,当一个进程正在向一个文件写数据时,该文件的目录可能被移动。这就产生了一个非常大的问题。例如,用户可能发现正在向/data写数据,但是却看不到文件增大,LSOF这个工具可以找到这样的错误,例如:
/usr/sbin/lsof +L1,通常可以看到下面的信息:
# lsof +L1
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINKNODE NAME
svrMgt_mi458 root 4r VREG 8,00 0 3418 / (/dev/rz0a)
yes 677 root 1w VREG 8,0 186523648 0 92888 / (/de v/rz0a)
# lsof +L1
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
svrMgt_mi458 root 4r VREG 8,0 0 0 3418 / (/dev/rz0a)
yes 677 root1w VREG 8,0 2735882240 92888 / (/dev/rz0a)
我们可以用kill -9 PID命令来结束PID显示的命令排除错误,释放空间。
我们还可以用-a选项来限制lsof报告单文件系统中的链接数量。例如,为了限制到/data部分的输出,可以输入:/usr/sbin/lsof –a +L1 /data
4、搜索被程序打开的所有文件及打开的文件相关联进程
如果想知道执行PID号为637的sendmail命令打开了哪些文件的话,可以执行lsof -p 637命令。输出的结果如下:
# lsof -p 637
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
sendmail 637 root cwd VDIR8,6 512 470400 /usr/var/spool/mqueue
sendmail 637 root txt VREG8,6 466944 9650 /usr (/dev/rz0g)
sendmail 637 root txt VREG8,0 139264 16016 /sbin/loader
sendmail 637 root txt VREG8,0 1663104 38402 /shlib/libc.so
sendmail 637 root0r VCHR2,2 0t0 9607 /dev/null
sendmail 637 root1w VCHR2,2 0t0 9607 /dev/null
sendmail 637 root2w VCHR2,2 0t0 9607 /dev/null
sendmail 637 root3u unix 0x0c2fc280 0t0->0x1ead2b40
sendmail 637 root4u inet 0x0c34c200 0t0TCP *:smtp (LISTEN)
上述输出信息显示了该程序当前打开的所有文件、设备、库及套接字等。
执行下面的命令可以发现哪些进程正在使用某个特定的文件,如下所示,可以看出,只有系统记录后台进程syslogd打开messages这个文件。
# lsof /var/adm/messages
COMMAND PID USER FD TYPE DEVICE SIZE/OFF INODE NAME
syslogd 147 root 16w VREG8,6 2653365 22501 /usr/var/adm/messages
5、其它使用命令(更详细的资料请man lsof,这部分参看了一些资料给大家总结一下)
若沒有加上任何的参数,lsof 会列出所有被程序打开的文件。
参数可以相互结合,ex: -a -b -c 等同于 -abc
-? -h 这两个参数意思相同,显示出 lsof 的使用说明。
-a 参数被视为 AND (注意:-a参数一但加上 ,会影响全部的参数。)
-c c 显示出以字母 c开头进程现在打开的文件
例:显示以init进程现在打开的文件
# lsof -c init
COMMAND PID USER FD TYPE DEVICE SIZE/OFF INODE NAME
init 1 root cwd VDIR 4095,365376 8192 2 /
init 1 root txt VREG 4095,365376 286720 463 /sbin/init
+d s 依照文件夹s来搜寻,此参数将不会继续深入搜寻此文件夹
例:显示在/usr/users/tongxl目录下被程序正在打开的文件(如下所示)
# lsof +d /usr/users/tongxl
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
ksh 26946 root cwd VDIR8,6 512 51281 /usr/users/tongxl/c
a.out 26953 root cwd VDIR8,6 512 51281 /usr/users/tongxl/c
+D D 同上,但是会搜索目录下的目录,时间较长。(注意︰lsof以此参数进行时,须花费较多的动态记忆体。尤其在处理较大的文件夹时,请务必审慎使用之。)
例:显示在/usr/local/文件夹下被程序正在打开的文件(如下)很明显可以看出二者的差别
# lsof +D /usr/users/tongxl
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
ksh 26946 root cwd VDIR8,6 512 51281 /usr/users/tongxl/c
a.out 26953 root cwd VDIR8,6 512 51281 /usr/users/tongxl/c
a.out 26953 root txt VREG8,624576 51311 /usr/users/tongxl/c/a.out
-d s 此参数以file descriptor (FD)值显示结果,可以采用范围表示,如 1-3 或 3-10 但 最前面的数一定要比最后面的数小。
举例:以FD为4显示
# lsof -d 4
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
syslogd 147 root4u inet 0x1fe0b980 0t0UDP *:syslog
binlogd 151 root4u inet 0x1fe0bd40 0t0UDP *:*
portmap 319 root4u inet 0x1fe0b740 0t0UDP *:111
mountd321 root4u VREG8,6 253 22516 /usr (/dev/rz0g)
nfsd 323 root4u inet 0x0c349e00 0t0TCP *:2049 (LISTEN)
rpc.statd 330 root4u inet 0x1ab42000 0t0TCP xpp3:1024 (LISTEN)
rpc.lockd 332 root4u inet 0x1fe0bbc0 0t0UDP xpp3:1028
snmpd 449 root4u unix 0x1aaf6500 0t0/var/esnmp/esnmpd
svrMgt_mi 457 root4r VREG8,00 3424 / (/dev/rz0a)
os_mibs 458 root4u inet 0x1ab475c0 0t0UDP *:*
cpq_mibs 460 root4u unix 0x1aaf77c0 0t0/var/esnmp/esnmp_sub460
advfsd472 root4u inet 0x0c320000 0t0TCP *:AdvFS (LISTEN)
insightd 475 root4r VDIR8,6 512 25610 /usr (/dev/rz0g)
inetd 506 root4u inet 0x1ab26700 0t0TCP *:ftp (LISTEN)
lpd 567 root4wW VREG8,64 451219 /usr (/dev/rz0g)
dtlogin 605 root4w VREG8,64 344028 /usr (/dev/rz0g)
Xdec 616 root4w VREG8,64 344028 /usr (/dev/rz0g)
sendmail 702 root4u inet 0x0c321900 0t0TCP *:smtp (LISTEN)
dtlogin 891 root4w VREG8,64 344028 /usr (/dev/rz0g)
dxconsole 907 root4w VREG8,64 344028 /usr (/dev/rz0g)
dtgreet 908 root4w VREG8,64 344028 /usr (/dev/rz0g)
-g [s] 以程序的PGID (process group IDentification)显示,也可以采用范围(1-3)或个别(3,5)表示,若没有特别指定,则显示全部。
举例:以PGID为3显示
# lsof -g 3
COMMAND PID PGID USER FD TYPE DEVICE SIZE/OFF NODE NAME
kloadsrv 33 root cwd VDIR8,0 2560 2 /
kloadsrv 33 root txt VREG8,0 221184 16041 /sbin/kloadsrv
kloadsrv 33 root0r VCHR0,0 0t0 9608 /dev/console
kloadsrv 33 root1w VCHR0,0 0t0 9608 /dev/console
kloadsrv 33 root2w VCHR0,0 0t0 9608 /dev/console
-i [i] 用以监听有关的任何符合的位址。若没有相关位置被指定,则监听全部。
语法: lsof -i[46] [protocol][@hostname|hostaddr][:service|port]
46 --> IPv4 or IPv6
protocol --> TCP or UDP
hostname --> Internet host name
hostaddr --> IPv4位置
service --> /etc/service中的 service name (可以不只一个)
port --> 埠号 (可以不只一个)
# lsof -i tcp@xp001
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
telnetd 26862 root0u inet 0x0c349000 0t0 TCP xpp3:telnet->xp001:3807 (ESTABLISHED)
telnetd 26862 root1u inet 0x0c349000 0t0 TCP xpp3:telnet->xp001:3807 (ESTABLISHED)
telnetd 26862 root2u inet 0x0c349000 0t0 TCP xpp3:telnet->xp001:3807 (ESTABLISHED)
telnetd 26986 root0u inet 0x1ab27100 0t0 TCP xpp3:telnet->xp001:3988 (ESTABLISHED)
telnetd 26986 root1u inet 0x1ab27100 0t0 TCP xpp3:telnet->xp001:3988 (ESTABLISHED)
telnetd 26986 root2u inet 0x1ab27100 0t0 TCP xpp3:telnet->xp001:3988 (ESTABLISHED)
-l此参数禁止将user ID转换为登入名称。(预设显示登入名称)
# lsof -l|more
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
kernel0 0 cwd VDIR8,02560 2 /
init 1 0 cwd VDIR8,02560 2 /
init 1 0 txt VREG8,0 286720 16015 / (/dev/rz0a)
kloadsrv 3 0 cwd VDIR8,02560 2 /
kloadsrv 3 0 txt VREG8,0 221184 16041 /sbin/kloadsrv
kloadsrv 3 0 0r VCHR0,0 0t0 9608 /dev/console
kloadsrv 3 0 1w VCHR0,0 0t0 9608 /dev/console
kloadsrv 3 0 2w VCHR0,0 0t0 9608 /dev/console
+|-L [l] +或-表示正在打开或取消显示文件连结数. 若只有单纯的+L,后面没有任何数字,则表示显示全部。若其后有加上数字,只有文件连结数少于该数字的会被列出。
-n不将IP位址转换成hostname,预设是不加上-n参数。
举例: lsof -i tcp@xp001 -n
(您可以和上两张图比较一下,原先的hostname便回ip位置了)
# lsof -i tcp@xp001 -n
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
telnetd 26862 root0u inet 0x0c349000 0t0 TCP 10.65.69.147:telnet->10.65.69.131:3807 (ESTABLISHED)
telnetd 26862 root1u inet 0x0c349000 0t0 TCP 10.65.69.147:telnet->10.65.69.131:3807 (ESTABLISHED)
telnetd 26862 root2u inet 0x0c349000 0t0 TCP 10.65.69.147:telnet->10.65.69.131:3807 (ESTABLISHED)
telnetd 26986 root0u inet 0x1ab27100 0t0 TCP 10.65.69.147:telnet->10.65.69.131:3988 (ESTABLISHED)
telnetd 26986 root1u inet 0x1ab27100 0t0 TCP 10.65.69.147:telnet->10.65.69.131:3988 (ESTABLISHED)
telnetd 26986 root2u inet 0x1ab27100 0t0 TCP 10.65.69.147:telnet->10.65.69.131:3988 (ESTABLISHED)
# lsof -i tcp@xp001
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
telnetd 26862 root0u inet 0x0c349000 0t0 TCP xpp3:telnet->xp001:3807 (ESTABLISHED)
telnetd 26862 root1u inet 0x0c349000 0t0 TCP xpp3:telnet->xp001:3807 (ESTABLISHED)
telnetd 26862 root2u inet 0x0c349000 0t0 TCP xpp3:telnet->xp001:3807 (ESTABLISHED)
telnetd 26986 root0u inet 0x1ab27100 0t0 TCP xpp3:telnet->xp001:3988 (ESTABLISHED)
telnetd 26986 root1u inet 0x1ab27100 0t0 TCP xpp3:telnet->xp001:3988 (ESTABLISHED)
telnetd 26986 root2u inet 0x1ab27100 0t0 TCP xpp3:telnet->xp001:3988 (ESTABLISHED)
-s列出文件的大小,若该文件没有大小,则留下空白。
# lsof -s
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
kernel0 root cwd VDIR8,0 2560 2 /
init 1 root cwd VDIR8,0 2560 2 /
init 1 root txt VREG8,0 286720 16015 / (/dev/rz0a)
kloadsrv 3 root cwd VDIR8,0 2560 2 /
kloadsrv 3 root txt VREG8,0 221184 16041 /sbin/kloadsrv
kloadsrv 3 root0r VCHR0,09608 /dev/console
kloadsrv 3 root1w VCHR0,09608 /dev/console
kloadsrv 3 root2w VCHR0,09608 /dev/console
-u s 以login name(登入名称)或UID,列出所正在打开文件。
# lsof -u tongxl
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
csh 26939 tongxl cwd VDIR8,6 1024 243236 /usr -- tongxl
csh 26939 tongxl txt VREG8,6 253952 12856 /usr (/dev/rz0g)
csh 26939 tongxl txt VREG8,0 139264 16016 /sbin/loader
csh 26939 tongxl txt VREG8,0 1663104 38402 /shlib/libc.so
csh 26939 tongxl0r VCHR1,0 0t0 9612 /dev/tty
csh 26939 tongxl 15u VCHR6,20t328 9618 /dev/pts/2
csh 26939 tongxl 16u VCHR6,20t328 9618 /dev/pts/2
csh 26939 tongxl 17u VCHR6,20t328 9618 /dev/pts/2
csh 26939 tongxl 18u VCHR6,20t328 9618 /dev/pts/2
csh 26939 tongxl 19u VCHR6,20t328 9618 /dev/pts/2
csh 26990 tongxl cwd VDIR8,6 1024 243236 /usr -- tongxl
csh 26990 tongxl txt VREG8,6 253952 12856 /usr (/dev/rz0g)
csh 26990 tongxl txt VREG8,0 139264 16016 /sbin/loader
csh 26990 tongxl txt VREG8,0 1663104 38402 /shlib/libc.so
csh 26990 tongxl0r VCHR1,0 0t0 9612 /dev/tty
csh 26990 tongxl 15u VCHR6,1 0t147797 9616 /dev/pts/1
csh 26990 tongxl 16u VCHR6,1 0t147797 9616 /dev/pts/1
csh 26990 tongxl 17u VCHR6,1 0t147797 9616 /dev/pts/1
csh 26990 tongxl 18u VCHR6,1 0t147797 9616 /dev/pts/1
csh 26990 tongxl 19u VCHR6,1 0t147797 9616 /dev/pts/