一、生活中“死锁”的场景
三个人,甲乙丙, 甲借了丙的钱,丙借了乙的钱,乙借了甲的钱。
甲找乙还钱,乙说:“别人还我 我就还你 ”,甲说:“好,那我等你”
乙找丙还钱,丙说:“别人还我 我就还你 ”,乙说:“好,那我等你”
丙找甲还钱,甲说:“别人还我 我就还你 ”,丙说:“好,那我等你”
三个人就这么等着,天长地久。
夫妻俩,吵架后,丈夫:你先给我道歉,我就给你道歉。 妻子:你先给我道歉,我就给你道歉。
两个人互相不让就僵持不下了。
二、代码中的死锁场景
代码中的锁就是上面的“钱”,三个人就是三个线程。
代码实例
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex3 = PTHREAD_MUTEX_INITIALIZER;
#define OPEN_LOG
#ifdef OPEN_LOG
#define log(cnt, ...) fprintf(stdout, "[LOCK][%s][%d]: " cnt "\n", __func__, __LINE__, ##__VA_ARGS__)
#else
#define log(cnt, ...) NULL
#endif
void *pthread_funA(void *arg) {
pthread_mutex_lock(&mutex1); // 借钱
log("get mtx1 success");
sleep(1);
log("will get mtx2");
pthread_mutex_lock(&mutex2); // 要钱
log("get mtx2 success");
pthread_mutex_unlock(&mutex2); // 存钱
pthread_mutex_unlock(&mutex1); // 还钱
return NULL;
}
void *pthread_funB(void *arg) {
pthread_mutex_lock(&mutex2);
log("get mtx2 success");
sleep(1);
log("will get mtx3");
pthread_mutex_lock(&mutex3);
log("get mtx2 success");
pthread_mutex_unlock(&mutex3);
pthread_mutex_unlock(&mutex2);
return NULL;
}
void *pthread_funC(void *arg) {
pthread_mutex_lock(&mutex3);
log("get mtx3 success");
sleep(1);
log("will get mtx1");
pthread_mutex_lock(&mutex1);
log("get mtx2 success");
pthread_mutex_unlock(&mutex1);
pthread_mutex_unlock(&mutex3);
return NULL;
}
int main(void) {
pthread_t tid1, tid2, tid3;
pthread_create(&tid1, NULL, pthread_funA, NULL);
pthread_create(&tid2, NULL, pthread_funB, NULL);
pthread_create(&tid3, NULL, pthread_funC, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_join(tid3, NULL);
return 0;
}
执行后程序不会退出
$ ./a.out
[LOCK][pthread_funA][21]: get mtx1 success
[LOCK][pthread_funB][33]: get mtx2 success
[LOCK][pthread_funC][45]: get mtx3 success
[LOCK][pthread_funC][47]: will get mtx1
[LOCK][pthread_funB][35]: will get mtx3
[LOCK][pthread_funA][23]: will get mtx2
^C
图示
总结并分析解决方案
通过图示,可以看出三个线程构成一个有向的环路,如果能把两个线程间的的指向(依赖)关系记录下来,并实时检测判断这些关系是否构成一个回路,如果发现有回路则说明有死锁现象。
这就需要“图式”存储结构,我这里选择“邻接表”。
根据代码逻辑,在即将加锁前,找下是否有线程持锁,有的话就构成了一个指向关系,即一条有向边。把这条边关系加入到邻接矩阵中即可。
这里又有个问题,持锁前,找哪个线程已经持过这个锁,则需要在线程持锁后把这个关系保存下来才能找到,所以又需要一个数组或链表保存。
1. 实现带有环路检测的“邻接表”
2. 数组/链表 保存已经持锁的关系
3. 持锁前把指向关系(有向边)加入到“图”中,并检测是否构成回路。
4. 由于我们的检测代码不能影响原有的项目代码,则需要把线程创建和持锁的操作都用dlsym函数hook主,在里面做我们需要的逻辑即可,这就是“外挂式”操作。
三、“邻接矩阵”的环路检测
这里只提供写好的代码,并分析环路检测的实现。不展开讨论图的实现。(可自己查阅)
/*
代码为了死锁检测专门写的 邻接链表创建的接口 及 环路检测接口
并未实现深度、广度搜索函数
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#define OPEN_LOG
#ifdef OPEN_LOG
#define log(cnt, ...) fprintf(stdout, "[map][%s][%d]: " cnt "\n", __func__, __LINE__, ##__VA_ARGS__)
#else
#define log(cnt, ...) NULL
#endif
#define MAX_VNODS 100
typedef uint64_t vnode_id_t;
struct vnode {
vnode_id_t id;
uint8_t seekFlag; // 0:unvisted 1:visting 2:visted
struct vnode *next;
};
struct vnode varry[MAX_VNODS];
char tmplog[1000] = {0};
void map_init(void) {
int i;
memset(varry, 0, sizeof(struct vnode) * MAX_VNODS);
}
int get_pos_from_id(vnode_id_t id) {
int i;
for (i = 0; i < MAX_VNODS; i++) {
if (varry[i].id == id) {
return i;
}
}
log("seek pos err");
return -1;
}
/// @brief
/// @param id , must be > 0
/// @return
int map_add_vnode(vnode_id_t id) {
int i = get_pos_from_id(0);
if (i != -1) {
// log("will add pos:%d", i);
varry[i].next = NULL;
varry[i].id = id;
return 0;
}
return -1;
}
void next_seek(struct vnode *next) {
if (next == NULL) {
return;
} else {
next_seek(next->next);
// log("will free: %p", next);
free(next);
next = NULL;
}
return;
}
int map_rm_vnode(vnode_id_t id) {
int i = get_pos_from_id(id);
if (i != -1) {
varry[i].id = 0;
if (varry[i].next != NULL) {
next_seek(varry[i].next);
}
}
return 0;
}
/// @brief add 'from' point to 'to'
/// @param from
/// @param to
/// @return
int map_add_edge(vnode_id_t from, vnode_id_t to) {
if (get_pos_from_id(to) == -1) {
return -1;
}
int pos = get_pos_from_id(from);
if (pos == -1) return -1;
struct vnode *newNext = malloc(sizeof(struct vnode));
if (newNext == NULL) {
log("malloc err");
exit(EXIT_FAILURE);
}
newNext->id = to;
newNext->next = NULL;
struct vnode *tmp = &varry[pos];
for (;tmp->next != NULL; tmp = tmp->next);
tmp->next = newNext;
// log("add %p attach to %ld", newNext, from);
return 0;
}
int map_rm_edge(vnode_id_t from, vnode_id_t to) {
if (get_pos_from_id(to) == -1) {
return -1;
}
int pos = get_pos_from_id(from);
if (pos == -1) return -1;
struct vnode *tmp = &varry[pos];
for (; tmp != NULL && tmp->next != NULL; tmp = tmp->next) {
if (tmp->next->id == to) {
struct vnode *newNext = tmp->next->next;
free(tmp->next);
tmp->next = newNext;
return 0;
}
}
return -1;
}
int traverse_vnode_arry_from_pos(int pos) {
int i;
struct vnode *tmp = &varry[pos];
// log("id: %ld", tmp->id); // 遍历打印每个节点打印时可以打开
if (varry[pos].seekFlag == 1) return -1; // 已经动态访问中 说明遇到环形关系了
if (varry[pos].seekFlag == 2) return 0; // 访问过了这个节点下的所有关系 则返回
varry[pos].seekFlag = 1; // 标记为 开始访问
sprintf(tmplog + strlen(tmplog), "%ld-->", tmp->id);
for (tmp = tmp->next; tmp != NULL; tmp = tmp->next) {
int seekp = get_pos_from_id(tmp->id);
if (varry[seekp].seekFlag == 0) {
int ret = traverse_vnode_arry_from_pos(seekp);
if (ret == -1) return ret;
} else if (varry[seekp].seekFlag == 1) {
sprintf(tmplog + strlen(tmplog), "%ld", varry[seekp].id);
log("has the circle: %ld", varry[seekp].id);
return -1;
}
}
// pos节点下的所有关系以及递归访问结束没遇到环形关系 标记为 已访问
varry[pos].seekFlag = 2;
memset(tmplog, 0, sizeof(tmplog));
return 0;
}
/// @brief 遍历所有节点
/// @return 如果遇到环形关系 返回-1 否则0
int traverse_all_vnodes() {
int i;
for (i = 0; i < MAX_VNODS; i++) {
if (varry[i].id > 0) varry[i].seekFlag = 0;
}
memset(tmplog, 0, sizeof(tmplog));
for (i = 0; i < MAX_VNODS; i++) {
if (varry[i].seekFlag || varry[i].id == 0) continue;
if (traverse_vnode_arry_from_pos(i) == -1) {
return -1;
}
}
return 0;
}
int main(int argc, char *argv[]) {
map_init();
map_add_vnode(1);
map_add_vnode(2);
map_add_vnode(3);
map_add_vnode(4);
map_add_vnode(5);
map_add_edge(1, 2);
map_add_edge(1, 5);
map_add_edge(2, 3);
map_add_edge(3, 4);
map_add_edge(4, 1);
map_add_edge(4, 2);
map_add_edge(5, 2);
// map_rm_edge(1, 4);
if (traverse_all_vnodes() < 0) {
log("has circle: %s", tmplog);
}
map_rm_vnode(1);
map_rm_vnode(2);
map_rm_vnode(3);
map_rm_vnode(4);
map_rm_vnode(5);
return 0;
}
$ ./a.out
[map][traverse_vnode_arry_from_pos][134]: id: 1
[map][traverse_vnode_arry_from_pos][134]: id: 5
[map][traverse_vnode_arry_from_pos][134]: id: 2
[map][traverse_vnode_arry_from_pos][134]: id: 3
[map][traverse_vnode_arry_from_pos][134]: id: 4
[map][traverse_vnode_arry_from_pos][146]: has the circle: 1
[map][main][192]: has circle: 1-->5-->2-->3-->4-->1
环路检测的核心逻辑在于使用深度优先搜索(DFS)遍历图,并通过标记节点的访问状态来判断是否存在环路。以下是环路检测逻辑的详细解释:
核心思想
-
节点访问状态的三种标记:
- 未访问 (unvisited,
visitState = 0
): 初始状态,节点未被访问。 - 访问中 (visiting,
visitState = 1
): 节点正在被访问,表示当前递归路径中已经访问到该节点。 - 已访问 (visited,
visitState = 2
): 节点及其所有邻居都已访问完成,递归路径回溯到该节点后,其状态被标记为已访问。
- 未访问 (unvisited,
-
环路的判定:
- 如果在访问某节点时,其邻居节点的状态为
visiting
(访问中),说明邻居节点已在当前递归路径中,再次访问到它就形成了一个环。 - 如果状态为
visited
,说明该节点已被完全处理,不需要再次递归。
- 如果在访问某节点时,其邻居节点的状态为
-
路径记录:
- 在遍历路径时,记录经过的节点 ID,方便输出完整路径(包括环路路径)。
环路检测逻辑的具体实现
1. traverse_all_vnodes
函数
功能:
- 遍历所有节点,初始化节点的访问状态。
- 对未访问的节点调用
traverse_check_cycle
递归检查是否存在环路。
逻辑:
- 遍历节点数组
varry
。 - 如果节点状态为
unvisited
且有有效 ID,则调用递归函数检查。 - 如果检测到环路,立即返回并打印路径。
2. traverse_check_cycle
函数
功能:
- 使用递归的方式进行深度优先搜索,检查从当前节点开始的路径中是否存在环路。
逻辑:
- 检测当前节点状态:
- 如果节点状态为
visiting
,说明已经形成环路,记录环路路径并返回true
。 - 如果状态为
visited
,说明无需处理,返回false
。
- 如果节点状态为
- 标记当前节点为
visiting
:- 记录当前节点为正在访问状态。
- 将节点 ID 追加到路径记录中。
- 递归访问所有邻居节点:
- 遍历当前节点的邻居(即链表中的
next
节点)。 - 对每个邻居节点,根据其 ID 找到对应的位置,并递归调用
traverse_check_cycle
。 - 如果某个邻居返回
true
(即检测到环路),立即返回。
- 遍历当前节点的邻居(即链表中的
- 回溯时标记节点为
visited
:- 如果所有邻居访问完成且没有环路,标记当前节点为已访问状态。
四、dlsym的hook方法
dlsym
是用于动态链接库符号解析的一个常用函数,通常与 LD_PRELOAD
配合使用,可以用来对动态库中的函数进行 Hook(拦截并重定义)。以下是一个详细的说明和示例代码,帮助理解 dlsym
的 Hook 使用。
核心思路
- 目的:通过
LD_PRELOAD
替换原生动态库的函数行为。例如,我们希望在调用malloc
时记录分配信息。 - 实现:
- 使用
dlsym(RTLD_NEXT, "function_name")
获取被 Hook 函数的真实地址。 - 编写同名函数,重写其逻辑,同时调用真实函数以实现功能增强或修改。
- 使用
- 关键点:
dlsym(RTLD_NEXT, "function_name")
是解析同名函数在后续库中的实际实现。- 防止递归调用:在 Hook 函数内部调用真实函数时,必须避免重新进入 Hook 函数。
示例:Hook malloc
目标:拦截 malloc
调用,在分配内存时打印日志,并最终返回原始函数结果。
Code
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
// 定义函数指针以保存原始 malloc 函数地址
static void* (*real_malloc)(size_t size) = NULL;
// 自定义 malloc 函数(重写原生 malloc)
void* malloc(size_t size) {
if (!real_malloc) {
// 通过 dlsym 获取原始 malloc 的地址
real_malloc = dlsym(RTLD_NEXT, "malloc");
if (!real_malloc) {
fprintf(stderr, "Error locating original malloc: %s\n", dlerror());
exit(EXIT_FAILURE);
}
}
// 打印日志信息
fprintf(stderr, "Hooked malloc: Allocating %zu bytes\n", size);
// 调用真实的 malloc 函数
void* ptr = real_malloc(size);
// 打印分配结果
fprintf(stderr, "Hooked malloc: Allocated at %p\n", ptr);
return ptr;
}
// 同理,可以 Hook 其他函数
void free(void* ptr) {
static void (*real_free)(void*) = NULL;
if (!real_free) {
real_free = dlsym(RTLD_NEXT, "free");
if (!real_free) {
fprintf(stderr, "Error locating original free: %s\n", dlerror());
exit(EXIT_FAILURE);
}
}
// 打印日志信息
fprintf(stderr, "Hooked free: Freeing memory at %p\n", ptr);
// 调用真实的 free 函数
real_free(ptr);
}
编译和运行
-
编译为共享库:
gcc -shared -fPIC -o hookmalloc.so hookmalloc.c -ldl
-
设置环境变量
LD_PRELOAD
并运行程序:LD_PRELOAD=./hookmalloc.so ./your_program
例如:
LD_PRELOAD=./hookmalloc.so ls
在运行
ls
的过程中,所有内存分配调用都会被 Hook 并记录日志。
示例输出
运行 ls
时可能会输出如下内容:
Hooked malloc: Allocating 64 bytes
Hooked malloc: Allocated at 0x55f92d5a7000
Hooked malloc: Allocating 128 bytes
Hooked malloc: Allocated at 0x55f92d5a7080
Hooked free: Freeing memory at 0x55f92d5a7000
Hooked free: Freeing memory at 0x55f92d5a7080
关键问题和解决方法
-
递归调用问题:
- 如果在 Hook 函数内部直接调用原始函数,而未正确通过
dlsym
获取原函数地址,可能导致无限递归。 - 解决:使用
real_malloc
保存原始函数地址,并确保仅调用真实的malloc
。
- 如果在 Hook 函数内部直接调用原始函数,而未正确通过
-
性能影响:
- Hook 函数会增加额外的日志或逻辑处理,可能对性能有一定影响。
- 优化:在 Hook 中避免过多的操作,仅记录关键信息。
-
多线程安全:
- 使用
dlsym
是线程安全的,但若 Hook 函数中有共享状态,需要加锁保护。
- 使用
扩展:Hook 多个函数
可以将多个函数的 Hook 实现集中在一个动态库中,例如同时 Hook malloc
, free
, realloc
。
应用场景
- 调试和监控:追踪内存分配和释放,检测内存泄漏。
- 行为修改:对动态库的行为进行增强或调整,例如限制内存分配的最大值。
- 模拟环境:在测试中替换特定函数的实现。
六、死锁检测的代码实现
map.h
#ifndef __MAP_H
#define __MAP_H
#include <stdint.h>
#include <pthread.h>
typedef pthread_t vnode_id_t;
#define MAX_VNODS 100 // 支持检测的最多元素数
void map_init(void);
/// @brief
/// @param id , must be > 0
/// @return -1 失败 0成功
int map_add_vnode(vnode_id_t id);
int map_rm_vnode(vnode_id_t id);
/// @brief add 'from' point to 'to'
/// @param from
/// @param to
/// @return -1 失败 0成功
int map_add_edge(vnode_id_t from, vnode_id_t to);
/// @return -1 失败 0成功
int map_rm_edge(vnode_id_t from, vnode_id_t to);
/// @brief 遍历所有节点
/// @return 如果遇到环形关系 返回-1 否则0
int traverse_all_vnodes();
#endif
map.c
/*
代码为了死锁检测专门写的 邻接链表创建的接口 及 环路检测接口
并未实现深度、广度搜索函数
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "map.h"
#define OPEN_LOG
#ifdef OPEN_LOG
#define log(cnt, ...) fprintf(stdout, "[map][%s][%d]: " cnt "\n", __func__, __LINE__, ##__VA_ARGS__)
#else
#define log(cnt, ...) NULL
#endif
struct vnode {
vnode_id_t id;
uint8_t seekFlag; // 0:unvisted 1:visting 2:visted
struct vnode *next;
};
struct vnode varry[MAX_VNODS];
char tmplog[1000] = {0};
void map_init(void) {
memset(varry, 0, sizeof(struct vnode) * MAX_VNODS);
}
static int get_pos_from_id(vnode_id_t id) {
int i;
for (i = 0; i < MAX_VNODS; i++) {
if (varry[i].id == id) {
return i;
}
}
// log("seek pos err");
return -1;
}
/// @brief
/// @param id , must be > 0
/// @return
int map_add_vnode(vnode_id_t id) {
int i = get_pos_from_id(0);
if (i != -1) {
// log("will add pos:%d", i);
varry[i].next = NULL;
varry[i].id = id;
return 0;
}
return -1;
}
static void next_seek(struct vnode *next) {
if (next == NULL) {
return;
} else {
next_seek(next->next);
// log("will free: %p", next);
free(next);
next = NULL;
}
return;
}
int map_rm_vnode(vnode_id_t id) {
int i = get_pos_from_id(id);
if (i != -1) {
varry[i].id = 0;
if (varry[i].next != NULL) {
next_seek(varry[i].next);
}
}
return 0;
}
/// @brief add 'from' point to 'to'
/// @param from
/// @param to
/// @return
int map_add_edge(vnode_id_t from, vnode_id_t to) {
if (get_pos_from_id(to) == -1) {
return -1;
}
int pos = get_pos_from_id(from);
if (pos == -1) return -1;
struct vnode *newNext = malloc(sizeof(struct vnode));
if (newNext == NULL) {
log("malloc err");
exit(EXIT_FAILURE);
}
newNext->id = to;
newNext->next = NULL;
struct vnode *tmp = &varry[pos];
for (;tmp->next != NULL; tmp = tmp->next);
tmp->next = newNext;
// log("add %p attach to %ld", newNext, from);
return 0;
}
int map_rm_edge(vnode_id_t from, vnode_id_t to) {
if (get_pos_from_id(to) == -1) {
return -1;
}
int pos = get_pos_from_id(from);
if (pos == -1) return -1;
struct vnode *tmp = &varry[pos];
for (; tmp != NULL && tmp->next != NULL; tmp = tmp->next) {
if (tmp->next->id == to) {
struct vnode *newNext = tmp->next->next;
free(tmp->next);
tmp->next = newNext;
return 0;
}
}
return -1;
}
static int traverse_vnode_arry_from_pos(int pos) {
struct vnode *tmp = &varry[pos];
// log("id: %ld", tmp->id); // 遍历打印每个节点打印时可以打开
if (varry[pos].seekFlag == 1) return -1; // 已经动态访问中 说明遇到环形关系了
if (varry[pos].seekFlag == 2) return 0; // 访问过了这个节点下的所有关系 则返回
varry[pos].seekFlag = 1; // 标记为 开始访问
sprintf(tmplog + strlen(tmplog), "%ld-->", tmp->id);
for (tmp = tmp->next; tmp != NULL; tmp = tmp->next) {
int seekp = get_pos_from_id(tmp->id);
if (varry[seekp].seekFlag == 0) {
int ret = traverse_vnode_arry_from_pos(seekp);
if (ret == -1) return ret;
} else if (varry[seekp].seekFlag == 1) {
sprintf(tmplog + strlen(tmplog), "%ld", varry[seekp].id);
log("has the circle: %ld", varry[seekp].id);
return -1;
}
}
// pos节点下的所有关系以及递归访问结束没遇到环形关系 标记为 已访问
varry[pos].seekFlag = 2;
memset(tmplog, 0, sizeof(tmplog));
return 0;
}
/// @brief 遍历所有节点
/// @return 如果遇到环形关系 返回-1 否则0
int traverse_all_vnodes() {
int i;
for (i = 0; i < MAX_VNODS; i++) {
if (varry[i].id > 0) varry[i].seekFlag = 0;
}
memset(tmplog, 0, sizeof(tmplog));
for (i = 0; i < MAX_VNODS; i++) {
if (varry[i].seekFlag || varry[i].id == 0) continue;
if (traverse_vnode_arry_from_pos(i) == -1) {
return -1;
}
}
return 0;
}
#if 0
int main(int argc, char *argv[]) {
map_init();
map_add_vnode(1);
map_add_vnode(2);
map_add_vnode(3);
map_add_vnode(4);
map_add_vnode(5);
map_add_edge(1, 2);
map_add_edge(1, 5);
map_add_edge(2, 3);
map_add_edge(3, 4);
map_add_edge(4, 1);
map_add_edge(4, 2);
map_add_edge(5, 2);
// map_rm_edge(1, 4);
if (traverse_all_vnodes() < 0) {
log("has circle: %s", tmplog);
}
map_rm_vnode(1);
map_rm_vnode(2);
map_rm_vnode(3);
return 0;
}
#endif
deadlock.h
#ifndef __DEADLOCK_H_
#define __DEADLOCK_H_
void deadlock_init(void);
#endif
deadlock.c
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "map.h"
#define MAX_THREADS MAX_VNODS
#define OPEN_LOG
#ifdef OPEN_LOG
#define log(cnt, ...) fprintf(stdout, "[LOCK][%s][%d]: " cnt "\n", __func__, __LINE__, ##__VA_ARGS__)
#else
#define log(cnt, ...) NULL
#endif
static int (*pthread_create_f)(pthread_t *, const pthread_attr_t *, void *(*) (void *), void *) = NULL;
static int (*pthread_mutex_lock_f)(pthread_mutex_t *) = NULL;
static int (*pthread_mutex_unlock_f)(pthread_mutex_t *) = NULL;
struct thread_mxt {
pthread_t tid;
pthread_mutex_t *mxt;
};
static struct thread_mxt g_info[MAX_THREADS];
void thread_mxt_info_init(void) {
memset(g_info, 0, sizeof(struct thread_mxt) * MAX_THREADS);
return;
}
int thread_mxt_info_add(pthread_t tid, pthread_mutex_t *pmxt) {
int i;
for (i = 0; i < MAX_THREADS; i++) {
if (g_info[i].mxt == NULL && g_info[i].tid == 0) {
g_info[i].mxt = pmxt;
g_info[i].tid = tid;
return 0;
}
}
return -1;
}
int thread_mxt_info_rm(pthread_t tid, pthread_mutex_t *pmxt) {
int i;
for (i = 0; i < MAX_THREADS; i++) {
if (g_info[i].mxt == pmxt && g_info[i].tid == tid) {
g_info[i].mxt = NULL;
g_info[i].tid = 0;
return 0;
}
}
return -1;
}
pthread_t thread_mxt_info_seek(pthread_mutex_t *pmxt) {
int i;
for (i = 0; i < MAX_THREADS; i++) {
if (g_info[i].mxt == pmxt) {
return g_info[i].tid;
}
}
return 0;
}
// 监控线程
void* thread_monitor(void *arg) {
(void)arg;
while (1) {
if (traverse_all_vnodes() == -1) {
log("find circle");
}
sleep(5);
}
}
void deadlock_init(void) {
pthread_t tid;
if (pthread_create_f == NULL) {
pthread_create_f = dlsym(RTLD_NEXT, "pthread_create");
if (!pthread_create_f) {
log("Error locating original malloc: %s", dlerror());
exit(EXIT_FAILURE);
}
}
if (pthread_mutex_lock_f == NULL) {
pthread_mutex_lock_f = dlsym(RTLD_NEXT, "pthread_mutex_lock");
if (!pthread_mutex_lock_f) {
log("Error locating original malloc: %s", dlerror());
exit(EXIT_FAILURE);
}
}
if (pthread_mutex_unlock_f == NULL) {
pthread_mutex_unlock_f = dlsym(RTLD_NEXT, "pthread_mutex_unlock");
if (!pthread_mutex_unlock_f) {
log("Error locating original malloc: %s", dlerror());
exit(EXIT_FAILURE);
}
}
map_init();
thread_mxt_info_init();
int ret = pthread_create(&tid, NULL, thread_monitor, NULL);
if (ret < 0) {
log("pthread create err");
return;
}
}
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg) {
if (pthread_create_f == NULL) return -1;
int ret = pthread_create_f(thread, attr, start_routine, arg);
if (ret >= 0) {
map_add_vnode(*thread);
} else {
log("thread create err: %d", ret);
}
return ret;
}
int pthread_mutex_lock(pthread_mutex_t *mutex) {
if (pthread_mutex_lock_f == NULL) return 0;
pthread_t tid = thread_mxt_info_seek(mutex);
if (tid > 0) {
map_add_edge(pthread_self(), tid);
}
int ret = pthread_mutex_lock_f(mutex);
map_rm_edge(pthread_self(), tid);
thread_mxt_info_add(pthread_self(), mutex);
return ret;
}
int pthread_mutex_unlock(pthread_mutex_t *mutex) {
thread_mxt_info_rm(pthread_self(), mutex);
return pthread_mutex_unlock_f(mutex);
}
test.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include "deadlock.h"
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex3 = PTHREAD_MUTEX_INITIALIZER;
#define OPEN_LOG
#ifdef OPEN_LOG
#define log(cnt, ...) fprintf(stdout, "[LOCK][%s][%d]: " cnt "\n", __func__, __LINE__, ##__VA_ARGS__)
#else
#define log(cnt, ...) NULL
#endif
void *pthread_funA(void *arg) {
(void)arg;
pthread_mutex_lock(&mutex1); // 借钱
log("get mtx1 success");
sleep(1);
log("will get mtx2");
pthread_mutex_lock(&mutex2); // 要钱
log("get mtx2 success");
pthread_mutex_unlock(&mutex2); // 存钱
pthread_mutex_unlock(&mutex1); // 还钱
return NULL;
}
void *pthread_funB(void *arg) {
(void)arg;
pthread_mutex_lock(&mutex2);
log("get mtx2 success");
sleep(1);
log("will get mtx3");
pthread_mutex_lock(&mutex3);
log("get mtx2 success");
pthread_mutex_unlock(&mutex3);
pthread_mutex_unlock(&mutex2);
return NULL;
}
void *pthread_funC(void *arg) {
(void)arg;
pthread_mutex_lock(&mutex3);
log("get mtx3 success");
sleep(1);
log("will get mtx1");
pthread_mutex_lock(&mutex1);
log("get mtx2 success");
pthread_mutex_unlock(&mutex1);
pthread_mutex_unlock(&mutex3);
return NULL;
}
int main(void) {
pthread_t tid1, tid2, tid3;
deadlock_init();
pthread_create(&tid1, NULL, pthread_funA, NULL);
pthread_create(&tid2, NULL, pthread_funB, NULL);
pthread_create(&tid3, NULL, pthread_funC, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_join(tid3, NULL);
return 0;
}
Makefile
CC = gcc
AR = ar
CFLAGS = -Wall -Wextra -O0 -pthread -ldl -g
LIB = libdeadlock.a
TARGET = test
OBJS = deadlock.o map.o
all: $(TARGET)
$(TARGET): $(LIB)
$(CC) test.c -o $@ -L./ -ldeadlock $(CFLAGS)
$(LIB): $(OBJS)
$(AR) rcs $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(OBJS) $(TARGET)
执行结果
$ make
gcc -Wall -Wextra -O0 -pthread -ldl -g -c deadlock.c -o deadlock.o
ar rcs libdeadlock.a deadlock.o map.o
gcc test.c -o test -L./ -ldeadlock -Wall -Wextra -O0 -pthread -ldl -g
$ ./test
[LOCK][pthread_funA][22]: get mtx1 success
[LOCK][pthread_funC][48]: get mtx3 success
[LOCK][pthread_funB][35]: get mtx2 success
[LOCK][pthread_funC][50]: will get mtx1
[LOCK][pthread_funB][37]: will get mtx3
[LOCK][pthread_funA][24]: will get mtx2
[map][traverse_vnode_arry_from_pos][140]: has the circle: 140313289017088
[LOCK][thread_monitor][75]: find circle
^C
总结
可以看到test.c代码中仅调用了deadlock_init函数,线程创建和加锁解锁的代码均未变动。
map.c中的打印还有问题,你们可自己再优化改下。不过功能没问题。