博客园  :: 首页  :: 新随笔  :: 管理

3.1.2 死锁检测组件

Posted on 2023-03-11 21:51  wsg_blog  阅读(63)  评论(0编辑  收藏  举报

Linux C/C++服务器

死锁检测组件

死锁存在的条件

死锁,是指多个线程或者进程在运行过程中因争夺资源而造成的一种僵局,当进程或者线程处于这种僵持状态,若无外力作用,它们将无法再向前推进。如下图所示,线程 A 想获取线程 B 的锁,线程 B 想获取线程 C 的锁,线程 C 想获取线程 D 的锁,线程 D 想获取线程 A 的锁,从而构建了一个资源获取环。

死锁的存在是因为有资源获取环的存在,所以只要能检测出资源获取环,就等同于检测出死锁的存在。

多线程死锁demo

typedef

定义回调函数的函数指针

typedef 的作用有以下几点:

  1. typedef 的一个重要用途是定义机器无关的类型。例如,定义一个叫“REAL”的浮点类型,该浮点类型在目标机器上可以获得最高的精度:
    typedef long double REAL;
    如果在不支持 long double 的机器上运行相关代码,只需要修改对应的 typedef 语句,
    typedef double REAL;
  2. 使用 typedef 为现有类型创建别名,给变量定义一个易于记忆且意义明确的新名字,
    typedef unsigned int UINT;
  3. 使用 typedef 简化一些比较复杂的类型声明,函数指针固定的写法:
    typedef void (*PFunCallBack)(char* pMsg, unsigned int nMsgLen);
    上述声明引入了 PFunCallBack 类型作为函数指针的同义字,PFunCallBack 类型定义的指针会指向一个函数,该函数包含两个类型分别为 char* 和 unsigned int 的参数,以及一个类型为 void 的返回值。通常,当函数的参数是一个回调函数时,就可能会使用 typedef 来简化声明。
    例如,承接上面的示例的后续示例如下:
    RedisSubCommand(const string& strKey, PFunCallBack pFunCallback, bool bOnlyOne);
    注意:上述语句中类型名 PFunCallBack 与变量名 pFunCallback 的大小写区别。
    RedisSubCommand 函数的第二个参数是一个回调函数,因此通过“PFunCallBack pFunCallback”(即,类型+变量)的形式,给出对应回调函数(pFunCallback)的地址。
    在这个示例中,如果不使用 typedef 简化声明,RedisSubCommand 的函数声明内容如下:
    RedisSubCommand(const string& strKey, void (*pFunCallback)(char* pMsg, unsigned int nMsgLen), bool bOnlyOne);
    从上面两个函数声明可以看出,在不使用 typedef 的情况下,RedisSubCommand 函数的声明会复杂得多,不利于代码的维护,同时增加出错风险。
    所以,在某些复杂的类型声明中,使用 typedef 进行声明的简化是很有必要的。

hook系统函数

作用:修改自己调用的系统函数 或者在系统函数上做一层封装,加一些日志信息等等

如何实现hook:

  1. 定义目标函数一样的类型(函数指针类型)
  2. 实现目标函数名一致
  3. dlsym
    比如接下来,我们对pthread_mutex_lock和pthread_mutex_unlock做hook操作
#define _GNU_SOURCE
#include <dlfcn.h>

typedef int (*pthread_mutex_lock_t)(pthread_mutex_t *mutex);	//函数指针类型别名,pthread_mutex_lock_t可以定义函数指针变量
typedef int (*pthread_mutex_unlock_t)(pthread_mutex_t *mutex);

pthread_mutex_lock_t pthread_mutex_lock_f;	//定义一个函数指针变量
pthread_mutex_unlock_t pthread_mutex_unlock_f;

int pthread_mutex_lock(pthread_mutex_t *mutex) {
	pthread_t selfid = pthread_self();	
	pthread_mutex_lock_f(mutex);
	printf("pthread_mutex_lock: %ld, %p\n", selfid, mutex);
} 
int pthread_mutex_unlock(pthread_mutex_t *mutex) {
	pthread_t selfid = pthread_self();	
	pthread_mutex_unlock_f(mutex);
	printf("pthread_mutex_unlock: %ld, %p\n", selfid, mutex);
} 
//hook修改内核函数很常用
//dlsym返回一个函数指针,只要有这个出现,程序就会执行我们自己写的pthread_mutex_lock这个函数,而不是去调用系统的此函数
void init_hook(void) {
	pthread_mutex_lock_f = dlsym(RTLD_NEXT, "pthread_mutex_lock");
	pthread_mutex_unlock_f = dlsym(RTLD_NEXT, "pthread_mutex_unlock");
}

这样定义完后,我们在调用pthread_mutex_lock函数,会执行我们自己实现的pthread_mutex_lock函数而非系统中的此函数

死锁现象demo

死锁检测实现的原理

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

构建图代码

检测图有没有环代码