【Linux】程序卡死、失败位置|pstack和starce看进程调用|分析耗时函数

目录

即看即用

1,starce

2,pstack

查找进程卡死原因的工具

starce

pstack

利用pstack 和 strace分析程序在哪里耗时?


即看即用

区别:

使用 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 查看调用栈。

步骤:
  1. 找到进程 ID:

    bash

    复制

    ps aux | grep test

    假设进程 ID 为 1234

  2. 使用 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 跟踪系统调用。

步骤:
  1. 使用 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. 互斥条件:资源一次只能被一个线程占用。

  2. 占有并等待:线程持有至少一个资源,并等待获取其他被占用的资源。

  3. 不可抢占:线程已持有的资源不能被其他线程强行抢占。

  4. 循环等待:存在一个线程等待的循环链。

如果以上四个条件同时满足,就会发生死锁。


二、如何判断是否发生了死锁

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 检查锁的持有者:
  1. 附加到进程:

    bash

    复制

    gdb -p <pid>
  2. 查看线程信息:

    bash

    复制

    info threads
  3. 切换到某个线程:

    bash

    复制

    thread <thread_id>
  4. 查看锁的状态:

    bash

    复制

    p mutex1
    p mutex2

如果发现锁的持有者是另一个线程,并且另一个线程也在等待当前线程持有的锁,就可以确认死锁。


3. 使用死锁检测工具

一些工具可以自动检测死锁,例如:

  • valgrind --tool=drd:用于检测多线程程序中的数据竞争和死锁。

  • helgrind:Valgrind 的一个工具,专门用于检测多线程问题。


三、为什么不能是其他线程在占用锁?

如果两个线程都在等待锁,确实有可能是其他线程占用了锁,而不是死锁。要区分这两种情况,需要进一步分析:

  1. 检查锁的持有者

    • 如果锁的持有者是第三个线程,那么这两个线程只是在等待,而不是死锁。

  2. 查看线程数量

    • 如果程序中只有这两个线程,那么它们互相等待锁就是死锁。

  3. 分析锁的获取顺序

    • 如果所有线程都按照相同的顺序获取锁,通常不会发生死锁。如果顺序不一致,就可能发生死锁。


四、示例分析

示例 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 并不一定是死锁,也可能是它们在等待其他线程释放锁。

  • 要确认是否发生了死锁,需要:

    1. 分析调用栈,查看线程的等待关系。

    2. 检查锁的持有者,确认是否有循环等待。

    3. 使用工具(如 gdb 或 valgrind)进一步分析。

  • 如果两个线程互相等待对方持有的锁,就是死锁;如果它们等待的锁被其他线程持有,则不是死锁。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值