从 pstack 实现来看 /proc 目录
实现
pstack 是 Linux 下查看进程运行堆栈的程序,如分析一个进程 ID 为 1518 的程序:
[root@AlexWoo-CentOS ~]# pstack 1518
#0 0x0076c424 in __kernel_vsyscall ()
#1 0x009b2508 in __epoll_wait_nocancel () from /lib/libc.so.6
#2 0x080688c8 in ngx_epoll_process_events ()
#3 0x080613ac in ngx_process_events_and_timers ()
#4 0x0806715f in ngx_worker_process_cycle ()
#5 0x080659b4 in ngx_spawn_process ()
#6 0x08066604 in ngx_start_worker_processes ()
#7 0x080676c7 in ngx_master_process_cycle ()
#8 0x0804b6d3 in main ()
[root@AlexWoo-CentOS ~]#
pstack 其实是一个 Shell 脚本,位于 /usr/bin 目录下
[root@AlexWoo-CentOS ~]# cat /usr/bin/pstack
#!/bin/sh
if test $# -ne 1; then
echo "Usage: `basename $0 .sh` <process-id>" 1>&2
exit 1
fi
# 用于查看指定的进程是否存在
if test ! -r /proc/$1; then
echo "Process $1 not found." 1>&2
exit 1
fi
# 用于查看指定进程是否为多线程运行:
# 对于新的内核,直接查看 /proc/pid/task 下文件数是否大于 1,
# 该目录存放了当前运行进程的所有线程 ID
# 对于旧的内核,查看 /proc/pid/maps 文件中进程是否链接了 pthread 库,
# 这种方法并不一定准确,因为链接了 pthread 库不代表该进程有多线程
# 这里主要原因还是上面说的,gdb 中的 bt 命令只能查看主线程的堆栈,
# 而查看所有线程的堆栈需要使用 thread apply all bt
# GDB doesn't allow "thread apply all bt" when the process isn't
# threaded; need to peek at the process to determine if that or the
# simpler "bt" should be used.
backtrace="bt"
if test -d /proc/$1/task ; then
# Newer kernel; has a task/ directory.
if test `/bin/ls /proc/$1/task | /usr/bin/wc -l` -gt 1 2>/dev/null ; then
backtrace="thread apply all bt"
fi
elif test -f /proc/$1/maps ; then
# Older kernel; go by it loading libpthread.
if /bin/grep -e libpthread /proc/$1/maps > /dev/null 2>&1 ; then
backtrace="thread apply all bt"
fi
fi
GDB=${GDB:-/usr/bin/gdb}
if $GDB -nx --quiet --batch --readnever > /dev/null 2>&1; then
readnever=--readnever
else
readnever=
fi
# 这里调用 gdb 接入内存,并使用 bt 或 thread apply all bt 查看程序堆栈
# --quiet 用于关闭 gdb 接入的版本信息显示
# -nx 便是不读取 .gdbinit 文件 ^-^ 具体这个文件读与不读有什么影响不太清楚
# --readnever 是关闭堆栈中函数所在文件位置的信息,如果需要实际可以把这个参数删掉
# 这里 /proc/pid/exe 是进程实际执行文件的软链接
# Run GDB, strip out unwanted noise.
$GDB --quiet $readnever -nx /proc/$1/exe $1 <<EOF 2>&1 |
set width 0
set height 0
set pagination no
$backtrace
EOF
/bin/sed -n \
-e 's/^\((gdb) \)*//' \
-e '/^#/p' \
-e '/^Thread/p'
总结
- pstack 使用 gdb 实现了函数堆栈的查看
- 运行中的进程,在 /proc 目录下会有一个以进程 pid 命名的目录。实际内核在处理过程中,会把进程运行的内存挂载到 /proc 目录下的文件上,不要轻易对这个目录下的文件进行操作,也不要把自己的文件放到该目录下
- /proc/$pid/maps 文件下有执行程序当前链接的库的信息
/proc/$pid/task 目录下为执行进程的所有线程的线程 ID (每个线程一个目录),以此,我们可以写一个脚本,用于查看系统中哪些程序是多线程运行的
#!/bin/bash procspid=`/bin/ls /proc/ | grep [0-9][0-9]*` for proc in $procspid do if test `/bin/ls /proc/$proc/task | /usr/bin/wc -l` -gt 1 then echo $proc fi done
进一步探讨,我们发现 /proc/$pid 目录下面还有 fd 和 fdinfo 目录
[root@AlexWoo-CentOS 1518]# ls -l fd fdinfo
fd:
总用量 0
lrwx------. 1 nobody nobody 64 9月 26 18:47 0 -> /dev/pts/0
lrwx------. 1 nobody nobody 64 9月 26 18:47 1 -> /dev/pts/0
l-wx------. 1 nobody nobody 64 9月 26 18:08 2 -> /usr/local/nginx/logs/error.log
l-wx------. 1 nobody nobody 64 9月 26 18:47 4 -> /usr/local/nginx/logs/error.log
l-wx------. 1 nobody nobody 64 9月 26 18:47 5 -> /usr/local/nginx/logs/access.log
lrwx------. 1 nobody nobody 64 9月 26 18:47 6 -> socket:[12574]
lrwx------. 1 nobody nobody 64 9月 26 18:47 7 -> socket:[12578]
lrwx------. 1 nobody nobody 64 9月 26 18:47 8 -> [eventpoll]
lrwx------. 1 nobody nobody 64 9月 26 18:47 9 -> [eventfd]
fdinfo:
总用量 0
-r--------. 1 nobody nobody 0 9月 26 18:47 0
-r--------. 1 nobody nobody 0 9月 26 18:47 1
-r--------. 1 nobody nobody 0 9月 26 18:47 2
-r--------. 1 nobody nobody 0 9月 26 18:47 4
-r--------. 1 nobody nobody 0 9月 26 18:47 5
-r--------. 1 nobody nobody 0 9月 26 18:47 6
-r--------. 1 nobody nobody 0 9月 26 18:47 7
-r--------. 1 nobody nobody 0 9月 26 18:47 8
-r--------. 1 nobody nobody 0 9月 26 18:47 9
[root@AlexWoo-CentOS 1518]# cat fdinfo/9
pos: 0
flags: 02
[root@AlexWoo-CentOS 1518]# cat fdinfo/0
pos: 0
flags: 0100002
[root@AlexWoo-CentOS 1518]# cat fdinfo/4
pos: 132296
flags: 0102001
[root@AlexWoo-CentOS 1518]# cat fdinfo/2
pos: 132296
flags: 0102001
可以看见:
- fd 目录下的数字是当前进程打开文件的文件描述符,0 是标准输入,1 是标准输出,2 是标准错误,他们分别被指向了 /dev/pts/0,/dev/pts/0 和 /usr/local/nginx/logs/error.log 下,8 是一个 epollfd
- fdinfo 中的文件描述符保存了当前文件的偏移量 pos 和文件设置的标识位 flags
实际上在 /proc/pid/task/tid 下也有 fd 和 fdinfo 目录,只不过这两个目录存放的是对应线程连接的文件描述符信息
还有一些其他信息,可以查找相关资料进一步学习