文章目录

  • 1.定位一个“运行慢”的进程
  • 2.strace -cp 27288
  • 3. pstack 27288
  • 3.进程状态和WCHAN字段
  • 4.进程有什么活动或者完全挂死了?
  • 5.用/proc文件系统查看内核态信息
  • 6.诊断和"修复"
  • 7.查看操作系统日志:dmesg -T
  • 8.查看linux最大文件句柄数:ulimit -a
  • 9.查看指定进程ID已打开的内容:lsof -p
  • 12.关于lsof命令查看某个文件被哪个进程打开
  • 11.gdb打印正在运行程序的全局变量
  • 12.Linux内核调度策略查看

1.定位一个“运行慢”的进程

  • 一个DBA想知道为什么他的find命令运行起来"非常慢",并且很长时间都没有返回任何结果。
    了解环境之后,我对这个问题的起因有一个直觉的答案,但是他问我,对于这种正在发生中的问题,有没有系统性的方法立刻进行定位。
[root@oel6 ~]# ps -ef | grep find
root     27288 27245  4 11:57 pts/0 00:00:01 find . -type f
root     27334 27315  0 11:57 pts/1 00:00:00 grep find

是的,他还在 —— PID 27288 (在整个定位问题的过程中我将会一直使用这个pid)。
  • 让我们从最基本的开始,先看下这个进程的瓶颈在什么地方
    (1)如果不是被什么操作阻塞的话(例如从缓存中读取需要的数据),CPU占用率应该是100%。
    (2)如果瓶颈是IO或者连接问题,CPU占用率应该很低,或者就是0%。
[root@oel6 ~]# top -cbp 27288
top - 11:58:15 up 7 days,  3:38,  2 users,  load average: 1.21, 0.65, 0.47
Tasks:   1 total,   0 running,   1 sleeping,   0 stopped,   0 zombie
Cpu(s):  0.1%us,  0.1%sy,  0.0%ni, 99.8%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:   2026460k total,  1935780k used,  90680k free,    64416k buffers
Swap:  4128764k total,   251004k used,  3877760k free,   662280k cached

  PID USER    PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
27288 root    20   0  109m 1160  844 D  0.0  0.1   0:01.11 find . -type f

top的结果显示这个进程的CPU占用率是0%,或者非常接近0%(因此输出被四舍五入为0%)。
这两种情况实际上有着重要的差别,一种情况是进程完全挂死,根本没有机会获得CPU,
另一种情况是进程不时的退出等待状态(例如,某些轮询操作不时的超时,而进程选择继续sleep)。
因此,Linux上的top并不是一个适合显示这种差别的工具 —— 但是至少我们知道了进程并不是占用了大量的CPU。

2.strace -cp 27288

  • 天啊,strace命令也挂住了!strace很长时间都没有打印任何东西,并也不能响应CTRL+C,因此我不得不用CTRL+Z,并杀死它。简单的诊断手段就这些了。
[root@oel6 ~]# strace -cp 27288
Process 27288 attached - interrupt to quit

^C
^Z
[1]+  Stopped                 strace -cp 27288

[root@oel6 ~]# kill -9 %%
[1]+  Stopped                 strace -cp 27288
[root@oel6 ~]# 
[1]+  Killed                  strace -cp 27288

3. pstack 27288

  • 让我们再试试pstack(在Linux上,pstack就是GDB调试器的一个shell包装)。
    尽管pstack并不能查看内核态信息,它仍然能够告诉我们是哪个系统调用被执行了**(通常,有一个相应的libc库调用显示在用户态堆栈的顶端上):**
[root@oel6 ~]# pstack 27288

^C
^Z
[1]+  Stopped                pstack 27288

[root@oel6 ~]# kill %%
[1]+  Stopped                pstack 27288
[root@oel6 ~]# 
[1]+  Terminated              pstack 27288
  • pstatck也挂死了,什么都没返回!
    因此,我们还是不知道我们的进程是100%(无可救药的)挂死了还是99.99%的挂住了(进程还在运行只是在睡眠) —— 以及在哪儿挂住了。

3.进程状态和WCHAN字段

  • 进程状态和WCHAN字段,可以通过古老而美好的ps(也许我早就应该运行这个命令,以确认进程到底是不是僵死了):
[root@oel6 ~]# ps -flp 27288
F S UID     PID  PPID  C PRI  NI ADDR SZ **WCHAN**  STIME TTY         TIME CMD
0 D root     27288 27245  0  80   0 - 28070 **rpc_wa** 11:57 pts/0  00:00:01 find . -type f

最好使用:
ps -efl|grep 27288
如果找不到最上面的参数,可以使用:
ps -efl|head

(1)你应该多运行几次ps命令,以确保进程一直是同一个状态(你肯定不想被一个偶然的单独采样所误导),为了简洁一点这里只显示一次结果。

(2)进程状态是D(不可中断睡眠状态,也就是不会被任何外部信号唤醒),这个状态通常与磁盘IO相关(ps帮助上也这样说)。
并且WCHAN字段(表示导致进程睡眠或者等待的函数)被截断了一点。

我可以用ps选项(参考帮助)把这个字段打印得跟宽一点,但是既然这个信息是来自proc文件系统,就让我们直接到源头去查询吧
(再强调一次,既然我们不确定我们的进程到底是完全挂死了还是仅仅只是经常处于睡眠状态,那么最好把这个命令多执行几次以获取多次采样结果):

[root@oel6 ~]# cat /proc/27288/wchan
rpc_wait_bit_killable

嗯,进程是在等待某个RPC调用。RPC通常意味着进程是在和其它进程通信(可能是本地服务进程或者远程服务进程)。
但是我们还是不知道为什么挂住。

4.进程有什么活动或者完全挂死了?

  • 在我们进入这篇文章中真正有营养的部分之前,让我们先弄清楚进程到底有没有完全挂死。
    在最新的系统内核上/proc/PID/status 可以告诉我们答案:
[root@oel6 ~]# cat /proc/27288/status 
Name:   find
State:  D (disk sleep)
Tgid:   27288
Pid:    27288
PPid:   27245
TracerPid:  0
Uid:    0   0   0   0
Gid:    0   0   0   0
FDSize: 256
Groups: 0 1 2 3 4 6 10 
VmPeak:   112628 kB
VmSize:   112280 kB
VmLck:         0 kB
VmHWM:      1508 kB
VmRSS:      1160 kB
VmData:      260 kB
VmStk:       136 kB
VmExe:       224 kB
VmLib:      2468 kB
VmPTE:        88 kB
VmSwap:        0 kB
Threads:    1
SigQ:   4/15831
SigPnd: 0000000000040000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000000000000
SigCgt: 0000000180000000
CapInh: 0000000000000000
CapPrm: ffffffffffffffff
CapEff: ffffffffffffffff
CapBnd: ffffffffffffffff
Cpus_allowed:   ffffffff,ffffffff
Cpus_allowed_list:  0-63
Mems_allowed:   
00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,
00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,
00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,
00000000,00000000,00000000,00000000,00000001
Mems_allowed_list:  0
voluntary_ctxt_switches:    9950
nonvoluntary_ctxt_switches: 17104

进程状态是D —— Disk Sleep(不可中断睡眠)。
然后看看voluntaryctxtswitches 和nonvoluntaryctxtswitches的数值 —— 它可以告诉你进程占用(或者释放)了多少次CPU。
等几秒钟之后,再次执行该命令,看看这些数值有没有增加

在我这个案例中,这些数值没有增加,据此我可以得出结论,这个进程是完全挂死了(额,至少在执行命令的这几秒钟内是完全挂死的)。
所以,现在我更有信心认为这个进程是完全挂死了(而不是在飞行在雷达探测不到地带 —— 在0.04%以下的低CPU占用率下运行)。
  • 顺便说一句,有两个地方可以获得上下文切换次数(并且第二种方法还可以在老的系统内核上工作):
[root@oel6 ~]# cat /proc/27288/sched
find (27288, #threads: 1)
---------------------------------------------------------
se.exec_start                      :     617547410.689282
se.vruntime                        :       2471987.542895
se.sum_exec_runtime                :          1119.480311
se.statistics.wait_start           :             0.000000
se.statistics.sleep_start          :             0.000000
se.statistics.block_start          :     617547410.689282
se.statistics.sleep_max            :             0.089192
se.statistics.block_max            :         60082.951331
se.statistics.exec_max             :             1.110465
se.statistics.slice_max            :             0.334211
se.statistics.wait_max             :             0.812834
se.statistics.wait_sum             :           724.745506
se.statistics.wait_count           :                27211
se.statistics.iowait_sum           :             0.000000
se.statistics.iowait_count         :                    0
se.nr_migrations                   :                  312
se.statistics.nr_migrations_cold   :                    0
se.statistics.nr_failed_migrations_affine:                    0
se.statistics.nr_failed_migrations_running:                   96
se.statistics.nr_failed_migrations_hot:                 1794
se.statistics.nr_forced_migrations :                  150
se.statistics.nr_wakeups           :                18507
se.statistics.nr_wakeups_sync      :                    1
se.statistics.nr_wakeups_migrate   :                  155
se.statistics.nr_wakeups_local     :                18504
se.statistics.nr_wakeups_remote    :                    3
se.statistics.nr_wakeups_affine    :                  155
se.statistics.nr_wakeups_affine_attempts:                  158
se.statistics.nr_wakeups_passive   :                    0
se.statistics.nr_wakeups_idle      :                    0
avg_atom                           :             0.041379
avg_per_cpu                        :             3.588077
nr_switches                        :                27054
nr_voluntary_switches              :                 9950
nr_involuntary_switches            :                17104
se.load.weight                     :                 1024
policy                             :                    0
prio                               :                  120
clock-delta                        :                   72

你需要看看nr_switches的数值(等于nrvoluntaryswitches+nrinvoluntaryswitches)。
并且它不会增加...

5.用/proc文件系统查看内核态信息

  • 那么,怎么看到底挂在哪个系统调用上呢 —— 没法用strace或者pstack?
    幸运的是我运行的是现代的操作系统内核 —— 跟/proc/PID/syscall打个招呼吧!
[root@oel6 ~]# cat /proc/27288/syscall
262 0xffffffffffffff9c 0x20cf6c8 0x7fff97c52710 0x100 0x100 0x676e776f645f616d 0x7fff97c52658 0x390e2da8ea

好了,我可以拿他干嘛呢? 嗯,这些数字代表某些东西。
如果它是一个"0x很大的数",它通常表示一个内存地址(并且,pmap之类的工具可以用来查看它指向那里);
但是如果是一个很小的数字,那么很可能是一个数组索引 —— 例如打开的文件描述符数组(可以从/prco/PID/fd读取到),
或者是当前进程正在执行的系统调用号 —— 既然在这个例子中,我们正在处理系统调用。
那么,这个进程是挂死在#262号系统调用上吗?
  • 注意在不同的OS类型、版本或者平台之间,系统调用号可能不同,因此你需要看看对应的OS上的.h文件。
    通常应该在/usr/include中搜索"syscall*"。
    在我的Linux上,系统调用定义在/usr/include/asm/unistd_64.h中:
[root@oel6 ~]# grep 262 /usr/include/asm/unistd_64.h 
#define __NR_newfstatat             262


找到了!系统调用262是某个叫做newfstatat的东西。打开手册看看它到底是什么。
关于系统调用名称有一个小小的技巧 —— 如果在手册中找不到这个系统调用,试试去掉后缀或者前缀。

无论如何,系统调用"new-fstat-at"允许你读取文件属性,非常像通常的"stat"系统调用。
那么我们挂在这个文件元数据读取操作上。我们前进了一步,但是仍然不知道为什么会挂在这儿?
  • /proc/PID/stack:读取进程的内核堆栈的调试信息
[root@oel6 ~]# cat /proc/27288/stack
[] rpc_wait_bit_killable+0x24/0x40 [sunrpc]
[] __rpc_execute+0xf5/0x1d0 [sunrpc]
[] rpc_execute+0x43/0x50 [sunrpc]
[] rpc_run_task+0x75/0x90 [sunrpc]
[] rpc_call_sync+0x42/0x70 [sunrpc]
[] nfs3_rpc_wrapper.clone.0+0x35/0x80 [nfs]
[] nfs3_proc_getattr+0x47/0x90 [nfs]
[] __nfs_revalidate_inode+0xcc/0x1f0 [nfs]
[] nfs_revalidate_inode+0x36/0x60 [nfs]
[] nfs_getattr+0x5f/0x110 [nfs]
[] vfs_getattr+0x4e/0x80
[] vfs_fstatat+0x70/0x90
[] sys_newfstatat+0x24/0x50
[] system_call_fastpath+0x16/0x1b
[] 0xffffffffffffffff

最上面的函数就是在内核代码中挂住的地方 —— 它跟WCHAN输出完全吻合;
我们可以从下而上的看一下函数调用,从而理解是怎么最终调用到rpc_wait_bit_killable的,这个函数结束了对调度器的调用并
使进程进入睡眠模式。

底端的system_call_fastpath是一个通用的内核调用处理函数,它为我们处理过的newfstatat系统调用执行内核代码。
然后继续向上,我们可以看到好几个NFS函数。这是100%无可抵赖的证据,证明我们处在某些NFS代码路径下(under NFS codepath)。

我没有说在NFS代码路径中(in NFS codepath),当你继续向上看的时候,你会看到最上面的NFS函数接着调用了
某些RPC函数(rpc_call_sync)以便跟其它进程通信 —— 在这个例子中可能是[kworker/N:N]、 [nfsiod]、 [lockd] 或者 [rpciod]
内核IO线程。

并且因为某些原因一直没有从这些线程收到应答(通常的怀疑点是网络连接丢失、数据包丢失或者仅仅是网络连通性问题)。

6.诊断和"修复"

  • 既然在堆栈最顶端的函数是一个可杀死的、可安全杀死的函数(rpc_wait_bit_killable),我们可以用kill -9杀死它:
[root@oel6 ~]# ps -fp 27288
UID     PID  PPID  C STIME TTY        TIME CMD
root     27288 27245  0 11:57 pts/0 00:00:01 find . -type f
[root@oel6 ~]# kill -9 27288

[root@oel6 ~]# ls -l /proc/27288/stack
ls: cannot access /proc/27288/stack: No such file or directory

[root@oel6 ~]# ps -fp 27288
UID     PID  PPID  C STIME TTY        TIME CMD
[root@oel6 ~]#
进程不见了。

7.查看操作系统日志:dmesg -T

  • man dmesg
  • dmesg是一种程序,用于检测和控制内核环缓冲。程序用来帮助用户了解系统的启动信息。
dmesg -k -L
-k, --kernel
              Print kernel messages
-L, --color
              Colorize important messages

dmesg -u
-u, --userspace
      Print userspace messages

8.查看linux最大文件句柄数:ulimit -a

  • 显示系统资源的设置
[root@w3cschool.cc ~]# ulimit -a
core file size     (blocks, -c) 0
data seg size      (kbytes, -d) unlimited
file size        (blocks, -f) unlimited
pending signals         (-i) 1024
max locked memory    (kbytes, -l) 32
max memory size     (kbytes, -m) unlimited
open files           (-n) 1024
pipe size      (512 bytes, -p) 8
POSIX message queues   (bytes, -q) 819200
stack size       (kbytes, -s) 10240
cpu time        (seconds, -t) unlimited
max user processes       (-u) 4096
virtual memory     (kbytes, -v) unlimited
file locks           (-x) unlimited
  • ulimit指令参数详解:
-H 设置硬资源限制.
-S 设置软资源限制.
-a 显示当前所有的资源限制.
-c size:设置core文件的最大值.单位:blocks
-d size:设置数据段的最大值.单位:kbytes
-f size:设置创建文件的最大值.单位:blocks
-l size:设置在内存中锁定进程的最大值.单位:kbytes
-m size:设置可以使用的常驻内存的最大值.单位:kbytes
-n size:设置内核可以同时打开的文件描述符的最大值.单位:n
-p size:设置管道缓冲区的最大值.单位:kbytes
-s size:设置堆栈的最大值.单位:kbytes
-t size:设置CPU使用时间的最大上限.单位:seconds
-v size:设置虚拟内存的最大值.单位:kbytes
-u <程序数目>  用户最多可开启的程序数目
  • Linux 一个进程所能创建的最大线程数,参考:链接
1)系统可生成最大线程数:
执行cat /proc/sys/kernel/threads-max,结果为254933

2)进程最大线程数:
执行cat /proc/sys/vm/max_map_count,结果为65530

3)用户最大进程数:
执行ulimit -u,结果为1024

9.查看指定进程ID已打开的内容:lsof -p

root@OpenWrt:/tmp# lsof -p 1248

COMMAND    PID      USER   FD      TYPE     DEVICE     SIZE       NODE      NAME 
init       1         root  cwd      DIR       3,3       1024       2         / 
init       1         root  rtd      DIR       3,3       1024       2         / 
init       1         root  txt      REG       3,3       38432      1763452  /sbin/init 
init       1         root  mem      REG       3,3       106114     1091620  /lib/libdl-2.6.so 
init       1         root  mem      REG       3,3       7560696    1091614  /lib/libc-2.6.so 
init       1         root  mem      REG       3,3       79460      1091669  /lib/libselinux.so.1 
init       1         root  mem      REG       3,3       223280     1091668  /lib/libsepol.so.1 
init       1         root  mem      REG       3,3       564136     1091607  /lib/ld-2.6.so 
init       1         root  10u      FIFO      0,15                  1309     /dev/initctl 


参数详解:
COMMAND:进程的名称 
PID:进程标识符
USER:进程所有者
FD:文件描述符,应用程序通过文件描述符识别该文件。如cwd、txt等
TYPE:文件类型,如DIR、REG等
DEVICE:指定磁盘的名称
SIZE:文件的大小
NODE:索引节点(文件在磁盘上的标识)
NAME:打开文件的确切名称
  • FD列中的文件描述符:
    (1)cwd值表示应用程序的当前工作目录,这是该应用程序启动的目录,除非它本身对这个目录进行更改,
    txt类型的文件是程序代码,如应用程序二进制文件本身或共享库,如上列表中显示的/sbin/init程序。
    其次数值表示应用程序的文件描述符,这是打开该文件时返回的一个整数。如上的最后一行文件/dev/initctl,其文件描述符为 10。
    (2)u 表示该文件被打开并处于读取/写入模式,而不是只读 ? 或只写 (w) 模式。
    同时还有大写 的W 表示该应用程序具有对整个文件的写锁。
    该文件描述符用于确保每次只能打开一个应用程序实例。
    初始打开每个应用程序时,都具有三个文件描述符,从0到2,分别表示标准输入、输出和错误流。所以大多数应用程序所打开的文件的FD都是从3开始。
  • Type列:
    (1)文件和目录分别称为REG和DIR。
    (2)CHR表示字符;(fopen,打开文件)
    (3)BLK表示块设备;
    (4)UNIX、FIFO和IPv4,分别表示UNIX 域套接字、先进先出(FIFO)队列和网际协议(IP)套接字。
    (5)FIFO表示先进先出;(popen,pipe)
    (6)inet表示网际协议(IP)套接字tcp/udp;(socket)
    (7)netlink表示netlink

12.关于lsof命令查看某个文件被哪个进程打开

  • 不要加-r参数
    -r参数是一直重复检测当前打开的文件
lsof /home/www/phpinfo.php
  • lsof其他用法
lsof `which httpd` //那个进程在使用apache的可执行文件
lsof /etc/passwd //那个进程在占用/etc/passwd
lsof /dev/hda6 //那个进程在占用hda6
lsof /dev/cdrom //那个进程在占用光驱
lsof -c sendmail //查看sendmail进程的文件使用情况
lsof -c courier -u ^zahn //显示出那些文件被以courier打头的进程打开,但是并不属于用户zahn
lsof -p 30297 //显示那些文件被pid为30297的进程打开
lsof -D /tmp 显示所有在/tmp文件夹中打开的instance和文件的进程。但是symbol文件并不在列

lsof -u1000 //查看uid是100的用户的进程的文件使用情况
lsof -utony //查看用户tony的进程的文件使用情况
lsof -u^tony //查看不是用户tony的进程的文件使用情况(^是取反的意思)
lsof -i //显示所有打开的端口
lsof -i:80 //显示所有打开80端口的进程
lsof -i -U //显示所有打开的端口和UNIX domain文件
lsof -i UDP@[url]www.akadia.com:123 //显示那些进程打开了到www.akadia.com的UDP的123(ntp)端口的链接
lsof -i tcp@ohaha.ks.edu.tw:ftp -r //不断查看目前ftp连接的情况(-r,lsof会永远不断的执行,直到收到中断信号,+r,lsof会一直执行,直到没有档案被显示,缺省是15s刷新)
lsof -i tcp@ohaha.ks.edu.tw:ftp -n //lsof -n 不将IP转换为hostname,缺省是不加上-n参数
  • lsof |grep delete,可以看到很多僵尸文件,正确的处理方式:linux系统空间不足,不重启进程,清理僵尸文件。 lsof看到delete状态的文件的解释:
    linux系统空间不足,lsof看到异常的delete状态的文件。
    linux下删除文件后没有释放空间
    Linux文件已删除,引用未释放(deleted)

11.gdb打印正在运行程序的全局变量

gdb -p 进程pid <<EOF
p 变量名字
EOF

或者
gdb --quiet -nx -p 进程pid<<EOF
p 变量名字
detach
EOF

12.Linux内核调度策略查看

  • 使用指令:ps -efc
    查看所有线程:ps -efL
TS  SCHED_OTHER
FF  SCHED_FIFO
RR  SCHED_RR
B   SCHED_BATCH
ISO SCHED_ISO
IDL SCHED_IDLE
  • 查看进程的调度策略及优先级
chrt -p $pid

比如,我想查看Emacs进程的调度策略及优先级那么可以
chrt -p $(pidof emacs)
pid 1032 当前的调度策略︰ SCHED_OTHER
pid 1032 的当前调度优先级:0
  • 查看每种策略的有效优先级范围
chrt -m

SCHED_OTHER 最小/最大优先级    : 0/0
SCHED_FIFO 最小/最大优先级     : 1/99
SCHED_RR 最小/最大优先级       : 1/99
SCHED_BATCH 最小/最大优先级    : 0/0
SCHED_IDLE 最小/最大优先级     : 0/0
SCHED_DEADLINE 最小/最大优先级 : 0/0
  • 设置进程的调度策略
chrt [-b/-f/-o/-r/-i/-d] -p [$priority] $pid