怎么通过 Linux 调用命令查找到死锁

死锁简单概要

产生的原因:

  1. 系统资源竞争
  2. 进程推进顺序非法

死锁产生的必要条件

  1. 互斥条件:进程要求对所分配的资源进行排他型控制,即在一段时间内,某个资源仅为一个进程所占有
  2. 不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来主动释放
  3. 请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放
  4. 循环等待条件:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被链中下一个进程所请求。

如何解决死锁的方法便是破坏上述四个条件之一:

  1. 资源一次性分配,剥夺请求和保持条件
  2. 可剥夺资源,当进程新的资源未得到满足时,释放已占有的资源,从而破坏不可剥夺条件
  3. 资源有序分配法,系统给每类资源赋予一个序号,每个进程按编号递增的请求资源,释放则相反,从而破坏循环等待条件
  4. 死锁检测,每个进程请求锁或者获得锁的情况都利用某种数据结构记录,通过遍历锁的关系图,来检查是否由死锁存在,如果存在就进行相关的操作。收回所有锁重新分配或者回退之类

Linux 命令

gdb + attach

测试代码

#include <unistd.h>
#include <pthread.h>
#include <string.h>

// 互斥量
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex3 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex4 = PTHREAD_MUTEX_INITIALIZER;

static int sequenc1 = 0;
static int sequenc2 = 0;

int func1(){
    pthread_mutex_lock(&mutex1);
    ++sequenc1;
    sleep(1);
    pthread_mutex_lock(&mutex2);
    ++sequenc2;
    pthread_mutex_unlock(&mutex2);
    pthread_mutex_unlock(&mutex1);

    return sequenc1;
}

int func2(){
    pthread_mutex_lock(&mutex2);
    ++sequenc2;
    sleep(1);
    pthread_mutex_lock(&mutex1);
    ++sequenc1;
    pthread_mutex_unlock(&mutex1);
    pthread_mutex_unlock(&mutex2);
    return sequenc2;
}

void* thread1(void* arg){
    while( 1 ){
        int iRetValue = func1();
        if( iRetValue == 100000 ){
            pthread_exit(NULL);
        }
    }
}

void* thread2(void* arg){
    while( 1 ){
        int iRetValue = func2();
        if( iRetValue == 10000 ){
            pthread_exit(NULL);
        }
    }
}

void* thread3(void* arg){
    while( 1 ){
        sleep(1);
        char szBuf[128];
        memset(szBuf, 0, sizeof(szBuf));
        strcpy(szBuf, "thread3");
    }
}

void* thread4(void* arg){
    while( 1 ){
        sleep( 1 );
        char szBuf[128];
        memset(szBuf, 0, sizeof(szBuf));
        strcpy(szBuf, "thread3");
    }
}

int main(){
    pthread_t tid[4];
    if( pthread_create(&tid[0], NULL, &thread1, NULL) != 0 ){
        _exit(1);
    }
    if( pthread_create(&tid[1], NULL, &thread2, NULL) != 0 ){
        _exit(1);
    }
    if( pthread_create(&tid[2], NULL, &thread3, NULL) != 0 ){
        _exit(1);
    }
    if( pthread_create(&tid[3], NULL, &thread4, NULL) != 0 ){
        _exit(1);
    }
    sleep(5);
    pthread_join(tid[0], NULL);
    pthread_join(tid[1], NULL);
    pthread_join(tid[2], NULL);
    pthread_join(tid[3], NULL);

    pthread_mutex_destroy(&mutex1);
    pthread_mutex_destroy(&mutex2);
    pthread_mutex_destroy(&mutex3);
    pthread_mutex_destroy(&mutex4);

    return 0;
}

ps -ef | grep lock

在这里插入图片描述

gdb attach uid

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LPIO7eev-1616910960881)(image/image-20210328134735938.png)]

thread info

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kRNIPRwu-1616910960884)(image/image-20210328134710149.png)]
从这里就可以看到 2 线程和 3 线程 调用了 lock_wait() 函数

thread 2

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mm58sSWJ-1616910960889)(image/image-20210328134843068.png)]

where

查看代码调用顺序
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AKt8ZkBQ-1616910960891)(image/image-20210328134922744.png)]

f 2

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3RrDFlXA-1616910960893)(image/image-20210328134952687.png)]

p mutex2

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9L7FdjlK-1616910960894)(image/image-20210328135017256.png)]
线程2在等待 mutex2 锁,用该命令查看得知该锁的持有者是线程 ID = 10916 的线程,也就是线程 3

thread 3 & where

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dxv6Jqm9-1616910960895)(image/image-20210328135213362.png)]

f 2

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KE6GIORx-1616910960896)(image/image-20210328135246241.png)]
进入线程 3, 发现线程3在等待 mutex1 的锁资源,并且发现持有者为 10915 也就是线程 2。由此发现死锁

注意: 在查找资料过程中,发现可能会需要用 pstack 来进行查看运行过程中各线程的运行来大概发现是哪些线程出现问题后,再用 attach 来逐步定位。不过我在使用过程中,pstack 在我的 ubuntu 上运行有问题,并且换了所谓的脚本后还是没有改善,就没用 pstack 了。应该还有更好的方法。
然后在使用 attach 跟踪线程的时候有用户权限问题,我为了方便直接用了 root 命令。请注意

如果需要脚本:

文件位置:sudo vim /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

# 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

# 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'

总结

要学的东西可太多了 = =

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值