目录
即看即用
区别:
使用 pstack 获得的进程堆栈是程序的静态信息,而使用 strace 可以获得程序的动态信息,即程序现在正在做什么(执行哪些系统调用和所接收的信号)
ps 查找进程的pid
pstack 打印进程或者线程的栈信息
strace 统计每一步系统调用花费的时间
1,starce
查看一个进程的系统调用,包括参数,返回值,执行消耗的时间。
strace /path/to/myexe #查看myexe 执行起来后的系统调用
strace -p 1002297 #查看进程ID为1002297的进程的系统调用
strace -f -F -o ./strace.txt myexe
-f -F:同时跟踪fork和vfork出来的进程,-o:输出写到 ./strace.txt ,myexe 要启动和调试的程序。
strace -o output.txt -T -tt -e trace=all -p 28979
跟踪28979进程的所有系统调用(-e trace=all),并统计系统调用的花费时间,以及开始时间(并以可视化的时分秒格式显示),最后将记录结果存在output.txt文件里面。
让strace只记录execve的调用用这个命令:
strace -f -o configure-strace.txt -e execve ./configure
2,pstack
显示每个进程所有线程栈的调⽤栈。可以使⽤pstack来查看进程正在挂起的执⾏⽅法,也可以⽤来查看进程的本地线程堆栈。它实际上是gstack的一个链接,而gstack本身是基于gdb封装的shell脚本。pstack是gdb的一部分,如果系统没有pstack命令,使用yum搜索安装gdb即可。
pstack用来跟踪进程栈,比如我们发现一个服务一直处于work状态(如假死状态,好似死循环),使用这个命令就能轻松定位问题所在;可以在一段时间内,多执行几次pstack,若发现代码栈总是停在同一个位置...
pstack 171211
查找进程卡死原因的工具
starce
1、 strace -p [进程号]
strace -p 1002297
strace: Process 1002297 attached
futex(0x7fcbb95f3f84, FUTEX_WAIT_PRIVATE, 1, NULL
可以看到死在了futex(0x7fcbb95f3f84, FUTEX_WAIT_PRIVATE, 1, NULL
用strace查找进程卡死原因实例
最近遇到进程卡死的情况,但是自己调试的过程中并不一定能复现,都是需要运行一段时间某些条件下才会触发,对于这种运行着不能破坏现场的情况,我们可以使用gdb -p和strace -p来跟踪。
首先我们用ps auxf查看我们的进程执行到了哪一步:
可以看到执行到了docker exec -i 178.20.1.229_0115034556 ls然后就卡死了
然后我们进一步通过strace查看执行这个操作死在哪个系统回调了:
这里可以看到死在了系统回调read这里,描述符19的具体意义我们可以进入/proc/pid/fd再查看一下:
我们可以发现,19代表的是pipe,我们这里是死在了读pipe上面。
/********************************************************************************************************************************/
分割线,后面再次出现这个问题
我们先用ps auxf查看进程号和进程执行到了哪一步,可以看到进程号是27678,卡在docker exec
root 27678 0.3 0.4 512172 16500 Sl python /wns/cloud/app/com_host/main.pyc
root 25011 0.0 0.0 4332 652 S \_ /bin/sh -c docker exec -i mongo_docker_master ls
root 25014 0.0 0.2 136592 10600 Sl \_ docker exec -i mongo_docker_master ls
继续用strace -p 27678跟踪,发现卡在read,文件描述符是14
root@localhost:/# strace -p 27678
Process 27678 attached
read(14,
接着我们cd /proc/27678/,在这里我们可以查看进程状态
root@localhost:/proc/27678# cat status
Name: python
State: S (sleeping)
Tgid: 27678
Ngid: 0
Pid: 27678
PPid: 27677
查看进程的内核堆栈的调试信息,wchan表示导致进程睡眠或者等待的函数
root@localhost:/proc/27678# cat stack
[<ffffffff811a91ab>] pipe_wait+0x6b/0x90
[<ffffffff811a9c04>] pipe_read+0x344/0x4f0
[<ffffffff811a00bf>] do_sync_read+0x7f/0xb0
[<ffffffff811a0681>] vfs_read+0xb1/0x130
[<ffffffff811a1110>] SyS_read+0x80/0xe0
[<ffffffff818d4c49>] system_call_fastpath+0x16/0x1b
[<ffffffffffffffff>] 0xffffffffffffffff
root@localhost:/proc/27678# cat wchan
pipe_wait
现在我们查看一下进程打开的文件描述符14代表什么,pipe文件
root@localhost:/proc/27678# ls -l ./fd
total 0
lr-x------ 1 root root 64 Mar 26 17:19 0 -> pipe:[30690124]
l-wx------ 1 root root 64 Mar 26 17:19 1 -> pipe:[30690125]
lrwx------ 1 root root 64 Mar 26 17:19 10 -> socket:[30691732]
lr-x------ 1 root root 64 Mar 26 17:19 11 -> /dev/urandom
lrwx------ 1 root root 64 Mar 26 17:19 12 -> socket:[30719611]
lrwx------ 1 root root 64 Mar 26 17:19 13 -> socket:[30719610]
lr-x------ 1 root root 64 Mar 26 17:19 14 -> pipe:[38483750]
我们已经可以确定main创建子进程执行shell命令docker exec -i mongo_docker_master ls,同时通过pipe和子进程通信,结果卡在了read pipe上。
其实在这里我们也可以使用lsof来定位,可以看到进程27678打开的FD 14是pipe,这里u代表可读可写,r代表可读
sangfor ~ # lsof -d 14
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
mongod 1907 root 14u REG 251,0 36864 130683 /wns/data/mongodb/db/collection-7--588642557116981989.wt
syslog-ng 3446 root 14u unix 0xffff88012227d800 0t0 40557736 /dev/log
dockerd 4025 root 14u unix 0xffff8800b8d5d800 0t0 13941 /run/docker/libnetwork/a73bd949b5fbb89c2b8bec3b4ac6af0a948a944958c8b037d9e6c9b324b44331.sock
docker-co 9382 root 14u 0000 0,9 0 9553 anon_inode
docker-co 21204 root 14u 0000 0,9 0 9553 anon_inode
python 27678 root 14r FIFO 0,8 0t0 38483750 pipe
也可以直接查看进程27678打开的,可以看到14是pipe
sangfor ~ # lsof -p 27678
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
python 27678 root 0r FIFO 0,8 0t0 30690124 pipe
python 27678 root 1w FIFO 0,8 0t0 30690125 pipe
python 27678 root 2w FIFO 0,8 0t0 30690126 pipe
python 27678 root 3u 0000 0,9 0 9553 anon_inode
python 27678 root 4u 0000 0,9 0 9553 anon_inode
python 27678 root 5u pack 30691718 0t0 unknown type=SOCK_RAW
python 27678 root 6w REG 251,0 76106652 130565 /wns/data/com_host/etc/config/err.log
python 27678 root 7u IPv4 30691716 0t0 TCP Sangfor:53102->Sangfor:42457 (ESTABLISHED)
python 27678 root 8u IPv4 30691717 0t0 TCP Sangfor:42457->Sangfor:53102 (ESTABLISHED)
python 27678 root 9u IPv4 30691731 0t0 TCP db.sdwan:54072->sdwan.io:27017 (ESTABLISHED)
python 27678 root 10u IPv4 30691732 0t0 TCP db.sdwan:54074->sdwan.io:27017 (ESTABLISHED)
python 27678 root 11r CHR 1,9 0t0 30690329 /dev/urandom
python 27678 root 12u IPv4 30719611 0t0 TCP db.sdwan:51404->db.sdwan:37017 (ESTABLISHED)
python 27678 root 13u IPv4 30719610 0t0 TCP db.sdwan:47124->db.sdwan:27017 (ESTABLISHED)
python 27678 root 14r FIFO 0,8 0t0 38483750 pipe
————————————————
原文链接:https://blog.youkuaiyun.com/peng314899581/article/details/79064616
pstack
(更多强大功能:linux命令-- pstack命令(跟踪进程栈) - milkty - 博客园)
pstack用来跟踪进程栈,这个命令在排查进程问题时非常有用,比如我们发现一个服务一直处于work状态(如假死状态,好似死循环),使用这个命令就能轻松定位问题所在;可以在一段时间内,多执行几次pstack,若发现代码栈总是停在同一个位置,那个位置就需要重点关注,很可能就是出问题的地方;
示例:查看bash程序进程栈:
/opt/app/tdev1$ps -fe| grep bash
tdev1 7013 7012 0 19:42 pts/1 00:00:00 -bash
tdev1 11402 11401 0 20:31 pts/2 00:00:00 -bash
tdev1 11474 11402 0 20:32 pts/2 00:00:00 grep bash
/opt/app/tdev1$pstack 7013
#0 0x00000039958c5620 in __read_nocancel () from /lib64/libc.so.6
#1 0x000000000047dafe in rl_getc ()
#2 0x000000000047def6 in rl_read_key ()
#3 0x000000000046d0f5 in readline_internal_char ()
#4 0x000000000046d4e5 in readline ()
#5 0x00000000004213cf in ?? ()
#6 0x000000000041d685 in ?? ()
#7 0x000000000041e89e in ?? ()
#8 0x00000000004218dc in yyparse ()
#9 0x000000000041b507 in parse_command ()
#10 0x000000000041b5c6 in read_command ()
#11 0x000000000041b74e in reader_loop ()
#12 0x000000000041b2aa in main ()
strace
strace常用来跟踪进程执行时的系统调用和所接收的信号。 在Linux世界,进程不能直接访问硬件设备,当进程需要访问硬件设备(比如读取磁盘文件,接收网络数据等等)时,必须由用户态模式切换至内核态模式,通过系统调用访问硬件设备。strace可以跟踪到一个进程产生的系统调用,包括参数,返回值,执行消耗的时间。
示例:
$strace cat /dev/null
execve("/bin/cat", ["cat", "/dev/null"], [/* 22 vars */]) = 0
brk(0) = 0xab1000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f29379a7000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
...
每一行都是一条系统调用,等号左边是系统调用的函数名及其参数,右边是该调用的返回值。 strace 显示这些调用的参数并返回符号形式的值。strace 从内核接收信息,而且不需要以任何特殊的方式来构建内核。
跟踪可执行程序
strace -f -F -o ~/straceout.txt myserver
-f -F选项告诉strace同时跟踪fork和vfork出来的进程,-o选项把所有strace输出写到~/straceout.txt里 面,myserver是要启动和调试的程序。
跟踪服务程序
strace -o output.txt -T -tt -e trace=all -p 28979
跟踪28979进程的所有系统调用(-e trace=all),并统计系统调用的花费时间,以及开始时间(并以可视化的时分秒格式显示),最后将记录结果存在output.txt文件里面。
链接:https://www.jianshu.com/p/d6686cb72f68
利用pstack 和 strace分析程序在哪里耗时?
目前测试了nws服务器代码中的while(!stop){...}死循环,发现while循环一次有的线程需要100ms左右,这个时间就有点长了。于是利用如下linux的ps、pstack和strace对程序进行了分析。
ps 查找进程的pid
pstack 打印进程或者线程的栈信息
strace 统计每一步系统调用花费的时间
实例分析
1. ps -aux | grep nws
可以看出nws的pid为171211
2. pstack 171211
打印出nws进程下所有的线程栈信息。可以看出程序好几个线程都卡在pwrite这一步。
3. strace -o output.txt -T -tt -e trace=all -p 171264
nws进程中的171264线程进行系统调用跟踪, 将输出的信息保存在output.txt中
可以发现pwrite调用一次需要这么长的时间,这里就可以优化一下。我用的是SATA盘,考虑用SSD固态硬盘,这时间就可以缩短为十几微妙。
如果还有什么好的办法,欢迎大家的意见。
本段摘自:https://blog.youkuaiyun.com/lotluck/article/details/77943152
pstack和starce的区别是什么?能举个例子说明么
pstack
和 strace
是两个常用的 Linux 调试工具,但它们的功能和使用场景有所不同。以下是它们的区别以及示例说明。
一、pstack
和 strace
的区别
1. pstack
-
功能:
-
pstack
用于打印正在运行的进程的调用栈(stack trace)。 -
它通过读取进程的内存和符号表,显示当前进程中所有线程的调用栈信息。
-
-
使用场景:
-
用于分析进程的当前执行状态,查看函数调用关系。
-
适合调试死锁、卡死或性能瓶颈问题。
-
-
优点:
-
简单易用,直接显示调用栈。
-
-
缺点:
-
只能显示当前时刻的调用栈,无法跟踪动态行为。
-
2. strace
-
功能:
-
strace
用于跟踪进程的系统调用(system calls)和信号(signals)。 -
它可以显示进程与操作系统内核之间的交互,包括文件操作、网络通信、进程控制等。
-
-
使用场景:
-
用于分析进程的系统调用行为,排查文件访问、网络通信等问题。
-
适合调试程序崩溃、权限问题或资源访问问题。
-
-
优点:
-
可以动态跟踪进程的行为,显示详细的系统调用信息。
-
-
缺点:
-
输出信息较多,可能影响程序性能。
-
二、示例说明
示例程序
以下是一个简单的 C 程序,模拟文件读取和死锁场景:
#include <stdio.h> #include <pthread.h> #include <unistd.h> #include <fcntl.h> pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER; void* thread1(void* arg) { pthread_mutex_lock(&mutex1); sleep(1); pthread_mutex_lock(&mutex2); // 死锁点 printf("Thread 1\n"); pthread_mutex_unlock(&mutex2); pthread_mutex_unlock(&mutex1); return NULL; } void* thread2(void* arg) { pthread_mutex_lock(&mutex2); sleep(1); pthread_mutex_lock(&mutex1); // 死锁点 printf("Thread 2\n"); pthread_mutex_unlock(&mutex1); pthread_mutex_unlock(&mutex2); return NULL; } int main() { pthread_t t1, t2; pthread_create(&t1, NULL, thread1, NULL); pthread_create(&t2, NULL, thread2, NULL); // 模拟文件读取 int fd = open("test.txt", O_RDONLY); char buf[100]; read(fd, buf, 100); close(fd); pthread_join(t1, NULL); pthread_join(t2, NULL); return 0; }
编译并运行:
bash
复制
gcc -o test test.c -lpthread ./test
1. 使用 pstack
分析死锁
假设程序卡死,可以使用 pstack
查看调用栈。
步骤:
-
找到进程 ID:
bash
复制
ps aux | grep test
假设进程 ID 为
1234
。 -
使用
pstack
打印调用栈:bash
复制
pstack 1234
输出示例:
plaintext
复制
Thread 2 (Thread 0x7f8c8b7fe700 (LWP 1235)): #0 0x00007f8c8c0d4f0d in __lll_lock_wait () from /lib/x86_64-linux-gnu/libpthread.so.0 #1 0x00007f8c8c0d07ca in pthread_mutex_lock () from /lib/x86_64-linux-gnu/libpthread.so.0 #2 0x000055f5a5b4b1d9 in thread2 (arg=0x0) at test.c:20 #3 0x00007f8c8c0ce609 in start_thread () from /lib/x86_64-linux-gnu/libpthread.so.0 #4 0x00007f8c8bf0b293 in clone () from /lib/x86_64-linux-gnu/libc.so.6 Thread 1 (Thread 0x7f8c8bffe700 (LWP 1234)): #0 0x00007f8c8c0d4f0d in __lll_lock_wait () from /lib/x86_64-linux-gnu/libpthread.so.0 #1 0x00007f8c8c0d07ca in pthread_mutex_lock () from /lib/x86_64-linux-gnu/libpthread.so.0 #2 0x000055f5a5b4b15d in thread1 (arg=0x0) at test.c:11 #3 0x00007f8c8c0ce609 in start_thread () from /lib/x86_64-linux-gnu/libpthread.so.0 #4 0x00007f8c8bf0b293 in clone () from /lib/x86_64-linux-gnu/libc.so.6
分析:
-
可以看到两个线程都卡在
pthread_mutex_lock
,说明发生了死锁。
2. 使用 strace
分析文件读取
假设程序文件读取失败,可以使用 strace
跟踪系统调用。
步骤:
-
使用
strace
运行程序:bash
复制
strace ./test
输出示例:
plaintext
复制
open("test.txt", O_RDONLY) = -1 ENOENT (No such file or directory) read(-1, 0x7ffd8b7fe700, 100) = -1 EBADF (Bad file descriptor) close(-1) = -1 EBADF (Bad file descriptor)
分析:
-
open
系统调用返回-1
,说明文件test.txt
不存在。 -
后续的
read
和close
调用也失败,因为文件描述符无效。
三、总结
pstack
的使用场景
-
适合分析进程的当前状态,尤其是多线程程序的死锁、卡死问题。
-
示例:通过
pstack
发现两个线程卡在pthread_mutex_lock
,确认死锁。
strace
的使用场景
-
适合分析进程的系统调用行为,尤其是文件操作、网络通信等问题。
-
示例:通过
strace
发现open
系统调用失败,确认文件不存在。
结合使用
-
如果需要分析进程的整体行为,可以先使用
strace
跟踪系统调用。 -
如果发现进程卡死或性能问题,再使用
pstack
查看调用栈。
通过结合 pstack
和 strace
,可以更全面地分析和调试 Linux 进程的行为。
两个线程都卡在 pthread_mutex_lock,说明发生了死锁?为什么不能是其他线程在占用,它们俩个在等等呢
你提到的这个问题非常好!两个线程都卡在 pthread_mutex_lock
并不一定意味着死锁,也可能是它们正在等待其他线程释放锁。要确定是否发生了死锁,需要进一步分析线程的调用栈和锁的持有情况。
一、死锁的定义
死锁是指两个或多个线程在执行过程中,因为争夺资源而造成的一种互相等待的现象。死锁的四个必要条件是:
-
互斥条件:资源一次只能被一个线程占用。
-
占有并等待:线程持有至少一个资源,并等待获取其他被占用的资源。
-
不可抢占:线程已持有的资源不能被其他线程强行抢占。
-
循环等待:存在一个线程等待的循环链。
如果以上四个条件同时满足,就会发生死锁。
二、如何判断是否发生了死锁
1. 分析调用栈
通过 pstack
或 gdb
查看线程的调用栈,可以了解线程当前的状态。如果两个线程都在等待对方持有的锁,就可能是死锁。
示例:
假设有两个线程:
-
线程 1:
-
持有锁 A,正在等待锁 B。
-
-
线程 2:
-
持有锁 B,正在等待锁 A。
-
这种情况下,两个线程互相等待,形成了死锁。
调用栈示例:
plaintext
复制
Thread 1: #0 pthread_mutex_lock (mutex=0x123456) at pthread_mutex_lock.c:123 #1 thread1 () at test.c:10 Thread 2: #0 pthread_mutex_lock (mutex=0x654321) at pthread_mutex_lock.c:123 #1 thread2 () at test.c:20
如果锁 A 和锁 B 分别是 0x123456
和 0x654321
,并且线程 1 持有锁 A,线程 2 持有锁 B,那么这就是死锁。
2. 检查锁的持有情况
通过工具(如 gdb
)可以检查锁的持有者,确认是否有线程互相等待对方持有的锁。
使用 gdb
检查锁的持有者:
-
附加到进程:
bash
复制
gdb -p <pid>
-
查看线程信息:
bash
复制
info threads
-
切换到某个线程:
bash
复制
thread <thread_id>
-
查看锁的状态:
bash
复制
p mutex1 p mutex2
如果发现锁的持有者是另一个线程,并且另一个线程也在等待当前线程持有的锁,就可以确认死锁。
3. 使用死锁检测工具
一些工具可以自动检测死锁,例如:
-
valgrind --tool=drd
:用于检测多线程程序中的数据竞争和死锁。 -
helgrind
:Valgrind 的一个工具,专门用于检测多线程问题。
三、为什么不能是其他线程在占用锁?
如果两个线程都在等待锁,确实有可能是其他线程占用了锁,而不是死锁。要区分这两种情况,需要进一步分析:
-
检查锁的持有者:
-
如果锁的持有者是第三个线程,那么这两个线程只是在等待,而不是死锁。
-
-
查看线程数量:
-
如果程序中只有这两个线程,那么它们互相等待锁就是死锁。
-
-
分析锁的获取顺序:
-
如果所有线程都按照相同的顺序获取锁,通常不会发生死锁。如果顺序不一致,就可能发生死锁。
-
四、示例分析
示例 1:死锁
c
复制
void* thread1(void* arg) { pthread_mutex_lock(&mutex1); sleep(1); pthread_mutex_lock(&mutex2); // 等待 mutex2 printf("Thread 1\n"); pthread_mutex_unlock(&mutex2); pthread_mutex_unlock(&mutex1); return NULL; } void* thread2(void* arg) { pthread_mutex_lock(&mutex2); sleep(1); pthread_mutex_lock(&mutex1); // 等待 mutex1 printf("Thread 2\n"); pthread_mutex_unlock(&mutex1); pthread_mutex_unlock(&mutex2); return NULL; }
-
线程 1 持有
mutex1
,等待mutex2
。 -
线程 2 持有
mutex2
,等待mutex1
。 -
这是典型的死锁。
示例 2:非死锁(等待其他线程释放锁)
c
复制
void* thread1(void* arg) { pthread_mutex_lock(&mutex1); sleep(1); pthread_mutex_lock(&mutex2); // 等待 mutex2 printf("Thread 1\n"); pthread_mutex_unlock(&mutex2); pthread_mutex_unlock(&mutex1); return NULL; } void* thread2(void* arg) { pthread_mutex_lock(&mutex1); // 等待 mutex1 sleep(1); pthread_mutex_lock(&mutex2); printf("Thread 2\n"); pthread_mutex_unlock(&mutex2); pthread_mutex_unlock(&mutex1); return NULL; }
-
线程 1 持有
mutex1
,等待mutex2
。 -
线程 2 等待
mutex1
。 -
如果
mutex2
被第三个线程持有,那么线程 1 和线程 2 只是在等待,而不是死锁。
五、总结
-
两个线程都卡在
pthread_mutex_lock
并不一定是死锁,也可能是它们在等待其他线程释放锁。 -
要确认是否发生了死锁,需要:
-
分析调用栈,查看线程的等待关系。
-
检查锁的持有者,确认是否有循环等待。
-
使用工具(如
gdb
或valgrind
)进一步分析。
-
-
如果两个线程互相等待对方持有的锁,就是死锁;如果它们等待的锁被其他线程持有,则不是死锁。