首页> 中国专利> 操作系统中的死锁检测方法

操作系统中的死锁检测方法

摘要

本发明公开了一种操作系统中的死锁检测方法,所述方法针对操作系统中多进程或线程并发执行时进行以下检测:1)每隔预定的死锁检测周期,检查锁的持有者链表HOLDER_LIST是否为空;2)当锁的持有者链表HOLDER_LIST不为空时,检查锁的持有者链表HOLDER_LIST中每个锁的持有者是不是锁的等待者;3)当锁的持有者为锁的等待者时,检测锁的持有者与锁的等待者之间是否会形成循环等待图;当且仅当锁的持有者链表HOLDER_LIST不为空,锁的持有者链表HOLDER_LIST中锁的持有者是锁的等待者,锁的持有者与锁的等待者之间形成循环等待图三者条件同时满足时,判断操作系统中线程或进程并行处理时存在死锁;否则判断不存在死锁。该方法不仅能准确检测出操作系统中的死锁,而且不需要对操作系统源码以及要检测的源程序做任何修改,对操作系统性能影响也在1%以内。

著录项

  • 公开/公告号CN103399818A

    专利类型发明专利

  • 公开/公告日2013-11-20

    原文格式PDF

  • 申请/专利权人 中国科学技术大学苏州研究院;

    申请/专利号CN201310351342.3

  • 申请日2013-08-13

  • 分类号G06F11/36(20060101);G06F9/46(20060101);

  • 代理机构32103 苏州创元专利商标事务所有限公司;

  • 代理人范晴

  • 地址 215123 江苏省苏州市工业园区独墅湖高教区仁爱路166号

  • 入库时间 2024-02-19 21:01:19

法律信息

  • 法律状态公告日

    法律状态信息

    法律状态

  • 2016-05-18

    授权

    授权

  • 2013-12-18

    实质审查的生效 IPC(主分类):G06F11/36 申请日:20130813

    实质审查的生效

  • 2013-11-20

    公开

    公开

说明书

技术领域

本发明属于操作系统优化技术领域,具体涉及一种操作系统中的死锁检 测方法。

背景技术

为了充分发挥cpu多核的性能,并发程序设计已经十分广泛,但是开发 并发程序面临很多挑战,死锁就是其中的一个。在设备驱动错误中有19% 的错误是由于并发导致的,在这些并发错误中72%(67/93)跟死锁有关, 文献S.Lu,S.Park,E.Seo,et al.Learning from mistakes–a  comprehensive study on real world concurrency bug characteristics.In  Proc.13th Intl.Conf.on Architectural Support for Programming  Languages and Operating Systems,2008通过对4大开源软件:MySQL、 Apache、Mozilla和OpenOffice中的并发错误进行分析,发现30%(31/105) 的错误是由死锁造成的。

死锁检测的方法分为静态和动态两种,静态检测通过工具来分析待检测 程序的源代码来找出可能要发生死锁的程序位置,这种方法不适用于检测代 码量大的程序,而且很不准确,动态检测是在程序运行时,检查可能发生的 死锁,但是动态监测性能开销大,不适用于内核、驱动中死锁的检测。

关于java多线程程序中的死锁检测,无论是静态方法还是动态方法, 过去都已经做过大量研究,而且目前已经有比较成熟的工具可以直接检查 java程序中的死锁,如jstack、lockstat等。由于操作系统代码量大、对性 能敏感,过去关于操作系统死锁方面的研究比较少,死锁检测工具pluse是 在操作系统层实现的,但是该工具只能检测应用程序中的死锁,并不能检测 操作系统本身的死锁。本发明因此而来。

发明内容

为了克服背景知识中的不足,本发明提供一种操作系统中动态检测死锁 的方法,在保证性能的条件下,可以准确检测出操作系统中的死锁。

为了解决现有技术中的这些问题,本发明提供的技术方案是:

一种操作系统中的死锁检测方法,其特征在于所述方法包括操作系统中 多线程或进程并发执行时进行以下检测:

1)每隔预定的死锁检测周期检查锁的持有者链表HOLDER_LIST是否 为空;

2)当锁的持有者链表HOLDER_LIST不为空时,检查锁的持有者链表 HOLDER_LIST中每个锁的持有者是不是锁的等待者;

3)当锁的持有者为锁的等待者时,检测锁的持有者与锁的等待者之间 是否会形成循环等待图;

当且仅当锁的持有者链表HOLDER_LIST不为空,锁的持有者链表 HOLDER_LIST中锁的持有者是锁的等待者,锁的持有者与锁的等待者之间 形成循环等待图三者条件同时满足时,判断操作系统中线程或进程并行处理 时存在死锁;否则判断不存在死锁。

优选的,所述方法中先构建空的锁的持有者链表HOLDER_LIST,采 用操作系统内核跟踪探测工具在操作系统的加锁函数的函数出口处探测,获 取锁的持有者信息;当加锁函数退出时,将进程号或线程号、锁类型、资源 地址作为新的锁的持有者信息插入锁的持有者链表HOLDER_LIST;当解锁 函数退出时,将相应的锁的持有者信息从锁的持有者链表HOLDER_LIST 删除。

优选的,所述方法中操作系统内核跟踪探测工具选自linux内核函数的 工具systemtap、Solaris内核或FreeBSD内核中的DTrace。

优选的,所述方法中锁的等待者是通过筛选异常进程或线程的方法获取 的,所述异常进程或线程为在预定阈值内获不到处理器或在预定阈值内一直 占有处理器的进程或线程。

优选的,所述方法中进程或线程的等待时间通过使用当前的系统时间减 去进程或线程描述符中的进程或线程最后一次运行的时间来获得的。

优选的,所述方法中预定阈值为进程的时间片与进程持有锁的时间的较 大值。

优选的,所述方法中进程持有锁的时间是通过加锁函数与解锁函数之间 的时间统计出来的;所述进程的时间片是通过进程的描述符中记录进程总的 运行时间与运行次数的比值统计获得的。

优选的,所述方法中预定阈值为10s。

优选的,所述方法中异常进程或线程的筛选是通过判断异常进程的内核 栈上有无加锁函数,从异常进程中筛选出锁的等待者。

优选的,所述方法中循环等待图是通过进程或线程间的资源依赖关系进 行判断的;当线程或进程T1等待资源R2,线程或进程T2是资源R2的持 有者,那么线程或进程T1与T2之间的关系称作资源依赖关系;当线程或 进程间存在线程或进程指向自己的资源依赖关系则判断线程或进程间形成 循环等待图。

本发明涉及如何检测操作系统中的死锁。针对操作系统代码量大、对性 能敏感,本发明提供一种开销小、不需要修改内核源码的检测操作系统中的 死锁方法。具体的,本发明涉及linux操作系统中的死锁检测,该方法包含 三个步骤:(1)获得锁的持有者。锁的持有者是那些成功申请到锁但还没有 解锁的进程或线程,因此通过检测进程或线程加锁与解锁是否匹配来获得锁 的持有者;(2)获得锁的等待者。进程或线程无法获得锁便处于等待状态, 因此可以从长时间等待的异常进程或线程中筛选出锁的等待者;(3)通过检 查锁的持有者与等待者是否会形成循环等待图来判定死锁,如果锁的持有者 与等待者之间形成一个循环等待图,那么就发生了死锁。通过实验发现,该 方法不需要修改linux内核和模块的源代码,而且对系统的性能影响很小(小 于1%),可以非常方便的应用到linux服务器上。

本发明得到一种动态检测死锁的方法,在保证性能的条件下,可以准确 检测出操作系统中的死锁。在操作系统中线程通过加锁函数来申请锁,通过 解锁函数来释放锁,成功加锁的线程在解锁之前便是锁的持有者,也即锁的 持有者是那些没有执行解锁函数的线程。因此,获取锁的持有者可以跟踪线 程的加锁与解锁操作,找出只成功加锁而没有解锁的线程。本发明使用可以 动态探测linux内核函数的工具systemtap来获得锁的持有者,它既不需要 重新编译内核,也不需要修改任何源程序和库函数。

死锁的等待者需要从异常进程或线程中筛选。异常进程或线程是那些长 期占有处理器或长期没有获得处理器的进程或线程。由于发生死锁的进程或 线程要么永远得不到处理器,要么永远占有处理器,所以死锁进程或线程必 定是异常进程或线程。异常进程或线程中只有一部分是因为等待锁而异常 的,为了获取这些锁的等待者,还需要对判定为异常的进程或线程进行筛选。 本发明使用进程或线程描述符task_struct结构体中的last_arrival为参考, 来判断进程或线程的等待(包括忙等)时间是否达到threshold_time。使用 当前的系统时间减去last_arrival便是进程或线程睡眠或忙等的时间,若该 差值超过threshold_time,那么就认为该进程或线程是异常进程或线程。本 发明通过判断异常进程或线程的内核栈上有无加锁函数,来从异常进程或线 程中筛选出锁的等待者。

为了检查线程间是否发生了死锁,需要判断线程的资源依赖关系是否会 形成一个循环等待图。根据是否存在线程指向自己的依赖关系判断线程间是 否形成循环等待图。从HOLDER_LIST中选择任意进程或线程P1,从进程 或线程的内核栈上找出进程或线程等待的资源R2,然后从HOLDER_LIST 找出资源R2的持有者P2,这样便确定了进程或线程P1和P2的资源依赖 关系P1->P2。接下来从进程或线程P2开始重复上述过程,直到遍历完 HOLDER_LIST链表或找到依赖关系Pj->Pi,Pi是已经遍历过的进程或线 程,这样便形成了进程或线程间的循环等待,可以断定这些进程或线程发生 了死锁。

相对于现有技术中的方案,本发明的优点是:

并发程序设计存在的一个普遍问题便是死锁,随着并发特性在操作系统 中越来越多的应用,使得操作系统发生死锁的概率也越来越大。但是操作系 统代码量大,对性能敏感,因此检测操作系统中的死锁问题具有一定的难度。 本发明结合linux系统中的探针技术提供一种动态检测操作系统中死锁的方 法,该方法不仅能准确检测出操作系统中的死锁,而且不需要对操作系统源 码以及要检测的源程序做任何修改,对操作系统性能影响也在1%以内。

附图说明

下面结合附图及实施例对本发明作进一步描述:

图1为本发明线程持有睡眠锁的时间统计结果;

图2是本发明进程或线程时间片统计结果;

图3是本发明三线程形成死锁时的循环等待图。

具体实施方式

以下结合具体实施例对上述方案做进一步说明。应理解,这些实施例是 用于说明本发明而不限于限制本发明的范围。实施例中采用的实施条件可以 根据具体厂家的条件做进一步调整,未注明的实施条件通常为常规实验中的 条件。

实施例死锁检测方法及实验结果

1、死锁检测方法原理描述

本实施例中死锁检测方法的原理是每隔一定时间(指定search_cycle), 检查锁持有者链表HOLDER_LIST是否为空,若不为空则检查这些锁的持 有者是不是锁的等待者。若检查结果为ture,则检查这些锁的持有者与等待 者之间是否会形成一个循环等待图,如果存在则说明操作系统发生了死锁。

具体的可以参考以下伪代码描述的死锁检测算法:

2、锁的持有者的获取过程

在操作系统中线程通过加锁函数来申请锁,通过解锁函数来释放锁,成 功加锁的线程在解锁之前便是锁的持有者,也即锁的持有者是那些没有执行 解锁函数的线程。因此,获取锁的持有者可以跟踪线程的加锁与解锁操作, 找出只成功加锁而没有解锁的线程。为了能够探测加锁和解锁函数,文献 [Jual,H.,Tralamazza,D.,Zamfir,C.,et al,Deadlock immunity:Enabling  systems to defend against deadlocks.In OSDI(Dec.2008),pp.295–308.]针 对java程序中的monitor entry/exit操作采用一个面向切面的编译器 AspectJ,而针对于pthread多线程程序则需要修改相关的库函数,这些方 法不利于软件的升级和维护。

本实施例使用可以动态探测linux内核函数的工具systemtap,它既不 需要重新编译内核,也不需要修改任何源程序和库函数。

systemtap既可以在函数的入口处进行探测,也可以在函数的出口处进 行探测。若在加锁函数退出的地方进行探测,那么就可以获得锁的持有者信 息,因为只有成功获得锁,进程或线程(本实施例中线程与进程不分别讨论) 才能从加锁函数中退出,否则便处于等待状态。为了唯一标识进程或线程加 锁和解锁操作,使用由进程或线程号(pid)、锁类型(lock_type)、资源地 址(resource_addr)组成的三元组<pid,lock_type,resource_addr>,当进 程或线程加锁的时候,将<pid,lock_type,resource_addr>插入死锁检测程序 维护的链表HOLDER_LIST中,当进程或线程解锁时,将<pid,lock_type, resource_addr>从HOLDER_LIST链表中删除,那么HOLDER_LIST链表 中保存的便是那些只有成功加锁而没有解锁的进程或线程,当死锁检查周期 search_cycle(该值允许用户设置,但应大于预定阈值threshold_time)时间 到的时候,便可以从HOLDER_LIST中获得锁的持有者(算法如图2)。

锁持有者的算法描述可以参考以下伪代码:

3、锁的等待者的获取过程

使用systemtap在加锁函数的入口处进行探测,就可以获得该锁的所有 申请者,申请者中除去锁的持有者,剩下的便是等待该锁的进程或线程。操 作系统中的互斥锁只允许有一个持有者,但却允许有n个等待着,当n很大 时,这种获得锁等待者的方法,将会对系统的性能差生很大影响。这是因为 systemtap运行在进程或线程执行的上下文中,在进程或线程执行的上下文 使用什么样的数据结构和算法在来高效的组织这n个等待者将是一个非常 棘手的问题。因此,本实施例使用通过筛选异常进程或线程的方法来获得锁 的等待者,该方法可以在1s以内完成系统中300个线程的分析。

本实施例中异常进程或线程是指等待时间超过阈值threshold_time的 进程或线程。这里的等待既包含进程或线程获不到处理器的等待,也包含进 程或线程一直占有处理器的忙等。由于发生死锁的进程或线程要么永远得不 到处理器,要么永远占有处理器,所以死锁进程或线程必定是异常进程或线 程。异常进程或线程中只有一部分是因为等待锁而异常的,为了获取这些锁 的等待者,还需要对判定为异常的进程或线程进行筛选。

本实施例使用进程或线程描述符task_struct结构体中的last_arrival 为参考,来判断进程或线程的等待(包括忙等)时间是否达到threshold_time。 在进程或线程的状态切换中,只有当进程或线程由就绪状态切换到运行态的 时候,才改变last_arrival的值。因此,无论进程或线程是在阻塞状态还是 在运行态(忙等),该值均不会改变,使用当前的系统时间减去last_arrival 便是进程或线程睡眠或忙等的时间,若该差值超过threshold_time,那么就 认为该进程或线程是异常进程或线程。

异常进程或线程中只有一部分是因为等待锁而异常的,因此需要对异常 进程或线程进行筛选,找出因等待锁而异常的进程或线程。当进程或线程执 行加锁函数的时候,内核会将加锁函数的返回地址压入进程或线程的内核 栈,当进程或线程从加锁函数成功退出的时候,再将该地址从栈中弹出。因 加锁函数而异常的进程或线程,其内核栈上必定存在加锁函数的返回地址, 内核提供的函数kallsyms_lookup可以根据该地址找到对应的加锁函数。因 此,可以通过判断异常进程或线程的内核栈上有无加锁函数(可以参考表1), 来从异常进程或线程中筛选出锁的等待者。

表1 操作系统中睡眠锁的加锁

4、预定阈值threshold_time的确定

判断进程或线程是否是异常进程或线程,需要确认时间阈值 threshold_time,该值不能太大也不能太小,太大将会延长死锁线程对系统 的影响,太小可能会误报死锁。threshold_time必须大于进程或线程持有锁 的时间lock_time,否则会将正常持有锁的进程或线程判为异常进程或线程, 另外阈值threshold_time也必须大于进程或线程的时间片time_slice,这是 为了筛选因为忙等而异常的进程或线程。阈值threshold_time应该大于两者 中的最大值,即thread_time>max(lock_time,time_slice)。

(1)统计进程或线程持有锁时间lock_time

操作系统中的锁可以分为两类:睡眠锁和非睡眠锁,睡眠锁允许锁的获 得者在锁保护的临界区中睡眠,而非睡眠锁则不允许。由于睡眠锁粒度大, 进程或线程持有睡眠锁的时间要比持有非睡眠锁的时间长,因此统计进程或 线程持有睡眠锁的时间更有意义。

操作系统中的睡眠锁有信号量(semaphores)、读/写信号量(read/write  semaphore)和互斥体(mutex),针对不同的使用场景,这些睡眠锁提供了 不同的加锁函数(表1),如down_killable允许通过信号(signal)来杀死 等待者,其他函数含义及使用方法参见linux内核源码。

加锁函数与解锁函数之间的时间便是线程持有该锁的时间,在表2所示 的环境上测10分钟,筛选出各个睡眠锁允许线程的最大持有时间如图1所 示。从图1上可以看出,互斥体(mutex)允许线程持有更长的时间,但是 该时间也没有超过14毫秒。

表2 测试环境

cpu 内存 操作系统 Q8400 4G Debian6.0(linux-kernel2.6.38)

(2)统计时间片time_slice

在进程或线程的描述符中会记录进程或线程总的运行时间 (sum_exec_runtime)以及运行次数(pcount),这两者之间的比值便是进 程或线程的平均时间片time_slice。目前,linux的进程或线程调度器CFS 会对cpu密集型的任务进行奖励,从而让其占有更长时间的处理器。为了获 得cpu密集型任务的时间片,使用stress工具来模拟cpu密集型的任务,然 后在表2所示的环境上使用systemtap统计出10分钟内,时间片最长的3 个任务(图2),从图2可知进程或线程stress的时间片最长,但是也没有 超过900毫秒。

通过对进程或线程持有锁时间lock_time以及进程或线程时间片 time_slice的统计发现,阈值threshold_time设定为10s是合适的,该值允 许用户自己设定。

5、循环等待图的确定

根据死锁检测方法的原理,为了检查线程间是否发生了死锁,需要判断 线程或进程的资源依赖关系是否会形成一个循环等待图(第6行代码:if  there is a cycle in waits_for_graph)。若线程T1、T2、T3发生死锁,那么 它们的资源依赖关系将如图3所示。

本实施例中所述的资源依赖关系可以定义为:线程或进程T1等待资源 R2,线程或进程T2是资源R2的持有者,那么线程或进程T1与T2之间的 关系称作资源依赖关系,表示为T1->T2。

这种资源依赖关系是可传递的,若T1->T2,T2->T3,则有T1->T3。死 锁发生的充要条件是形成图3的循环等待图,从图3中的任意线程Ti,使用 资源依赖关系的传递性质,均可以获得线程指向自己的依赖:Ti->Ti。因此, 发生死锁的充要条件可以描述为,是否存在线程指向自己的依赖关系。

从HOLDER_LIST中选择任意进程或线程P1,从进程或线程的内核栈 上找出进程或线程等待的资源R2,然后从HOLDER_LIST找出资源R2的 持有者P2,这样便确定了进程或线程P1和P2的资源依赖关系P1->P2。接 下来从进程或线程P2开始重复上述过程,直到遍历完HOLDER_LIST链表 或找到依赖关系Pj->Pi,Pi是已经遍历过的进程或线程,这样便形成了进程 或线程间的循环等待,可以断定这些进程或线程发生了死锁。

6、实验结果

为了检测死锁检测方法的有效性,使用操作系统中的信号量模拟三个线 程访问三种临界资源,每个线程获得一种资源后,延迟一段时间,然后申请 另外一种资源。线程第一次申请的是三种不同的资源,可以成功获得,第二 次申请是其它线程已经拥有的资源,由于加锁顺序不当导致发生死锁。使用 本实施例提供的死锁检测方法,可以准确检测出该死锁以及线程间的资源依 赖关系。

其检测结果可以如下所示:

在表2所示的测试环境上使用linux自带的性能检测工具top,来测试 该死锁检测方法对操作系统性能的影响。top工具每隔一秒种收集系统的性 能参数,发现在死锁检测程序运行之前cpu使用率为0%~1%,在死锁检测 程序运行时,cpu使用率依然是0%~1%,这说明该方法对cpu性能的影响 小于1%。

上述实例只为说明本发明的技术构思及特点,其目的在于让熟悉此项技 术的人是能够了解本发明的内容并据以实施,并不能以此限制本发明的保护 范围。凡根据本发明精神实质所做的等效变换或修饰,都应涵盖在本发明的 保护范围之内。

去获取专利,查看全文>

相似文献

  • 专利
  • 中文文献
  • 外文文献
获取专利

客服邮箱:kefu@zhangqiaokeyan.com

京公网安备:11010802029741号 ICP备案号:京ICP备15016152号-6 六维联合信息科技 (北京) 有限公司©版权所有
  • 客服微信

  • 服务号