死锁检测组件

死锁检测组件

一 死锁存在的条件

死锁,是指多个线程或者进程在运行过程中因争夺资源而造成的一种僵局,当进程或者线程

处于这种僵持状态,若无外力作用,它们将无法再向前推进。

二 死锁检测的原理

资源获取环可以采用图来存储,使用有向图来存储。 线程 A 获取线程 B 已占用的锁,则为线程 A 指向线程 B 。 线程 B 获取成功的锁就是线程B占用的锁。检测原理就是采用另一个线程定时对图进程检测是否有环的存在。转化为有向图的检测。

三 有向图的构建

在这里,我使用了邻接表法构建了一个有向图。这个有向图的数据结构如下:

struct source_type {
	uint64 id;
	enum Type type;
	uint64 lock_id;
	int degress;
};

struct vertex { //有向图的点
	struct source_type s;
	struct vertex *next;
};

struct task_graph { //临接法的图
	struct vertex list[MAX]; //数组上的是点,每个数组元素后面挂的节点是由数组元素指向节点的有向边
	int num; //有向图中点的个数
	struct source_type locklist[MAX]; //线程和锁的映射关系
	int lockidx; //锁的数量
};

当一个线程上了一把锁,这个线程和锁便会作为节点加入这个有向图中。

当一个线程加了一把已经被别的线程加过的锁,这个线程便会作为一个节点加入有向图,并在有向图中加一个指向拥有目标锁的线程的边。

四 三个原语操作

为了完成第三部分中的最后两点功能,我设置了三个函数,分别是lock_before、lock_after和unlock_after。
它们的用法是:

...
lock_before(selfid, (uint64)mutex);
pthread_mutex_lock_f(mutex);
lock_after(selfid, (uint64)mutex);
...
pthread_mutex_unlock_f(mutex);
unlock_after(selfid, (uint64)mutex);

这三个源于操作的参数都是线程id和锁的地址。下面详细介绍它们的作用

  1. lock_before:
    这个函数紧挨着加锁的函数之前调用。
    如果他要加的锁已经被别的线程加过,那么他首先将作为一个点加入有向图,然后将那个线程也加入有向图,最后在把他指向那个线程的有向边加入有向图中。
    如果要加的锁没有被其他线程使用,那什么也不做。
  2. lock_after:
    这个函数紧挨着加锁的函数之后调用。
    如果这把锁没有在有向图的locklist数组中,就把这把锁和线程id组成一个元素加入到locklist中。
    如果这把锁在有向图中,如果,本线程有指向其他拥有锁的线程的边,那删除掉这条边。
  3. unlock_after:
    如果locklist里面有包含这个锁的元素,且这把锁已经没有别的线程使用或将要使用,那就从locklist中删除掉这个元素。

通过这三个操作,可以将加解锁和有向图关联起来。

五 hook包装函数

如果每次使用加解锁函数还需要在前后相关地方加上那三个函数,未免太过于复杂。为了方便用户的使用,这里,我使用了hook技术包装了这些操作。

Hook即钩子,截获API调用的技术,是将执行流程重定向到你自己的代码,如可以使程序运行时调用你自己实现的malloc函数代替调用系统库中的malloc函数。

这里给出必要的代码。

...
#include <dlfcn.h>
...
int pthread_mutex_lock(pthread_mutex_t *mutex) {
    pthread_t selfid = pthread_self(); 
	lock_before(selfid, (uint64)mutex);
    pthread_mutex_lock_f(mutex);
	lock_after(selfid, (uint64)mutex);
}

int pthread_mutex_unlock(pthread_mutex_t *mutex) {
	pthread_t selfid = pthread_self();
    pthread_mutex_unlock_f(mutex);
	unlock_after(selfid, (uint64)mutex);
}

static int init_hook() {
    pthread_mutex_lock_f = dlsym(RTLD_NEXT, "pthread_mutex_lock"); //之后再代码中调用pthread_mutex_lock_f 就相当于调用pthread_mutex_lock系统调用,下同。
    pthread_mutex_unlock_f = dlsym(RTLD_NEXT, "pthread_mutex_unlock");
}
...

int main(int argc,char **argv) {
	init_hook();
	...
}

dlsym(RTLD_NEXT, “pthread_mutex_lock”)的用处是,返回动态库中第一个名为pthread_mutex_lock的函数的指针。

当在main函数中使用了ini_hook后,以后在这个份原文件中调用pthread_mutex_lock和pthread_mutex_unlock时,就会调用我们自己定义的这两个函数。

六 启动线程检测

将死锁线程程序作为一个单独运行的线程独立运行。这个程序会循环检测所有已经加锁的线程,分析这个线程会不会和其他线程形成一个环。代码如下:

void check_dead_lock(void) {
	int i = 0;
	deadlock = 0;
	for (i = 0;i < tg->num;i ++) {
		if (deadlock == 1) break;
		search_for_cycle(i);
	}
	if (deadlock == 0) {
		printf("no deadlock\n");
	}
}

static void *thread_routine(void *args) {
	while (1) {
		sleep(5);
		check_dead_lock();
	}
}

void start_check(void) {
	tg = (struct task_graph*)malloc(sizeof(struct task_graph));
	tg->num = 0;
	tg->lockidx = 0;
	pthread_t tid;
	pthread_create(&tid, NULL, thread_routine, NULL);
}

int main(int argc,char **argv) {
	...
	start_check(); //启动线程检测程序
	...
}

这样,当其他线程再调用各种锁时,如果出现了环,则会立即再终端中打印出可能会有死锁的提示,并打印出形成环的全部线程id。

七 我的代码

我编写了一个可以直接使用的死锁检测组件,感兴趣的朋友可以直接拿过去使用,代码链接是:https://github.com/q962875152/mycode/blob/master/deadlock.c

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值