原标题:初探Linux内核态——通过proc文件系统作快速问题定位

本文是对一篇blog的翻译,感谢译者Hualet Wang。原文通过一个例子为我们展示了,在分析进程运行缓慢的问题时,strace和pstack都束手无策的情况下,不要忘了还有proc。

简介

文章翻译自Peeking into Linux kernel-land using /proc filesystem for quick’n’dirty troubleshooting

这篇博客的内容完全是关于现代Linux内核的。换句话说,指的是与RHEL6一样使用的2.6.3x系列内核,而不是古老的RHEL5所使用的2.6.18内核,虽然大部分企业都还在使用RHEL5。另外,这篇文章也不会涉及内核调试器或者SystemTap脚本之类的东西,完全是最最简单地在有用的proc文件系统节点上执行“cat /proc/PID/xyz”这样的命令。

定位一个程序“运行缓慢”的问题

下面要举的这个例子是这样的:一个DBA反映说他们的find命令一直运行缓慢,半天都没有什么输出,他们想知道这是为什么。听到这个问题的时候我就大概有直觉造成这个问题的原因,但是他们还是想知道怎么系统地追踪这类问题,并找到解决方案。刚好出问题的现场还在……

还好,系统是运行在OEL6上的,内核比较新,确切地说是2.6.39 UEK2。

首先,让我们看看find进程是否还在:

[root@oel6 ~] # ps -ef | grep find

root 2728827245411: 57pts/ 000: 00: 01find . -type f

root 2733427315011: 57pts/ 100: 00: 00grep find

跑的好好的,PID是27288(请记好这个将会伴随整篇博客的数字)。

那么,我们就从最基础的开始分析它的瓶颈:如果它不是被什么操作卡住了(例如从cache中加载它所需要的内容),它应该是100%的CPU占用率;如果它的瓶颈在IO或者资源竞争,那么它应该是很低的CPU占用率,或者是%0。

我们先看下top:

[root@oel6 ~] # top -cbp 27288

top - 11: 58: 15up 7days, 3: 38, 2users, load average: 1.21, 0.65, 0.47

Tasks: 1total, 0running, 1sleeping, 0stopped, 0zombie

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

27288root 200109m 1160844D 0.00.10: 01.11find . -type f

结果很清楚:这个进程的CPU占用率很低,几乎为零。但是CPU占用低也分情况:一种是进程完全卡住了,根本没有机会获得时间片;另一种是进程在不停进入等待的状态(例如poll动作就是时不时超时后,进程进入休眠状态)。虽然这剩下的细节top还不足以完全给我们展示,但是至少我们知道了这个进程没有在烧CPU时间。

通常情况下,如果进程处于这种状态(%0的CPU占用一般说明进程是卡在了某个系统调用,因为这个系统调用阻塞了,内核需要把进程放到休眠状态),我都会用strace跟踪一下这个进程具体卡在了哪个系统调用。而且,如果进程不是完全卡住了,那进程中的系统调用情况也会在strace的输出中有所展示(因为一般阻塞的系统调用会在超时返回后,过一段时间再进入阻塞等待的状态)。

让我们试试strace:

[root@oel6 ~] # strace -cp 27288

Process 27288attached - 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

尴尬,strace自己也卡住了!半天没有输出,也不响应Ctrl-C,我不得不通过Ctrl-Z把它扔到后台再杀掉它。想简单处理还真是不容易啊。

那只好再试试pstack了(Linux上的pstack只是用shell脚本包了一下GDB)。尽管pstack看不到内核态的内容,但是至少它能告诉我们是哪个系统调用最后执行的(通常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

呵呵,pstack也卡住了,什么输出都没有!

至此,我们还是不知道我们的程序是怎么卡住了,卡在哪里了。

好吧,还怎么进行下去呢?还有一些常用的信息可以搜集——进程的status字段和WCHAN字段,这些使用古老的ps就能查看(或许最开始就应该用ps看看这个进程是不是已经成僵尸进程了):

[root@oel6 ~] # ps -flp 27288

F S UID PID PPID C PRI NI ADDR SZ WCHAN STIME TTY TIME CMD

0D root 27288272450800- 28070rpc_wa 11: 57pts/ 000: 00: 01find . -type f

需要注意的是,你要多运行几次ps以确保进程还在同一个状态(不然在不凑巧的时候获取了一个错误的状态就麻烦了),我这里为了简短就只贴一次输出了。

进程此时正处于D状态,按照man手册的说法,这通常是因为磁盘IO导致的。而WCHAN字段(表示导致程序处于休眠/等待状态的函数调用)则有点儿被切掉了。显然我可以翻一下ps的man手册,看看怎么把这个字段调宽一点好完整打印出来,不过既然我都知道了这个信息来自于proc文件系统,就没这个必要了。

直接从它的源头读一下看看(再次说明一下,多试几次看看,毕竟我们还不知道这个进程到底是不是完全卡死了呢):

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

rpc_ wait_bit_killable

额……这个进程居然在等待某些RPC调用。RPC调用通常意味着这个程序在跟其他进程通信(不管是本地还是远程)。总归,我们还是不知道为什么会卡住。

到底是不是完全卡住了?

在我们揭开这篇文章最后的谜底之前,我们还是先搞清楚这个进程到底是不是完全卡住了。

其实,在新一点的Linux内核中,/proc/PID/status 这个文件可以告诉我们这点:

[root@oel6 ~] # cat /proc/27288/status

Name: find

State: D (disk sleep)

Tgid: 27288

Pid: 27288

PPid: 27245

TracerPid: 0

Uid: 0000

Gid: 0000

FDSize: 256

Groups: 01234610

VmPeak: 112628kB

VmSize: 112280kB

VmLck: 0kB

VmHWM: 1508kB

VmRSS: 1160kB

VmData: 260kB

VmStk: 136kB

VmExe: 224kB

VmLib: 2468kB

VmPTE: 88kB

VmSwap: 0kB

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

(主要看State字段和最后两行:voluntary_ctxt_switches和 nonvoluntary_ctxt_switches)

进程处于D——Disk Sleep(不可打断休眠状态),voluntary_ctxt_switches 和 nonvoluntary_ctxt_switches的数量则能告诉我们这个进程被分给时间片的次数。过几秒再看看这些数字有没有增加,如果没有增加,则说明这个进程是完全卡死的,目前我在追踪的例子就是这个情况,反之,则说明进程不是完全卡住的状态。

顺便提一下,还有两种办法也可以获取进程的上下文切换数量(第二种在老旧的内核上也能工作):

[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字段(等于 nr_voluntary_switches + nr_involuntary_switches),值是27054,跟/proc/PID/schedstat 中的第三个字段是一致的:

[root@oel6 ~] # cat /proc/27288/schedstat

111948031172474550627054

等一段时间,也是一样的结果,数量没有增长……

通过/proc文件系统初探Linux内核态世界

看情况我们的程序是卡死无疑了,strace和pstack这些使用ptrace系统调用来attach到进程上来进行跟踪的调试器也没啥用,因为进程已经完全卡住了,那么ptrace这种系统调用估计也会把自己卡住。(顺便说一下,我曾经用strace来跟踪attach到其他进程上的strace,结果strace把那个进程搞挂了。别说我没警告过你)!

没了strace和pstack,我们还能怎么查程序卡在了哪个系统调用呢?当然是 /proc/PID/syscall 了!

[root@oel6 ~] # cat /proc/27288/syscall

2620xffffffffffffff9c 0x20cf6c8 0x7fff97c52710 0x100 0x100 0x676e776f645f616d 0x7fff97c52658 0x390e2da8ea

这鬼输出怎么看呢?很简单,0x加很大的数字一般就是内存地址(使用pmap类似的工具可以看具体他们指向了什么内容),如果是很小的数字的话,很可能就是一些索引号(例如打开的文件描述符,就像/proc/PID/fd的内容一样)。在这个例子中,因为是syscall文件,你可以”合理猜测“这是一个系统调用号:进程卡在了262号系统调用!

需要注意的是系统调用号在不同的系统上可能是不一致的,所以你必须从一个合适的.h文件中去查看它具体指向了哪个调用。通常情况下,在/usr/include中搜索 syscall* 是个很好的切入点。在我的系统上,这个系统调用是在 /usr/include/asm/unistd_64.h中定义的:

[root@oel6 ~] # grep 262 /usr/include/asm/unistd_64.h

#define __NR_newfstatat 262

根据上面显示的结果可以看到这个系统调用叫 newfstatat,我们只需要man newfstatat就可以知道这是干啥的了。针对系统调用有一个实用小技巧分享:如果man手册中没有发现一个系统调用,那么请尝试删除一些特定的前缀或者后缀(例如man pread64不行就尝试man pread)。还或者,干脆google之……

总之,这个叫“new-fstat-at”的系统调用的作用就是让你可以像 stat 系统调用一样读取文件的属性,我们的程序就是卡在这个操作上,终于在调试这个程序的道路上迈出了一大步,不容易!但是为啥会卡在这个调用呢?

好吧,终于要亮真本事了。隆重介绍:/proc/PID/stack,能让你看到一个进程内核态的调用栈信息的神器,而且只是通过cat一个proc文件!!!

[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。(注意:实际上不一定最顶部的函数就是我们想要的,因为内核可能也执行了 schedule 之类的函数来让程序进入休眠或者运行。这里没有显示可能是因为卡主是因为其他调用卡主了才进入睡眠状态,而不是相反的逻辑)。

多亏了这个神器,我们现在可以从头到尾推导出程序卡主的整个过程和造成最终 rpc_wait_bit_killable 函数卡主的原因了:

最底部的 system_call_fastpath 是一个非常常见的系统调用处理函数,正是它调用了我们刚才一直有疑问的 newfstatat 系统调用。然后,再往上可以看到一堆nfs相关的子函数调用,这样我们基本可以断定正nfs相关的下层代码导致了程序卡住。我没有推断说问题是出在nfs的代码里是因为继续往上可以看到rpc相关的函数,可以推断是nfs为了跟其他进程进行通信又调用了rpc相关的函数——在这个例子中,可能是[kworker/N:N],[nfsiod],[lockd]or[rpciod]

等内核的IO线程,但是这个进程因为某些原因(猜测是网络链接断开之类的问题)再也没有收到响应的回复。

同样的,我们可以利用以上的方法来查看哪些辅助IO的内核线程为什么会卡在网络相关的操作上,尽管kworkers就不简简单单是NFS的RPC通信了。在另外一次问题重现的尝试(通过NFS拷贝大文件)中,我刚好捕捉到一次kwrokers等待网络的情况:

[root@oel6 proc] # for i in `pgrep worker` ; do ps -fp $i ; cat /proc/$i/stack ; done

UID PID PPID C STIME TTY TIME CMD

root 5320Feb14 ? 00: 04: 34[kworker/ 1: 1]

[] __cond_resched+ 0x2a/ 0x40

[] lock_sock_nested+ 0x35/ 0x70

[] tcp_sendmsg+ 0x29/ 0xbe0

[] inet_sendmsg+ 0x48/ 0xb0

[] sock_sendmsg+ 0xef/ 0x120

[] kernel_sendmsg+ 0x41/ 0x60

[] xs_send_kvec+ 0x8e/ 0xa0 [sunrpc]

[] xs_sendpages+ 0x173/ 0x220 [sunrpc]

[] xs_tcp_send_request+ 0x5d/ 0x160 [sunrpc]

[] xprt_transmit+ 0x83/ 0x2e0 [sunrpc]

[] call_transmit+ 0xa8/ 0x130 [sunrpc]

[] __rpc_execute+ 0x66/ 0x1d0 [sunrpc]

[] rpc_async_schedule+ 0x15/ 0x20 [sunrpc]

[] process_one_work+ 0x13e/ 0x460

[] worker_thread+ 0x17c/ 0x3b0

[] kthread+ 0x96/ 0xa0

[] kernel_thread_helper+ 0x4/ 0x10

通过开启内核的tracing肯定可以确切找到是内核的哪两个线程之间再通信,不过限于文章篇幅,这里就不展开了,毕竟这只是一个实用(且简单)的问题追踪定位练习。

诊断和“修复”

多亏了现代内核提供的栈信息存样,我们得以系统地追踪到我们的find命令卡在了哪里——内核中的NFS代码。而且一般情况下,NFS相关卡死,最需要怀疑的就是网络问题。你猜我是怎么重现上面的这个问题的?其实就是在功过NFS挂在虚拟中的一块磁盘,执行find命令,然后让虚拟机休眠……这样就可以重现类似网络(配置或者防火墙)导致的链接默默断掉但是并没有通知TCP另一端的进程的情况。

因为 rpc_wait_bit_killable 是可以直接被安全干掉的函数,这里我们选择通过 kill -9直接干掉它:

[root@oel6 ~] # ps -fp 27288

UID PID PPID C STIME TTY TIME CMD

root 2728827245011: 57pts/ 000: 00: 01find . -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 ~] #

进程被杀掉了,好了,问题解决 :)

注:文章没有翻译完,下面还有一段不是那么有意思的小工具和广告,没动力翻下去了 :P

本文转自:

https://hualet.org/blog/2018/01/23/%E5%88%9D%E6%8E%A2linux%E5%86%85%E6%A0%B8%E6%80%81%E9%80%9A%E8%BF%87proc%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E4%BD%9C%E5%BF%AB%E9%80%9F%E9%97%AE%E9%A2%98%E5%AE%9A%E4%BD%8D/返回搜狐,查看更多

责任编辑:

linux内核态删除文件函数,初探Linux内核态——通过proc文件系统作快速问题定位...相关推荐

  1. linux内核快速倒车,初探Linux内核态——通过proc文件系统作快速问题定位

    这篇博客的内容完全是关于现代Linux内核的.换句话说,指的是与RHEL6一样使用的2.6.3x系列内核,而不是古老的RHEL5所使用的2.6.18内核(都什么鬼了?!),虽然大部分企业都还在使用RH ...

  2. linux rm 删除文件,linux rm命令删除文件详解

    linux rm命令删除文件详解 linux中rm命令可以删除一个目录中的一个或多个文件或目录,也可以将某个目录及其下属的所有文件及其子目录均删除掉.对于链接文件,只是删除整个链接文件,而原有文件保持 ...

  3. linux c 删除文件,linux c remove 删除文件或目录函数

    linux c remove 删除文件或目录函数 头文件:#include remove()函数用于删除指定的文件,其原型如下: int remove(char * filename); [参数]fi ...

  4. linux删除的文件有回收站么,Linux命令行删除文件到回收站

    先说方法,原理后面分析. 由于我使用的是Ubuntu14,这里以该系统为主,其他系统略有区别. 可以新加别名del,用于删除文件. alias del='mv -t ~/.local/share/Tr ...

  5. linux 批量选中文本删除,教程 | 【七牛云】Qshell Linux下批量删除文件教程

    原标题:教程 | [七牛云]Qshell Linux下批量删除文件教程 项目内容 本文将教您使用七牛云提供的Qshell工具在linux下安装并且使用批量查询和批量删除命令行进行远程删除文件. 项目教 ...

  6. Linux 创建、删除文件夹

    Linux 创建.删除文件夹 创建文件夹 创建aa文件夹,查看列表:可以看到已经有文件夹aa; mkdir aa 批量创建文件夹(bb cc dd),查看列表:可以看到有文件夹多了三个(bb cc d ...

  7. linux下的删除文件命令,Linux下删除文件命令

    在linux中删除文件与文件夹我们可以直接使用rm就可以删除了,彻底删除文件或文件夹我们可以使用shred命令来完成,接下来是小编为大家收集的Linux下删除文件命令,希望能帮到大家. Linux下删 ...

  8. linux 如何安全删除文件,Linux系统安全删除文件方法是什么.docx

    PAGE PAGE # Linux系统安全删除文件方法是什么 很多时候我们直接删除文件都会被各种手段恢复,那么怎么 安全删除文件呢?以Linux系统为例.只有通过一些工具,才能安全 地删除数据.具体怎 ...

  9. linux ssh怎样删除文件夹,Linux服务器一键删除文件夹SSH命令

    Linux服务器管理不像Windows服务器管理那么方便,当然Linux也可以直观可视化操作,本人习惯使用SSH命令管理操作,常常会碰到网站产生 大量垃圾文件的苦恼,直接登录FTP删除的话,曾经试过删 ...

  10. [C#]使用CMD命令删除文件函数

    #region 使用CMD命令删除文件函数/// <summary>/// 使用CMD命令删除文件函数/// </summary> /// <param name=&qu ...

最新文章

  1. 在.Net程序中使用log4net记录日志(示例)
  2. JDK与JRE的关系和path的作用浅谈
  3. java 不同数据类型之间的转换
  4. ubuntu 命令整合1
  5. 在php storm中怎么搭建环境,PhpStorm搭建git环境
  6. AKOJ-2037-出行方案
  7. AI框架你只会调包,这种想法很危险!
  8. bootstrap样式
  9. 【华为解决方案】 华为云架构解决方案
  10. Oracle创建一个用户并赋予其他用户表的访问权限
  11. 软件工程(2018)第三次个人作业
  12. 优化工作的冰山一角,app瘦身
  13. foremost使用简介
  14. Python安全攻防-从入门到入狱
  15. layui上传图片回显
  16. 微软收购雅虎要中国政府审批?
  17. python按钮事件,Python按钮的响应事件详解
  18. SQL 使用别名(使用 AS 关键字)
  19. 心跳信号分类---(中)
  20. 做完基线后centos /linux 系统修改密码报passwd: Module is unknown 未知模块 解决思路

热门文章

  1. Presto常用命令:查看版本号
  2. 随机过程第二章part2
  3. mysql查询本年的函数_sql 查询本年、本月、本日记录的语句,附SQL日期函数
  4. python中转义符的用法_一篇文章搞懂python的转义字符及用法
  5. git包名大小写不敏感的百分百管用但是不完美的解决方法
  6. eclipse中导入jar包源文件
  7. 杭电HDUacm2037
  8. 循环神经网络-Recurrent Neural Networks
  9. 计算机视觉领域的一些牛人博客,超有实力的研究机构等的网站链接---个人整理
  10. 数据库新技术:分布式数据库的体系结构,特点与查询优化(思维导图版总结)